├── .DS_Store ├── Alone in the world ├── Alone In The World项目信息.pdf └── Alone in the world(提交版).html ├── CantonVoicesF5 ├── Canton Voices复古网页设计-3945d7f720.html └── Canton-Voices-F5.pdf.textClipping ├── IO 数字生命档案馆 ├── IO 数字生命档案馆.pdf └── IO 数字生命档案馆.rtf ├── Idolverse └── Web3 Idolverse Demo.zip ├── README.md ├── 古事妗解 ├── .DS_Store └── 古事妗解.md.rtf ├── 极速吵架王-薛 ├── app.js ├── assets │ └── background.png ├── index.html └── styles.css └── 薛律师代码1.rar /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xherstory/WWW-5/42116870b03a142947018cfc6da6852257b0388d/.DS_Store -------------------------------------------------------------------------------- /Alone in the world/Alone In The World项目信息.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xherstory/WWW-5/42116870b03a142947018cfc6da6852257b0388d/Alone in the world/Alone In The World项目信息.pdf -------------------------------------------------------------------------------- /Alone in the world/Alone in the world(提交版).html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ALONE IN THE WORLD 7 | 357 | 358 | 359 |
360 |
361 |
钱包状态:未连接
362 | 365 |
366 |
367 | 368 |
369 |

ALONE IN THE WORLD

370 |

371 | 如果无人注视,你还会想完成什么?请写下那些仍在心底闪烁的微光瞬间,点击 MINT,让它化作一枚独属于你的心迹 NFT。 372 |

373 |
374 | 375 |
376 | 377 |
378 | 385 | 386 |
387 |
388 | 389 |
390 |
391 |

Minted Choices

392 | 尚未 Mint 393 |
394 | 395 |
396 | 目前还没有被 Mint 的心迹。写下第一个属于你的心迹吧。 397 |
398 | 399 | 400 |
401 | 402 | 403 |
404 | 405 | 406 | 712 | 713 | -------------------------------------------------------------------------------- /CantonVoicesF5/Canton Voices复古网页设计-3945d7f720.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Canton Voices - 粤语文化传播平台 7 | 8 | 9 | 322 | 323 | 324 | 325 |
326 |
327 |
加载中...
328 |
329 | 330 | 331 | 345 | 346 | 347 | 361 | 362 | 363 |
364 | 365 |
366 | 377 | 378 | 379 |
380 |
381 |
382 |
383 |
384 | 385 |
386 |
387 |
388 | 391 | 394 |
395 |
396 | 397 |
398 |

"女人嘅嘢,自己话事"

399 | 400 |
401 |

释义

402 |

女性的事情由自己做主,强调女性拥有独立自主的权利和自主选择的能力,不受传统性别角色束缚。

403 | 404 |

文化背景

405 |

这句话诞生于1980年代香港女性主义运动兴起时期,反映了当时女性争取社会地位和自主权利的思潮。在港剧《狂潮》《家变》等作品中,独立女性角色常以类似台词表达自主意识,成为粤语流行文化中女性赋权的标志性语句。它挑战了传统性别角色期待,鼓励女性掌控自身命运,至今仍是香港女权话语的重要符号。

406 |
407 | 408 |
409 | 女性主义 410 | 独立自主 411 | 粤语文化 412 |
413 |
414 |
415 |
416 | 417 | 418 |
419 | 420 |
421 |
当前分数: 0
422 |
关卡进度: 1/3
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 | 466 | 467 | 468 | 486 | 487 | 488 | 500 |
501 | 502 | 503 |
504 | 507 |
508 |
509 | 510 | 511 |
512 |
513 | 514 |
515 | 沟通困境 516 |

沟通困境

517 |

金句:真系同你communicate唔到
收集日期: 2025-09-27

518 |
519 | 稀有度: ★★★ 520 | 523 |
524 |
525 | 526 | 527 |
528 | 世间无最贱 529 |

世间无最贱

530 |

金句:原来这个世界是没有最贱
收集日期: 2025-09-27

531 |
532 | 稀有度: ★★ 533 | 536 |
537 |
538 | 539 | 540 |
541 | 三十而死 542 |

三十而死

543 |

金句:三十而死
收集日期: 2025-09-27

544 |
545 | 稀有度: ★★★★ 546 | 549 |
550 |
551 |
552 |
553 |
554 |
555 | 556 | 557 | 570 | 571 | 735 | 736 | -------------------------------------------------------------------------------- /CantonVoicesF5/Canton-Voices-F5.pdf.textClipping: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xherstory/WWW-5/42116870b03a142947018cfc6da6852257b0388d/CantonVoicesF5/Canton-Voices-F5.pdf.textClipping -------------------------------------------------------------------------------- /IO 数字生命档案馆/IO 数字生命档案馆.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xherstory/WWW-5/42116870b03a142947018cfc6da6852257b0388d/IO 数字生命档案馆/IO 数字生命档案馆.pdf -------------------------------------------------------------------------------- /IO 数字生命档案馆/IO 数字生命档案馆.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg936\cocoartf2822 2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\expandedcolortbl;;} 5 | \paperw11900\paperh16840\margl1440\margr1440\vieww11520\viewh8400\viewkind0 6 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 7 | 8 | \f0\fs24 \cf0 IO \uc0\u25968 \u23383 \u29983 \u21629 \u26723 \u26696 \u39302 \u65306 https://stellar-legacy-vault.lovable.app/} -------------------------------------------------------------------------------- /Idolverse/Web3 Idolverse Demo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xherstory/WWW-5/42116870b03a142947018cfc6da6852257b0388d/Idolverse/Web3 Idolverse Demo.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WWW-5 2 | womenweb3women 3 | # Women Web3 Wave #5 Demoday 4 | 5 | 6 | 大女人们,🎉🎉恭喜完成7 days in Web3 挑战🎉🎉!度日如年,好在我们坚持下来啦,现在是展示你的想法,发声的时候啦。开始你的改变世界之旅吧。 7 | 8 | 本次活动由Herstory社区主持 9 | 10 | ## [代码提交] 11 | 12 | ### 1. 创建你的 HerSolidity-Minihackathon-2025 项目 13 | 14 | 1. 报名注册完成后,可以立即开始项目创建,fork 本代码仓库,到你们团队成员 repo 里: [https://github.com/0xherstory/womenweb3wave4.5.git](https://github.com/0xherstory/WWW-5.git) 15 | 2. 先在 `projects` 内生成一个目录,以你们项目名称命名,里面先放个空档案,或 readme 简单介绍项目。提交一个 PR 进来。目的是预留一个目录作为你们项目空间。**注意我们会把目录改名,在项目名称前加个编号。请 pull 下来。** 16 | 17 | 3. 之后,所有参赛项目相关代码都放在你们的项目名称里的目录里进行。可以这种形式存放: 18 | 19 | ``` 20 | projects 21 | L 00-proj-template/ // 项目目录名称 22 | L src/ 23 | L contracts/ // 合约相关代码 24 | L backend/ // 后端相关代码 25 | L ui/ // 前端相关代码 26 | L 。。。 // 其他档案 27 | L docs/ // 存放文档。视频和PPT等大文件不要直接上传,放链接地址即可 28 | L README.md // 可参考此模板 29 | ``` 30 | 31 | 4. 推送最终PR的分支,建议以项目命名, 且只可修改本团队项目目录内容。 32 | 33 | 5. 请在 `2025年9月27日上午24:00` (北京时区)前,初始化团队项目目录。 34 | 35 | 6. 最终PR,在 `2025年9月27日24:00` (北京时区)前提交,且各团队自己发起MR请求。 36 | 37 | 7. [项目参考模块,点我查看详情](https://github.com/0xherstory/womenweb3wave4.5/blob/main/demo-projects-submit-here/00-proj-template/project-intro-includes.md) 38 | 39 | 40 | -------------------------------------------------------------------------------- /古事妗解/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xherstory/WWW-5/42116870b03a142947018cfc6da6852257b0388d/古事妗解/.DS_Store -------------------------------------------------------------------------------- /古事妗解/古事妗解.md.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg936\cocoartf2822 2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\expandedcolortbl;;} 5 | \paperw11900\paperh16840\margl1440\margr1440\vieww11520\viewh8400\viewkind0 6 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 7 | 8 | \f0\fs24 \cf0 \uc0\u25105 \u30340 \u39033 \u30446 \u21517 \u31216 \u21483 \u12304 \u21476 \u20107 \u22935 \u35299 \u12305 \ 9 | \uc0\u24847 \u24605 \u23601 \u26159 \u65292 \u20170 \u22825 \u30340 \u22899 \u24615 \u37325 \u20889 \u21476 \u20107 \u12290 \ 10 | \uc0\u25105 \u24076 \u26395 \u35299 \u20915 \u19977 \u20010 \u38656 \u27714 \u23601 \u26159 \u65292 \u39318 \u20808 \u26368 \u32456 \u25366 \u25496 \u21476 \u20195 \u22899 \u24615 \u30340 \u25925 \u20107 \u25110 \u37325 \u22609 \u22905 \u20204 \u30340 \u20215 \u20540 \u65292 \u37325 \u20889 \u22899 \u24615 \u21382 \u21490 \u32463 \u39564 \u30340 \u38656 \u27714 \u12290 \u37325 \u26032 \u23450 \u20041 \u65292 \u24314 \u31435 \u25105 \u20204 \u30340 \u22899 \u24615 \u21382 \u21490 \ 11 | \uc0\u25105 \u20250 \u36890 \u36807 \u35299 \u20915 \u65306 \u22823 \u23478 \u21487 \u20197 \u38598 \u20013 \u38405 \u35272 \u20102 \u35299 \u21476 \u20195 \u22899 \u24615 \u65292 \u20102 \u35299 \u30007 \u20154 \u21382 \u21490 \u31713 \u25913 \u20043 \u22806 \u30340 \u22899 \u24615 \u21382 \u21490 \u12290 \u36798 \u21040 \u20102 \u35299 \u21476 \u20195 \u22899 \u24615 \u30340 \u30446 \u30340 \u12290 \u36825 \u20063 \u26159 \u19968 \u20010 \u38656 \u27714 \u12290 \ 12 | \uc0\u36824 \u21487 \u20197 \u25910 \u38598 \u21508 \u20010 \u22320 \u21306 \u38750 \u23448 \u26041 \u65292 \u27665 \u38388 \u21475 \u21475 \u30456 \u20256 \u30340 \u22899 \u24615 \u31070 \u35805 \u65292 \u20256 \u35828 \u12290 \ 13 | \uc0\u36825 \u37096 \u20998 \u22823 \u37096 \u20998 \u25918 \u22312 web2\u25910 \u38598 \u20250 \u23481 \u26131 \u65292 \u25152 \u20197 \u32463 \u36807 \u31649 \u29702 \u21592 \u30340 \u23457 \u26680 \u19978 \u20256 \u36827 \u20837 \u25105 \u20204 \u30340 \u21382 \u21490 \u36164 \u26009 \u24211 \u12290 \u23601 \u20250 \u26377 \u28304 \u28304 \u19981 \u26029 \u30340 \u26032 \u25925 \u20107 \u26469 \u28304 \u12290 \u26356 \u26032 \u22899 \u24615 \u21382 \u21490 \u36164 \u26009 \u24211 \u12290 \u22899 \u24615 \u21382 \u21490 \u36164 \u26009 \u24211 \u30340 \u20316 \u29992 \u21448 \u21487 \u20197 \u26356 \u22909 \u30340 \u20102 \u35299 \u21382 \u21490 \u22899 \u24615 \u65292 \u21448 \u21487 \u20197 \u20026 \u22935 \u35299 \u20570 \u19968 \u20010 \u32463 \u39564 \u21069 \u32622 \u12290 \ 14 | \uc0\u30475 \u35265 \u21382 \u21490 \u22899 \u24615 \u65292 \u27979 \u35797 \u33258 \u24049 \u30340 \u22899 \u26412 \u25991 \u24847 \u35782 \u65292 \u25105 \u35774 \u35745 \u20102 \u19968 \u20010 \u19968 \u20998 \u38047 \u25361 \u25112 \u36187 \u12290 \u36824 \u26377 \u19968 \u20010 \u38544 \u34255 \u36187 \u65292 \u22312 \u25361 \u25112 \u36187 \u31572 \u20986 \u20116 \u20010 \u20197 \u19978 \u22899 \u24615 \u21382 \u21490 \u21517 \u20154 \u20250 \u36339 \u20986 \u12290 \ 15 | \ 16 | \uc0\u36825 \u20010 \u39033 \u30446 \u36319 web3\u38142 \u25509 \u65306 \u29616 \u22312 \u22269 \u20869 \u23457 \u26680 \u21024 \u38500 \u22899 \u24615 \u20027 \u20041 \u36134 \u21495 \u29616 \u35937 \u65292 \u29305 \u21035 \u27867 \u28389 \u21644 \u20005 \u37325 \u65292 \u20027 \u35201 \u26159 \u25105 \u24819 \u25226 \u36825 \u20123 \u21382 \u21490 \u36164 \u26009 \u20445 \u23384 \u22312 \u38142 \u19978 \u65292 \u27704 \u20037 \u23384 \u22312 \u12290 \ 17 | \uc0\u30408 \u21033 \u30340 \u35805 \u65292 \u38500 \u20102 \u20986 \u21382 \u21490 \u22899 \u24615 \u24065 \u65292 \u30446 \u21069 \u23601 \u26159 \u25193 \u22823 \u24433 \u21709 \u65292 \u25509 \u21463 \u25424 \u21161 \u65292 \u38142 \u25509 \u32447 \u19979 \u30340 \u27963 \u21160 \u12290 \u23558 \u22899 \u24615 \u21382 \u21490 ai\u20070 \u20889 \u30340 \u36825 \u37096 \u20998 \u65288 \u22935 \u35299 \u65289 \u25480 \u35838 \u12290 \u21487 \u33021 \u21040 \u26102 \u20505 \u23601 \u26159 \u19981 \u25552 \u22899 \u24615 \u20294 \u22788 \u22788 \u37117 \u26159 \u22899 \u24615 \u65288 \u36991 \u20813 \u23457 \u26597 \u65289 \u20063 \u26159 \u19968 \u20010 \u24456 \u22909 \u30340 \u38450 \u27490 \u20030 \u25253 \u30340 \u20256 \u25773 \u22899 \u24615 \u24847 \u35782 \u30340 \u36884 \u24452 \u12290 \ 18 | \ 19 | \ 20 | } -------------------------------------------------------------------------------- /极速吵架王-薛/app.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const STORAGE_USERS = 'jsjj_users'; 3 | const STORAGE_CURRENT = 'jsjj_current_user'; 4 | 5 | const baseModules = [ 6 | { 7 | id: 'base-1', 8 | title: '模块一:高速意面论', 9 | body: `意大利面拌42号混凝土 我不敢苟同。我个人认为这个意大利面就应该拌42号混凝土。因为这个螺丝钉的长度,它很容易会直接影响到挖掘机的扭距,你往里砸的时候,一瞬间它就会产生大量的高能蛋白,俗称UFO会严重影响经济的发展。照你这么说,炸鸡块要用92#汽油,毕竟我们无法用光学透镜探测苏格拉底,如果二氧化氢持续侵蚀这个机床组件,那么我们早晚要在斐波那契曲线上安装一个胶原蛋白,否则我们将无法改变蜜雪冰城与阿尔别克的叠加状态,因为众所周知爱吃鸡摩人在捕鲲的时候往往需要用氢的同位素当做诱饵,但是原子弹的新鲜程度又会直接影响到我国东南部的季风和洋流,所以说在西伯利亚地区开设农学院显然是不合理的。 10 | 我知道你一定会反驳我,告诉我农业的底层思维是什么,就是不用化肥农药和种子,还包括生命之源氮气,使甲烷分子直接转化成能够捕获放射性元素释放的β射线的单质,并且使伽马射线在常温下就能用老虎钳折弯成78,否则在用望远镜观察细胞结构时,根本发现不了时空重叠时到底要叠几层才能使潼关肉夹馍更酥脆的原因。` 11 | }, 12 | { 13 | id: 'base-2', 14 | title: '模块二:小区军权论', 15 | body: `黄龙江一派全戴狼牙耳机 你有这么高速运转的机械进入中国。记住我给出的原理,小的时候,就是研发人就是研发这个东西的原理是是阴间政权管着。 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 | const AudioCtx = window.AudioContext || window.webkitAudioContext; 42 | 43 | const els = { 44 | samplePlay: document.getElementById('sample-play'), 45 | sampleStatus: document.getElementById('sample-status'), 46 | sampleModule: document.getElementById('sample-module'), 47 | sampleScore: document.getElementById('sample-score'), 48 | sampleDuration: document.getElementById('sample-duration'), 49 | sampleAudio: document.getElementById('sample-audio'), 50 | moduleGrid: document.getElementById('module-grid'), 51 | moduleOverlay: document.getElementById('module-overlay'), 52 | moduleHeading: document.getElementById('module-heading'), 53 | moduleBody: document.getElementById('module-body'), 54 | moduleClose: document.getElementById('module-close'), 55 | moduleStatus: document.getElementById('module-status'), 56 | moduleRecordToggle: document.getElementById('module-record-toggle'), 57 | moduleDuration: document.getElementById('module-duration'), 58 | moduleAudio: document.getElementById('module-audio'), 59 | moduleScore: document.getElementById('module-score-value'), 60 | moduleCreateTrigger: document.getElementById('module-create-trigger'), 61 | moduleCreateOverlay: document.getElementById('module-create-overlay'), 62 | moduleCreateClose: document.getElementById('module-create-close'), 63 | moduleCreateForm: document.getElementById('module-create-form'), 64 | moduleCreateTitle: document.getElementById('module-create-title'), 65 | moduleCreateBody: document.getElementById('module-create-body'), 66 | moduleCreateMessage: document.getElementById('module-create-message'), 67 | moduleCreateSubmit: document.getElementById('module-create-submit'), 68 | authGreeting: document.getElementById('auth-greeting'), 69 | pointsDisplay: document.getElementById('points-display'), 70 | loginTrigger: document.getElementById('login-trigger'), 71 | registerTrigger: document.getElementById('register-trigger'), 72 | logoutBtn: document.getElementById('logout-btn'), 73 | authOverlay: document.getElementById('auth-overlay'), 74 | authHeading: document.getElementById('auth-heading'), 75 | authForm: document.getElementById('auth-form'), 76 | authUsername: document.getElementById('auth-username'), 77 | authPassword: document.getElementById('auth-password'), 78 | authMessage: document.getElementById('auth-message'), 79 | authSubmit: document.getElementById('auth-submit'), 80 | authSwitch: document.getElementById('auth-switch'), 81 | authClose: document.getElementById('auth-close') 82 | }; 83 | 84 | const state = { 85 | currentModuleId: null, 86 | lastFocusedModule: null, 87 | bestSample: null, 88 | authMode: 'login', 89 | userModules: [], 90 | userPoints: 0 91 | }; 92 | 93 | const moduleStates = new Map(); 94 | 95 | const loadUsers = () => { 96 | try { 97 | const raw = localStorage.getItem(STORAGE_USERS); 98 | return raw ? JSON.parse(raw) : {}; 99 | } catch (error) { 100 | console.error('读取用户数据失败', error); 101 | return {}; 102 | } 103 | }; 104 | 105 | const saveUsers = (users) => { 106 | try { 107 | localStorage.setItem(STORAGE_USERS, JSON.stringify(users)); 108 | } catch (error) { 109 | console.error('保存用户数据失败', error); 110 | } 111 | }; 112 | 113 | const getCurrentUsername = () => localStorage.getItem(STORAGE_CURRENT); 114 | 115 | const setCurrentUsername = (username) => { 116 | if (username) { 117 | localStorage.setItem(STORAGE_CURRENT, username); 118 | } else { 119 | localStorage.removeItem(STORAGE_CURRENT); 120 | } 121 | refreshAuthState(); 122 | }; 123 | 124 | const ensureUserRecord = (username) => { 125 | const users = loadUsers(); 126 | if (!users[username]) { 127 | users[username] = { 128 | password: '', 129 | createdAt: Date.now(), 130 | points: 0, 131 | modules: [] 132 | }; 133 | saveUsers(users); 134 | } 135 | return users[username]; 136 | }; 137 | 138 | const hashPassword = (password) => { 139 | try { 140 | return btoa(unescape(encodeURIComponent(password))); 141 | } catch (error) { 142 | return password; 143 | } 144 | }; 145 | 146 | const getCombinedModules = () => [...baseModules, ...state.userModules]; 147 | 148 | const getModuleById = (moduleId) => getCombinedModules().find((module) => module.id === moduleId) || null; 149 | 150 | const getModuleState = (moduleId) => { 151 | if (!moduleStates.has(moduleId)) { 152 | moduleStates.set(moduleId, { 153 | recorder: null, 154 | stream: null, 155 | chunks: [], 156 | timerId: null, 157 | startTimestamp: null, 158 | latestUrl: null, 159 | lastDuration: 0, 160 | lastScore: null 161 | }); 162 | } 163 | return moduleStates.get(moduleId); 164 | }; 165 | 166 | const renderModules = () => { 167 | if (!els.moduleGrid) { 168 | return; 169 | } 170 | els.moduleGrid.innerHTML = ''; 171 | getCombinedModules().forEach((module) => { 172 | const button = document.createElement('button'); 173 | button.type = 'button'; 174 | button.className = 'module-card'; 175 | button.dataset.moduleId = module.id; 176 | 177 | const title = document.createElement('span'); 178 | title.className = 'module-title'; 179 | title.textContent = module.title; 180 | 181 | const hint = document.createElement('span'); 182 | hint.className = 'module-hint'; 183 | hint.textContent = '点击阅读'; 184 | 185 | button.appendChild(title); 186 | button.appendChild(hint); 187 | button.addEventListener('click', () => openModule(module.id)); 188 | 189 | els.moduleGrid.appendChild(button); 190 | }); 191 | }; 192 | 193 | const updatePointsUI = () => { 194 | const username = getCurrentUsername(); 195 | const pointsText = username ? `当前积分:${state.userPoints}` : '当前积分:0'; 196 | els.pointsDisplay.textContent = pointsText; 197 | 198 | const canCreate = Boolean(username) && state.userPoints >= 10; 199 | els.moduleCreateTrigger.disabled = !canCreate; 200 | els.moduleCreateTrigger.textContent = canCreate ? '添加模块(需10积分)' : '添加模块(需10积分)'; 201 | }; 202 | 203 | const updateAuthBar = () => { 204 | const username = getCurrentUsername(); 205 | if (username) { 206 | els.authGreeting.textContent = `欢迎你,${username}`; 207 | els.loginTrigger.classList.add('hidden'); 208 | els.registerTrigger.classList.add('hidden'); 209 | els.logoutBtn.classList.remove('hidden'); 210 | } else { 211 | els.authGreeting.textContent = '欢迎你,游客'; 212 | els.loginTrigger.classList.remove('hidden'); 213 | els.registerTrigger.classList.remove('hidden'); 214 | els.logoutBtn.classList.add('hidden'); 215 | } 216 | updatePointsUI(); 217 | }; 218 | 219 | const refreshAuthState = () => { 220 | const username = getCurrentUsername(); 221 | if (username) { 222 | const users = loadUsers(); 223 | const record = users[username] || ensureUserRecord(username); 224 | state.userPoints = record.points || 0; 225 | state.userModules = Array.isArray(record.modules) ? record.modules : []; 226 | } else { 227 | state.userPoints = 0; 228 | state.userModules = []; 229 | } 230 | updateAuthBar(); 231 | renderModules(); 232 | recalculateBestSample(); 233 | }; 234 | 235 | const saveCurrentUserData = () => { 236 | const username = getCurrentUsername(); 237 | if (!username) { 238 | return; 239 | } 240 | const users = loadUsers(); 241 | if (!users[username]) { 242 | users[username] = { 243 | password: '', 244 | createdAt: Date.now(), 245 | points: 0, 246 | modules: [] 247 | }; 248 | } 249 | users[username].points = state.userPoints; 250 | users[username].modules = state.userModules; 251 | saveUsers(users); 252 | updatePointsUI(); 253 | }; 254 | 255 | const createModuleCard = (title, body) => ({ 256 | id: `user-${Date.now()}-${Math.floor(Math.random() * 1000)}`, 257 | title, 258 | body 259 | }); 260 | 261 | const clamp = (value, min, max) => Math.min(Math.max(value, min), max); 262 | 263 | const analyzeRecording = (buffer) => { 264 | const channelData = buffer.getChannelData(0); 265 | const sampleRate = buffer.sampleRate; 266 | const duration = buffer.duration || channelData.length / sampleRate; 267 | 268 | let sumSquares = 0; 269 | let activeSamples = 0; 270 | let transitions = 0; 271 | let isActive = false; 272 | let cooldown = 0; 273 | const threshold = 0.05; 274 | const cooldownSamples = Math.floor(sampleRate * 0.03); 275 | 276 | for (let i = 0; i < channelData.length; i++) { 277 | const sample = channelData[i]; 278 | sumSquares += sample * sample; 279 | const magnitude = Math.abs(sample); 280 | 281 | if (magnitude > threshold) { 282 | activeSamples++; 283 | cooldown = cooldownSamples; 284 | if (!isActive) { 285 | transitions++; 286 | isActive = true; 287 | } 288 | } else if (isActive) { 289 | cooldown--; 290 | if (cooldown <= 0) { 291 | isActive = false; 292 | } 293 | } 294 | } 295 | 296 | const rms = Math.sqrt(sumSquares / channelData.length); 297 | const activeRatio = activeSamples / channelData.length; 298 | 299 | const loudnessScore = clamp(rms / 0.08, 0, 1); 300 | const presenceScore = clamp(activeRatio / 0.45, 0, 1); 301 | const clarityScore = clamp((loudnessScore * 0.6 + presenceScore * 0.4) * 100, 0, 100); 302 | 303 | const wordsPerMinute = duration > 0 ? (transitions / duration) * 60 : 0; 304 | const gaussian = Math.exp(-Math.pow((wordsPerMinute - 120) / 60, 2)); 305 | const speechRateScore = clamp(gaussian * 100, 0, 100); 306 | 307 | const totalScore = clamp((clarityScore * 0.5) + (speechRateScore * 0.5), 0, 100); 308 | 309 | return { 310 | clarity: clarityScore, 311 | speechRate: speechRateScore, 312 | total: totalScore, 313 | duration 314 | }; 315 | }; 316 | 317 | const decodeBlob = async (blob) => { 318 | if (!AudioCtx) { 319 | throw new Error('当前浏览器不支持 Web Audio API'); 320 | } 321 | 322 | const arrayBuffer = await blob.arrayBuffer(); 323 | const ctx = new AudioCtx(); 324 | try { 325 | return await ctx.decodeAudioData(arrayBuffer.slice(0)); 326 | } finally { 327 | ctx.close(); 328 | } 329 | }; 330 | 331 | const formatScore = (score) => `${Math.round(score)} 分`; 332 | const formatDuration = (seconds) => `${seconds.toFixed(1)}秒`; 333 | 334 | const updateSampleDisplay = () => { 335 | const best = state.bestSample; 336 | if (!best) { 337 | els.samplePlay.disabled = true; 338 | els.samplePlay.textContent = '暂无示例语音'; 339 | els.sampleStatus.textContent = '完成任意模块录音后,这里会播放综合评分最高的示例语音。'; 340 | els.sampleModule.textContent = '--'; 341 | els.sampleScore.textContent = '--'; 342 | els.sampleDuration.textContent = '--'; 343 | els.sampleAudio.pause(); 344 | els.sampleAudio.classList.add('hidden'); 345 | return; 346 | } 347 | 348 | els.samplePlay.disabled = false; 349 | els.samplePlay.textContent = '播放最高分示例'; 350 | els.sampleStatus.textContent = `当前最高分来自:${best.title}`; 351 | els.sampleModule.textContent = best.title; 352 | els.sampleScore.textContent = formatScore(best.score); 353 | els.sampleDuration.textContent = formatDuration(best.duration); 354 | 355 | if (els.sampleAudio.src !== best.url) { 356 | els.sampleAudio.src = best.url; 357 | } 358 | els.sampleAudio.classList.remove('hidden'); 359 | }; 360 | 361 | const recalculateBestSample = () => { 362 | let best = null; 363 | moduleStates.forEach((moduleState, moduleId) => { 364 | if (moduleState.lastScore == null || !moduleState.latestUrl) { 365 | return; 366 | } 367 | if (!best || moduleState.lastScore > best.score) { 368 | const module = getModuleById(moduleId); 369 | best = { 370 | moduleId, 371 | score: moduleState.lastScore, 372 | duration: moduleState.lastDuration || 0, 373 | url: moduleState.latestUrl, 374 | title: module ? module.title : '未知模块' 375 | }; 376 | } 377 | }); 378 | state.bestSample = best; 379 | updateSampleDisplay(); 380 | }; 381 | 382 | const stopModuleTimer = (moduleState) => { 383 | if (moduleState.timerId) { 384 | window.clearInterval(moduleState.timerId); 385 | moduleState.timerId = null; 386 | } 387 | }; 388 | 389 | const startModuleTimer = (moduleState) => { 390 | moduleState.startTimestamp = performance.now(); 391 | stopModuleTimer(moduleState); 392 | moduleState.timerId = window.setInterval(() => { 393 | const elapsed = (performance.now() - moduleState.startTimestamp) / 1000; 394 | els.moduleDuration.textContent = formatDuration(elapsed); 395 | }, 100); 396 | }; 397 | 398 | const resetModuleUI = (moduleState) => { 399 | els.moduleRecordToggle.textContent = '开始录音'; 400 | els.moduleRecordToggle.classList.remove('recording'); 401 | els.moduleRecordToggle.setAttribute('aria-pressed', 'false'); 402 | stopModuleTimer(moduleState); 403 | }; 404 | 405 | const cleanupModuleStream = (moduleState) => { 406 | moduleState.stream?.getTracks().forEach((track) => track.stop()); 407 | moduleState.stream = null; 408 | }; 409 | 410 | const explainPermissionError = (error) => { 411 | if (error?.name === 'NotAllowedError' || error?.name === 'PermissionDeniedError') { 412 | return '麦克风权限被拒。请在浏览器地址栏允许麦克风访问后重试。'; 413 | } 414 | if (error?.name === 'NotFoundError' || error?.name === 'DevicesNotFoundError') { 415 | return '未检测到可用的麦克风设备,请连接设备后再试。'; 416 | } 417 | if (error?.name === 'SecurityError') { 418 | return '浏览器因不安全的来源阻止了录音,请通过 https:// 或 http://localhost 访问此页面。'; 419 | } 420 | return '麦克风权限被拒或不可用。'; 421 | }; 422 | 423 | const grantPoints = (points) => { 424 | const username = getCurrentUsername(); 425 | if (!username || points <= 0) { 426 | return; 427 | } 428 | state.userPoints += points; 429 | saveCurrentUserData(); 430 | els.moduleStatus.textContent = `本次综合得分达标,奖励 ${points} 积分!`; 431 | }; 432 | 433 | const applyModuleSnapshot = (moduleId) => { 434 | const moduleState = getModuleState(moduleId); 435 | resetModuleUI(moduleState); 436 | els.moduleDuration.textContent = formatDuration(moduleState.lastDuration || 0); 437 | 438 | if (moduleState.lastScore != null) { 439 | els.moduleScore.textContent = formatScore(moduleState.lastScore); 440 | els.moduleStatus.textContent = `上一次综合得分:${Math.round(moduleState.lastScore)} 分;再录一遍冲击更高分!`; 441 | } else { 442 | els.moduleScore.textContent = '--'; 443 | els.moduleStatus.textContent = '点击开始录音,测测你的模块造势。'; 444 | } 445 | 446 | if (moduleState.latestUrl) { 447 | els.moduleAudio.src = moduleState.latestUrl; 448 | els.moduleAudio.classList.remove('hidden'); 449 | } else { 450 | els.moduleAudio.removeAttribute('src'); 451 | els.moduleAudio.classList.add('hidden'); 452 | } 453 | }; 454 | 455 | const handleModuleRecordingComplete = async (moduleId) => { 456 | const moduleState = getModuleState(moduleId); 457 | resetModuleUI(moduleState); 458 | cleanupModuleStream(moduleState); 459 | 460 | if (!moduleState.chunks.length) { 461 | els.moduleStatus.textContent = '没有捕获到声音,请重新录制。'; 462 | return; 463 | } 464 | 465 | const blob = new Blob(moduleState.chunks, { type: moduleState.recorder?.mimeType || 'audio/webm' }); 466 | moduleState.chunks = []; 467 | moduleState.recorder = null; 468 | 469 | if (moduleState.latestUrl) { 470 | if (state.bestSample && state.bestSample.url === moduleState.latestUrl) { 471 | state.bestSample = null; 472 | } 473 | URL.revokeObjectURL(moduleState.latestUrl); 474 | } 475 | 476 | const audioUrl = URL.createObjectURL(blob); 477 | moduleState.latestUrl = audioUrl; 478 | 479 | const isCurrent = state.currentModuleId === moduleId && !els.moduleOverlay.classList.contains('hidden'); 480 | if (isCurrent) { 481 | els.moduleAudio.src = audioUrl; 482 | els.moduleAudio.classList.remove('hidden'); 483 | els.moduleStatus.textContent = '正在分析录音...'; 484 | } 485 | 486 | try { 487 | const buffer = await decodeBlob(blob); 488 | const results = analyzeRecording(buffer); 489 | moduleState.lastDuration = results.duration || 0; 490 | moduleState.lastScore = results.total; 491 | 492 | if (results.total >= 60) { 493 | grantPoints(1); 494 | } 495 | 496 | if (isCurrent) { 497 | els.moduleDuration.textContent = formatDuration(moduleState.lastDuration); 498 | els.moduleScore.textContent = formatScore(results.total); 499 | if (results.total >= 60) { 500 | els.moduleStatus.textContent = `本次综合得分 ${Math.round(results.total)} 分,再接再厉!`; 501 | } else { 502 | els.moduleStatus.textContent = `当前得分 ${Math.round(results.total)} 分,继续练就更快语速。`; 503 | } 504 | } 505 | } catch (error) { 506 | console.error(error); 507 | moduleState.lastDuration = 0; 508 | moduleState.lastScore = null; 509 | if (isCurrent) { 510 | els.moduleStatus.textContent = '录音处理失败,请缩短时长后重试。'; 511 | els.moduleScore.textContent = '--'; 512 | } 513 | } finally { 514 | recalculateBestSample(); 515 | saveCurrentUserData(); 516 | } 517 | }; 518 | 519 | const stopModuleRecording = () => { 520 | if (!state.currentModuleId) { 521 | return; 522 | } 523 | const moduleState = getModuleState(state.currentModuleId); 524 | if (moduleState.recorder && moduleState.recorder.state !== 'inactive') { 525 | moduleState.recorder.stop(); 526 | } 527 | }; 528 | 529 | const startModuleRecording = async () => { 530 | if (!state.currentModuleId) { 531 | return; 532 | } 533 | 534 | if (!window.isSecureContext) { 535 | els.moduleStatus.textContent = '当前页面不在安全环境,浏览器禁止录音。请通过 https:// 或 http://localhost 访问后再试。'; 536 | return; 537 | } 538 | 539 | if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { 540 | els.moduleStatus.textContent = '当前浏览器不支持录音功能。'; 541 | return; 542 | } 543 | 544 | const moduleState = getModuleState(state.currentModuleId); 545 | els.moduleStatus.textContent = '正在请求麦克风权限...'; 546 | 547 | try { 548 | const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); 549 | moduleState.stream = stream; 550 | moduleState.chunks = []; 551 | 552 | const options = {}; 553 | if (MediaRecorder.isTypeSupported?.('audio/webm;codecs=opus')) { 554 | options.mimeType = 'audio/webm;codecs=opus'; 555 | } 556 | 557 | const recorder = new MediaRecorder(stream, options); 558 | moduleState.recorder = recorder; 559 | 560 | recorder.ondataavailable = (event) => { 561 | if (event.data.size > 0) { 562 | moduleState.chunks.push(event.data); 563 | } 564 | }; 565 | 566 | recorder.onstop = () => handleModuleRecordingComplete(state.currentModuleId); 567 | recorder.start(); 568 | 569 | els.moduleStatus.textContent = '录音中...让理论落地成声浪!'; 570 | els.moduleRecordToggle.textContent = '停止录音'; 571 | els.moduleRecordToggle.classList.add('recording'); 572 | els.moduleRecordToggle.setAttribute('aria-pressed', 'true'); 573 | startModuleTimer(moduleState); 574 | } catch (error) { 575 | console.error(error); 576 | els.moduleStatus.textContent = explainPermissionError(error); 577 | resetModuleUI(moduleState); 578 | cleanupModuleStream(moduleState); 579 | } 580 | }; 581 | 582 | const toggleModuleRecording = () => { 583 | if (!state.currentModuleId) { 584 | return; 585 | } 586 | const moduleState = getModuleState(state.currentModuleId); 587 | if (moduleState.recorder && moduleState.recorder.state === 'recording') { 588 | stopModuleRecording(); 589 | } else { 590 | startModuleRecording(); 591 | } 592 | }; 593 | 594 | const openModule = (moduleId) => { 595 | const module = getModuleById(moduleId); 596 | if (!module) { 597 | return; 598 | } 599 | 600 | if (state.currentModuleId && state.currentModuleId !== moduleId) { 601 | const previous = getModuleState(state.currentModuleId); 602 | if (previous.recorder && previous.recorder.state === 'recording') { 603 | previous.recorder.stop(); 604 | } 605 | stopModuleTimer(previous); 606 | cleanupModuleStream(previous); 607 | } 608 | 609 | state.currentModuleId = moduleId; 610 | state.lastFocusedModule = document.activeElement; 611 | 612 | els.moduleHeading.textContent = module.title; 613 | els.moduleBody.textContent = module.body; 614 | applyModuleSnapshot(moduleId); 615 | 616 | els.moduleOverlay.classList.remove('hidden'); 617 | els.moduleClose.focus(); 618 | document.body.style.overflow = 'hidden'; 619 | }; 620 | 621 | const closeModule = () => { 622 | if (els.moduleOverlay.classList.contains('hidden')) { 623 | return; 624 | } 625 | 626 | if (state.currentModuleId) { 627 | const moduleState = getModuleState(state.currentModuleId); 628 | if (moduleState.recorder && moduleState.recorder.state === 'recording') { 629 | moduleState.recorder.stop(); 630 | } 631 | stopModuleTimer(moduleState); 632 | cleanupModuleStream(moduleState); 633 | } 634 | 635 | els.moduleOverlay.classList.add('hidden'); 636 | document.body.style.overflow = ''; 637 | els.moduleAudio.pause(); 638 | 639 | if (state.lastFocusedModule && typeof state.lastFocusedModule.focus === 'function') { 640 | state.lastFocusedModule.focus(); 641 | } 642 | }; 643 | 644 | const handleOverlayClick = (event) => { 645 | if (event.target === els.moduleOverlay) { 646 | closeModule(); 647 | } 648 | if (event.target === els.authOverlay) { 649 | closeAuthOverlay(); 650 | } 651 | if (event.target === els.moduleCreateOverlay) { 652 | closeModuleCreateOverlay(); 653 | } 654 | }; 655 | 656 | const playSample = async () => { 657 | if (!state.bestSample) { 658 | return; 659 | } 660 | try { 661 | els.sampleAudio.currentTime = 0; 662 | await els.sampleAudio.play(); 663 | els.sampleStatus.textContent = `正在播放:${state.bestSample.title}`; 664 | } catch (error) { 665 | console.error(error); 666 | els.sampleStatus.textContent = '播放失败,请检查浏览器的自动播放或声音设置。'; 667 | } 668 | }; 669 | 670 | const showAuthOverlay = (mode) => { 671 | state.authMode = mode; 672 | const isLogin = mode === 'login'; 673 | els.authHeading.textContent = isLogin ? '用户登录' : '用户注册'; 674 | els.authSubmit.textContent = isLogin ? '登录' : '注册'; 675 | els.authMessage.textContent = ''; 676 | els.authMessage.className = 'auth-message'; 677 | els.authForm.reset(); 678 | els.authSwitch.innerHTML = isLogin 679 | ? '还没有账号?' 680 | : '已经有账号了?'; 681 | 682 | els.authOverlay.classList.remove('hidden'); 683 | document.body.style.overflow = 'hidden'; 684 | setTimeout(() => { 685 | els.authUsername?.focus(); 686 | }, 50); 687 | 688 | const switchButton = els.authSwitch.querySelector('button'); 689 | switchButton?.addEventListener('click', () => { 690 | showAuthOverlay(isLogin ? 'register' : 'login'); 691 | }, { once: true }); 692 | }; 693 | 694 | const closeAuthOverlay = () => { 695 | if (els.authOverlay.classList.contains('hidden')) { 696 | return; 697 | } 698 | els.authOverlay.classList.add('hidden'); 699 | document.body.style.overflow = ''; 700 | }; 701 | 702 | const handleAuthSubmit = (event) => { 703 | event.preventDefault(); 704 | const username = els.authUsername.value.trim(); 705 | const password = els.authPassword.value; 706 | 707 | if (username.length < 3) { 708 | els.authMessage.textContent = '用户名长度至少 3 个字符。'; 709 | els.authMessage.className = 'auth-message error'; 710 | return; 711 | } 712 | 713 | if (password.length < 6) { 714 | els.authMessage.textContent = '密码长度至少 6 个字符。'; 715 | els.authMessage.className = 'auth-message error'; 716 | return; 717 | } 718 | 719 | const users = loadUsers(); 720 | const hashed = hashPassword(password); 721 | 722 | if (state.authMode === 'register') { 723 | if (users[username]) { 724 | els.authMessage.textContent = '该用户名已被注册,请尝试其他名称。'; 725 | els.authMessage.className = 'auth-message error'; 726 | return; 727 | } 728 | users[username] = { 729 | password: hashed, 730 | createdAt: Date.now(), 731 | points: 0, 732 | modules: [] 733 | }; 734 | saveUsers(users); 735 | els.authMessage.textContent = '注册成功,已自动为你登录。'; 736 | els.authMessage.className = 'auth-message success'; 737 | setCurrentUsername(username); 738 | setTimeout(() => { 739 | closeAuthOverlay(); 740 | }, 600); 741 | } else { 742 | const record = users[username]; 743 | if (!record || record.password !== hashed) { 744 | els.authMessage.textContent = '用户名或密码错误,请重试。'; 745 | els.authMessage.className = 'auth-message error'; 746 | return; 747 | } 748 | els.authMessage.textContent = '登录成功,欢迎回来!'; 749 | els.authMessage.className = 'auth-message success'; 750 | setCurrentUsername(username); 751 | setTimeout(() => { 752 | closeAuthOverlay(); 753 | }, 400); 754 | } 755 | }; 756 | 757 | const handleLogout = () => { 758 | setCurrentUsername(null); 759 | }; 760 | 761 | const showModuleCreateOverlay = () => { 762 | const username = getCurrentUsername(); 763 | if (!username) { 764 | showAuthOverlay('login'); 765 | return; 766 | } 767 | if (state.userPoints < 10) { 768 | els.moduleCreateMessage.textContent = '积分不足,录制更多高分语音获取积分。'; 769 | els.moduleCreateMessage.className = 'auth-message error'; 770 | } else { 771 | els.moduleCreateMessage.textContent = ''; 772 | els.moduleCreateMessage.className = 'auth-message'; 773 | } 774 | els.moduleCreateForm.reset(); 775 | els.moduleCreateOverlay.classList.remove('hidden'); 776 | document.body.style.overflow = 'hidden'; 777 | setTimeout(() => { 778 | els.moduleCreateTitle?.focus(); 779 | }, 50); 780 | }; 781 | 782 | const closeModuleCreateOverlay = () => { 783 | if (els.moduleCreateOverlay.classList.contains('hidden')) { 784 | return; 785 | } 786 | els.moduleCreateOverlay.classList.add('hidden'); 787 | document.body.style.overflow = ''; 788 | }; 789 | 790 | const handleModuleCreate = (event) => { 791 | event.preventDefault(); 792 | const username = getCurrentUsername(); 793 | if (!username) { 794 | els.moduleCreateMessage.textContent = '请先登录再添加模块。'; 795 | els.moduleCreateMessage.className = 'auth-message error'; 796 | return; 797 | } 798 | 799 | if (state.userPoints < 10) { 800 | els.moduleCreateMessage.textContent = '积分不足,录制更多高分语音后再试。'; 801 | els.moduleCreateMessage.className = 'auth-message error'; 802 | return; 803 | } 804 | 805 | const title = els.moduleCreateTitle.value.trim(); 806 | const body = els.moduleCreateBody.value.trim(); 807 | 808 | if (title.length < 3) { 809 | els.moduleCreateMessage.textContent = '标题至少 3 个字符。'; 810 | els.moduleCreateMessage.className = 'auth-message error'; 811 | return; 812 | } 813 | 814 | if (body.length < 10) { 815 | els.moduleCreateMessage.textContent = '内容至少 10 个字符,写明你的吵架秘籍。'; 816 | els.moduleCreateMessage.className = 'auth-message error'; 817 | return; 818 | } 819 | 820 | const module = createModuleCard(title, body); 821 | state.userModules.push(module); 822 | state.userPoints -= 10; 823 | saveCurrentUserData(); 824 | renderModules(); 825 | els.moduleCreateMessage.textContent = '模块已添加,积分已扣除 10。'; 826 | els.moduleCreateMessage.className = 'auth-message success'; 827 | setTimeout(() => { 828 | closeModuleCreateOverlay(); 829 | }, 600); 830 | }; 831 | 832 | const handleEscape = (event) => { 833 | if (event.key === 'Escape') { 834 | closeModule(); 835 | closeAuthOverlay(); 836 | closeModuleCreateOverlay(); 837 | } 838 | }; 839 | 840 | els.samplePlay?.addEventListener('click', playSample); 841 | els.moduleRecordToggle?.addEventListener('click', toggleModuleRecording); 842 | els.loginTrigger?.addEventListener('click', () => showAuthOverlay('login')); 843 | els.registerTrigger?.addEventListener('click', () => showAuthOverlay('register')); 844 | els.logoutBtn?.addEventListener('click', handleLogout); 845 | els.authClose?.addEventListener('click', closeAuthOverlay); 846 | els.authForm?.addEventListener('submit', handleAuthSubmit); 847 | els.moduleClose?.addEventListener('click', closeModule); 848 | els.moduleOverlay?.addEventListener('click', handleOverlayClick); 849 | els.authOverlay?.addEventListener('click', handleOverlayClick); 850 | els.moduleCreateOverlay?.addEventListener('click', handleOverlayClick); 851 | els.moduleCreateClose?.addEventListener('click', closeModuleCreateOverlay); 852 | els.moduleCreateForm?.addEventListener('submit', handleModuleCreate); 853 | els.moduleCreateTrigger?.addEventListener('click', showModuleCreateOverlay); 854 | 855 | window.addEventListener('keydown', handleEscape); 856 | 857 | refreshAuthState(); 858 | })(); 859 | -------------------------------------------------------------------------------- /极速吵架王-薛/assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xherstory/WWW-5/42116870b03a142947018cfc6da6852257b0388d/极速吵架王-薛/assets/background.png -------------------------------------------------------------------------------- /极速吵架王-薛/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 极速吵架王 7 | 8 | 9 | 10 |
11 |
12 |
13 | 欢迎你,游客 14 |
15 | 当前积分:0 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 | 64 | 83 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /极速吵架王-薛/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg-top: #0f172a; 3 | --bg-bottom: #1e293b; 4 | --panel-bg: rgba(15, 23, 42, 0.85); 5 | --panel-border: rgba(148, 163, 184, 0.25); 6 | --accent: #38bdf8; 7 | --accent-strong: #0ea5e9; 8 | --accent-soft: rgba(56, 189, 248, 0.15); 9 | --accent-alert: #f87171; 10 | --accent-overlay: rgba(15, 23, 42, 0.85); 11 | --text-primary: #e2e8f0; 12 | --text-subtle: #cbd5f5; 13 | --success: #22c55e; 14 | --warning: #f59e0b; 15 | font-family: "Segoe UI", Arial, sans-serif; 16 | color-scheme: dark; 17 | } 18 | 19 | * { 20 | box-sizing: border-box; 21 | } 22 | 23 | body { 24 | margin: 0; 25 | min-height: 100vh; 26 | background: linear-gradient(160deg, var(--bg-top), var(--bg-bottom)); 27 | background-size: cover; 28 | background-position: center; 29 | background-repeat: no-repeat; 30 | background-attachment: fixed; 31 | color: var(--text-primary); 32 | display: flex; 33 | align-items: center; 34 | justify-content: center; 35 | padding: 40px 20px; 36 | } 37 | 38 | .app-shell { 39 | width: min(920px, 100%); 40 | background: var(--panel-bg); 41 | border: 1px solid var(--panel-border); 42 | border-radius: 24px; 43 | padding: 48px clamp(24px, 4vw, 56px); 44 | box-shadow: 0 24px 60px rgba(15, 23, 42, 0.45); 45 | backdrop-filter: blur(20px); 46 | } 47 | 48 | .hero { 49 | display: grid; 50 | gap: 16px; 51 | text-align: center; 52 | margin-bottom: 32px; 53 | } 54 | 55 | .auth-bar { 56 | display: grid; 57 | grid-template-columns: auto 1fr auto; 58 | align-items: center; 59 | gap: 12px; 60 | background: rgba(56, 189, 248, 0.08); 61 | border: 1px solid rgba(56, 189, 248, 0.25); 62 | border-radius: 999px; 63 | padding: 10px 18px; 64 | font-size: 0.95rem; 65 | color: var(--text-subtle); 66 | } 67 | 68 | .auth-meta { 69 | justify-self: center; 70 | font-size: 0.9rem; 71 | color: var(--accent); 72 | } 73 | 74 | .auth-actions { 75 | display: flex; 76 | gap: 8px; 77 | } 78 | 79 | .auth-actions button, 80 | #logout-btn { 81 | padding: 8px 18px; 82 | font-size: 0.9rem; 83 | } 84 | 85 | .hero h1 { 86 | margin: 0; 87 | font-size: clamp(2.4rem, 3vw, 3.2rem); 88 | letter-spacing: 0.04em; 89 | } 90 | 91 | .lead { 92 | margin: 0; 93 | font-size: 1.1rem; 94 | color: var(--text-subtle); 95 | } 96 | 97 | .interaction { 98 | display: grid; 99 | gap: 20px; 100 | background: var(--accent-soft); 101 | padding: clamp(24px, 5vw, 36px); 102 | border-radius: 20px; 103 | border: 1px solid rgba(56, 189, 248, 0.25); 104 | align-items: center; 105 | justify-items: center; 106 | text-align: center; 107 | } 108 | 109 | button { 110 | border: none; 111 | border-radius: 999px; 112 | padding: 14px 28px; 113 | font-size: 1rem; 114 | font-weight: 600; 115 | background: linear-gradient(135deg, var(--accent), var(--accent-strong)); 116 | color: #0f172a; 117 | cursor: pointer; 118 | transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; 119 | box-shadow: 0 12px 20px rgba(56, 189, 248, 0.35); 120 | } 121 | 122 | button:hover, 123 | button:focus-visible { 124 | transform: translateY(-1px); 125 | box-shadow: 0 18px 26px rgba(14, 165, 233, 0.45); 126 | } 127 | 128 | button:active { 129 | transform: translateY(0); 130 | } 131 | 132 | button.secondary { 133 | background: rgba(56, 189, 248, 0.18); 134 | color: var(--text-primary); 135 | box-shadow: none; 136 | border: 1px solid rgba(56, 189, 248, 0.35); 137 | } 138 | 139 | button.secondary:disabled, 140 | #sample-play:disabled { 141 | background: rgba(148, 163, 184, 0.25); 142 | color: var(--text-subtle); 143 | cursor: not-allowed; 144 | box-shadow: none; 145 | transform: none; 146 | } 147 | 148 | .sample-info { 149 | display: grid; 150 | gap: 8px; 151 | justify-items: center; 152 | text-align: center; 153 | } 154 | 155 | .status, 156 | .sample-meta { 157 | margin: 0; 158 | color: var(--text-subtle); 159 | font-size: 0.95rem; 160 | } 161 | 162 | .sample-meta span { 163 | color: var(--accent); 164 | font-weight: 600; 165 | } 166 | 167 | audio { 168 | width: 100%; 169 | max-width: 320px; 170 | } 171 | 172 | .modules { 173 | margin-top: 48px; 174 | display: grid; 175 | gap: 24px; 176 | } 177 | 178 | .modules-header { 179 | display: flex; 180 | justify-content: space-between; 181 | align-items: center; 182 | gap: 20px; 183 | flex-wrap: wrap; 184 | } 185 | 186 | .modules h2 { 187 | margin: 0 0 8px; 188 | font-size: 1.6rem; 189 | } 190 | 191 | .modules-lead { 192 | margin: 0; 193 | color: var(--text-subtle); 194 | } 195 | 196 | 197 | .module-card { 198 | position: relative; 199 | z-index: 1; 200 | display: grid; 201 | gap: 8px; 202 | justify-items: start; 203 | padding: 20px 24px; 204 | border-radius: 18px; 205 | background: rgba(56, 189, 248, 0.12); 206 | border: 1px solid rgba(56, 189, 248, 0.25); 207 | color: var(--text-primary); 208 | text-align: left; 209 | } 210 | 211 | 212 | .module-card:hover, 213 | .module-card:focus-visible { 214 | box-shadow: 0 18px 30px rgba(56, 189, 248, 0.35); 215 | } 216 | 217 | .module-title { 218 | font-size: 1.1rem; 219 | font-weight: 600; 220 | } 221 | 222 | .module-hint { 223 | font-size: 0.9rem; 224 | color: var(--text-subtle); 225 | } 226 | 227 | .module-overlay { 228 | position: fixed; 229 | inset: 0; 230 | background: rgba(15, 23, 42, 0.65); 231 | backdrop-filter: blur(14px); 232 | display: grid; 233 | place-items: center; 234 | padding: 24px; 235 | z-index: 999; 236 | } 237 | 238 | .module-content { 239 | width: min(720px, 100%); 240 | max-height: min(80vh, 720px); 241 | overflow-y: auto; 242 | background: var(--accent-overlay); 243 | border: 1px solid var(--panel-border); 244 | border-radius: 20px; 245 | padding: 32px clamp(18px, 5vw, 40px); 246 | box-shadow: 0 24px 60px rgba(15, 23, 42, 0.6); 247 | position: relative; 248 | display: grid; 249 | gap: 20px; 250 | } 251 | 252 | .auth-content { 253 | width: min(420px, 100%); 254 | gap: 16px; 255 | } 256 | 257 | .create-content { 258 | width: min(520px, 100%); 259 | gap: 16px; 260 | } 261 | 262 | .module-close { 263 | position: absolute; 264 | top: 16px; 265 | right: 16px; 266 | width: 38px; 267 | height: 38px; 268 | border-radius: 50%; 269 | background: rgba(148, 163, 184, 0.2); 270 | color: var(--text-primary); 271 | font-size: 1.6rem; 272 | line-height: 1; 273 | box-shadow: none; 274 | } 275 | 276 | .module-close:hover, 277 | .module-close:focus-visible { 278 | background: rgba(148, 163, 184, 0.35); 279 | } 280 | 281 | .module-content h3 { 282 | margin: 0; 283 | font-size: 1.4rem; 284 | } 285 | 286 | .module-body { 287 | margin: 0; 288 | min-height: 100vh; 289 | background: linear-gradient(160deg, var(--bg-top), var(--bg-bottom)); 290 | background-size: cover; 291 | background-position: center; 292 | background-repeat: no-repeat; 293 | background-attachment: fixed; 294 | color: var(--text-primary); 295 | display: flex; 296 | align-items: center; 297 | justify-content: center; 298 | padding: 40px 20px; 299 | } 300 | 301 | .module-tool { 302 | display: grid; 303 | gap: 12px; 304 | padding: 20px; 305 | border-radius: 18px; 306 | background: rgba(56, 189, 248, 0.1); 307 | border: 1px solid rgba(56, 189, 248, 0.2); 308 | text-align: center; 309 | } 310 | 311 | .module-tool-title { 312 | margin: 0; 313 | font-size: 1.1rem; 314 | font-weight: 600; 315 | color: var(--text-primary); 316 | } 317 | 318 | #module-record-toggle.recording { 319 | background: linear-gradient(135deg, var(--accent-alert), #ef4444); 320 | color: #fff5f5; 321 | box-shadow: 0 16px 24px rgba(248, 113, 113, 0.45); 322 | } 323 | 324 | .module-score { 325 | margin: 0; 326 | font-size: 1rem; 327 | color: var(--text-subtle); 328 | } 329 | 330 | .module-score span { 331 | font-weight: 700; 332 | color: var(--accent); 333 | } 334 | 335 | .auth-form, 336 | .create-form { 337 | display: grid; 338 | gap: 16px; 339 | } 340 | 341 | .auth-field { 342 | display: grid; 343 | gap: 6px; 344 | text-align: left; 345 | color: var(--text-subtle); 346 | font-size: 0.95rem; 347 | } 348 | 349 | .auth-field input, 350 | .auth-field textarea { 351 | padding: 12px 14px; 352 | border-radius: 12px; 353 | border: 1px solid rgba(148, 163, 184, 0.4); 354 | background: rgba(15, 23, 42, 0.6); 355 | color: var(--text-primary); 356 | font-size: 1rem; 357 | } 358 | 359 | .auth-field textarea { 360 | resize: vertical; 361 | min-height: 160px; 362 | } 363 | 364 | .auth-field input:focus-visible, 365 | .auth-field textarea:focus-visible { 366 | outline: 2px solid var(--accent-strong); 367 | outline-offset: 0; 368 | } 369 | 370 | .auth-message { 371 | min-height: 1.2rem; 372 | margin: 0; 373 | font-size: 0.9rem; 374 | } 375 | 376 | .auth-message.error { 377 | color: var(--accent-alert); 378 | } 379 | 380 | .auth-message.success { 381 | color: var(--success); 382 | } 383 | 384 | .auth-switch { 385 | margin: 0; 386 | font-size: 0.9rem; 387 | color: var(--text-subtle); 388 | text-align: center; 389 | } 390 | 391 | .auth-switch button { 392 | margin-left: 8px; 393 | padding: 6px 14px; 394 | font-size: 0.85rem; 395 | } 396 | 397 | .hidden { 398 | display: none; 399 | } 400 | 401 | @media (max-width: 640px) { 402 | body { 403 | margin: 0; 404 | min-height: 100vh; 405 | background: linear-gradient(160deg, var(--bg-top), var(--bg-bottom)); 406 | background-size: cover; 407 | background-position: center; 408 | background-repeat: no-repeat; 409 | background-attachment: fixed; 410 | color: var(--text-primary); 411 | display: flex; 412 | align-items: center; 413 | justify-content: center; 414 | padding: 40px 20px; 415 | } 416 | 417 | .app-shell { 418 | padding: 32px 18px; 419 | } 420 | 421 | .auth-bar { 422 | grid-template-columns: 1fr; 423 | text-align: center; 424 | } 425 | 426 | .auth-actions { 427 | justify-content: center; 428 | } 429 | 430 | .modules-header { 431 | flex-direction: column; 432 | align-items: stretch; 433 | } 434 | 435 | } 436 | 437 | 438 | 439 | .module-grid { 440 | display: grid; 441 | gap: 16px; 442 | grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); 443 | position: relative; 444 | padding: 24px; 445 | border-radius: 24px; 446 | overflow: hidden; 447 | } 448 | .module-grid::before { 449 | content: ""; 450 | position: absolute; 451 | inset: 0; 452 | background: url('./assets/background.png') center/cover no-repeat; 453 | opacity: 0.6; 454 | pointer-events: none; 455 | z-index: 0; 456 | } 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | -------------------------------------------------------------------------------- /薛律师代码1.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xherstory/WWW-5/42116870b03a142947018cfc6da6852257b0388d/薛律师代码1.rar --------------------------------------------------------------------------------