// ==UserScript== // @name 划词翻译发音:专注英语学习 // @namespace http://tampermonkey.net/ // @version 2.5.2 // @description 支持有道词典、微软翻译、谷歌翻译与 DeepSeek AI 翻译;集成 Edge TTS 发音引擎;内置笔记本和单词本(支持回收站与云端同步);兼容 PC 端和移动端。 // @author mini_soso // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @connect youdao.com // @connect dict.youdao.com // @connect translate.googleapis.com // @connect translate.google.com // @connect translate-pa.googleapis.com // @connect bing.com // @connect www.bing.com // @connect edge.microsoft.com // @connect api-edge.cognitive.microsofttranslator.com // @connect api.cognitive.microsofttranslator.com // @connect api.deepseek.com // @connect api.cloudflare.com // @connect iliili.qzz.io // @connect upstash.io // @require https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js // @license MIT // ==/UserScript== (function() { 'use strict'; const ICONS = { youdao: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAFE1JREFUeJztXXuUlHX9Jn/9AczuykUE2kUEy4CEFG9d1JQUrNAMj6WVxwzNI/qzOnhStCyt7KKmHLDIS5aXSgStSIFFCTQFCRAwAYG9zM7OfWZ3Z2dn77Of3/N833nffd933rlwmZmt33zOec4sc3m/38/zfG7fd+eww4aVrWxlK1vZyla2spWtbGUrW9nKVrayla1sR2H+mprh4UmTpoVrauaFTjrpDmBp+KSTXsDjWoXJk9MQBiLEySevjUyZYsXUqWsjp5xy5LBfj2sAYYd9qL1oe30ktfdLgEnRKVOGl5rXnCbDhh0Xrq6eFaqpeT40adIeIILNDwBiYPJkC8Inn2zFlCmDmDo1Haeckh+cPmu+NmFa176v1H659zCwBb78GQE1DT5+oNQ8O1pw0qQxwZqaO0B+LdCHDUsabEKEdWQSIJsQdkGyvcfpmuY1U/swET+Iwf33wq918HEBfB1Var4N89TUHIfNTcPmtgADgChkIx8I5yPA0WSAkyhHIoLdj5qaJLCJJVbOPLO02RAYP/44RMRNwFZADGCjaYAzQacyxPQnQIgFIEwBRFrw4Q/nB/vn9OvZ19HXt5Uffb+Ovmh+bgFuZACWToBJk2ZhEzuBvqwCmBzKSb5OlJ18O8Ef+YgzsolhvnYOEYLZRND8pM9bwcG0kpCfqvlbLMTnIt8sQDbynUh3IDtsQ05BsmWDXQSzAJlFUJmAqa+qqORz2mHDBQbSBMgkhn0S0h12EsFBgDDhQHouOJYkuwDmvdj6QAbSzSAHi4s6HQWrq6cHtYkgM/k2ARxH0TwFOFLyM4qQSYD0MTQfAYg1oYkTizMZ+aurh2PB59Pqfj7Rn63+Zyg/OQk+9VQN+YpgFyJTFpgCJg8RugI1NdeSm8ILUFNzKrALkKzAZgP2PsDmRsBJBTiuACIU7KXHqaaDbAs++tH054hsPSG1jrEu96DvidD3aav/9CmLz0+Tm2IIMBcIQ22xwGFTAYdMyCVCTgGcRDgc8u0C6OvnID+YjXzN//3AnMILUF19JzCQS4BAhlKUTYBQPgI4Ec4ssGdCvhmQrwApnzIKUF2dBG4thgDL0sjPRwCnMnQ45DsRngnZhDBngbkE6SJkyYAcAoivuvrBYgiwOp/y4xj92QQw1/9MkZ8iOExMm+YMJyGOJAuc5v/cAqwquAC+D31oPSAGtIUHgQ35MjRlPxxSgIN+OKpg7gEkBOQogCgL8o1+E/lBHebr8Npcx0Q+96D2wn0R+j4dGq/uX5rfGh/rCy/AxIm1uQTINBUZ5JsECNimIEcBMpGPiLcgkwh2AXQRUusGdAHsIvzXCKA7YhcAjgfM0a8LYCM/aCffTnwuEezZ5JQFeQrgH3ICOIiQVQBbCconA9KiP5sAdhHyyIA0AcwlyCGg0gQY5KLwAnghACAGsLABh15gAM4owDEf781feqn48ahngvoZpCiAoIBDFgRBqAUg2wLzaxl6AK+trn/22dqath4QmjtX/KefPrhfux8p/+irxXeNi8IL0HwMBAh9/vPS/cYbErvnHglddJGjAE5lKCv52UQwXYvCx5culc5Vq8SP18wCBM8/XzqefVYSeC1yzTXinzHjsARoLooAEyZYBbCJkFGAlAj+M85QDuqWjEal/Re/kMDMmYYAgQIIEAHxid//Xgba29W6A11dWhakBAiee650vfaaDHR3a6/jsXf/folceaVj402LfgowcWIRBBg/vtY7YYIYsAnh2BdMArQuXix99fVito5nnhH/9OlaFnAqcWrGZiGcylGO0hNCdHe9+urgogMDEr7iCqP0xJcvl4FEwrKvvgMHJHjBBY713ky8ArhAcBZeAA8EaNYW06Apr8FWjgyAfML3sY9JYvVqq6P9/RL91rfEhyhkb/ClSlEg1QcM6P8GscyW4JlnSgDXC4B0C/i6DtPnWW7aH3nEQnDLokXi4yBw1lnS39RkJf/QIWn90Y/Ei6Ax+8H3M5joq8X3kglgE4Hkh7/6VQlddpkEEHUBRFBwzhwJzZsnrUuWSLKtzeJoL6IsevPN0nLLLdLyne9I6x13SOsPfiBt990n7Q88IO3Llkn8scck8cIL0rV5s/Tu2SPJcFg6//53CaOX+EG6Auu5DgjTctttEp4/X0IXX6zWDn/hC0pos/gMhuitt0r3P/9p2dNAZ6fEkZWt2EPs4YfVz4mXX1Z7bcdeAp/4hBP5RRRg/HgxYBOC6di5Zo30NTdL73vvSe/evWrjfQ0Nqt6nGTJgoKdHPUoyqUpDVsN7eK22739fguecIwFMLCwl/NnPSCeQIf0+nyp1ag/vvy99dXXS53YjtPsGica67AVq3Tys3++XKILEB5FtxOtclF4AQqW6ydEjMpBCgpIdHUq43n37pBNR2I5aHb3+egl+8pPi58x/4YXS+de/SnzFCvGfdpomADKie+vWo1ufgQAf+kMh6f7Xv6TjT3+SyMKFakRlKSqZAE0QACKIBdiADrUZ1OsWNNv4736nGmwHykd/IGDxr3P9eonccIOErrpKgiglAY6jn/60+GbPVr3CC3K96AVe9AbCh58VcG0fSNYRWrBA+pFt+jWDn/mMilCWv3aUjwSI63juOel48kmJ//rXEvvlL6X1Jz+RxF/+Msg1iOZ7ApiUfJiMmrF+M9byYGT2mEuNCWafdR7ATREEOPHEWs+JJ4oBmxiWbGCk8HCDntAfiQw6jDrsR29QDY7vwWMziQbpJMDP6Ead9SGiVWM2C6CLkEIE2aD3lYHeXolj1AwiKyxi8TF1nWY8Rm+/XXrefdfYTy8abgt6T/jrX5fA5z4nzSwxaLqq0ZqbrSni04IQXICbEghgE8Fekvyf+pRqYkadZbQhI5glFIFp3f7oo+ps0PnKK9L1j3+oZtu1aZN0rV0rHX/4g7TedZd2OjWLkAIzjU1TN2YDn1Ovp6YqH0sVmnAriO/44x9VT1J9R692EJB9gqJ0b9smiZdekiiauA+TVoZmm0Z+aQWwi5ASwoPo7nj+ea3BpoyNMbFunfRx7EPE5mv9Xq+0/fSnqrQYxPKePqYtrsHGrBtFZLT7MV7G7r9ffdboSdgLyyHJZsliKUqgh7DOJ+PxwSEAjxwkVEaYybdHvlmAceMKL4AbAmAhsUBT34AuSOgb35ABNFGL2aacZCwmXW+8IXHU4NhvfiNtv/qVxFCrO1aulGRLi/W9IIhk633B6A8zZkjw8su1gxaI7sHk4+W3G770JenZscPyedZ/Pu8/7zzxolmz1hNeiBVB1Hfv2mVZk/vwoIzaSo0VKR7cJ5xQIgFsInCTPtTxDqRyxojGSBcHmS133y1BlAfVgEGCj6UGjyxPrT/7mZpCLJ/DeOlFbyDBBlJCsKH3IJJZRrwgjf2BY6huPZikvLNmab1J7z0ohep62K+P94lQHs3W9eab4kVTHjoCjBuXUwAfyFRjoMMoykaZwCGqefp08aDBESTD9/GPS+Czn5UwekLLD38oMfaF2lrpx6HLnjU86JF4NvgYRt7wV76ihCPpLDsUtBkTDA90fakJSUUzyo0PzT38zW9KG0oTyw6HA9VD9DOIba3u7dvFe8YZ5jrvSH7RBGg84YTaRiyWESC0AzV1IFXfOeKZDzqsv8HrrpMm1FXCgzIQ/d73pHPDBq05kowch7EITrQk2IPSwZtmzCaWJh+I4vMKFAciJltbLdHc9dZbWlnMdeDTP4NTsgdN3BLpmXwvhgAN2QQAoQFEJ+u6MpDfCYeTpimFJAeuvFKR75szR1pR8/saG7WpJE9SQtdco0j2Iur1z3BN3rthSWFp8eAxjjOA+dZDsr1dBQRPvxwCOtGsmY0dnL6YDTypmwYGWiey0INs0wXISD7QUBQBxo6thdJpizej7MRR8znSMYrpXPDaayWKMmAuRZyA/PPnS+yJJ7SpQ7Qs6cOk0rN7t3SsXi1tKCtslq0//7n01tVZCKFQbJicsIIQ0v5ayz33qIOUFyMkI94gFEL1Hjwo4ZtuUnO+noE63EAjiI5jfbO1YwzOh/ziCTBmTG3j2LGiREjBjZGQhDOy2DTDS5aIB+WATrUtW2ZxKL5mjbSjXOhzOKOy5cEHxY8pxsubXDyBglwP7z6CaNZps3EyakJ08/Xg175mmedpFDF49dXiw4m4Z+9e43n2gvAtt6ie08Tewxt4EDCC8kehOYHFcHLvtd0q5yDgdiLf5L+OoghQDwGQBVzMQCPICGDiCGGMC+A06cbpswFRw422owyYjUSoVEdEciqJ3nefNM2cqcoX0YTopHjNiGAfMqVn/37r591ucaPENPGkisYd/fGPpZdngFSk895RlAc3TDQsbbp1YtTllMPnYwiKBAKBIyevx6BhCdNLlNnC8MmIcDNM/ptQRAFsIqQBm6zHY9eWLeJkMaS2ByNhI7/wdMMNEn/xRTW/syypu5Ss1Yxu251KNkVmVlMqklU0oxfEHn98MGpRwoKYjIxexM/hPMCSZGQMb/YhYxUy3TjEe5ovukiL7uzEaxg7tkgCjBkjCroQdugbAjm9pihUhsjvBhluTBZulJsoarZ5UsllzAgfxlVFvqmGBxYs0G5tpzIgfPPNVmKxLntTx8svS/TeeyW0aJEEUMJYxoI4MEbRc1iyzJ9JooG7UapsJGdDcQQAxAAWdgRLE0jut51m+4NBiaBHsEQFMU52m26K5WMcOxOYWgKo800gxz1hggKnL0ZsN3pA4MtflghEME9VLH1tjz2mIlqVSAhoRDX24sa5pONvf7P0FJ5BGtCP6IuBTP5qKLwAdaNH19aPHi0GzGLY0Ix6ax7rGIERRLybUwjqfhcPaymSWAq633lHRSLJY2Pu5S9QMhmuy+yKoIfwmt07d6pJKoDJi2Ut9vTTlkjmezxo8g3oHQ32es4pZ/Zs1SfMJY9i1unkZvFTAVyAmxIIkEWEACLcbKzxHhz5mcrNmFJ4G1i3jvXrxYsTrJmYDoys+aXFgKr3AYyYJJ+Exp55xjgMklRmHkXipBPBeSG0eLEaGpTYDzwgiddes/QMWhyZli/5pRUggwiMOrOR0EacUFkvWQrMI18LDmTuGTMsAnTZRtBsBzVGeRCE6hHunTdPNfo+j8f0pqRq8n0+n8oers/XWWoGHO7MMoscCR8/XhrgR0kEOAQB6kaNkjRoGzBwCHU5ZhtBQ7ffLnXYMOHGBKQITpHKiG06+2yjf9QB9v4RW7lSfGiYiQ0btGi1/y6XDR49pQVTUBPKTT3EqOOJGyfn1uXLJYEsYxaS9D5kRD8OjWr8xCNFYJ/owWGt8+231VnFd9VVFp8a+L2lhQslvnateC65RHvexMGh448voQA2ERrPPVcSr78+SA5qtvuCCwwB6kFM66OPGiT3+f2qNBgNDRFmvy3Q8tBD0ohm6cZJN/jtb6uy5ZQVjGbWbwpB8rkW0YgDHIVpBnneyy4TH2/ioXn70NC9X/yiIrXpvPOkEdevR/NlECh/EPFu/mIJp2RmT9uzz2ri2vwvvQAmEViPGU1G9iPS6qdM0V6nCCC5CX2AJ2O9PLAn+NFE6VwjDmJ2C995p5b6HAdxLc+FF0rrb3+rxEvLBtb9SERlROyppySIUzCJV8Sl1q/nWSUF/lsPDpXBfORXKZk9K1ZIYtMmdYBkWfJgDHbyfWgIANRj43HeEdVHOkQp0/aQOWVTQtVjJIwhovp5FuCsjs90Yx7v3LbNGtV43o9Tdp3eFM3jLiaqwI03SmLjRq00ZegVvAZPzSSzfdUqaVm6VCKYusLoVeG775YwzgdRNGSWTpahXvaP1G1qihn87ne1zMjgd9EEOMTFssDN+zD6hAMHeHjyzJ/v/H6KwF+qoBREH35YGz0dvqfDGt2MsmFEqQMa+Et9lJPI/fdr2ZfP93303wFk+E4Sb0+0Pfec+CD+IQieze+DQ0WAIEY8NjZunpEfQPrX4eCT8TNMeYyODajvvuuvlxb0hjhOrD11dapHcMIhoSw5lmafKhfm0sG63cBvQoCw1ieflC5kkv6F28My9B826ggygv2MQ0Uuv4sjwPHH1wKSDdxwE8bMBpQG1tJcG88IiFKPxtnIO6uckNAMc5W/tLLAXoO9MCha+Lvm2lrp2r5duvmNOYyi6ht8KDUUuAsHwcTmzarmMyPr0YyNveTwmRgyAqThSAXIgLyId4pQZggPaqedJo3nnCNNc+ZIE84LTXPnivv886Xh9NOlfurU9KDJ08+iCIBFag9qix0+SMIxQM5SkC8giIFM7zk8HwsvwIGqqiMX4BgLUTAcoV/gpggCVFbWHqyqkqPG0YpYSByhT8URAIscEwGGoghH6U9RBNhfVfXiAW2xYwumcClwDH14v6pqdeEFqKxc/n5lpRQMmiOFRwH2vq+y8qGCC4BF7sJiAwUVoZBiFG6/SQTnbYUXoKrqUiwWKYoARytKEfcF8g8gOC8uuADvVVZOxmJvl0yAIQqQv4zcFFyAXSNGjNhXUfHC/oqKPkDKUOjaW1FxHbkpuAC03S7XWRBh4xBwfEgAXKzbVVExtijk01YOG/aBdysqrsbiyVI7PwSQ3F1RccXKYv+39juhONLurSFAQElBDrZWVBT3vy7WbfeIEbP3uVy7sZH+UhNRAvSj9OzY5XLNLAn5tFcrKz/4nsv1vxBhx36XS/4/AT5vh++LNowe/T8lE4D20qhRH9zhcs3CZnbu0zb2344kfN2yc+TImWuGDSvd3w6w2+aRI8fscbmW/NvlWo9Ndg8Boo4p9sInEL9up8u1YKPLNXT+hInZnkJEvOlyjUNdvPFdl+tFbLiBEVNq8o4y2usQWCveGTly4VsjRox9aqj+ER+zbaysdG1Hc0K0XI4zw73AE3sqKl6BKJuRIa8PZXCPGLFfwZ4f597fcbnmb3O5pm4YPtxVal7LVrayla1sZStb2cpWtrKVrWxlK1vZyvafbf8HibbecOZtcYcAAAAASUVORK5CYII=", microsoft: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAAAsTAAALEwEAmpwYAAANeUlEQVR4nO1caVAUdx6dL1ugguAxUYQAasxWJZvsJpVNvmzigkRRBDUKgqAgzIpropVkk2iya5RDjFwCgyAw3IccAwKiKBJB5PYEAiiIRhQVCCo4KJf8trqHnum5u3umG9R+Ve8D5cyUvPfm1+//6xk4HBYsWLBgwYIFCxYsWLBgwYIFCxYstMRbqa4zF+XwXCyFvDhLoVe9ZS6vxzLXa2Sh0AsQWuKZg9FTymxPsJDhVrDIUqR5lgeYZyrhMYTu8CaeGRi3SJm+BcxwNE13GzFLdesxTdtcb5bqFmuW7uY8R+BpyHlZsDCP97ZlLi9+odBrcGEuD1AKecREz1EiuhLhzRGqEd1cqejqhN8sZpoiTVPdwDTVddA01VVgmu6yhDNVYZb9zTTLXF6wpdBrVCq87tJuTkPaVYouEV6WC1JcR0xTNgVaJLrrc6YSFgm9l1gKeU2vRtrdZJmC0RXlghRXMEneVGOe6mrCmQqwyPH8AJnvRNNu8ZKk3VROdJTJUpokudw1TXJ+fwok36vnVU77AhnhN0mZtAlMEl3uchMd50+K+MgctMjxuvY6pH0BTnRUeAldwCTB+ZJZtuM0xg2wzPEMfp3SvkBe+EQcE5x9GBV/4THe2xbZnqM6Ez3zJUl7onLOT3B+yugosszeGv/apj1RknpEeAnnJThHMXbCtcjaOji10u7GZNoVGe8M8wUbRXMEDvSfmC2ytrq8jGn/Z/4PcP5+E/hfyoB3MrdplXaJ6Cg3SjgvYeNGJgyIo5L2qJaiSU37u5nb4MaTe4BgcHQIIhoL4M8ZPGppx4uOUIDRKYYJA+qppB1BVHMR/WlPVj3b3z22DdomTEBQ332DetplhEfFh3kCxxraDTDP8uilMtsxhDYdJ5X2RRlbdTrbPX8NlfxfHgz2aZF2THQnmBcn5htxTt20G/DmMY9hKrMdj9DG4wppN0/fAutL/CG2tRgu97bDw2ePYHhsFH38s9FhuD3wEIo7L8H+i2nwaf5/VIru/msw7KtPUTnbzZJcobyrAVoe3QHHYj8t0i4VfkJ84MY5DtFuANUmI4/QxjxUeMt0D9hdm4AKTAbI+Nj8a5BC2tufdKH/7njmAOXZ/l7GNqh92Ao/VSdqFB1lrKOE9BtAsckoQ0pbKbQ86iQl/MDIM8jpuAD/Kg+D97K2y4yZvwt3SR4XdCUH/pb5b1hdtBe8y8Lh57pkCL2aCwktZyCvowrK7zXA+a5GlBe7b0BDbwfK5r470PusH32NsfEXsLtKACbIO0BBeKnojBpAtcloiztPe2BPbQIsTvdQWR/9LqYDHUAMUpZ2GcZsAG7MBgYMoNjbqWL0xRgEXRXCwjR3tb3dpmAP+u7A8Mfzfqh+0AJnO69Awa0aSL1eCtFNRcBvKEAZdi0PNSz4ihD9OaW1FLLayuH52LDkNYbGRuD8vUb0XaBcdKnwXOYMINnbJ5oLFfQNDYDDqf2EDku3+h/IPBcZO1SajGjkOfr83JsX4O3krWrTLsOjYtJvAMXeThZPhkVglf8D4VNqZls59A8Pooes2N9OgUm8C6Ums6MsAjzPBhNKOyY69+h6CWk3gEjalfV2MhiHcXA9e0irU6pFkht8kr0LVhT8BM6nA8D7XDh8VxkLfvXpEHQ5B/jX8lFGXMuHtsf34L6oD7aWBJNKO174udFi0m8AqVOqq4RkL3om2uxkBBuh/bG4jpJFbvsFwmnHCy/mF8wZoC7tyk6pZBHZWKjVKfWvGd5QcKta8npIwgtv1YBPXRp4lARB8OUc9AKPH3nC9gp0/BBNOyY6yigxaTeASNqVnVKpgN9YQCjtCnMdN9s/ztwJ76d7o73948yvIOSyEHaWR8LIC/Epu+PJfdhZFglmAhfSaccLPzkGkNjJUAUfMYHiakC+t0c3npB57aSWEjCNc6acdoxzUK6DOUfWMWAAgbQrW4ZpA35DAaG0azqlHrqYJfO6mTfKFERfkbcbbHK/Jyi8WHQ8GTCA2t0lbcFvKCCVdoRvJbnDR8e+hOV5e8DppD+ktpbKvOb1R3ehoKNawqJbtfBiHOlg4xDdUAgLYpyUp11OdAkjGTCASNqV9XZdgN+QrzLtsU2noO7hdbRS9j57InOBpYqa+y1q046JPidyLcyeIAMGULmBrZt9+zw1aUdWBuow8mIMHoj6ZFpR6GUh/FARC1+diwDPkmDwPCOlx+lAsM39UW3a8cKj5DNlgK7vpQq0m+1Ib1+cuBkqcCb83v8QctoqYE+lAJ3pJrFOYC6QXouQcaNstuMvqERFl3IN/QYwn3ZHjRtIjIgR/8j6Bt5J9lLaZLjRG9D5jiCmoYjUbFct/BopIxgxYPLT/gaJ1QC+ySxOkG5ld52L1DrtGGdFSEm/AVMk7VwCwjsV+aE3WVKaS+Db8mj47nyMxICP0naoEH4t4bTjhZ8V4QCzwh0YMoDBtHMpph3hitzdMDA8qHBBbuq9rZO0o6JPCI+RdgN0kXbzRDewP7EX9temoPuZ5r7f0eqIdHAEfc8HoP7hdfCtS4MlSe5anVI/TNsO9Q+uyxiwozRcJ2mXp3G4PUMGUEi7pD7GOUKX6A/CXfzxkAjcig9qTLu6JjM/egPwrxyHJ0MiOHK1QGdpF4vuAMZh9hLSb4AOZvvVnptABmPjL8CxyJfYTkZlk9Fx2nGiM2rAPJJpVzbbT96ug0fPB+DYjTL4sSoeXRM4FvmBV0kI7KtOhgtdTZK6iKFzoEe8GtBxb9cm7RIeRrgaJf0GUG0yuPr4SeZO6RbyqPLZjiQeub2Ix6aTAVMq7XjhjSbIgAHk0q5Nk/m5OknGgMCL2VMq7ZjoRqFS0m8A1cMShSbzWdY3MgZEXSvUmHaf6hTQBcgJbych7QaoF56K6OtVNhmkQuJx5FoBobT7VCXrxgANaccLbxRiBzNDmDBAh2n/IG078EpCIORSDiQ3l6AUNJ1CL8QbT/jBZ5lfy4gScTmP8Gzfr6UJRNKOiI4JL+YqBg3QIu1fFO6DugetGkVAVsh4HL4kJHVB1cYEImnHCz8zWEz6DdBitnOj1kPEleMKFZMoguqzSDeZ/ZXUTCCSdkx0PGk3QJvZfqBO8cOzj4eeQnLzGfA8HQRLM7+FD1O84dNjX4N78SFIaDot89iDtRmqRVfTZPZXJlEwQFnaV6kU3jBoJUr6DdBiJyN/m7DwZjUsFmxW2du5kV/IPN6vOpVyb6+/30rOAAJpxwvPoAHUdjLhV/JkfsGzv1+GuZHrNF5QsW/JINhXmUSpt285EUD6HUAk7TIMFJN2Awh/Tkaur1/tlt3/2OR8T+iUil8n772QSPqUujJ7NwxOfOKZlAHywgepE95WQoYMIH8vFVkxY0BSjaafr/mC2jP4WPI8f2QEkTilepz8BZ7JrTPIGqAu7ZjoBggPiUm/AUQ3kBOHJWy2I192wPB8dBjmoslfo7HJ3OnvljwvpD6bkPALo10gpakEtAGRtOOFF3MFMwZQ2UB2PZW9B+CQ9z9CTeYm7lPOR67kq10NWEa5QEB1Grpp1RZE0o6JLuEvDBhAJO3KZntZ51WZXxD52MhHKduViv4Gfx3wioOhoadD5jkJjcUKwi+JcQPeqSAoaKuSeZcRQWN3BwiuFik3gEDaMdExzmDcABIf19hxNlzhlxweG4XcGxXw34p42FXKB5+qFChsr4b+IcX7uAhO3qxVWA04Ht8PNfeaSQnf/ugefHk6DIyD7dCUB1SlKTFAc9ox0cVcDjMOLmfAgMi1w1T27XP5a6HirvpPr8kj9loR/NZ7W/JzU88tlauBD+J5sKcsFjUQEVg08YU95EveXQO9UNfVCuH1Qlie8R0YyV9cAxVNIJJ2THQJAz6n/4vas/lre0nf5JgYM2ZRTpDWfBa9xagKL8bHofT2JViR9T2a9HN3pKMLuaerdjUg19tnh9qTajIBlVITiKRdntMP2ND/pwpm8dfUaxZe+QUV6+1/ifeEr0sj0e1mUtNpyGw9B8F1WeB9OgTeOupKegOp9JSqTHQCTWZvebzYAE1pl00+TBeT/j/WMZu/JpZI2nVxd8mIRNqp9nZlsz2gMlVj2nGiS+lvc5R2A2bxHZzJpF256MTuLhnRnHbCs10iugrhDyC0gWn+nzvSbgA30tHAOGKN6FVJuwGB2a5adLHwKP2XiTj7lhpwmIBxuIPgdU/79AlOQ5NvA/r+y2I5TMEwzG7JrDD7kdc27QfEoku5bFjv4LJFHCZhHGYf+CqlfTqJtOOEF9PX+iCHaVgkLtU3Ory6Zsql/Re6044T3g+hdTUn3FaPMxngBq6ab3TYrnMqpX0G3Wn3k1LPz7prmr+1KWcyYRxm//7MkNV3dZ922ymYdjH1EfF9rTr/5GvzHmcqwCDcljszxO68LtNuMMXSro/R1xoRv3r6vqWT82frVSLcVs8w2M7HMGSV6OVM+zI1wlujwuv7Wg3r+VgHTNrMJ4LpgavmGwatijIMXiVSKfpLlHZ9hD5WIn1f6xi9fQxXTW3AjVxqMOPQyo2GQSuPGgTa1hoGruw2DLQdnvpptxrW87Xq1vO1rtX3tYrW97VyYuyEy4IFCxYsWLBgwYIFCxYsWLBgwYLzKuP/ix2SJ0OdDUIAAAAASUVORK5CYII=", google: "data:image/png;base64,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", deepseek: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAADmtJREFUeJztnA2QFMUVx7k7PgVE5VBEETB+m4gGLUEMlEYNJhx3O72LciJYxgB3O7MrlyApBc+QSEw0xpgoxqBlyhgtNRot1GiZaKmgEknwA60SBRFjVCJ+y4f0dH5vdw+4u9m9nd2dvTWZf9Wr2b2b7X79Xvfr915/9OgRIkSIECFChAgRIkSIECFChAgRIkSIECFChAgS05tMT2Xr6Zatf2XZOw7vbn4qGg226Wk5ZoBy9BDoQAQ3MpZwD7UcfTCfh/McVJ80VX7KtOJmrHLcJynPtRw3EUmYPYLi/0sJ5ZgaZbu1VlwfZdluHQq4DIHdj8DWIvSP+GwQ/FY+v8az1UqaPfMte2qz6UM5LZTxqZRDPX+GhgfZnm5BLGaqGhzTH2EOpscNi9pmhJUwo1TCjORvw6F9+T7Immd6tv2G73sq23wVAVkI5xcI+Dmen6cE1Zk09CIKuDiWNHvny5eKu19B4HfsVs6mqOOeNjVpaoKRRJkQaza9EN4B2NVxlgjQ1rNo3ELoKhp8A89bodugP0A3Q9dClyPsJO9P5/1Gvi+CHoY2ZxH67vQ8v5s8edYuBeYD6jlZftuhrLvh8bCgZBMYYnNNFT15H8zEqQh+Ho24hcYshzZC2/IQYltP/iBPobfRDuqbWgjPKG0K9H6H8rajGDqH/lZdwvQvlXwwnaPh80JG//GlKnMn1FwzAMHT092lNGAV9JEPARZFNGp9ZI7p65fnWKt0GHeaKNCjXDc9MvR10OmxWaZPMfKxbHMC5d0LfQy/DxVTVufCHfN1eszvKfzVVO8pk+B3kX65EL4bLzQ12Pvzuih/W8q82foKmbP81jFrlqlGNhMp40FoS6bDfFIIv50wyTG9GL5JEQAFby2/4HfSFpU0R/jlv+4ihCPzTdfly2j4kLbeQk/Oa27AHNdEHDMKq3ARv/1He/noN3wLuyMY8oMoaInyZ6uDM0OOfrixyQzw2w56Z5R2bMmznk94fzGC3W1eMFUxxxzH31ugO+jdy3g+Aa2GNqi0KdbtyrH1o0UJn0BnCIXc0829viNtTzU+bg710xYUdwYKeNNHPS9QzyT5bSRujkUOd/K3j1Xa9OoMuRnyLgMlFiz8hmZ3P5i+rcKE30YujVuHQM+ta3HzmpR5/3h+95SPOrbS/h9Sxww+ry+Ex4htJhUk/GjcHUzFv1ZtUWPl0n/g83Ir7g5TzaY6V5swJ8Msx/1tzh7bmf6tCjS9KO89YpV+voUfS0j+xZ2bqby7BZwPib3+HW7x0djorMEZAWMNyprN+x+Wgy8UsMS38K1UAsz9NgWsrgDB+iHsMjbado+34qZX1vY5+jjefUT5GwWF0GaCsHG+FRCVTGPa7uuAGQyCvkAB90InqiwjIWabvlZ6dL8bIB/ITl8P+fPUYK4/wUqTqhB3s9DGS/QZibsn4Ll4pqqVbQ5GSZKfypbsK4pQ8Aqex8aSueekTuCHR8CYHy+hUkmU8Chu6rFe7SRiraJ3jmXeeEzJqClt3WsghSn3ly7hB/1QgK0yIfT/AIkSllsETl7tndYic53+Bu9Ih/PKDxVCL6PYmdGEHuhL+GkF6P2hpytAcKUkFyE/jfs52qvNdQmUENejeec+VdykvA3LIRP7mb7tviAWN9VR2z1NdUtyLXDagYBX4Bkd49n2Waa6vkkPofNdV5CCbf0W5S+wbPcgvJ6s3ldOUEhv6IYKEFZQJOZoZTRhTswmA7ymk/MtD4GLUldh3loijnsgn30tDHkpoL/k2bMxr9ITlaRqt2ZoW+ZvQfvSJSXa+JKyd0SmJd0+eHxV7WWwY1EXQqenu5cxkiYS0Pk3M7nA0Dladgl0qBSfWm+EHrLEp7X1Qty3pvTqF6G/o5fyfIrvr6t0xCxJqlJNZkHS27S1lechUSe98NLgmF787aXcv9N3Q8NKKvg2qLTv37FSmdHr6pO5Q3tC/6G8NwlF/AiFPKDSbtjmCh8dMnofg86F5yPo2aeorgJPWxPg6QMCUoCn/b8P19TXwkdjk+nLaBpLeZdAooz1XTascJJyZX1XFkCeySj+A5/1bUGof4PXZ7rsMJKSD0wBtv6rR6UPooCvFVpmQzy1jjBdVpZUevmy1IrA7OmFTIQTrIQZoyR/ZesFPBGo+1nJFW67N0H7lVLuO4GQXveodDkV+k8m7Qb87KoGx61FMDHqkND/7VL1fsp76qykGbR7fbiUElxJzn8JyilpGh03cyEdMu9NYL6A/fbK/bwkOwRKUT6TXA12diRlyn4hiTyLjTfEXGxAsa08z2YUjI3EdwmH78MR2OLUDrpSKdx2z67PkWUtCjTEi9GNKCZS0nocs0fUkUyllkWRjvtzfAtFpfP661DuKgS0SHbdtdWFQoZY6c1fRTsDyOFdaGIpZdEOFO5ln7fQgy6Y5PjM6HWBaS2mmvB/KAJrofxiTJIsFz6Mmbwqo4RNVkI3Tpm3a5uhbByjc/2xaAU4+lnIM51REsjelSyVXx6Lm72CqdPsEbHd76jsAWBX9A5lSNbxYD7fpFJ5H/cHuM3tlgBVsx6ZCsCKUoB7e8QxI4OQQ0YYWXviXTTwkKDqxUxUx5JmdCrg8y8YGaH3q/Qe1LW0QRJv34subD9i8cZqUPRk/pfvdkkvWhBJGP8ZznwBcy94Vmzr56GxgVWcAQHdKGz1K6qo4E1vox0zvNtnBkN+F+Pb6H2UawUqAJi/N0vln1qymWlO8Nu3rYR7VCYdUHC8EHV0y1SPhRDmsSqUM57yCxlpT4hrG2jj6eU/z8HAL6EhgTLQI22OInF3QsZeFzoSHmBSPg573anDYEqH8f+lPstzKW8JvAUTgLVBxc2MHENbNqsWHBH7AULqg3cUo9HrC1QA7rReShlj1OxdPrsEhEykw/m/35z/h3TO5unfD9gCROOG4Z81k7mdBk2jF/QOlIkMqGdv6ptPvZsKVIIk2iSDOxs6BZqAUs6CZCRv8FmWZHuLygbkhalxPYCK3sjGCAK5ExocOCMZWM16BD3vRlXc+rQEaZKoWwm9VYBZk+D0GqupfbojEDDk+0C5ApbNKOCbYqcDZ6ZHepkwaruSVX2sAMGVitZitmLlaK/MATXKNufmYogRcpcsXZaFoR6p7ZF9MR/fpe6sIzNA+kLaSyQ9tFztlVFwGD0u117Qz2KOOalsDPWQJJ6WVMJvVBDp5dz0RsRxG88pcRomJxgBgzO7xbIzZutHWlvLyFQP2SrpHs1IWF5GUyRnD+6ZOifAyNcLRKM9lRyMtnWunWIuruL0svIlLqS9Q44W/atMCngr6pgzytnGnUD4h6S29OVkUG+sj7sjysnXxFaZo1LLpkHv2pMlyivL2bZ2iCVNP5iYgxJybVqVc7p31TcFkyXNhoa4kZ17Xa/dFkGUvyJyYf4n7wMBPVxyMg920dCPcNEW4CUUZCcbkqY3pqw2mjCDYj4u28BbO5263wlGAXpDpMxOhidiqXRAyv3L2VDeeZPY4HwE6XuTEh7GySjwCsq5lIafGbHzdfdSu5olSi71san3VJZMarcAr2MkdHMXNlfy72sQZCM909cRf5S3WO1yLeXUohxmmIIyu4w6G5swk3ZqSbNUm8CImPWl0fPcyrmwI5O+PRXGVnZhimR3wmpGwnTLyX/HgGxmVZ3PaRF56p+ihNFy20qu32O6RlLnshII/3M6w9WMyLKlWfKGXGRE746r9OUbuRqh6ZFrcGGbUMI++ZTNu3IG7RWPssS0PJS6QaU5+2ignmp4O4l3nyhC+OLvL7Xi7iir2d+lT2UDDa2F0atV15dxyCh5E6EstpJ6f7kYI1e5zDN7ZxbLvZYJJQ5ZJ4vtkRzLofDWm/omqXTCzbcC+O2fIrZ7TCRe4XcF0UsOzJyUz2dN9QPeFw9KDsllVULqiFD6PMK6HGWJp3U/dY8/x2OBJcVbXEu+SKn0tkSfCtDP8qyTnFNw0isBJs+SSNQdAcOPq/yWC7+wJKdk6/mMhOzHRW255EmLHc81mcpdPq9EHffsWNL7wPPUhJZryCK8672unYNPlV7vXaZsM/nIE8qbYvGNWLMZijBe9NFAmRuepXH19OBOF+NhhuTyvdUqj8DKkgs2bHdRNIvLiwIYUVrMUcfbsPIhqV8OXLxKORfBVzAbcEsBlCBHeZ5U/k4Wyp0OmyTBhUn5Cbb7EtnoKgce8hF+O7L1I1azeyTPfjGPiZNRNY7/PZ6nucxC+lN4u52OE/hukILQ0OLuA5Ny8d3HhTeycEKRsk57jYq74/h8EH+rZWTIhX/9o3HTN5ZaXpXDFEWlsMW9vnXmTH930pUNaq6uFZ9dpddYu+VEPcLfAv1dpXfFydWWLYys2VHZAGy7F2eOXEkG9T3fI81xP+H3V6KAyvWQGO4DmZxn0BtXFNnbgiBMZGo/5wVyT2h6dSt1el1u+5K4RoLA7WrXXT+y/iubAF6DVsqRLNrneaqyotAQT+WNxqvUfvzU0mEl3C8hns3tRLfjI7jCU+aZqshcMxC3eIysd6CQ8/n//MwIvpqn3FH6Y/6WlNsYee+khoD2wwYGPIf9lYyG9E2BZbkKxoOkR0vqZJ6VILpNVLhbWWqkLm919JH0ogvoVcvERpdJ8DvoweJCXqkS7sRossRHSL9sqG/CLDn6cDncgXBu9HlHm68ej1v7T+YhMR8Too4eImcPurv9FYP6pKlhItsXBYxWcl+n7d6CoNaqIm4nsdI7oFfhcl4rF8fy+VDq2Kve77Uw/09AQFVQbxSwZ8TRQxHkaAQ4TY4PoZDb0qdc9HNiRvj+fIaeg/6SuiDWdn/G+8moY86MJLHtjq7l+8BIvEJ99ErGxFZTrRKS0jCnI8j5CPnG1E0l4sraek1G8EIrrPTB6OtRwCUocCYKGDN5XoUnzkKECBEiRIgQIUKECBEiRIgQIUKECBEiRIjKwX8BMGY3rSJWHYAAAAAASUVORK5CYII=", speaker: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEJUlEQVR4nO2ZX2xTVRzHr5EENbwaHxQfNPGNB/TFB4kvGmcC9JyWgkgUeTBGEjQBA/jC3TltgQEz6QDFGTJDMlEWgk6JxAwHATXojH+iEQJjjEE2GV0ZWEdXfudr7sq9Pe1u73rJ3WxJv8kvaU57e36f/n7n/H7n1DDqqquuuu56mWE8Jhh1CE5XJaOvEovwkFErkiHMk4wGJVewTXA6ZNSC4hxPC04juvN5o4xR7YqF8ZxkdMNxOlIMYVSzRAiLJKMxx+GlCpvW1ghAI8cKySlnOxp/SaHJVEgIfwAyhHmNDMuTDZg9c788x2rJiGwnN7+isHuHQvI9he1bKgeIR/GwHUHBqW1GnJccG3QHm1YptO1R2Nuq8MGuPESlACbDE4KTmvgsIzI5npw2x80o5ghGLbpzzW8odLQD+9sV9rUVIPykkOB00Pk8o2/9OfUsZokQ1ktOPZJR9nYor0uGpfkvx/Pu26NCy9sKh78AOg8BBw8UQ/gBMK3ix+lmAQJPVe48p25X51bTpb5efLhnHZ11e7/1XYXuo0DXN8DXhydDlAMwo5gjOe20zHqtRaFNi8LuO8pl27YsVzjZCVzsB74/AmxdkR9PLFPY9ZbCl/uAUz8A350Ajne7Q5QDiHGscao0I+mMMzyjFb+0uRL3VQBAPfZDXe1AahgYSQHDV4ChQeDypTxE33mg9xxw5jTw5x/Ab78CP/d4Q5QDkByLNYAr9tYJA/dIXoi2CGHB1AC3c96y0TRwfRS4lg4GohzAgSjuFZzOFyDwovaDthaew4YKIlCYJJMB/rkRHITXIpacmrQotDjjIbympdHnvgDGxhAohBeACOMFrVs94fgTwXwN4BdfANksAoXwAohb1bew4wza47EQ5mrjA74AcjkECuEFkGzAbC0CN+3x5iju11LrX18At24hUAgvgOYyjpoL8YCvc0QpQJAQXgAmxyNuKWRG8agWmYu+AIgQKIQXQIyjwW0RW43cHS9iCyBICM9diNM2t220kWOVr7O0Xshy4wgUIr7ESZFsaSGTnPrcCplg9JFWiddPCSAY/WQ/cGw/MkFCdCQVYmHKCU6bp62VEBNt9ORmrpzFIpTd+SYN/XgE4xVF4i80ls4Z05o5yUlovizw3cxZ7bTkdMwPhG2fJCiTTk0BcXYygBlkO21DWJGYSCdtTVRiezfS6MiwB0TvZAA3xRgeL5o7gvnGTB0p319D6atD7hD9/ZUBCP1IyalrWpwvmpBhkw6RfJ1GhgaKIQYuAEc/xcd6mvzvh/rSxehMzBW2r6TU5b4CROs7Tj6frLprFVuS41X9Ymvry5S6cBpkQciwU5CUUY0XW+WuFhPL6Nq53zHuVYmr/nI3HtEuemsBoOav1++KPzhKbtk+k4z+lpw6E1E86LxZV1111WXUsv4DqNalNBhQdZAAAAAASUVORK5CYII=" }; const WECHAT_REWARD_QR_BASE64 = 'data:image/png;base64,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'; const DEFAULT_DEEPSEEK_PROMPT_WORD = `你是一位专业的电子词典与词汇辅导专家。请将用户输入的单词翻译为简体中文,并提供系统化的词汇解析以辅助高效记忆。 请严格按以下 **4 个部分** 输出结构化内容: --- ### 1. 简明释义 提供单词的标准发音(音标)、常见词性及精准中文释义。 ### 2. 常用搭配 提取 2-3 个高频固定搭配或习惯用语,并附带简短中文翻译。 ### 3. 词性拓展与近反义词 * **衍生词/同根词**:提供相关词性的拓展词汇(如动词变名词、形容词等) * **近义词 / 反义词**:各列举 1-2 个常用词汇,便于对比记忆 ### 4. 实用例句 提供 1-2 个地道、简短的例句,包含: * 英文原句 * 对应中文译文 --- **输出要求:** - 排版清晰,使用 Markdown 语法(标题、列表、表格、加粗等)增强可读性 - 直接输出结果,不要使用代码块(\`\`\`)包裹整个输出`; const DEFAULT_DEEPSEEK_PROMPT_SENTENCE = `你是一位专业的外语私教。请将用户输入的句子翻译为简体中文,并进行深度解析以辅助学习。 请严格按以下 **4 个部分** 输出结构化内容: --- ### 1. 准确翻译 提供自然、地道的译文,必要时可附上直译对照。 ### 2. 语法分析 - 拆解句子核心成分(主谓宾、从句、修饰关系等) - 解释时态、语态、虚拟语气等关键语法点 - 如有特殊句型(倒装、强调、省略等),单独说明 ### 3. 核心句型 提取可复用的句型或固定搭配,并给出: - 句型结构公式 - 一个同结构例句 ### 4. 核心单词 提取 1-3 个生词或重点词汇,每个词包含: - **词性**(如 v. / n. / adj.) - **释义**(中文解释) - **高频搭配**(1-2 个常用短语) - **例句**(简短实用) --- **输出要求:** - 排版清晰,使用 Markdown 语法(标题、列表、表格、加粗等)增强可读性 - 直接输出结果,不要使用代码块(\`\`\`)包裹整个输出 - 如用户输入非完整句子(如单词或短语),可适当调整分析深度`; const DEFAULT_DEEPSEEK_PROMPT_CHAT = `你是一位博学的通用 AI 知识助手,同时擅长语言学习辅导。 请针对用户的问题提供清晰、简明且全面的解答。 **输出要求:** - 如果问题涉及**外语学习、翻译、语法、词汇、写作**等语言相关主题,请主动提供: 1. **核心解答**(直接回答用户问题) 2. **语言点解析**(如有必要,解释相关语法、词义、搭配等) 3. **实用例句**(提供 1-2 个地道例句及中文翻译) 4. **扩展知识**(如近义词辨析、常见错误、文化背景等,可选) - 如果问题属于**一般知识类**(如历史、科学、技术等),按常规方式清晰作答,可适当列出要点或分步骤说明。 - **排版要求**: - 使用 Markdown 语法(标题 # ## ###、列表 - 1.、加粗 **、表格 | 等)增强可读性。 - 直接输出结果,**不要使用代码块(\`\`\`)包裹整个输出**,只对代码片段或公式可使用行内代码或单行代码块。 - 避免空泛的套话(如“好的,现在我来回答”),直接进入主题。 `; const ALL_ENGINES = [ { id: 'youdao', label: '有道词典' }, { id: 'microsoft', label: '微软翻译' }, { id: 'google', label: '谷歌翻译' }, { id: 'deepseek', label: 'DeepSeek 翻译' } ]; const LANG_MAP = { 'en': '英文', 'zh-Hans': '中文 (简体)', 'zh-CN': '中文 (简体)', 'zh-TW': '中文 (繁体)', 'zh-Hant': '中文 (繁体)', 'ja': '日语', 'ko': '韩语', 'fr': '法语', 'de': '德语', 'es': '西班牙语', 'ru': '俄语', 'it': '意大利语', 'pt': '葡萄牙语' }; const DICTIONARY_SOURCES = [ { id: 'oxford', label: '牛津词典', url: word => `https://www.oxfordlearnersdictionaries.com/definition/english/${encodeURIComponent(word)}` }, { id: 'merriam', label: '韦氏词典', url: word => `https://www.merriam-webster.com/dictionary/${encodeURIComponent(word)}` }, { id: 'cambridge', label: '剑桥词典', url: word => `https://dictionary.cambridge.org/dictionary/english/${encodeURIComponent(word)}` }, { id: 'collins', label: '柯林斯词典', url: word => `https://www.collinsdictionary.com/dictionary/english/${encodeURIComponent(word)}` }, { id: 'longman', label: '朗文词典', url: word => `https://www.ldoceonline.com/dictionary/${encodeURIComponent(word)}` } ]; const TYPEWRITER_SPEED_MAP = { 'none': { label: '无打字效果', value: null }, 'very_slow': { label: '很慢 (60ms)', value: 60 }, 'slow': { label: '慢速 (40ms)', value: 40 }, 'normal': { label: '正常 (20ms)', value: 20 }, 'fast': { label: '快速 (10ms)', value: 10 }, 'very_fast': { label: '很快 (5ms)', value: 5 }, 'super_fast': { label: '极速 (2ms)', value: 2 } }; const TTS_SPEED_MAP = { 'very_slow': { label: '很慢 (0.5x)', value: '0.5' }, 'slow': { label: '慢速 (0.75x)', value: '0.75' }, 'normal': { label: '正常 (1x)', value: '1' }, 'fast': { label: '快速 (1.25x)', value: '1.25' }, 'very_fast': { label: '很快 (1.5x)', value: '1.5' }, 'super_fast': { label: '极速 (2x)', value: '2' } }; const API_URL = 'https://youdao.com/result?word='; const DEFAULT_TYPEWRITER_SPEED_KEY = 'fast'; const DEFAULT_TTS_API = 'https://tts.iliili.qzz.io/v1/audio/speech'; const youdaoAudioCache = new Map(); const googleAudioCache = new Map(); const translationCache = new Map(); const MAX_CACHE_SIZE = 50; const wordDictCache = new Map(); const EDGE_TTS_CACHE_LIMIT = 15 const EDGE_TTS_CACHE_MAX_DURATION = 60; const edgeTtsCache = new Map(); const edgeTtsCacheOrder = []; const NOTES_STORAGE_KEY = 'notes'; const UPSTASH_NOTES_KEY = 'notes'; const MAX_RETRY = 3; const WORD_SYNC_STATUS_KEY = 'word_sync_success'; const NOTE_SYNC_STATUS_KEY = 'note_sync_success'; window.__translookup_confirm_showing = false; let currentPageType = 'idle'; let pendingRequests = new Map(); let webAudioCtx = null; let webAudioSource = null; let webAudioBuffer = null; let currentPreciseHighlightCleanup = null; function unwrapAllTtsSpans(container) { if (!container) return; const existing = container.querySelectorAll('.tts-read-token'); if (!existing.length) return; existing.forEach(sp => { if (!sp.parentNode) return; const parent = sp.parentNode; while (sp.firstChild) { parent.insertBefore(sp.firstChild, sp); } parent.removeChild(sp); }); if (container.normalize) container.normalize(); } function findTtsHighlightContainer(btn, text) { if (!btn || !text) return null; const scope = btn.closest('.gt-section') || btn.closest('.gt-card'); if (!scope) return null; const candidates = scope.querySelectorAll('.gt-text-source, .gt-text-target'); const needle = text.trim(); for (const el of candidates) { if (el.textContent.trim() === needle) return el; } return null; } function customConfirm(message) { return new Promise((resolve) => { window.__translookup_confirm_showing = true; const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 2147483647; display: flex; align-items: center; justify-content: center; animation: fadeIn 0.15s ease; `; const dialog = document.createElement('div'); dialog.style.cssText = ` background: #ffffff; padding: 20px 24px; border-radius: 14px; max-width: 300px; width: 85%; box-shadow: 0 8px 30px rgba(0,0,0,0.2); font-family: system-ui, -apple-system, sans-serif; color: #0f1115; animation: scaleIn 0.15s ease; `; const msg = document.createElement('p'); msg.textContent = message; msg.style.cssText = 'margin: 0 0 18px 0; font-size: 15px; line-height: 1.5; text-align: center;'; const btnContainer = document.createElement('div'); btnContainer.style.cssText = 'display: flex; justify-content: center; gap: 12px;'; const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.style.cssText = ` padding: 8px 20px; border: 1px solid #dadce0; border-radius: 8px; background: transparent; cursor: pointer; font-size: 14px; font-weight: 500; min-width: 60px; user-select: none; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; `; const confirmBtn = document.createElement('button'); confirmBtn.textContent = '确定'; confirmBtn.style.cssText = ` padding: 8px 20px; border: none; border-radius: 8px; background: #1a1d21; color: #ffffff; cursor: pointer; font-size: 14px; font-weight: 500; min-width: 60px; user-select: none; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; `; const style = document.createElement('style'); style.textContent = ` @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes scaleIn { from { transform: scale(0.95); opacity: 0; } to { transform: scale(1); opacity: 1; } } `; document.head.appendChild(style); btnContainer.appendChild(cancelBtn); btnContainer.appendChild(confirmBtn); dialog.appendChild(msg); dialog.appendChild(btnContainer); overlay.appendChild(dialog); document.body.appendChild(overlay); const cleanup = (result) => { if (overlay.parentNode) overlay.remove(); if (style.parentNode) style.remove(); window.__translookup_confirm_showing = false; resolve(result); }; cancelBtn.addEventListener('click', function(e) { e.stopPropagation(); cleanup(false); }); confirmBtn.addEventListener('click', function(e) { e.stopPropagation(); cleanup(true); }); }); } const GoogleService = (function() { const CONFIG = { transMinInterval: 400, maxTransRateLimitRetries: 3, audioMinInterval: 400, maxAudioRateLimitRetries: 3, cooldownDuration: 30000, timeout: 8000, audioCacheLimit: 30, ttsPrefetchCount: 2, ttsGap: 0, ttsMaxChars: 190, ttsTrimTail: true, ttsTrimHead: false, ttsSilenceWindowMs: 5, ttsSilenceFloor: 0.0005, ttsSilenceThresholdRatio: 0.02, ttsSilenceMinMs: 40, ttsSilenceSafetyMs: 120, ttsInitialScheduleMs: 30, ttsDecodedCacheLimit: 8, ttsDebug: true, }; const state = { transCache: new Map(), transPending: new Map(), transQueue: [], transProcessing: false, transLastTime: 0, transRateLimitCount: 0, transCooldownUntil: 0, audioCache: new Map(), audioPending: new Map(), audioQueue: [], audioProcessing: false, audioLastTime: 0, audioCooldownUntil: 0, audioCacheOrder: [], audioDecodedCache: new Map(), audioDecodedCacheOrder: [], audioDecodedPending: new Map(), }; function loadCooldown() { try { const audioCooldown = Number(GM_getValue('google_audio_cooldown_until', 0)); const transCooldown = Number(GM_getValue('google_trans_cooldown_until', 0)); state.audioCooldownUntil = audioCooldown || 0; state.transCooldownUntil = transCooldown || 0; } catch (e) { state.audioCooldownUntil = 0; state.transCooldownUntil = 0; } } function saveCooldown() { try { GM_setValue('google_audio_cooldown_until', state.audioCooldownUntil); GM_setValue('google_trans_cooldown_until', state.transCooldownUntil); } catch (e) {} } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function now() { return Date.now(); } function makeKey(text, lang, trimHead, trimTail) { return `${text || ''}|||${lang || ''}|||${!!trimHead}|||${!!trimTail}`; } function debug(...args) { if (CONFIG.ttsDebug) console.log('[Google TTS]', ...args); } loadCooldown(); function doTranslation(text, tl) { return new Promise((resolve, reject) => { const primaryUrl = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${encodeURIComponent(tl)}&dt=t&dt=rm&q=${encodeURIComponent(text)}`; function fallbackRequest() { return new Promise((res, rej) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://translate-pa.googleapis.com/v1/translateHtml', headers: { 'Content-Type': 'application/json+protobuf', 'X-Goog-API-Key': 'AIzaSyATBXajvzQLTDHEQbcpq0Ihe0vWDHmO520', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', }, data: JSON.stringify([ [ [text], 'auto', tl ], 'wt_lib' ]), timeout: CONFIG.timeout, onload(resp) { if (resp.status >= 200 && resp.status < 300) { try { const data = JSON.parse(resp.responseText); let translation = null; if (data && typeof data === 'object') { if (data.translatedText) translation = data.translatedText; else if (data.data && data.data.translations && data.data.translations.length) translation = data.data.translations[0].translatedText; else if (data.translations && data.translations.length) translation = data.translations[0].translatedText; else if (Array.isArray(data)) { for (const item of data) { if (typeof item === 'string') { translation = item; break; } if (Array.isArray(item)) { for (const sub of item) { if (typeof sub === 'string') { translation = sub; break; } else if (Array.isArray(sub) && sub.length && typeof sub[0] === 'string') { translation = sub[0]; break; } } if (translation) break; } } } } if (translation) { let srcLang = 'auto'; if (Array.isArray(data) && data.length > 1) { const maybeLang = data[1]; if (Array.isArray(maybeLang) && maybeLang.length > 0 && typeof maybeLang[0] === 'string') { srcLang = maybeLang[0]; } else if (typeof maybeLang === 'string') { srcLang = maybeLang; } } res({ word: text, translation, pinyin: '', srcPinyin: '', srcLang: srcLang, apiSource: 'fallback' }); } else { rej({ code: 'PARSE_ERROR', source: 'fallback', message: '备用API解析失败' }); } } catch (e) { rej({ code: 'PARSE_ERROR', source: 'fallback', message: '备用API数据解析异常' }); } return; } if (resp.status === 429) { rej({ code: 'RATE_LIMIT', source: 'fallback', status: 429 }); return; } if (resp.status >= 500) { rej({ code: 'SERVER_ERROR', source: 'fallback', status: resp.status }); return; } rej({ code: 'HTTP_ERROR', source: 'fallback', status: resp.status }); }, ontimeout() { rej({ code: 'NETWORK_ERROR', source: 'fallback', message: '备用API超时' }); }, onerror() { rej({ code: 'NETWORK_ERROR', source: 'fallback', message: '备用API网络错误' }); } }); }); } GM_xmlhttpRequest({ method: 'GET', url: primaryUrl, timeout: CONFIG.timeout, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer': 'https://translate.google.com/', 'Accept': 'application/json, text/plain, */*' }, onload(res) { if (res.status >= 200 && res.status < 300) { try { const data = JSON.parse(res.responseText); let translation = '', pinyin = '', srcPinyin = ''; if (data && data[0] && Array.isArray(data[0])) { translation = data[0].filter(item => Array.isArray(item) && typeof item[0] === 'string').map(item => item[0]).join(''); const rmChunk = data[0][data[0].length - 1]; if (rmChunk && Array.isArray(rmChunk)) { if (typeof rmChunk[2] === 'string') pinyin = rmChunk[2]; if (typeof rmChunk[3] === 'string') srcPinyin = rmChunk[3]; } } const srcLang = (data && data[2]) ? data[2] : 'auto'; if (translation) { resolve({ word: text, translation, pinyin, srcPinyin, srcLang, apiSource: 'primary' }); } else { fallbackRequest().then(resolve).catch(reject); } } catch (e) { fallbackRequest().then(resolve).catch(reject); } return; } if (res.status === 429) { fallbackRequest().then(resolve).catch(reject); return; } if (res.status >= 500) { fallbackRequest().then(resolve).catch(reject); return; } reject({ code: 'HTTP_ERROR', source: 'primary', status: res.status, message: `Primary 返回 ${res.status}` }); }, ontimeout() { fallbackRequest().then(resolve).catch(reject); }, onerror() { fallbackRequest().then(resolve).catch(reject); } }); }); } function processTranslationQueue() { if (state.transProcessing) return; if (!state.transQueue.length) return; if (Date.now() < state.transCooldownUntil) { const queue = state.transQueue; state.transQueue = []; const err = new Error('Google 翻译服务冷却中,请稍后再试'); err.code = 'COOLDOWN'; queue.forEach(item => item.reject(err)); return; } const now = Date.now(); const elapsed = now - state.transLastTime; if (elapsed < CONFIG.transMinInterval) { setTimeout(processTranslationQueue, CONFIG.transMinInterval - elapsed); return; } state.transProcessing = true; const item = state.transQueue.shift(); const key = `${item.text}:${item.tl}`; state.transLastTime = Date.now(); doTranslation(item.text, item.tl) .then(result => { state.transRateLimitCount = 0; state.transProcessing = false; state.transPending.delete(key); state.transCache.set(key, result); item.resolve(result); processTranslationQueue(); }) .catch(err => { if (err && err.code === 'RATE_LIMIT') { state.transRateLimitCount++; if (state.transRateLimitCount >= CONFIG.maxTransRateLimitRetries) { state.transPending.delete(key); state.transCooldownUntil = Date.now() + CONFIG.cooldownDuration; saveCooldown(); item.reject(new Error('Google 翻译服务已冷却,请 30 秒后重试')); while (state.transQueue.length) { const q = state.transQueue.shift(); q.reject(new Error('Google 翻译服务冷却中')); } state.transProcessing = false; return; } const delay = Math.min(1000 * Math.pow(2, state.transRateLimitCount), 5000); state.transQueue.unshift(item); state.transProcessing = false; setTimeout(processTranslationQueue, delay); return; } state.transPending.delete(key); state.transProcessing = false; let msg = err && err.message ? err.message : '翻译失败'; if (err && err.code === 'SERVER_ERROR') msg = `Google 服务异常 (${err.status})`; else if (err && err.code === 'NETWORK_ERROR') msg = '网络连接失败,请检查网络'; else if (err && err.code === 'PARSE_ERROR') msg = '翻译结果解析失败'; else if (err && err.code === 'HTTP_ERROR') msg = `请求失败 (${err.status})`; item.reject(new Error(msg)); processTranslationQueue(); }); } function translate(text, tl) { return new Promise((resolve, reject) => { text = String(text || '').trim(); tl = String(tl || '').trim(); if (!text) { reject(new Error('翻译文本为空')); return; } const key = `${text}:${tl}`; if (state.transCache.has(key)) { resolve(state.transCache.get(key)); return; } if (Date.now() < state.transCooldownUntil) { const err = new Error('Google 翻译服务冷却中,请稍后再试'); err.code = 'COOLDOWN'; reject(err); return; } if (state.transPending.has(key)) { state.transPending.get(key).then(resolve).catch(reject); return; } const promise = new Promise((res, rej) => { state.transQueue.push({ text, tl, resolve: res, reject: rej }); }); state.transPending.set(key, promise); processTranslationQueue(); promise.then(resolve).catch(reject); }); } function touchAudioCache(key) { const order = state.audioCacheOrder; const index = order.indexOf(key); if (index !== -1) order.splice(index, 1); order.push(key); } function setAudioCache(key, blob) { const order = state.audioCacheOrder; if (state.audioCache.has(key)) { state.audioCache.set(key, blob); touchAudioCache(key); return; } while (order.length >= CONFIG.audioCacheLimit) { const oldest = order.shift(); if (oldest) state.audioCache.delete(oldest); } order.push(key); state.audioCache.set(key, blob); } function getAudioCache(key) { if (!state.audioCache.has(key)) return null; const blob = state.audioCache.get(key); touchAudioCache(key); return blob; } function doAudio(text, lang) { return new Promise((resolve, reject) => { const url = `https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=${encodeURIComponent(lang)}&q=${encodeURIComponent(text)}`; GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'blob', timeout: CONFIG.timeout, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer': 'https://translate.google.com/', 'Accept': 'application/json, text/plain, */*' }, onload(res) { if (res.status === 429) { reject({ code: 'RATE_LIMIT', status: 429 }); return; } if (res.status >= 200 && res.status < 300 && res.response && res.response.size > 0) { resolve(res.response); return; } if (res.status >= 500) { reject({ code: 'SERVER_ERROR', status: res.status, message: `Google TTS 服务异常 (${res.status})` }); return; } reject({ code: 'HTTP_ERROR', status: res.status, message: `Google TTS 请求失败 (${res.status})` }); }, ontimeout() { reject({ code: 'NETWORK_ERROR', message: 'Google TTS 请求超时' }); }, onerror() { reject({ code: 'NETWORK_ERROR', message: 'Google TTS 网络连接失败' }); } }); }); } function processAudioQueue() { if (state.audioProcessing) return; if (!state.audioQueue.length) return; if (Date.now() < state.audioCooldownUntil) { const queue = state.audioQueue; state.audioQueue = []; const err = new Error('Google TTS 服务冷却中,请稍后再试'); err.code = 'COOLDOWN'; queue.forEach(item => item.reject(err)); return; } const now = Date.now(); const elapsed = now - state.audioLastTime; if (elapsed < CONFIG.audioMinInterval) { setTimeout(processAudioQueue, CONFIG.audioMinInterval - elapsed); return; } state.audioProcessing = true; const item = state.audioQueue.shift(); state.audioLastTime = Date.now(); if (typeof item.retryCount !== 'number') item.retryCount = 0; doAudio(item.text, item.lang) .then(blob => { state.audioProcessing = false; item.resolve(blob); processAudioQueue(); }) .catch(err => { if (err && err.code === 'RATE_LIMIT') { item.retryCount++; if (item.retryCount >= CONFIG.maxAudioRateLimitRetries) { state.audioCooldownUntil = Date.now() + CONFIG.cooldownDuration; saveCooldown(); item.reject({ code: 'COOLDOWN', message: 'Google TTS 服务已冷却,请 30 秒后重试' }); while (state.audioQueue.length) { const q = state.audioQueue.shift(); q.reject({ code: 'COOLDOWN', message: 'Google TTS 服务冷却中' }); } state.audioProcessing = false; return; } const delay = Math.min(1000 * Math.pow(2, item.retryCount), 5000); state.audioQueue.unshift(item); state.audioProcessing = false; setTimeout(processAudioQueue, delay); return; } state.audioProcessing = false; item.reject(err); processAudioQueue(); }); } function _getAudioBlob(text, lang) { return new Promise((resolve, reject) => { if (!text || !String(text).trim()) { resolve(null); return; } text = String(text).trim(); lang = String(lang || 'en'); const key = `google_${lang}_${text}`; const cached = getAudioCache(key); if (cached) { resolve(cached); return; } if (state.audioPending.has(key)) { state.audioPending.get(key).then(resolve).catch(reject); return; } if (Date.now() < state.audioCooldownUntil) { const err = new Error('Google TTS 服务冷却中,请稍后重试'); err.code = 'COOLDOWN'; reject(err); return; } const promise = new Promise((res, rej) => { const item = { text, lang, retryCount: 0, resolve(blob) { state.audioPending.delete(key); setAudioCache(key, blob); res(blob); }, reject(err) { state.audioPending.delete(key); rej(err); } }; state.audioQueue.push(item); }); state.audioPending.set(key, promise); processAudioQueue(); promise.then(resolve).catch(reject); }); } let ttsAudioContext = null; let ttsScheduledSources = new Set(); let ttsPlaybackStartTime = 0; let ttsPlaybackEndTime = 0; function _getAudioContext() { if (!ttsAudioContext) { const AudioContextClass = window.AudioContext || window.webkitAudioContext; if (!AudioContextClass) throw new Error('Web Audio API is not supported'); ttsAudioContext = new AudioContextClass(); } return ttsAudioContext; } function _stopScheduledSources() { for (const source of ttsScheduledSources) { try { source.onended = null; } catch (e) {} try { source.stop(0); } catch (e) {} try { source.disconnect(); } catch (e) {} } ttsScheduledSources.clear(); ttsPlaybackStartTime = 0; ttsPlaybackEndTime = 0; } let _stopCurrentAudioOriginal = null; let _stopCurrentAudioPatched = false; function _stopGoogleTts() { _stopScheduledSources(); try { if (typeof currentAudio !== 'undefined' && currentAudio) { try { currentAudio.pause(); } catch (e) {} try { currentAudio.currentTime = 0; } catch (e) {} try { if (currentAudio.blobUrl) URL.revokeObjectURL(currentAudio.blobUrl); } catch (e) {} try { currentAudio.src = ''; } catch (e) {} } } catch (e) {} try { window.currentAudio = null; } catch (e) {} try { window.currentAudioKey = null; } catch (e) {} if (typeof currentTtsCancel === 'function') { try { currentTtsCancel(); } catch (e) {} currentTtsCancel = null; } //debug('Google TTS stopped'); } function _patchStopCurrentAudio() { if (_stopCurrentAudioPatched) return; if (typeof window.stopCurrentAudio === 'function') { _stopCurrentAudioOriginal = window.stopCurrentAudio; window.stopCurrentAudio = function() { if (_stopCurrentAudioOriginal) { _stopCurrentAudioOriginal.apply(this, arguments); } _stopGoogleTts(); }; _stopCurrentAudioPatched = true; debug('stopCurrentAudio 已代理'); } else { setTimeout(_patchStopCurrentAudio, 200); } } _patchStopCurrentAudio(); function touchDecodedCache(key) { const index = state.audioDecodedCacheOrder.indexOf(key); if (index !== -1) state.audioDecodedCacheOrder.splice(index, 1); state.audioDecodedCacheOrder.push(key); } function setDecodedCache(key, value) { state.audioDecodedCache.set(key, value); touchDecodedCache(key); while (state.audioDecodedCacheOrder.length > CONFIG.ttsDecodedCacheLimit) { const oldKey = state.audioDecodedCacheOrder.shift(); if (oldKey !== undefined) state.audioDecodedCache.delete(oldKey); } } function getDecodedCache(key) { const value = state.audioDecodedCache.get(key); if (!value) return null; touchDecodedCache(key); return value; } function _analyzeSilence(audioBuffer, trimHead, trimTail) { const sampleRate = audioBuffer.sampleRate; const length = audioBuffer.length; const durationMs = audioBuffer.duration * 1000; if (!length) { return { startOffsetSec: 0, durationSec: audioBuffer.duration, leadingSilenceMs: 0, trailingSilenceMs: 0 }; } const numberOfChannels = audioBuffer.numberOfChannels; let peak = 0; for (let channel = 0; channel < numberOfChannels; channel++) { const data = audioBuffer.getChannelData(channel); const step = 32; for (let i = 0; i < data.length; i += step) { const value = Math.abs(data[i]); if (value > peak) peak = value; } } if (peak <= 0) { return { startOffsetSec: 0, durationSec: audioBuffer.duration, leadingSilenceMs: durationMs, trailingSilenceMs: 0 }; } const threshold = Math.max(CONFIG.ttsSilenceFloor, peak * CONFIG.ttsSilenceThresholdRatio); const windowSamples = Math.max(1, Math.floor(sampleRate * CONFIG.ttsSilenceWindowMs / 1000)); const minSilentWindows = Math.max(1, Math.ceil(CONFIG.ttsSilenceMinMs / CONFIG.ttsSilenceWindowMs)); let leadingSilenceMs = 0; let startOffsetSec = 0; if (trimHead) { let found = false; let firstNonSilentSample = 0; for (let i = 0; i < length; i += windowSamples) { const windowEnd = Math.min(length, i + windowSamples); let windowPeak = 0; for (let channel = 0; channel < numberOfChannels; channel++) { const data = audioBuffer.getChannelData(channel); for (let j = i; j < windowEnd; j++) { const value = Math.abs(data[j]); if (value > windowPeak) windowPeak = value; } if (windowPeak > threshold) break; } if (windowPeak > threshold) { firstNonSilentSample = i; found = true; break; } } if (found) { startOffsetSec = firstNonSilentSample / sampleRate; leadingSilenceMs = startOffsetSec * 1000; } } let trailingSilenceMs = 0; let effectiveEndSample = length; if (trimTail) { let silentWindows = 0; let lastNonSilentSample = length - 1; for (let windowEnd = length; windowEnd > 0; windowEnd -= windowSamples) { const windowStart = Math.max(0, windowEnd - windowSamples); let windowPeak = 0; for (let channel = 0; channel < numberOfChannels; channel++) { const data = audioBuffer.getChannelData(channel); for (let i = windowStart; i < windowEnd; i++) { const value = Math.abs(data[i]); if (value > windowPeak) windowPeak = value; if (windowPeak > threshold) break; } if (windowPeak > threshold) break; } if (windowPeak <= threshold) { silentWindows++; continue; } lastNonSilentSample = windowEnd - 1; break; } trailingSilenceMs = (length - 1 - lastNonSilentSample) / sampleRate * 1000; if (silentWindows >= minSilentWindows) { effectiveEndSample = Math.min(length, lastNonSilentSample + 1 + Math.ceil(sampleRate * CONFIG.ttsSilenceSafetyMs / 1000)); } } let effectiveDurationSec = effectiveEndSample / sampleRate; effectiveDurationSec = Math.max(0.05, Math.min(audioBuffer.duration, effectiveDurationSec)); if (trimHead) { effectiveDurationSec = Math.max(0.05, effectiveDurationSec - startOffsetSec); } return { startOffsetSec, durationSec: effectiveDurationSec, leadingSilenceMs, trailingSilenceMs, originalDurationSec: audioBuffer.duration }; } async function _decodeAudioBlob(blob) { const context = _getAudioContext(); const arrayBuffer = await blob.arrayBuffer(); let audioBuffer; try { audioBuffer = await context.decodeAudioData(arrayBuffer); } catch (error) { debug('decodeAudioData failed:', error); throw error; } return audioBuffer; } async function _getDecodedAudio(text, lang, trimHead = false, trimTail = true) { const key = makeKey(text, lang, trimHead, trimTail); const cached = getDecodedCache(key); if (cached) return cached; if (state.audioDecodedPending.has(key)) return state.audioDecodedPending.get(key); const promise = (async () => { const blob = await _getAudioBlob(text, lang); const audioBuffer = await _decodeAudioBlob(blob); const analysis = _analyzeSilence(audioBuffer, trimHead, trimTail); const result = { audioBuffer, ...analysis }; setDecodedCache(key, result); return result; })(); state.audioDecodedPending.set(key, promise); try { return await promise; } finally { state.audioDecodedPending.delete(key); } } function _splitText(text, maxLen = CONFIG.ttsMaxChars) { text = String(text || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim(); if (!text) return []; const abbreviations = new Set([ 'mr.', 'mrs.', 'ms.', 'dr.', 'prof.', 'rev.', 'hon.', 'capt.', 'lt.', 'col.', 'gen.', 'sgt.', 'cpl.', 'sr.', 'jr.', 'st.', 'mt.', 'vs.', 'ph.d.', 'm.d.', 'd.d.s.', 'd.v.m.', 'j.d.', 'm.a.', 'm.s.', 'b.a.', 'b.s.', 'co.', 'corp.', 'inc.', 'ltd.', 'llc.', 'etc.', 'e.g.', 'i.e.', 'et al.', 'viz.', 'cf.', 'u.s.', 'u.k.', 'u.n.', 'e.u.', 'u.a.e.', 'in.', 'ft.', 'yd.', 'oz.', 'lb.', 'pt.', 'qt.', 'no.', 'vol.', 'p.', 'pp.', 'approx.', 'a.m.', 'p.m.', 'jan.', 'feb.', 'mar.', 'apr.', 'may.', 'jun.', 'jul.', 'aug.', 'sep.', 'sept.', 'oct.', 'nov.', 'dec.', 'mon.', 'tue.', 'tues.', 'wed.', 'thu.', 'thurs.', 'fri.', 'sat.', 'sun.' ]); function isSentenceEndAt(index) { const char = text[index]; if (char !== '.' && char !== '?' && char !== '!' && char !== '。' && char !== '?' && char !== '!') return false; if (char !== '.') { return true; } let left = index - 1; while (left >= 0 && /[A-Za-z.]/.test(text[left])) left--; let right = index + 1; while (right < text.length && /[A-Za-z.]/.test(text[right])) right++; const token = text.slice(left + 1, right).trim(); if (!token) return true; if (abbreviations.has(token.toLowerCase())) { return false; } if (index + 1 < text.length && /\d/.test(text[index + 1])) { return false; } return true; } const sentences = []; let start = 0; for (let i = 0; i < text.length; i++) { if (!isSentenceEndAt(i)) continue; let end = i + 1; while (end < text.length && /\s/.test(text[end])) end++; const sentence = text.slice(start, end).trim(); if (sentence) sentences.push(sentence); start = end; i = end - 1; } if (start < text.length) { const rest = text.slice(start).trim(); if (rest) sentences.push(rest); } function isCompleteSegment(seg) { const trimmed = seg.trim(); if (!trimmed) return false; const lastChar = trimmed[trimmed.length - 1]; const isPunct = ['.', '?', '!', '。', '?', '!'].includes(lastChar); if (!isPunct) return false; const last5 = trimmed.slice(-5); for (const abbr of abbreviations) { if (last5.endsWith(abbr)) return false; } return true; } if (text.length <= maxLen) { return [{ text, isComplete: isCompleteSegment(text) }]; } const chunks = []; let current = ''; for (const sentence of sentences) { if (sentence.length > maxLen) { if (current) { chunks.push({ text: current.trim(), isComplete: isCompleteSegment(current) }); current = ''; } const parts = _splitLongSentence(sentence, maxLen, abbreviations); for (const part of parts) { chunks.push({ text: part.trim(), isComplete: false }); } continue; } const candidate = current ? current + ' ' + sentence : sentence; if (candidate.length <= maxLen) { current = candidate; } else { if (current) { chunks.push({ text: current.trim(), isComplete: isCompleteSegment(current) }); } current = sentence; } } if (current) { chunks.push({ text: current.trim(), isComplete: isCompleteSegment(current) }); } return chunks; } function _splitLongSentence(sentence, maxLen, abbreviations) { const unsafeEndWords = new Set([ 'a', 'an', 'the', 'and', 'or', 'but', 'if', 'of', 'to', 'for', 'with', 'from', 'in', 'on', 'at', 'by', 'as', 'than', 'that', 'which', 'who', 'whom', 'this', 'these', 'those', 'is', 'are', 'was', 'were', 'be', 'been', 'being' ]); const parts = []; let remaining = sentence.trim(); while (remaining.length > maxLen) { const limit = maxLen; let breakIndex = -1; const strongPunctuation = /[;;::—–]/g; let match; while ((match = strongPunctuation.exec(remaining.slice(0, limit + 1))) !== null) { breakIndex = match.index + 1; } if (breakIndex < 0) { const commaIndex = Math.max( remaining.lastIndexOf(',', limit), remaining.lastIndexOf(',', limit), remaining.lastIndexOf('、', limit) ); if (commaIndex >= Math.floor(maxLen * 0.45)) { breakIndex = commaIndex + 1; } } if (breakIndex < 0) { const whitespace = remaining.lastIndexOf(' ', limit); if (whitespace >= Math.floor(maxLen * 0.45)) { breakIndex = whitespace; } } if (breakIndex < 1) breakIndex = maxLen; let part = remaining.slice(0, breakIndex).trim(); let words = part.split(/\s+/); while (words.length > 1 && unsafeEndWords.has(words[words.length - 1].replace(/[.,!?;:]+$/, '').toLowerCase())) { words.pop(); const candidate = words.join(' ').trim(); if (candidate.length >= Math.floor(maxLen * 0.45)) { part = candidate; } else { break; } } if (!part) part = remaining.slice(0, maxLen).trim(); parts.push(part); remaining = remaining.slice(part.length).trim(); } if (remaining) parts.push(remaining); return parts; } function _prefetchSegments(segments, lang, startIndex, count, playId) { const end = Math.min(segments.length, startIndex + count); for (let i = startIndex; i < end; i++) { if (playId !== ttsPlayId) return; const seg = segments[i]; const trimHead = (i > 0); const trimTail = (i < segments.length - 1); _getDecodedAudio(seg.text, lang, false, true).catch(error => { if (playId === ttsPlayId) debug('Prefetch failed:', error); }); } } function _scheduleAudio(audioInfo, startTime, playId) { const context = _getAudioContext(); if (playId !== ttsPlayId) return null; const source = context.createBufferSource(); source.buffer = audioInfo.audioBuffer; source.connect(context.destination); ttsScheduledSources.add(source); source.onended = () => { ttsScheduledSources.delete(source); try { source.disconnect(); } catch (e) {} }; const offset = Math.max(0, audioInfo.startOffsetSec); const duration = Math.max(0.001, Math.min(audioInfo.durationSec, audioInfo.audioBuffer.duration - offset)); source.start(startTime, offset, duration); return { source, startTime, duration, endTime: startTime + duration }; } async function _playSegments(segments, lang, playId) { if (!segments || !segments.length) return; const context = _getAudioContext(); if (context.state === 'suspended') { try { await context.resume(); } catch (error) { debug('AudioContext resume failed:', error); throw error; } } if (playId !== ttsPlayId) return; const n = segments.length; const trimOptions = segments.map((seg, index) => { if (seg.isComplete) { return { trimHead: false, trimTail: false }; } else { const trimHead = index > 0; const trimTail = index < n - 1; return { trimHead, trimTail }; } }); const firstOpts = trimOptions[0]; const first = await _getDecodedAudio(segments[0].text, lang, firstOpts.trimHead, firstOpts.trimTail); if (playId !== ttsPlayId) return; let nextStartTime = context.currentTime + CONFIG.ttsInitialScheduleMs / 1000; ttsPlaybackStartTime = nextStartTime; for (let i = 0; i < n; i++) { if (playId !== ttsPlayId) return; let audioInfo; if (i === 0) { audioInfo = first; } else { const opts = trimOptions[i]; audioInfo = await _getDecodedAudio(segments[i].text, lang, opts.trimHead, opts.trimTail); } if (playId !== ttsPlayId) return; _prefetchSegments(segments, lang, i + 1, CONFIG.ttsPrefetchCount, playId); const minimumStartTime = context.currentTime + 0.005; if (nextStartTime < minimumStartTime) { debug(`Segment ${i} scheduling late:`, (minimumStartTime - nextStartTime).toFixed(3) + 's'); nextStartTime = minimumStartTime; } const scheduled = _scheduleAudio(audioInfo, nextStartTime, playId); if (!scheduled) return; nextStartTime = scheduled.endTime + CONFIG.ttsGap / 1000; ttsPlaybackEndTime = nextStartTime; } } let ttsPlayId = 0; let currentTtsCancel = null; function playAudio(text, lang) { _patchStopCurrentAudio(); _stopGoogleTts(); try { if (typeof stopCurrentAudio === 'function') stopCurrentAudio(); } catch (e) {} const playId = ++ttsPlayId; text = String(text || '').trim(); lang = String(lang || 'en'); if (!text) return; const segments = _splitText(text, CONFIG.ttsMaxChars); if (!segments.length) return; currentTtsCancel = () => { _stopScheduledSources(); }; (async () => { try { _prefetchSegments(segments, lang, 1, CONFIG.ttsPrefetchCount, playId); await _playSegments(segments, lang, playId); } catch (error) { if (playId !== ttsPlayId) return; console.error('[Google TTS] 播放失败:', error); try { if (typeof showToast === 'function') showToast('Google TTS 播放失败'); } catch (e) {} } finally { if (playId === ttsPlayId) currentTtsCancel = null; } })(); } function _reset() { ++ttsPlayId; _stopGoogleTts(); state.transQueue.length = 0; state.audioQueue.length = 0; state.transPending.clear(); state.audioPending.clear(); state.audioDecodedPending.clear(); state.transCache.clear(); state.audioCache.clear(); state.audioCacheOrder.length = 0; state.audioDecodedCache.clear(); state.audioDecodedCacheOrder.length = 0; state.transRateLimitCount = 0; state.audioRateLimitCount = 0; state.transCooldownUntil = 0; state.audioCooldownUntil = 0; saveCooldown(); if (ttsAudioContext) { try { _stopScheduledSources(); } catch (e) {} } debug('GoogleService reset'); } return { translate, playAudio, stop: _stopGoogleTts, _reset }; })(); function getCacheKey(engine, text, lang) { return `${engine}:${encodeURIComponent(text)}:${lang}`; } function setTranslationCache(key, value) { if (translationCache.size >= MAX_CACHE_SIZE) { const firstKey = translationCache.keys().next().value; translationCache.delete(firstKey); } translationCache.set(key, value); } function getAllNotes() { let data = GM_getValue(NOTES_STORAGE_KEY, []); if (!Array.isArray(data)) { console.warn('[笔记] 存储数据损坏,已重置为空数组'); data = []; GM_setValue(NOTES_STORAGE_KEY, data); } return data; } function getNotes() { return getAllNotes().filter(n => !n.deleted); } function getDeletedNotes() { return getAllNotes().filter(n => n.deleted === true && !n.permanentlyDeleted); } let notesSyncPromise = null; let notesSyncPending = false; let notesRetryCount = 0; async function syncNotesWithCloudAfterLocalChange() { if (!isUpstashConfigured()) return; if (notesSyncPromise) { notesSyncPending = true; return notesSyncPromise; } notesRetryCount = 0; notesSyncPromise = (async () => { const TIMEOUT_MS = 15000; let timeoutId = null; const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error('同步超时(15秒)')); }, TIMEOUT_MS); }); const syncLogic = (async () => { try { notesSyncPending = false; const cloud = await fetchCloudNotes(); let local = getAllNotes(); let merged = mergeNotes(local, cloud); const localNow = getAllNotes(); const finalMerged = mergeNotes(localNow, cloud); saveNotes(finalMerged, true); await pushNotesToCloud(finalMerged); GM_setValue(NOTE_SYNC_STATUS_KEY, true); return getAllNotes(); } catch (error) { console.error('[笔记同步] 同步逻辑错误:', error); GM_setValue(NOTE_SYNC_STATUS_KEY, false); throw error; } })(); try { const result = await Promise.race([syncLogic, timeoutPromise]); clearTimeout(timeoutId); return result; } catch (error) { console.warn('[笔记同步] 同步失败:', error); throw error; } finally { notesSyncPromise = null; if (notesSyncPending && notesRetryCount < MAX_RETRY) { notesRetryCount++; notesSyncPending = false; setTimeout(() => { syncNotesWithCloudAfterLocalChange().catch(console.warn); }, 300); } else if (notesSyncPending && notesRetryCount >= MAX_RETRY) { notesSyncPending = false; } else { notesSyncPending = false; } if (timeoutId) clearTimeout(timeoutId); } })(); return notesSyncPromise; } function saveNotes(notes, skipSync = false) { const normalized = notes.map(n => { if (typeof n.deleted === 'undefined') n.deleted = false; if (typeof n.deletedAt === 'undefined') n.deletedAt = 0; if (typeof n.permanentlyDeleted === 'undefined') n.permanentlyDeleted = false; if (typeof n.syncAt === 'undefined') { n.syncAt = n.deletedAt || n.updatedAt || n.createdAt || Date.now(); } return n; }); GM_setValue(NOTES_STORAGE_KEY, normalized); if (isUpstashConfigured() && !skipSync) { syncNotesWithCloudAfterLocalChange().catch(console.warn); } } function addNoteFromTranslation(title, content) { const maxTitleLen = 20; let noteTitle = (title || '').trim(); if (noteTitle.length > maxTitleLen) { noteTitle = noteTitle.slice(0, maxTitleLen) + '...'; } if (!noteTitle) noteTitle = '无标题'; const allNotes = getAllNotes(); const now = Date.now(); const newNote = { id: now, title: noteTitle, content: content.trim() || '(空内容)', createdAt: now, updatedAt: now, deleted: false, deletedAt: 0 }; allNotes.push(newNote); saveNotes(allNotes); showToast('已添加到笔记本'); } async function fetchCloudNotes(overrideUrl, overrideToken) { if (!isUpstashConfigured() && !overrideUrl) return []; try { const result = await upstashRequest('GET', `/get/${UPSTASH_NOTES_KEY}`, undefined, overrideUrl, overrideToken); if (typeof result === 'string') { if (result.trim() === '') return []; try { let parsed = JSON.parse(result); if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && typeof parsed.value === 'string') { parsed = JSON.parse(parsed.value); } return Array.isArray(parsed) ? parsed : []; } catch (e) { console.warn('[笔记] 云端数据不是合法 JSON:', result); return []; } } if (Array.isArray(result)) return result; if (result && typeof result === 'object' && typeof result.value === 'string') { try { const parsedValue = JSON.parse(result.value); return Array.isArray(parsedValue) ? parsedValue : []; } catch (e) { console.warn('[笔记] 云端 value 字段不是合法 JSON:', result.value); return []; } } return []; } catch (e) { console.warn('[笔记] 获取云端笔记失败:', e); throw e; } } async function pushNotesToCloud(notes) { if (!isUpstashConfigured()) return; try { await upstashRequest( 'POST', `/set/${UPSTASH_NOTES_KEY}`, { value: JSON.stringify(notes) } ); } catch (e) { console.warn( '[笔记] 推送云端失败:', e ); throw e; } } function getNoteSyncTime(note) { return Number(note?.syncAt || note?.deletedAt || note?.updatedAt || note?.createdAt || 0); } function mergeNotes(local, cloud) { const map = new Map(); const addItem = (item) => { if (!item || typeof item.id === 'undefined') return; const existing = map.get(item.id); if (!existing) { map.set(item.id, { ...item }); return; } const existingTime = getNoteSyncTime(existing); const newTime = getNoteSyncTime(item); if (newTime > existingTime) { map.set(item.id, { ...item }); } else if (newTime === existingTime) { if (item.permanentlyDeleted && !existing.permanentlyDeleted) { map.set(item.id, { ...item }); } else if (item.deleted && !existing.deleted) { map.set(item.id, { ...item }); } } }; (Array.isArray(local) ? local : []).forEach(addItem); (Array.isArray(cloud) ? cloud : []).forEach(addItem); return Array.from(map.values()); } function getTypewriterSpeedKey() { return GM_getValue('deepseek_typewriter_speed', DEFAULT_TYPEWRITER_SPEED_KEY); } function getTypewriterSpeedMs() { const key = getTypewriterSpeedKey(); const entry = TYPEWRITER_SPEED_MAP[key]; return entry ? entry.value : null; } function configureDeepSeekMarkdown() { if (!window.marked || configureDeepSeekMarkdown._configured) return; try { window.marked.setOptions({ gfm: true, breaks: true, pedantic: false, mangle: false, headerIds: false }); } catch (e) { console.warn('[DeepSeek Markdown] marked 配置失败,将使用默认配置:', e); } configureDeepSeekMarkdown._configured = true; } function renderDeepSeekMarkdown(text) { const source = typeof text === 'string' ? text : String(text ?? ''); if (!window.marked) return null; configureDeepSeekMarkdown(); try { return window.marked.parse(source, { gfm: true, breaks: true, pedantic: false, mangle: false, headerIds: false }); } catch (e) { console.warn('[DeepSeek Markdown] 渲染失败:', e); return null; } } function setDeepSeekMessageContent(element, text) { if (!element) return; const html = renderDeepSeekMarkdown(text); if (html !== null) { setElementHtml(element, html); } else { element.textContent = typeof text === 'string' ? text : String(text ?? ''); } } function scrollDeepSeekConversation(element) { const container = element?.closest('.ds-conversation'); if (!container) return; const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight; if (distanceFromBottom < 80 || element._forceAutoScroll) { container.scrollTop = container.scrollHeight; } } function typewriter(element, text, speedMs, useMarkdown = false, onComplete) { if (!element) return; const source = typeof text === 'string' ? text : String(text ?? ''); if (element._typewriterTimer) { clearTimeout(element._typewriterTimer); element._typewriterTimer = null; } element._typewriterSource = source; element._typewriterIndex = 0; element._forceAutoScroll = true; setElementHtml(element, ''); if (!useMarkdown) { if (speedMs === null || speedMs === undefined || speedMs <= 0) { element.textContent = source; element._forceAutoScroll = false; if (onComplete) onComplete(); return; } } if (speedMs === null || speedMs === undefined || speedMs <= 0 || !window.marked) { if (useMarkdown && window.marked) { setDeepSeekMessageContent(element, source); } else { element.textContent = source; } element._forceAutoScroll = false; if (onComplete) onComplete(); return; } configureDeepSeekMarkdown(); const charsPerFrame = speedMs <= 5 ? 4 : speedMs <= 10 ? 3 : speedMs <= 20 ? 2 : 1; const renderInterval = Math.max(10, Math.min(35, speedMs * charsPerFrame)); function addChunk() { if (element._typewriterSource !== source) return; if (element._typewriterIndex >= source.length) { setDeepSeekMessageContent(element, source); element._typewriterTimer = null; element._forceAutoScroll = false; scrollDeepSeekConversation(element); if (onComplete) onComplete(); return; } const nextIndex = Math.min( source.length, element._typewriterIndex + charsPerFrame ); const currentText = source.slice(0, nextIndex); element._typewriterIndex = nextIndex; const html = renderDeepSeekMarkdown(currentText); if (html !== null) { setElementHtml(element, html); } else { element.textContent = currentText; } scrollDeepSeekConversation(element); element._typewriterTimer = setTimeout(addChunk, renderInterval); } addChunk(); } function clearAllTypewriters() { const root = getPopupRoot(); if (!root) return; const messages = root.querySelectorAll('.ds-message.assistant'); messages.forEach(el => { if (el._typewriterTimer) { clearTimeout(el._typewriterTimer); el._typewriterTimer = null; } }); } let popup = null; let selectionTimeout = null; let lastSelectionText = ''; let savedRange = null; let currentAudio = null; let currentAudioKey = null; let currentEngine = 'youdao'; let microsoftSrcTtsSpeed = null; let microsoftTargetTtsSpeed = null; let microsoftLastPlayed = null; let targetLang = 'zh-Hans'; let currentCache = { text: '', youdao: null, microsoft: {}, google: {}, deepseek: { data: null, history: [] } }; let deepseekState = { history: [] }; let pointerIsDown = false; let interactingWithPopup = false; let popupInteractionTimeout = null; let currentRequestId = 0; let isConfigPageOpen = false; let mouseDownInPopup = false; let touchScrollActive = false; let touchStartX = 0; let touchStartY = 0; let suppressNextOutsideClick = false; let touchSelectionPending = false; let touchSource = null; let globalNoteTtsStop = null; let wordListPageState = { sortMode: 'time', favorites: [], originalFavorites: [] }; let notesPageState = { sortMode: 'time' }; function getTrashWords() { return getFavoriteWords().filter(item => item.deleted === true && !item.permanentlyDeleted); } function cleanOldDeletedItems() { const now = Date.now(); const sevenDays = 7 * 24 * 60 * 60 * 1000; const thirtyDays = 30 * 24 * 60 * 60 * 1000; let words = getFavoriteWords(); if (Array.isArray(words)) { const initialCount = words.length; words = words.filter(item => { if (item.deleted && !item.permanentlyDeleted && item.deletedAt) { return (now - item.deletedAt) <= sevenDays; } if (item.permanentlyDeleted && item.deletedAt) { return (now - item.deletedAt) <= thirtyDays; } return true; }); if (words.length !== initialCount) { saveFavoriteWords(words); } } let notes = getAllNotes(); if (Array.isArray(notes)) { const initialCount = notes.length; notes = notes.filter(item => { if (item.deleted && !item.permanentlyDeleted && item.deletedAt) { return (now - item.deletedAt) <= sevenDays; } if (item.permanentlyDeleted && item.deletedAt) { return (now - item.deletedAt) <= thirtyDays; } return true; }); if (notes.length !== initialCount) { saveNotes(notes); } } } function getCurrentPopupRect() { if (!popup) return null; const rect = popup.getBoundingClientRect(); return { left: rect.left + window.scrollX, top: rect.top + window.scrollY, width: rect.width, height: rect.height }; } function getEnabledEngines() { const defaultEnabled = ['youdao', 'microsoft', 'google', 'deepseek']; return GM_getValue('enabled_engines', defaultEnabled); } function isTTSBtnEnabled() { return GM_getValue('enable_tts_btn', true); } function isNotebookEnabled() { return GM_getValue('show_notebook_btn', true); } function isWordlistEnabled() { return GM_getValue('show_wordlist_btn', true); } function getThemeMode() { return GM_getValue('theme_mode', 'system'); } function getTTSApiUrl() { const custom = GM_getValue('custom_tts_api', '').trim(); return custom || DEFAULT_TTS_API; } function getTTSSpeed() { return GM_getValue('custom_tts_speed', 'normal'); } function getFavoriteWords() { let data = GM_getValue('favorite_words', []); if (!Array.isArray(data)) { console.warn('[单词本] 存储数据损坏,已重置为空数组'); data = []; GM_setValue('favorite_words', data); } return data; } function saveFavoriteWords(words, skipSync = false) { const normalized = words.map(item => { if (typeof item.deleted === 'undefined') item.deleted = false; if (typeof item.deletedAt === 'undefined') item.deletedAt = 0; if (typeof item.permanentlyDeleted === 'undefined') item.permanentlyDeleted = false; if (typeof item.syncAt === 'undefined') { item.syncAt = item.deletedAt || item.time || Date.now(); } return item; }); GM_setValue('favorite_words', normalized); if (isUpstashConfigured() && !skipSync) { syncFavoriteWithCloudAfterLocalChange().catch(console.warn); } } function migrateFavoriteData() { const words = getFavoriteWords(); if (!Array.isArray(words)) { GM_setValue('favorite_words', []); return; } let changed = false; const migrated = words.map(item => { if (typeof item.deleted === 'undefined') { changed = true; return { word: item.word, time: item.time || Date.now(), deleted: false, deletedAt: 0 }; } return item; }); if (changed) saveFavoriteWords(migrated); } const UPSTASH_FAVORITES_KEY = 'favorite_words'; function getUpstashUrl() { return GM_getValue('upstash_redis_rest_url', '').trim(); } function getUpstashToken() { return GM_getValue('upstash_redis_rest_token', '').trim(); } function isUpstashConfigured() { const enabled = GM_getValue('upstash_enabled', true); if (!enabled) return false; return !!(getUpstashUrl() && getUpstashToken()); } function setSyncingStatus(el) { if (!el) return; const html = ` 保存位置:本地 + Upstash 云端 | 正在同步 `; setElementHtml(el, html); } function upstashRequest(method, path, body, overrideUrl, overrideToken) { return new Promise((resolve, reject) => { const baseUrl = overrideUrl || getUpstashUrl(); const token = overrideToken || getUpstashToken(); if (!baseUrl || !token) { reject(new Error('Upstash 未配置')); return; } const url = baseUrl.replace(/\/$/, '') + path; GM_xmlhttpRequest({ method: method, url: url, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, data: body ? JSON.stringify(body) : undefined, timeout: 10000, onload(res) { if (res.status >= 200 && res.status < 300) { try { const data = JSON.parse(res.responseText); if (data.error) { reject(new Error(data.error)); } else { resolve(data.result); } } catch (e) { reject(new Error('Upstash 响应解析失败')); } } else { reject(new Error(`Upstash HTTP ${res.status}`)); } }, ontimeout() { reject(new Error('Upstash 请求超时')); }, onerror() { reject(new Error('Upstash 网络错误')); } }); }); } async function fetchCloudFavorites(overrideUrl, overrideToken) { if (!isUpstashConfigured() && !overrideUrl) return []; try { const result = await upstashRequest('GET', `/get/${UPSTASH_FAVORITES_KEY}`, undefined, overrideUrl, overrideToken); if (typeof result === 'string') { if (result.trim() === '') return []; try { let parsed = JSON.parse(result); if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && typeof parsed.value === 'string') { parsed = JSON.parse(parsed.value); } return Array.isArray(parsed) ? parsed : []; } catch (e) { console.warn('[同步] 云端数据不是合法 JSON:', result); return []; } } if (Array.isArray(result)) { return result; } if (result && typeof result === 'object' && typeof result.value === 'string') { try { const parsedValue = JSON.parse(result.value); return Array.isArray(parsedValue) ? parsedValue : []; } catch (e) { console.warn('[同步] 云端 value 字段不是合法 JSON:', result.value); return []; } } return []; } catch (e) { console.warn('[同步] 获取云端收藏失败:', e); throw e; } } async function pushCloudFavorites(words) { if (!isUpstashConfigured()) return; try { await upstashRequest('POST', `/set/${UPSTASH_FAVORITES_KEY}`, { value: JSON.stringify(words) }); } catch (e) { console.warn('推送云端收藏失败:', e); } } let favoritesSyncPromise = null; let favoritesSyncPending = false; let favoritesRetryCount = 0; async function syncFavoriteWithCloudAfterLocalChange() { if (!isUpstashConfigured()) return; if (favoritesSyncPromise) { favoritesSyncPending = true; return favoritesSyncPromise; } favoritesRetryCount = 0; favoritesSyncPromise = (async () => { const TIMEOUT_MS = 15000; let timeoutId = null; const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error('同步超时(15秒)')); }, TIMEOUT_MS); }); const syncLogic = (async () => { try { favoritesSyncPending = false; const cloud = await fetchCloudFavorites(); let local = getFavoriteWords(); let merged = mergeFavoriteWords(local, cloud); const localNow = getFavoriteWords(); const finalMerged = mergeFavoriteWords(localNow, cloud); saveFavoriteWords(finalMerged, true); await pushCloudFavorites(finalMerged); GM_setValue(WORD_SYNC_STATUS_KEY, true); return getFavoriteWords(); } catch (error) { console.error('[单词本同步] 同步逻辑错误:', error); GM_setValue(WORD_SYNC_STATUS_KEY, false); throw error; } })(); try { const result = await Promise.race([syncLogic, timeoutPromise]); clearTimeout(timeoutId); return result; } catch (error) { console.warn('[单词本同步] 同步失败:', error); throw error; } finally { favoritesSyncPromise = null; if (favoritesSyncPending && favoritesRetryCount < MAX_RETRY) { favoritesRetryCount++; favoritesSyncPending = false; setTimeout(() => { syncFavoriteWithCloudAfterLocalChange().catch(console.warn); }, 300); } else if (favoritesSyncPending && favoritesRetryCount >= MAX_RETRY) { favoritesSyncPending = false; } else { favoritesSyncPending = false; } if (timeoutId) clearTimeout(timeoutId); } })(); return favoritesSyncPromise; } function getFavoriteSyncTime(item) { return Number(item?.syncAt || item?.deletedAt || item?.time || 0); } function mergeFavoriteWords(local, cloud) { const map = new Map(); const addItem = (item) => { if (!item || !item.word) return; const word = item.word; const existing = map.get(word); if (!existing) { map.set(word, { ...item }); return; } const existingTime = getFavoriteSyncTime(existing); const newTime = getFavoriteSyncTime(item); if (newTime > existingTime) { map.set(word, { ...item }); } else if (newTime === existingTime) { if (item.permanentlyDeleted && !existing.permanentlyDeleted) { map.set(word, { ...item }); } else if (item.deleted && !existing.deleted) { map.set(word, { ...item }); } } }; (Array.isArray(local) ? local : []).forEach(addItem); (Array.isArray(cloud) ? cloud : []).forEach(addItem); return Array.from(map.values()); } function isWordFavorite(word) { return getFavoriteWords().some(item => item.word === word && !item.deleted); } function addFavoriteWord(word) { const words = getFavoriteWords(); const existing = words.find(item => item.word === word); if (existing) { if (existing.deleted) { existing.deleted = false; existing.deletedAt = 0; existing.time = Date.now(); existing.syncAt = Date.now(); existing.permanentlyDeleted = false; } } else { words.push({ word, time: Date.now(), deleted: false, deletedAt: 0 }); } saveFavoriteWords(words); syncFavoriteWithCloudAfterLocalChange(); } function addFavoriteWords(wordArray) { if (!wordArray || wordArray.length === 0) return; const words = getFavoriteWords(); const now = Date.now(); let addedCount = 0; wordArray.forEach(word => { const existing = words.find(item => item.word === word); if (existing) { if (existing.deleted) { existing.deleted = false; existing.deletedAt = 0; existing.time = now; existing.syncAt = now; existing.permanentlyDeleted = false; addedCount++; } } else { words.push({ word, time: now, deleted: false, deletedAt: 0 }); addedCount++; } }); if (addedCount > 0) { saveFavoriteWords(words); } return addedCount; } function removeFavoriteWord(word) { const words = getFavoriteWords(); const item = words.find(item => item.word === word); if (item && !item.deleted) { const now = Date.now(); item.deleted = true; item.deletedAt = now; item.syncAt = now; saveFavoriteWords(words); syncFavoriteWithCloudAfterLocalChange(); } } function toggleFavoriteWord(word) { const words = getFavoriteWords(); const item = words.find(item => item.word === word); if (item && !item.deleted) { const now = Date.now(); item.deleted = true; item.deletedAt = now; item.syncAt = now; saveFavoriteWords(words); syncFavoriteWithCloudAfterLocalChange(); return false; } else { if (item) { const now = Date.now(); item.deleted = false; item.deletedAt = 0; item.time = now; item.syncAt = now; item.permanentlyDeleted = false; } else { words.push({ word, time: Date.now(), deleted: false, deletedAt: 0 }); } saveFavoriteWords(words); syncFavoriteWithCloudAfterLocalChange(); return true; } } function enableAutoHideScrollbar(el, delay = 1000) { if (!el) return; el.style.setProperty('--scrollbar-opacity', '0'); const show = () => { el.style.setProperty('--scrollbar-opacity', '1'); clearTimeout(el._scrollHideTimer); }; const hide = () => { el._scrollHideTimer = setTimeout(() => { el.style.setProperty('--scrollbar-opacity', '0'); }, delay); }; el.addEventListener('mouseenter', show, { passive: true }); el.addEventListener('scroll', show, { passive: true }); el.addEventListener('mouseleave', hide, { passive: true }); el.addEventListener('touchend', hide, { passive: true }); el.addEventListener('touchcancel', hide, { passive: true }); if (el.matches(':hover')) { show(); } else { show(); hide(); } } const extraStyle = ` .ds-result-box { max-height: 180px; overflow-y: auto; margin-bottom: 8px; } .ds-result-content { font-size: 15px; font-weight: 500; color: #0f1115; line-height: 1.6; white-space: pre-wrap; word-break: break-word; padding: 6px 0; } .ds-conversation { display: flex; flex-direction: column; align-items: flex-start; max-height: 334px; overflow-y: auto; margin: 6px 0 8px 0; padding: 4px 0; width: 100%; } .ds-message-wrapper { display: flex; flex-direction: column; align-items: flex-start; margin-bottom: 6px; width: 100%; } .ds-message-wrapper .ds-message.assistant { margin-bottom: 0; } .ds-message-wrapper .copy-btn { background: transparent; border: none; padding: 4px; cursor: pointer; color: #61666b; border-radius: 50%; transition: opacity 0.4s ease, background 0.2s; opacity: 1; } .ds-message-wrapper .copy-btn.visible { opacity: 1; } .ds-message-wrapper .copy-btn:hover { background: rgba(0,0,0,0.06); } .ds-message-wrapper .play-btn { background: transparent; border: none; padding: 4px; cursor: pointer; color: #61666b; border-radius: 50%; transition: opacity 0.4s ease, background 0.2s; opacity: 1; } .ds-message-wrapper .play-btn:hover { background: rgba(0,0,0,0.06); } .ds-message-wrapper.user-wrapper { align-items: flex-end; } .ds-actions-row { display: flex; flex-direction: row; align-items: center; gap: 2px; margin-top: 2px; } .ds-message-wrapper.user-wrapper .ds-actions-row { justify-content: flex-end; } .ds-message-wrapper .copy-btn, .ds-message-wrapper .play-btn { margin-top: 0 !important; align-self: auto !important; padding: 4px; } .ds-message { max-width: 80%; padding: 8px 14px; border-radius: 14px; word-break: break-word; overflow-wrap: anywhere; font-size: 14px; line-height: 1.5; display: inline-block; background: #f0f0f0; color: #0f1115; } .ds-message.assistant { line-height: 1.55; padding: 10px 16px; background: #f0f0f0; color: #0f1115; border-radius: 18px 18px 18px 4px; max-width: 90%; white-space: normal; } .ds-message.assistant .message-text { display: block; white-space: normal; word-break: break-word; overflow-wrap: anywhere; min-width: 0; } .ds-message.assistant p { margin: 0 0 8px 0; line-height: 1.55; } .ds-message.assistant p:last-child { margin-bottom: 0; } .ds-message.assistant h1, .ds-message.assistant h2, .ds-message.assistant h3, .ds-message.assistant h4, .ds-message.assistant h5, .ds-message.assistant h6 { margin: 14px 0 7px; line-height: 1.35; font-weight: 650; } .ds-message.assistant h1:first-child, .ds-message.assistant h2:first-child, .ds-message.assistant h3:first-child, .ds-message.assistant h4:first-child, .ds-message.assistant h5:first-child, .ds-message.assistant h6:first-child { margin-top: 0; } .ds-message.assistant h1 { font-size: 1.45em; } .ds-message.assistant h2 { font-size: 1.28em; } .ds-message.assistant h3 { font-size: 1.15em; } .ds-message.assistant h4, .ds-message.assistant h5, .ds-message.assistant h6 { font-size: 1em; } .ds-message.assistant strong { font-weight: 650; } .ds-message.assistant em { font-style: italic; } .ds-message.assistant del { opacity: 0.72; } .ds-message.assistant ul, .ds-message.assistant ol { margin: 6px 0 9px; padding-left: 1.55em; } .ds-message.assistant ul ul, .ds-message.assistant ul ol, .ds-message.assistant ol ul, .ds-message.assistant ol ol { margin-top: 2px; margin-bottom: 2px; } .ds-message.assistant li { margin: 2px 0; padding-left: 2px; line-height: 1.5; } .ds-message.assistant li > p { margin: 0; } .ds-message.assistant code { background: rgba(0, 0, 0, 0.075); padding: 0.12em 0.36em; border-radius: 5px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; font-size: 0.88em; overflow-wrap: anywhere; } .ds-message.assistant pre { display: block; width: 100%; box-sizing: border-box; margin: 9px 0; padding: 11px 12px; background: rgba(0, 0, 0, 0.065); border: 1px solid rgba(0, 0, 0, 0.06); border-radius: 8px; overflow-x: auto; overflow-y: hidden; white-space: pre; line-height: 1.5; font-size: 0.88em; tab-size: 4; -webkit-overflow-scrolling: touch; } .ds-message.assistant pre code { display: block; padding: 0; background: transparent; border-radius: 0; font-size: inherit; white-space: pre; overflow-wrap: normal; word-break: normal; } .ds-message.assistant blockquote { margin: 8px 0; padding: 4px 0 4px 12px; border-left: 3px solid #c9cdd1; color: #5f6368; } .ds-message.assistant blockquote p:last-child { margin-bottom: 0; } .ds-message.assistant hr { border: 0; border-top: 1px solid #d9dce0; margin: 12px 0; } .ds-message.assistant a { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; } .ds-message.assistant a:hover { opacity: 0.72; } .ds-message.assistant table { display: block; width: 100%; max-width: 100%; overflow-x: auto; border-collapse: collapse; margin: 9px 0; font-size: 0.93em; } .ds-message.assistant th, .ds-message.assistant td { padding: 6px 9px; border: 1px solid #d9dce0; text-align: left; vertical-align: top; } .ds-message.assistant th { font-weight: 650; background: rgba(0, 0, 0, 0.035); } .ds-message.assistant img { display: block; max-width: 100%; height: auto; border-radius: 7px; margin: 8px 0; } .ds-message.assistant input[type="checkbox"] { margin-right: 0.45em; vertical-align: -1px; } .ds-message.user { align-self: flex-end; background: #2c2f33; color: #ffffff; border-radius: 18px 18px 4px 18px; padding: 10px 16px; max-width: 90%; } .ds-input-area { display: flex; gap: 6px; padding-top: 10px; margin-top: 4px; width: 100%; } .ds-input-area input { flex: 1; border: 1px solid #dadce0; border-radius: 10px; padding: 8px 14px; font-size: 13px; outline: none; background: #ffffff; color: #0f1115; min-width: 0; } .ds-input-area input:focus { border-color: #0f1115; } .ds-send-btn { padding: 8px 16px; background: #1a1d21; color: #ffffff; border: none; border-radius: 10px; cursor: pointer; font-size: 13px; font-weight: 500; transition: background 0.2s; white-space: nowrap; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .ds-send-btn:hover { background: #33373d; } .ds-send-btn:active { background: #000000; } .ds-loading { text-align: center; color: #61666b; font-size: 12px; padding: 4px 0; } .ds-conversation, .gt-scroll-box, .meanings, .word-list-container, .config-scroll-content, .notes-list-container, .note-editor-textarea, .deleted-notes-container { overflow-x: hidden; --scrollbar-opacity: 0; scrollbar-width: thin; scrollbar-color: rgba(128, 128, 128, calc(0.4 * var(--scrollbar-opacity, 1))) transparent; } .ds-conversation::-webkit-scrollbar, .gt-scroll-box::-webkit-scrollbar, .meanings::-webkit-scrollbar, .word-list-container::-webkit-scrollbar, .config-scroll-content::-webkit-scrollbar, .notes-list-container::-webkit-scrollbar, .note-editor-textarea::-webkit-scrollbar, .deleted-notes-container::-webkit-scrollbar { width: 4px; height: 4px; background: transparent; } .ds-conversation::-webkit-scrollbar-thumb, .gt-scroll-box::-webkit-scrollbar-thumb, .meanings::-webkit-scrollbar-thumb, .word-list-container::-webkit-scrollbar-thumb, .config-scroll-content::-webkit-scrollbar-thumb, .notes-list-container::-webkit-scrollbar-thumb, .note-editor-textarea::-webkit-scrollbar-thumb, .deleted-notes-container::-webkit-scrollbar-thumb { background: rgba(128, 128, 128, calc(0.4 * var(--scrollbar-opacity, 1))); border-radius: 4px; transition: background 0.3s; } .ds-conversation::-webkit-scrollbar-thumb:hover .gt-scroll-box::-webkit-scrollbar-thumb:hover, .meanings::-webkit-scrollbar-thumb:hover, .word-list-container::-webkit-scrollbar-thumb:hover, .config-scroll-content::-webkit-scrollbar-thumb:hover, .notes-list-container::-webkit-scrollbar-thumb:hover, .note-editor-textarea::-webkit-scrollbar-thumb:hover, .deleted-notes-container::-webkit-scrollbar-thumb:hover { background: rgba(128, 128, 128, 0.7); } .word-list-panel { display: flex; flex-direction: column; } .word-list-container { flex: 1 1 auto; overflow-y: auto; max-height: 264px; } .word-list-item { display: flex; align-items: center; justify-content: space-between; flex-wrap: nowrap; padding: 8px 16px; border-bottom: 1px solid #f0f0f0; } .word-list-word { white-space: normal; word-break: break-word; font-size: 16px; font-weight: 500; flex-shrink: 1; margin-right: 8px; min-width: 0; } .word-list-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; } .word-list-search.active { background: #f1f3f4 !important; color: #1a73e8 !important; } .word-list-sort-bar { display: flex; align-items: center; gap: 12px; padding: 10px 16px; background-color: #f7f7f7; font-size: 14px; flex-wrap: wrap; transition: background-color 0.2s, border-color 0.2s; } .config-label-sort{ color: #333333; font-weight: 500; margin-right: 4px; transition: color 0.2s; } .sort-option { display: inline-flex; align-items: center; padding: 1px 6px; background-color: transparent; color: #444444; border: 1px solid transparent; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s ease; user-select: none; white-space: nowrap; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .sort-option:hover { background-color: #e8e8e8; color: #222222; border-color: #cccccc; } .sort-option:active { transform: scale(0.96); } .sort-option.active { background-color: #222222; color: #ffffff; border-color: #222222; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); pointer-events: none; } .sort-option:focus-visible { outline: 2px solid #888888; outline-offset: 2px; } .word-list-sync-status { padding: 8px 16px; font-size: 13px; color: #61666b; background-color: #f7f7f7; border-bottom: 1px solid #e0e0e0; transition: background-color 0.2s, color 0.2s, border-color 0.2s; } .word-list-actions .gt-icon-btn { width: 28px; height: 28px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; } .word-list-actions .fav-star { display: inline-flex; align-items: center; justify-content: center; } .note-view-content { font-size: 14px; line-height: 1.55; color: #0f1115; padding: 4px 0; } .note-view-content h1, .note-view-content h2, .note-view-content h3, .note-view-content h4, .note-view-content h5, .note-view-content h6 { margin: 14px 0 7px; line-height: 1.35; font-weight: 650; } .note-view-content h1:first-child, .note-view-content h2:first-child, .note-view-content h3:first-child, .note-view-content h4:first-child, .note-view-content h5:first-child, .note-view-content h6:first-child { margin-top: 0; } .note-view-content h1 { font-size: 1.45em; } .note-view-content h2 { font-size: 1.28em; } .note-view-content h3 { font-size: 1.15em; } .note-view-content h4, .note-view-content h5, .note-view-content h6 { font-size: 1em; } .note-view-content strong { font-weight: 650; } .note-view-content em { font-style: italic; } .note-view-content del { opacity: 0.72; } .note-view-content p { margin: 0 0 8px 0; line-height: 1.55; } .note-view-content p:last-child { margin-bottom: 0; } .note-view-content ul, .note-view-content ol { margin: 6px 0 9px; padding-left: 1.55em; } .note-view-content ul ul, .note-view-content ul ol, .note-view-content ol ul, .note-view-content ol ol { margin-top: 2px; margin-bottom: 2px; } .note-view-content li { margin: 2px 0; padding-left: 2px; line-height: 1.5; } .note-view-content li > p { margin: 0; } .note-view-content code { background: rgba(0, 0, 0, 0.075); padding: 0.12em 0.36em; border-radius: 5px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; font-size: 0.88em; overflow-wrap: anywhere; } .note-view-content pre { display: block; width: 100%; box-sizing: border-box; margin: 9px 0; padding: 11px 12px; background: rgba(0, 0, 0, 0.065); border: 1px solid rgba(0, 0, 0, 0.06); border-radius: 8px; overflow-x: auto; overflow-y: hidden; white-space: pre; line-height: 1.5; font-size: 0.88em; tab-size: 4; -webkit-overflow-scrolling: touch; } .note-view-content pre code { display: block; padding: 0; background: transparent; border-radius: 0; font-size: inherit; white-space: pre; overflow-wrap: normal; word-break: normal; } .note-view-content blockquote { margin: 8px 0; padding: 4px 0 4px 12px; border-left: 3px solid #c9cdd1; color: #5f6368; } .note-view-content blockquote p:last-child { margin-bottom: 0; } .note-view-content hr { border: 0; border-top: 1px solid #d9dce0; margin: 12px 0; } .note-view-content a { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; } .note-view-content a:hover { opacity: 0.72; } .note-view-content table { display: block; width: 100%; max-width: 100%; overflow-x: auto; border-collapse: collapse; margin: 9px 0; font-size: 0.93em; } .note-view-content th, .note-view-content td { padding: 6px 9px; border: 1px solid #d9dce0; text-align: left; vertical-align: top; } .note-view-content th { font-weight: 650; background: rgba(0, 0, 0, 0.035); } .note-view-content img { display: block; max-width: 100%; height: auto; border-radius: 7px; margin: 8px 0; } .note-view-content input[type="checkbox"] { margin-right: 0.45em; vertical-align: -1px; } .note-view-title { flex: 1; min-width: 0; word-wrap: break-word; white-space: normal; margin: 0px 8px 0px 4px; padding-bottom: 4px; font-size: 18px; font-weight: 600; } .config-title { display: flex; align-items: flex-start; } .header-action-btn { flex-shrink: 0; } .word-result { background: #f9f9f9; } .word-result .phonetics { margin-bottom: 4px; } .word-result .meanings { font-size: 14px; line-height: 1.5; } .word-result .phonetic-item { font-size: 13px; } .word-trigger { cursor: pointer; text-decoration: underline dotted #9aa0a6 1px; text-underline-offset: 3px; transition: background 0.2s; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .word-trigger:hover { background: rgba(0, 0, 0, 0.05); } .word-trigger.active { background: rgba(0, 0, 0, 0.05); } .inline-word-card { max-height: 180px; overflow-y: auto; margin-top: 8px; padding: 6px 10px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e0e0e0; scrollbar-width: thin; scrollbar-color: rgba(128, 128, 128, calc(0.4 * var(--scrollbar-opacity, 1))) transparent; } .inline-word-card::-webkit-scrollbar { width: 4px; height: 4px; background: transparent; } .inline-word-card::-webkit-scrollbar-thumb { background: rgba(128, 128, 128, calc(0.4 * var(--scrollbar-opacity, 1))); border-radius: 4px; } .inline-word-card::-webkit-scrollbar-thumb:hover { background: rgba(128, 128, 128, 0.7); } .inline-word-card .meanings { max-height: none !important; overflow: visible !important; } .upstash-syncing { display: inline-flex; align-items: center; gap: 5px; } .upstash-sync-spinner { display: inline-block; width: 10px; height: 10px; box-sizing: border-box; border: 1.5px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: upstashSyncSpin 0.75s linear infinite; flex: 0 0 auto; vertical-align: middle; } @keyframes upstashSyncSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @media (prefers-reduced-motion: reduce) { .upstash-sync-spinner { animation: none; } } .word-forms { font-size: 12px; color: #888; display: flex; flex-wrap: wrap; gap: 4px 0; margin-top: 6px; } .examples-section { margin-top: 8px; padding-top: 6px; } .examples-title { font-weight: 500; font-size: 13px; color: #61666b; margin-bottom: 4px; } .example-item { margin-bottom: 6px; font-size: 13px; line-height: 1.5; } .example-en { color: #0f1115; } .example-en b { color: #638c0b; } .example-zh { color: #61666b; font-size: 12px; } .example-source { color: #9aa0a6; font-size: 11px; margin-top: 1px; } .gt-text-source, .gt-text-target { white-space: pre-wrap; word-break: normal; overflow-wrap: break-word; } .tts-read-token { border-radius: 3px; transition: background-color .1s ease, box-shadow .1s ease, color .1s ease; display: inline; word-break: normal; overflow-wrap: break-word; } .gt-text-target { display: block; width: 100%; box-sizing: border-box; white-space: pre-wrap; word-break: normal; overflow-wrap: break-word; } .gt-text-source.editable-word { display: block !important; width: 100%; box-sizing: border-box; white-space: pre-wrap; word-break: normal; overflow-wrap: break-word; } .tts-read-token.tts-reading-token { background: rgba(26, 115, 232, .22); box-shadow: 0 0 0 2px rgba(26, 115, 232, .15); color: #0b3d91; } :host(.theme-dark) .tts-read-token.tts-reading-token { background: rgba(138, 180, 248, .32); box-shadow: 0 0 0 2px rgba(138, 180, 248, .22); color: #e8eaed; } @media (prefers-color-scheme: dark) { :host(.theme-system) .tts-read-token.tts-reading-token { background: rgba(138, 180, 248, .32); box-shadow: 0 0 0 2px rgba(138, 180, 248, .22); color: #e8eaed; } } .gt-actions .custom-popover-select .popover-trigger { padding: 2px 4px; font-size: 12px; gap: 2px; white-space: nowrap; } .gt-actions .custom-popover-select .popover-dropdown { left: auto; right: 0; min-width: 110px; } :host(.theme-dark) .gt-actions .custom-popover-select .popover-trigger { color: #cfd3d6; } @media (prefers-color-scheme: dark) { :host(.theme-system) .gt-actions .custom-popover-select .popover-trigger { color: #cfd3d6; } } .note-view-content p, .note-view-content li, .note-view-content h1, .note-view-content h2, .note-view-content h3, .note-view-content h4, .note-view-content h5, .note-view-content h6, .note-view-content blockquote { position: relative; padding-right: 24px; } .paragraph-tts-btn { position: absolute; right: 0; top: 0.2em; transition: opacity 0.2s, color 0.2s, background 0.2s; cursor: pointer; color: #61666b; padding: 2px; background: transparent; border: none; line-height: 1; display: inline-flex; align-items: center; justify-content: center; border-radius: 4px; opacity: 0.35; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .note-view-content p:hover .paragraph-tts-btn, .note-view-content li:hover .paragraph-tts-btn, .note-view-content h1:hover .paragraph-tts-btn, .note-view-content h2:hover .paragraph-tts-btn, .note-view-content h3:hover .paragraph-tts-btn, .note-view-content h4:hover .paragraph-tts-btn, .note-view-content h5:hover .paragraph-tts-btn, .note-view-content h6:hover .paragraph-tts-btn, .note-view-content blockquote:hover .paragraph-tts-btn, .paragraph-tts-btn.active { opacity: 1; } .paragraph-tts-btn:hover { background: rgba(0, 0, 0, 0.06); } .paragraph-tts-btn.active { color: #1a73e8; background: rgba(26, 115, 232, 0.10); } :host(.theme-dark) .paragraph-tts-btn { color: #cfd3d6; } :host(.theme-dark) .paragraph-tts-btn:hover { background: rgba(255,255,255,0.08); } :host(.theme-dark) .paragraph-tts-btn.active { color: #8ab4f8; background: rgba(138, 180, 248, 0.15); } @media (prefers-color-scheme: dark) { :host(.theme-system) .paragraph-tts-btn { color: #cfd3d6; } :host(.theme-system) .paragraph-tts-btn:hover { background: rgba(255,255,255,0.08); } :host(.theme-system) .paragraph-tts-btn.active { color: #8ab4f8; background: rgba(138, 180, 248, 0.15); } } .note-tts-controls { display: flex; align-items: center; gap: 8px; padding: 10px 0 8px; margin-top: 8px; flex-wrap: wrap; } .note-tts-play-all-btn { flex: 1 1 auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 8px 14px; background: #1a1d21; color: #fff; border: none; border-radius: 10px; cursor: pointer; font-size: 13px; font-weight: 500; transition: background 0.2s; -webkit-tap-highlight-color: transparent; min-width: 100px; user-select: none; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .note-tts-play-all-btn:hover { background: #33373d; } .note-tts-play-all-btn.playing { background: #d93025; } .note-tts-play-all-btn.playing:hover { background: #b7231a; } .note-tts-select-wrap { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #61666b; flex-shrink: 0; } .note-tts-select-label { font-size: 12px; color: #61666b; white-space: nowrap; } .note-tts-select-wrap .custom-popover-select .popover-trigger { height: 30px; padding: 0 8px; border-radius: 8px; border: 1px solid #dadce0; background: #fff; min-width: 78px; justify-content: space-between; font-size: 12px; } .note-tts-select-wrap .custom-popover-select .popover-trigger:hover, .note-tts-select-wrap .custom-popover-select.open .popover-trigger { background: #f1f3f4; } .note-tts-select-wrap .custom-popover-select .popover-dropdown { left: auto; right: 0; min-width: 140px; } :host(.theme-dark) .note-tts-select-label { color: #cfd3d6; } :host(.theme-dark) .note-tts-select-wrap .custom-popover-select .popover-trigger { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-dark) .note-tts-select-wrap .custom-popover-select .popover-trigger:hover, :host(.theme-dark) .note-tts-select-wrap .custom-popover-select.open .popover-trigger { background: #3c4043; } :host(.theme-dark) .note-tts-play-all-btn { background: #e8eaed; color: #0f1115; } :host(.theme-dark) .note-tts-play-all-btn:hover { background: #ffffff; } :host(.theme-dark) .note-tts-play-all-btn.playing { background: #f28b82; color: #0f1115; } :host(.theme-dark) .note-tts-play-all-btn.playing:hover { background: #ee675c; } @media (prefers-color-scheme: dark) { :host(.theme-system) .note-tts-select-label { color: #cfd3d6; } :host(.theme-system) .note-tts-select-wrap .custom-popover-select .popover-trigger { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-system) .note-tts-select-wrap .custom-popover-select .popover-trigger:hover, :host(.theme-system) .note-tts-select-wrap .custom-popover-select.open .popover-trigger { background: #3c4043; } :host(.theme-system) .note-tts-play-all-btn { background: #e8eaed; color: #0f1115; } :host(.theme-system) .note-tts-play-all-btn:hover { background: #ffffff; } :host(.theme-system) .note-tts-play-all-btn.playing { background: #f28b82; color: #0f1115; } :host(.theme-system) .note-tts-play-all-btn.playing:hover { background: #ee675c; } } :host(.theme-dark) .note-view-content { color: #e8eaed; } :host(.theme-dark) .note-view-content a { color: #8ab4f8; } :host(.theme-dark) .note-view-content code { background: rgba(255, 255, 255, 0.10); color: #e8eaed; } :host(.theme-dark) .note-view-content pre { background: rgba(0, 0, 0, 0.28); border-color: rgba(255, 255, 255, 0.08); } :host(.theme-dark) .note-view-content blockquote { border-left-color: #777; color: #c9cdd1; } :host(.theme-dark) .note-view-content hr { border-top-color: #555; } :host(.theme-dark) .note-view-content th, :host(.theme-dark) .note-view-content td { border-color: #555; color: #e8eaed; } :host(.theme-dark) .note-view-content th { background: rgba(255, 255, 255, 0.06); } :host(.theme-dark) .word-result { background: #2c2c2c !important; } :host(.theme-dark) .word-result .word-result-content { color: #e8eaed; } :host(.theme-dark) .word-result-content a { color: #cfd3d6 !important; border-bottom-color: #555 !important; } :host(.theme-dark) .word-result .phonetic-item { color: #cfd3d6; } :host(.theme-dark) .word-result .phonetic-label, :host(.theme-dark) .word-result .phonetic-val { color: #adb2b8; } :host(.theme-dark) .word-result .pronounce-button { color: #cfd3d6; } :host(.theme-dark) .word-result .pronounce-button:hover { background: #3c4043; color: #e8eaed; } :host(.theme-dark) .word-result .meanings { color: #f9fafb; } :host(.theme-dark) .inline-word-card { background: #2c2c2c; border-color: #3c4043; } :host(.theme-dark) .word-trigger { text-decoration: underline dotted #5f6368 1px; } :host(.theme-dark) .word-trigger:hover { background: rgba(255, 255, 255, 0.05); } :host(.theme-dark) .word-trigger.active { background: rgba(255, 255, 255, 0.05); } :host(.theme-dark) .word-forms { color: #aaa; } :host(.theme-dark) .example-en { color: #e8eaed; } :host(.theme-dark) .example-zh { color: #cfd3d6; } :host(.theme-dark) .example-source { color: #9aa0a6; } @media (prefers-color-scheme: dark) { :host(.theme-system) .note-view-content { color: #e8eaed; } :host(.theme-system) .note-view-content a { color: #8ab4f8; } :host(.theme-system) .note-view-content code { background: rgba(255, 255, 255, 0.10); color: #e8eaed; } :host(.theme-system) .note-view-content pre { background: rgba(0, 0, 0, 0.28); border-color: rgba(255, 255, 255, 0.08); } :host(.theme-system) .note-view-content blockquote { border-left-color: #777; color: #c9cdd1; } :host(.theme-system) .note-view-content hr { border-top-color: #555; } :host(.theme-system) .note-view-content th, :host(.theme-system) .note-view-content td { border-color: #555; color: #e8eaed; } :host(.theme-system) .note-view-content th { background: rgba(255, 255, 255, 0.06); } :host(.theme-system) .word-result { background: #2c2c2c !important; } :host(.theme-system) .word-result .word-result-content { color: #e8eaed; } :host(.theme-system) .word-result-content a { color: #cfd3d6 !important; border-bottom-color: #555 !important; } :host(.theme-system) .word-result .phonetic-item { color: #cfd3d6; } :host(.theme-system) .word-result .phonetic-label, :host(.theme-system) .word-result .phonetic-val { color: #adb2b8; } :host(.theme-system) .word-result .pronounce-button { color: #cfd3d6; } :host(.theme-system) .word-result .pronounce-button:hover { background: #3c4043; color: #e8eaed; } :host(.theme-system) .word-result .meanings { color: #f9fafb; } :host(.theme-system) .inline-word-card { background: #2c2c2c; border-color: #3c4043; } :host(.theme-system) .word-trigger { text-decoration: underline dotted #5f6368 1px; } :host(.theme-system) .word-trigger:hover { background: rgba(255, 255, 255, 0.05); } :host(.theme-system) .word-trigger.active { background: rgba(255, 255, 255, 0.05); } :host(.theme-system) .word-forms { color: #aaa; } :host(.theme-system) .example-en { color: #e8eaed; } :host(.theme-system) .example-zh { color: #cfd3d6; } :host(.theme-system) .example-source { color: #9aa0a6; } } .note-editor-textarea { scrollbar-width: thin !important; scrollbar-color: rgba(128, 128, 128, 0.4) transparent !important; } .note-editor-textarea::-webkit-scrollbar { width: 4px !important; height: 4px !important; background: transparent !important; } .note-editor-textarea::-webkit-scrollbar-thumb { background: rgba(128, 128, 128, 0.4) !important; border-radius: 4px !important; } .note-editor-textarea::-webkit-scrollbar-thumb:hover { background: rgba(128, 128, 128, 0.7) !important; } :host(.theme-dark) .note-editor-textarea, :host(.theme-dark) .note-view-content { scrollbar-color: rgba(200, 200, 200, 0.3) transparent !important; } :host(.theme-dark) .note-editor-textarea::-webkit-scrollbar-thumb, :host(.theme-dark) .note-view-content::-webkit-scrollbar-thumb { background: rgba(200, 200, 200, 0.3) !important; } :host(.theme-dark) .note-editor-textarea::-webkit-scrollbar-thumb:hover, :host(.theme-dark) .note-view-content::-webkit-scrollbar-thumb:hover { background: rgba(200, 200, 200, 0.6) !important; } @media (prefers-color-scheme: dark) { :host(.theme-system) .note-editor-textarea, :host(.theme-system) .note-view-content { scrollbar-color: rgba(200, 200, 200, 0.3) transparent !important; } :host(.theme-system) .note-editor-textarea::-webkit-scrollbar-thumb, :host(.theme-system) .note-view-content::-webkit-scrollbar-thumb { background: rgba(200, 200, 200, 0.3) !important; } :host(.theme-system) .note-editor-textarea::-webkit-scrollbar-thumb:hover, :host(.theme-system) .note-view-content::-webkit-scrollbar-thumb:hover { background: rgba(200, 200, 200, 0.6) !important; } } :host(.theme-dark) .ds-conversation, :host(.theme-dark) .gt-scroll-box, :host(.theme-dark) .meanings, :host(.theme-dark) .word-list-container, :host(.theme-dark) .config-scroll-content, :host(.theme-dark) .notes-list-container, :host(.theme-dark) .note-editor-textarea, :host(.theme-dark) .deleted-notes-container { scrollbar-color: rgba(200, 200, 200, calc(0.3 * var(--scrollbar-opacity, 1))) transparent; } :host(.theme-dark) .ds-conversation::-webkit-scrollbar-thumb, :host(.theme-dark) .gt-scroll-box::-webkit-scrollbar-thumb, :host(.theme-dark) .meanings::-webkit-scrollbar-thumb, :host(.theme-dark) .word-list-container::-webkit-scrollbar-thumb, :host(.theme-dark) .config-scroll-content::-webkit-scrollbar-thumb, :host(.theme-dark) .notes-list-container::-webkit-scrollbar-thumb, :host(.theme-dark) .note-editor-textarea::-webkit-scrollbar-thumb, :host(.theme-dark) .deleted-notes-container::-webkit-scrollbar-thumb { background: rgba(200, 200, 200, calc(0.3 * var(--scrollbar-opacity, 1))); } :host(.theme-dark) .ds-conversation::-webkit-scrollbar-thumb:hover, :host(.theme-dark) .gt-scroll-box::-webkit-scrollbar-thumb:hover, :host(.theme-dark) .meanings::-webkit-scrollbar-thumb:hover, :host(.theme-dark) .word-list-container::-webkit-scrollbar-thumb:hover, :host(.theme-dark) .config-scroll-content::-webkit-scrollbar-thumb:hover, :host(.theme-dark) .notes-list-container::-webkit-scrollbar-thumb:hover, :host(.theme-dark) .note-editor-textarea::-webkit-scrollbar-thumb:hover, :host(.theme-dark) .deleted-notes-container::-webkit-scrollbar-thumb:hover { background: rgba(200, 200, 200, 0.6); } :host(.theme-dark) .ds-send-btn { background: #e8eaed; color: #0f1115; } :host(.theme-dark) .ds-send-btn:hover { background: #ffffff; } :host(.theme-dark) .ds-input-area input { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-dark) .ds-input-area input:focus { border-color: #f9fafb; } :host(.theme-dark) .ds-message.user { background: #e8eaed; color: #0f1115; } :host(.theme-dark) .ds-message.assistant { background: #3c4043; color: #ffffff; } :host(.theme-dark) .ds-message.assistant code { background: rgba(255,255,255,0.10); } :host(.theme-dark) .ds-message.assistant pre { background: rgba(0,0,0,0.28); border-color: rgba(255,255,255,0.08); } :host(.theme-dark) .ds-message.assistant blockquote { border-left-color: #777; color: #c9cdd1; } :host(.theme-dark) .ds-message.assistant hr { border-top-color: #555; } :host(.theme-dark) .ds-message.assistant th, :host(.theme-dark) .ds-message.assistant td { border-color: #555; } :host(.theme-dark) .ds-message.assistant th { background: rgba(255,255,255,0.06); } :host(.theme-dark) .word-list-sort-bar { background-color: #2c2c2c; } :host(.theme-dark) .config-label-sort { color: #bbbbbb; } :host(.theme-dark) .sort-option { color: #aaaaaa; } :host(.theme-dark) .sort-option:hover { background-color: #4a4a4a; color: #f0f0f0; border-color: #666666; } :host(.theme-dark) .sort-option.active { background-color: #e0e0e0; color: #1a1a1a; border-color: #e0e0e0; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4); } :host(.theme-dark) .sort-option:focus-visible { outline-color: #888888; } :host(.theme-dark) .ds-message-wrapper .copy-btn:hover { background: rgba(255,255,255,0.1); } :host(.theme-dark) .ds-message-wrapper .play-btn:hover { background: rgba(255,255,255,0.1); } :host(.theme-dark) .word-list-sync-status { background-color: #2c2c2c; color: #bbbbbb; border-bottom-color: #3c4043; } @media (prefers-color-scheme: dark) { :host(.theme-system) .ds-conversation, :host(.theme-system) .gt-scroll-box, :host(.theme-system) .meanings, :host(.theme-system) .word-list-container, :host(.theme-system) .config-scroll-content, :host(.theme-system) .notes-list-container, :host(.theme-system) .note-editor-textarea, :host(.theme-system) .deleted-notes-container { scrollbar-color: rgba(200, 200, 200, calc(0.3 * var(--scrollbar-opacity, 1))) transparent; } :host(.theme-system) .ds-conversation::-webkit-scrollbar-thumb, :host(.theme-system) .gt-scroll-box::-webkit-scrollbar-thumb, :host(.theme-system) .meanings::-webkit-scrollbar-thumb, :host(.theme-system) .word-list-container::-webkit-scrollbar-thumb, :host(.theme-system) .config-scroll-content::-webkit-scrollbar-thumb, :host(.theme-system) .notes-list-container::-webkit-scrollbar-thumb, :host(.theme-system) .note-editor-textarea::-webkit-scrollbar-thumb, :host(.theme-system) .deleted-notes-container::-webkit-scrollbar-thumb { background: rgba(200, 200, 200, calc(0.3 * var(--scrollbar-opacity, 1))); } :host(.theme-system) .ds-conversation::-webkit-scrollbar-thumb:hover, :host(.theme-system) .gt-scroll-box::-webkit-scrollbar-thumb:hover, :host(.theme-system) .meanings::-webkit-scrollbar-thumb:hover, :host(.theme-system) .word-list-container::-webkit-scrollbar-thumb:hover, :host(.theme-system) .config-scroll-content::-webkit-scrollbar-thumb:hover, :host(.theme-system) .notes-list-container::-webkit-scrollbar-thumb:hover, :host(.theme-system) .note-editor-textarea::-webkit-scrollbar-thumb:hover, :host(.theme-system) .deleted-notes-container::-webkit-scrollbar-thumb:hover { background: rgba(200, 200, 200, 0.6); } :host(.theme-system) .ds-send-btn { background: #e8eaed; color: #0f1115; } :host(.theme-system) .ds-send-btn:hover { background: #ffffff; } :host(.theme-system) .ds-input-area input { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-system) .ds-input-area input:focus { border-color: #f9fafb; } :host(.theme-system) .ds-message.user { background: #e8eaed; color: #0f1115; } :host(.theme-system) .ds-message.assistant { background: #3c4043; color: #ffffff; } :host(.theme-system) .ds-message.assistant code { background: rgba(255,255,255,0.10); } :host(.theme-system) .ds-message.assistant pre { background: rgba(0,0,0,0.28); border-color: rgba(255,255,255,0.08); } :host(.theme-system) .ds-message.assistant blockquote { border-left-color: #777; color: #c9cdd1; } :host(.theme-system) .ds-message.assistant hr { border-top-color: #555; } :host(.theme-system) .ds-message.assistant th, :host(.theme-system) .ds-message.assistant td { border-color: #555; } :host(.theme-system) .ds-message.assistant th { background: rgba(255,255,255,0.06); } :host(.theme-system) .word-list-sort-bar { background-color: #2c2c2c; } :host(.theme-system) .config-label-sort { color: #bbbbbb; } :host(.theme-system) .sort-option { color: #aaaaaa; } :host(.theme-system) .sort-option:hover { background-color: #4a4a4a; color: #f0f0f0; border-color: #666666; } :host(.theme-system) .sort-option.active { background-color: #e0e0e0; color: #1a1a1a; border-color: #e0e0e0; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4); } :host(.theme-system) .sort-option:focus-visible { outline-color: #888888; } :host(.theme-system) .ds-message-wrapper .copy-btn:hover { background: rgba(255,255,255,0.1); } :host(.theme-system) .ds-message-wrapper .play-btn:hover { background: rgba(255,255,255,0.1); } :host(.theme-system) .word-list-sync-status { background-color: #2c2c2c; color: #bbbbbb; border-bottom-color: #3c4043; } } .config-textarea { width: 100%; padding: 6px 8px; border: 1px solid #dadce0; border-radius: 10px; font-size: 12px; font-family: inherit; background: #ffffff; color: #0f1115; resize: vertical; transition: border-color 0.2s; } .config-textarea:focus { outline: none; border-color: #0f1115; } :host(.theme-dark) .config-textarea { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-dark) .config-textarea:focus { border-color: #f9fafb; } @media (prefers-color-scheme: dark) { :host(.theme-system) .config-textarea { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-system) .config-textarea:focus { border-color: #f9fafb; } :host(.theme-system) .cf-disabled .key-input-field { background: #3c4043 !important; color: #888 !important; } } .notes-list-container { flex: 1 1 auto; overflow-y: auto; max-height: 264px; } .notes-list-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; border-bottom: 1px solid #f0f0f0; } .notes-list-title { font-size: 15px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; margin-right: 8px; } .notes-list-actions { display: flex; gap: 2px; flex-shrink: 0; } .notes-list-actions .gt-icon-btn { width: 28px; height: 28px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; } .note-editor-title { font-size: 16px; font-weight: 600; margin-bottom: 12px; } .note-editor-title input { width: 100%; padding: 6px 8px; border: 1px solid #dadce0; border-radius: 8px; font-size: 14px; background: #ffffff; color: #0f1115; } .note-editor-title input:focus { outline: none; border-color: #0f1115; } .note-editor-content textarea { width: 100%; min-height: 180px; padding: 6px 8px; border: 1px solid #dadce0; border-radius: 8px; font-size: 14px; font-family: inherit; background: #ffffff; color: #0f1115; resize: vertical; } .note-editor-content textarea:focus { outline: none; border-color: #0f1115; } .note-view-content { max-height: 300px; overflow-y: auto; padding: 8px 4px; font-size: 14px; line-height: 1.6; } .note-view-content h1, .note-view-content h2, .note-view-content h3 { margin: 10px 0 6px; } .note-view-content p { margin: 6px 0; } .note-view-content ul, .note-view-content ol { padding-left: 1.5em; } .note-view-content code { background: rgba(0,0,0,0.06); padding: 0.1em 0.3em; border-radius: 4px; } .note-view-content pre { background: rgba(0,0,0,0.04); padding: 8px; border-radius: 6px; overflow-x: auto; } :host(.theme-dark) .note-view-content code { background: rgba(255,255,255,0.08); } :host(.theme-dark) .note-view-content pre { background: rgba(255,255,255,0.04); } :host(.theme-dark) .notes-list-item, :host(.theme-dark) .word-list-item { border-bottom-color: #3c4043; } :host(.theme-dark) .word-list-search.active { background: #3c4043 !important; color: #1a73e8 !important; } :host(.theme-dark) .note-editor-title input, :host(.theme-dark) .note-editor-content textarea { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-dark) .note-editor-title input:focus, :host(.theme-dark) .note-editor-content textarea:focus { border-color: #f9fafb; } @media (prefers-color-scheme: dark) { :host(.theme-system) .note-view-content code { background: rgba(255,255,255,0.08); } :host(.theme-system) .note-view-content pre { background: rgba(255,255,255,0.04); } :host(.theme-system) .notes-list-item, :host(.theme-system) .word-list-item { border-bottom-color: #3c4043; } :host(.theme-system) .word-list-search.active { background: #3c4043 !important; color: #1a73e8 !important; } :host(.theme-system) .note-editor-title input, :host(.theme-system) .note-editor-content textarea { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-system) .note-editor-title input:focus, :host(.theme-system) .note-editor-content textarea:focus { border-color: #f9fafb; } } .deleted-notes-container { flex: 1 1 auto; overflow-y: auto; max-height: 264px; } .deleted-notes-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; border-bottom: 1px solid #f0f0f0; } :host(.theme-dark) .deleted-notes-item { border-bottom-color: #3c4043; } @media (prefers-color-scheme: dark) { :host(.theme-system) .deleted-notes-item { border-bottom-color: #3c4043; } } `; const style = ` :host { position: absolute; z-index: 2147483646; display: none; } .popup-container { position: relative; width: 380px; padding: 18px 24px; background: #ffffff; border-radius: 24px; box-shadow: 0 4px 20px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.1); color: #0f1115; font-family: Google Sans, Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; line-height: 1.5; font-size: 14px; box-sizing: border-box; text-align: left; transition: width 0.15s ease, padding 0.15s ease; } :host(.theme-dark) .custom-tooltip { background: rgba(245, 245, 245, 0.96); color: #151515; box-shadow: 0 4px 12px rgba(0,0,0,0.35); } :host(.theme-dark) .custom-tooltip::after { background: rgba(245, 245, 245, 0.96); } @media (prefers-color-scheme: dark) { :host(.theme-system) .custom-tooltip { background: rgba(245, 245, 245, 0.96); color: #151515; } :host(.theme-system) .custom-tooltip::after { background: rgba(245, 245, 245, 0.96); } } .toast-msg { position: absolute; bottom: -40px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); color: #fff; padding: 6px 12px; border-radius: 4px; font-size: 12px; pointer-events: none; opacity: 0; transition: opacity 0.3s; max-width: min(300px, 80vw); white-space: normal; word-break: break-word; text-align: center; z-index: 10; box-sizing: border-box; } :host(.show-toast) .toast-msg { opacity: 1; } :host(.mini-mode) .popup-container { width: auto; padding: 8px 12px; user-select: none; -webkit-user-select: none; } :host(.mini-mode) .popup-content { display: flex; align-items: center; justify-content: center; gap: 8px; } .mini-icon-btn { background: none; border: none; cursor: pointer; padding: 4px; margin: 0; outline: none; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; border-radius: 12px; transition: background 0.2s ease, transform 0.1s ease; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; color: #61666b; } .mini-icon-btn:hover { background: #f1f3f4; transform: scale(1.05); color: #1a73e8; } .mini-icon-btn:active { transform: scale(0.95); } .mini-icon-btn img, .mini-icon-btn svg { width: 28px; height: 28px; display: block; pointer-events: none; } .popup-container *, .popup-container *::before, .popup-container *::after { box-sizing: border-box; } .popup-arrow { display: none; position: absolute; width: 12px; height: 12px; background: inherit; top: -5px; left: 50%; transform: translateX(-50%) rotate(45deg); z-index: 0; pointer-events: none; border-radius: 2px; } :host(.above-selection) .popup-arrow { top: auto; bottom: -5px; } .popup-content { position: relative; z-index: 1; display: block; width: 100%; min-width: 0; padding: 0; margin: 0; color: inherit; font: inherit; line-height: inherit; } .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; font-size: 12px; color: #61666b; } .header-actions { display: flex; align-items: center; gap: 4px; } .header-action-btn, .favorite-btn { width: 28px; height: 28px; cursor: pointer; color: #61666b; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; text-decoration: none; transition: background 0.2s, color 0.2s; background: transparent; border: none; padding: 0; outline: none; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .favorite-btn { opacity: 0; animation: favFadeIn 0.4s ease forwards; flex-shrink: 0; } @keyframes favFadeIn { from { opacity: 0; } to { opacity: 1; } } .header-action-btn:hover, .header-action-btn:active, .favorite-btn:hover, .favorite-btn:active{ background: #f1f3f4; color: #0f1115; } .header-action-btn.close-btn:hover, .header-action-btn.close-btn:active { color: #0f1115; } .engine-switch { display: inline-flex; align-items: center; border-radius: 10px; gap: 4px; } .engine-icon-btn { border: none; background: transparent; padding: 4px; border-radius: 12px; cursor: pointer; align-items: center; justify-content: center; transition: all 0.2s ease; outline: none; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .engine-icon-btn img { width: 28px; height: 28px; display: block; } .engine-icon-btn:hover { background: #f1f3f4; transform: scale(1.05); } .engine-icon-btn:active { transform: scale(.95); } .engine-icon-btn.active { background: #f1f3f4; transform: scale(.95); } .engine-icon-btn.active span { color: #1a73e8; font-weight: 600; } .gt-section { display: flex; flex-direction: column; gap: 4px; } .gt-lang-bar { display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: #61666b; font-weight: 500; margin-bottom: 2px; } .gt-lang-bar1{ padding-left: 4px; } .custom-popover-select { position: relative; display: inline-block; user-select: none; -webkit-user-select: none; } .popover-trigger { display: inline-flex; align-items: center; gap: 4px; background: transparent; color: #61666b; font-size: 12px; font-weight: 500; border: 1px solid transparent; border-radius: 6px; padding: 2px 4px; cursor: pointer; transition: background 0.2s, color 0.2s, border-color 0.2s; outline: none; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .popover-trigger:hover, .custom-popover-select.open .popover-trigger { background: #f1f3f4; } .popover-trigger svg { width: 12px; height: 12px; transition: transform 0.2s ease; stroke: currentColor; } .custom-popover-select.open .popover-trigger svg { transform: rotate(180deg); } .popover-dropdown { display: none; position: absolute; top: calc(100% + 4px); left: 0; min-width: 120px; background: #ffffff; border-radius: 10px; box-shadow: 0 4px 16px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.1); padding: 4px 4px; z-index: 100; transform-origin: top left; animation: popover-in 0.15s ease-out; } .custom-popover-select.open .popover-dropdown { display: block; } @keyframes popover-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } .popover-option { padding: 6px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; display: flex; align-items: center; justify-content: space-between; transition: background 0.15s; gap: 8px; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .popover-option:hover { background: #f1f3f4; } .popover-option .check-icon { width: 14px; height: 14px; display: none; stroke: currentColor; flex-shrink: 0; } .popover-option.selected .check-icon { display: block; } .gt-actions { display: flex; align-items: center; gap: 6px; } .gt-icon-btn { background: none; border: none; padding: 6px; margin: 0; cursor: pointer; color: #61666b; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; transition: background 0.2s, color 0.2s; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .gt-icon-btn:hover { background: #f1f3f4; } .gt-scroll-box { max-height: 180px; overflow-y: auto; padding: 0 4px; } .gt-text-source { font-size: 15px; font-weight: 500; color: #0f1115; line-height: 1.5; display: block; white-space: pre-wrap; word-break: normal; overflow-wrap: break-word; width: 100%; box-sizing: border-box; } .gt-text-target { font-size: 15px; font-weight: 500; color: #0f1115; line-height: 1.6; word-break: break-word; overflow-wrap: break-word; white-space: pre-wrap; } .gt-pinyin { font-size: 13px; color: #61666b; margin-top: 4px; word-break: break-word; } .gt-divider { width: 200px; height: 1.5px; background-color: #e0e0e0; margin: 14px 0 8px 66px; border: none; } .word-title { font-size: 15px; font-weight: 500; color: #0f1115; margin-bottom: 8px; word-break: break-word; white-space: pre-wrap; display: flex; align-items: center; gap: 8px; align-items: center; } .editable-word { display: inline-flex; align-items: center; min-height: 28px; outline: none; cursor: text; border-radius: 2px; transition: background-color 0.2s, border-color 0.2s; } .editable-word:hover, .editable-word:focus { background-color: rgba(0, 0, 0, 0.04); } .definition-section { margin-bottom: 12px; } .phonetics { display: flex; column-gap: 16px; flex-wrap: wrap; font-size: 13px; margin-bottom: 10px; align-items: center; } .phonetic-item { display: flex; align-items: center; gap: 6px; color: #61666b; font-family: 'Lucida Sans Unicode', 'Arial Unicode MS', Arial, sans-serif; } .phonetic-item .phonetic-label { color: #61666b; font-family: system-ui, -apple-system, sans-serif; font-weight: normal; } .phonetic-item .phonetic-val { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Calibri, Roboto, sans-serif; color: #61666b; font-size: 12px; font-weight: normal; -webkit-font-smoothing: antialiased; } .phonetic-item .pronounce-button { width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center; border: none; border-radius: 50%; background: transparent; color: #61666b; cursor: pointer; transition: background 0.2s, color 0.2s; user-select: none; flex-shrink: 0; padding: 0; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .pronounce-button:hover { background: #f1f3f4; color: #0f1115; } .meanings { font-size: 15px; font-weight: 500; color: #0f1115; max-height: 285px; overflow-y: auto; margin-bottom: 12px; padding-right: 4px; #white-space: pre-wrap; } .meanings div { margin-bottom: 6px; } .dict-tabs { display: flex; font-size: 12px; justify-content: space-between; align-items: center; width: 100%; box-sizing: border-box; margin-bottom: 4px; } .dict-tab { display: inline-flex; align-items: center; justify-content: center; font-size: 11px; color: #555555; background-color: rgba(0, 0, 0, 0.05); padding: 3px 8px; border-radius: 12px; line-height: 1; box-sizing: border-box; text-decoration: none; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .dict-tab:hover { background-color: rgba(0, 0, 0, 0.12); color: #1a1a1a; text-decoration: none; } .loading-wrapper { display: flex; flex-direction: column; gap: 12px; padding: 8px 0; min-height: 120px; } .skeleton-bar { height: 14px; background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 37%, #f0f0f0 63%); background-size: 400% 100%; border-radius: 4px; animation: skeleton-loading 1.4s ease infinite; } .skeleton-bar.short { width: 40%; } .skeleton-bar.medium { width: 70%; } .skeleton-bar.long { width: 95%; } @keyframes skeleton-loading { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } } .config-panel { display: flex; flex-direction: column; height: 100%; padding: 0; box-sizing: border-box; } .config-header { flex-shrink: 0; margin-bottom: 12px; } .config-scroll-content { flex: 1; overflow-y: auto; padding-right: 6px; max-height: 327px; } .config-footer { flex-shrink: 0; padding-top: 12px; margin-top: 8px; display: flex; justify-content: flex-end; gap: 10px; } .config-title { font-size: 16px; font-weight: bold; color: #0f1115; display: flex; justify-content: space-between; align-items: center; } .config-section { display: flex; flex-direction: column; gap: 6px; margin-bottom: 20px; } .config-label-row { display: flex; justify-content: space-between; align-items: center; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .config-label-row a, .reset-prompt-link, .config-title a { color: #61666b; text-decoration: none; font-size: 12px; display: inline-flex; align-items: center; gap: 2px; transition: color 0.2s; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .config-title span span { color: #61666b; text-decoration: none; font-size: 12px; display: inline-flex; align-items: center; gap: 2px; font-weight: normal; margin-left: 6px; } .switch-view-mode { cursor: pointer; padding: 4px; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .config-title a { font-weight: normal; margin-left: 6px; } .config-label-row a:hover, .reset-prompt-link:hover, .config-title a:hover { color: #0f1115; text-decoration: underline; } .config-label { font-size: 13px; font-weight: 500; color: #0f1115; } .config-label::before { content: "▪"; display: inline-block; margin-right: 6px; font-weight: bold; opacity: 0.7; #transform: translateY(-1px); } .nd-config-label { font-size: 12px; font-weight: 500; color: #61666b; } .rd-config-label { font-size: 12px; color: #61666b; } .config-checkbox-group, .config-radio-group { display: flex; flex-wrap: wrap; gap: 6px 10px; font-size: 13px; } .config-checkbox-label, .config-radio-label { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; user-select: none; color: #61666b; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .config-actions-bar { display: flex; justify-content: flex-end; align-items: center; gap: 10px; padding: 5px 0px; } .word-list-footer { display: flex; justify-content: flex-end; align-items: center; gap: 10px; padding: 10px 0px 5px; } .cancel-config-btn { padding: 8px 18px; background: none; color: #0f1115; border: 1px solid #dadce0; border-radius: 10px; cursor: pointer; font-size: 13px; font-weight: 500; user-select: none; transition: background 0.2s, color 0.2s; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .cancel-config-btn:hover { background: rgba(38,49,72,.06); } .save-config-btn { padding: 8px 18px; background: #1a1d21; color: #ffffff; border: none; border-radius: 10px; cursor: pointer; font-size: 13px; font-weight: 500; user-select: none; transition: background 0.2s; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .save-config-btn:hover { background: #33373d; } .tts-input-row { display: flex; gap: 6px; width: 100%; } .tts-btn-row, .ds-btn-row { display: flex; gap: 10px; justify-content: space-between; align-items: center; } .tts-speed-box, ds-speed-box { display: flex; align-items: center; color: #61666b; gap: 4px; } .tts-action-btn { padding: 4px 10px; border: 1px solid #dadce0; border-radius: 10px; background: none; color: #61666b; font-size: 12px; font-weight: 500; cursor: pointer; user-select: none; transition: all 0.2s; height: 28px; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .tts-action-btn:hover { background: #f1f3f4; color: #0f1115; } .tts-status-msg, .ds-status-msg, .upstash-status-msg { font-size: 12px; margin-top: 2px; display: none; } .tts-status-msg.success, .ds-status-msg.success, .upstash-status-msg.success { color: #1e8e3e; display: block; } .tts-status-msg.error, .ds-status-msg.error, .upstash-status-msg.error { color: #d93025; display: block; } .ds-key-config-card { display: flex; flex-direction: column; gap: 12px; width: 100%; padding: 14px 16px; background: #f8f9fa; border: 1px solid #dadce0; border-radius: 10px; margin: 8px 0; box-sizing: border-box; } .ds-key-config-header { display: flex; align-items: center; justify-content: space-between; font-size: 13px; color: #d93025; font-weight: 500; } .ds-key-config-header a { color: #61666b; text-decoration: none; font-size: 12px; display: inline-flex; align-items: center; gap: 2px; transition: color 0.2s; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .ds-key-config-header a:hover { color: #0f1115; text-decoration: underline; } .ds-key-form { display: flex; gap: 8px; width: 100%; } .input-with-eye { position: relative; flex: 1; display: flex; align-items: center; } .input-with-eye .key-input-field { width: 100%; padding-right: 32px; } .toggle-pwd-btn { position: absolute; right: 6px; background: none; border: none; padding: 4px; margin: 0; cursor: pointer; color: #61666b; display: inline-flex; align-items: center; justify-content: center; border-radius: 4px; outline: none; transition: color 0.2s; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .toggle-pwd-btn:hover { color: #0f1115; } .toggle-pwd-btn svg { width: 16px; height: 16px; display: block; } .key-input-field { flex: 1; height: 34px; padding: 0 10px; border: 1px solid #dadce0; border-radius: 10px; font-size: 13px; outline: none; background: #ffffff; color: #0f1115; transition: border-color 0.2s, box-shadow 0.2s; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .key-input-field:focus { border-color: #0f1115; } .save-key-btn { height: 34px; padding: 0 16px; background: #1a1d21; color: #ffffff; border: none; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; white-space: nowrap; -webkit-tap-highlight-color: transparent; -webkit-appearance: none; } .save-key-btn:hover { background: #33373d; } .save-key-btn:active { background: #000000; } :host(.theme-dark) .popup-container { background: #292a2d; color: #e8eaed; box-shadow: 0 4px 20px rgba(0,0,0,0.5); } .zdww { margin-top: 12px; border: 1px solid #e0e0e0; border-radius: 10px; overflow: hidden; } .zdw { padding: 8px 12px; background: #f8f9fa; cursor: pointer; user-select: none; } .zdt { font-size: 13px; font-weight: 500; } .prompts-arrow { font-size: 16px; color: #61666b; } .prompts-content { padding: 12px 16px; background: #ffffff; } :host(.theme-dark) .engine-icon-btn:hover { background: rgba(255, 255, 255, 0.1); } :host(.theme-dark) .engine-icon-btn.active { background: #3c4043; } :host(.theme-dark) .popover-trigger { color: #cfd3d6; } :host(.theme-dark) .popover-trigger:hover, :host(.theme-dark) .custom-popover-select.open .popover-trigger { background: #3c4043; } :host(.theme-dark) .popover-dropdown { background: #292a2d; box-shadow: 0 4px 16px rgba(0,0,0,0.5); } :host(.theme-dark) .popover-option { color: #cfd3d6; } :host(.theme-dark) .popover-option:hover { background: #3c4043; } :host(.theme-dark) .dict-tab { color: #c0c0c0; background-color: rgba(255, 255, 255, 0.08); } :host(.theme-dark) .dict-tab:hover { background-color: rgba(255, 255, 255, 0.18); color: #ffffff; } :host(.theme-dark) .gt-lang-bar { color: #cfd3d6; } :host(.theme-dark) .card-header, :host(.theme-dark) .phonetic-item, :host(.theme-dark) .gt-pinyin, :host(.theme-dark) .config-label, :host(.theme-dark) .config-title, :host(.theme-dark) .tts-speed-box, :host(.theme-dark) .ds-speed-box { color: #f9fafb; } :host(.theme-dark) .nd-config-label { color: #cfd3d6; } :host(.theme-dark) .config-title span{ color: #f9fafb } :host(.theme-dark) .word-title, :host(.theme-dark) .meanings, :host(.theme-dark) .gt-text-source, :host(.theme-dark) .gt-text-target { color: #f9fafb; } :host(.theme-dark) .gt-icon-btn:hover, :host(.theme-dark) .mini-icon-btn:hover { background: #3c4043; color: #e8eaed; } :host(.theme-dark) .header-action-btn, :host(.theme-dark) .favorite-btn { color: #cfd3d6; } :host(.theme-dark) .header-action-btn:hover, :host(.theme-dark) .header-action-btn:active, :host(.theme-dark) .favorite-btn:hover, :host(.theme-dark) .favorite-btn:active { background: #3c4043; } :host(.theme-dark) .header-action-btn.close-btn:hover, :host(.theme-dark) .header-action-btn.close-btn:active { color: #e8eaed; } :host(.theme-dark) .gt-icon-btn { color: #cfd3d6; } :host(.theme-dark) .gt-divider, :host(.theme-dark) .config-title, :host(.theme-dark) .config-actions-bar { border-color: #3c4043; } :host(.theme-dark) .gt-divider { background-color: #4a4e51; } :host(.theme-dark) .phonetic-label, :host(.theme-dark) .phonetic-val, :host(.theme-dark) .pronounce-button { background: transparent; color: #cfd3d6; } :host(.theme-dark) .pronounce-button:hover { background: #3c4043; color: #e8eaed; } :host(.theme-dark) .editable-word:hover, :host(.theme-dark) .editable-word:focus { background-color: rgba(255, 255, 255, 0.08); } :host(.theme-dark) .skeleton-bar { background: linear-gradient(90deg, #3c4043 25%, #4a4e51 37%, #3c4043 63%); background-size: 400% 100%; } :host(.theme-dark) .ds-key-config-card { background: #303134; border-color: #3c4043; } :host(.theme-dark) .ds-key-config-header { color: #f28b82; } :host(.theme-dark) .ds-key-config-header a, :host(.theme-dark) .config-label-row a, :host(.theme-dark) .reset-prompt-link, :host(.theme-dark) .config-title a { color: #cfd3d6; } :host(.theme-dark) .config-title span span { color: #cfd3d6; } :host(.theme-dark) .ds-key-config-header a:hover, :host(.theme-dark) .config-label-row a:hover, :host(.theme-dark) .reset-prompt-link:hover, :host(.theme-dark) .config-title a:hover { color: #f9fafb; } :host(.theme-dark) .key-input-field { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-dark) .key-input-field:focus { border-color: #f9fafb; } :host(.theme-dark) .toggle-pwd-btn { color: #cfd3d6; } :host(.theme-dark) .toggle-pwd-btn:hover { color: #f9fafb; } :host(.theme-dark) .save-key-btn { background: #e8eaed; color: #0f1115; } :host(.theme-dark) .save-key-btn:hover { background: #ffffff; } :host(.theme-dark) .tts-action-btn { border-color: #61666b; color: #e8eaed; } :host(.theme-dark) .tts-action-btn:hover { background: #3c4043; } :host(.theme-dark) .config-checkbox-label, :host(.theme-dark) .config-radio-label { color: #adb2b8; } :host(.theme-dark) .cancel-config-btn { border-color:#61666b; color: #f9fafb; } :host(.theme-dark) .cancel-config-btn:hover { background: rgba(255,255,255,.08); } :host(.theme-dark) .save-config-btn { background: #e8eaed; color: #0f1115; } :host(.theme-dark) .save-config-btn:hover { background: #ffffff; } :host(.theme-light) input[type="radio"], :host(.theme-light) input[type="checkbox"] { color-scheme: light; accent-color: #0f1115; } :host(.theme-dark) input[type="radio"], :host(.theme-dark) input[type="checkbox"] { color-scheme: dark; accent-color: #e8eaed; } :host(.theme-system) input[type="radio"], :host(.theme-system) input[type="checkbox"] { color-scheme: light dark; accent-color: #0f1115; } :host(.theme-dark) .config-scroll-content::-webkit-scrollbar-thumb { background: rgba(200, 200, 200, calc(0.3 * var(--scrollbar-opacity, 1))); } :host(.theme-dark) .config-scroll-content::-webkit-scrollbar-thumb:hover { background: rgba(200, 200, 200, 0.6); } :host(.theme-dark) .zdw { background: #303134; } :host(.theme-dark) .zdww { border-color: #3c4043; } :host(.theme-dark) .prompts-content { background: #292a2d; } :host(.theme-dark) .cf-hint, :host(.theme-dark) .rd-config-label { color: #adb2b8; } @media (prefers-color-scheme: dark) { :host(.theme-system) input[type="radio"], :host(.theme-system) input[type="checkbox"] { accent-color: #e8eaed; } :host(.theme-system) .popup-container { background: #292a2d; color: #e8eaed; box-shadow: 0 4px 20px rgba(0,0,0,0.5); } :host(.theme-system) .engine-icon-btn:hover { background: rgba(255, 255, 255, 0.1); } :host(.theme-system) .engine-icon-btn.active { background: #3c4043; } :host(.theme-system) .popover-trigger { color: #cfd3d6; } :host(.theme-system) .popover-trigger:hover, :host(.theme-system) .custom-popover-select.open .popover-trigger { background: #3c4043; } :host(.theme-system) .popover-dropdown { background: #292a2d; box-shadow: 0 4px 16px rgba(0,0,0,0.5); } :host(.theme-system) .popover-option { color: #cfd3d6; } :host(.theme-system) .popover-option:hover { background: #3c4043; } :host(.theme-system) .dict-tab { color: #c0c0c0; background-color: rgba(255, 255, 255, 0.08); } :host(.theme-system) .dict-tab:hover { background-color: rgba(255, 255, 255, 0.18); color: #ffffff; } :host(.theme-system) .gt-lang-bar { color: #cfd3d6; } :host(.theme-system) .card-header, :host(.theme-system) .phonetic-item, :host(.theme-system) .gt-pinyin, :host(.theme-system) .config-label, :host(.theme-system) .config-title, :host(.theme-system) .tts-speed-box, :host(.theme-system) .ds-speed-box { color: #f9fafb; } :host(.theme-system) .nd-config-label { color: #cfd3d6; } :host(.theme-system) .config-title span{ color: #f9fafb } :host(.theme-system) .word-title, :host(.theme-system) .meanings, :host(.theme-system) .gt-text-source, :host(.theme-system) .gt-text-target { color: #f9fafb; } :host(.theme-system) .gt-icon-btn:hover, :host(.theme-system) .mini-icon-btn:hover { background: #3c4043; color: #e8eaed; } :host(.theme-system) .header-action-btn, :host(.theme-system) .favorite-btn{ color: #cfd3d6; } :host(.theme-system) .header-action-btn:hover, :host(.theme-system) .header-action-btn:active, :host(.theme-system) .favorite-btn:hover, :host(.theme-system) .favorite-btn:active { background: #3c4043; } :host(.theme-system) .header-action-btn.close-btn:hover, :host(.theme-system) .header-action-btn.close-btn:active { color: #e8eaed; } :host(.theme-system) .gt-icon-btn { color: #cfd3d6; } :host(.theme-system) .gt-divider, :host(.theme-system) .config-title, :host(.theme-system) .config-actions-bar { border-color: #3c4043; } :host(.theme-system) .gt-divider { background-color: #4a4e51; } :host(.theme-system) .phonetic-label, :host(.theme-system) .phonetic-val, :host(.theme-system) .pronounce-button { background: transparent; color: #cfd3d6; } :host(.theme-system) .pronounce-button:hover { background: #3c4043; color: #e8eaed; } :host(.theme-system) .dict-tabs { border-top-color: #3c4043; } :host(.theme-system) .editable-word:hover, :host(.theme-system) .editable-word:focus { background-color: rgba(255, 255, 255, 0.08); } :host(.theme-system) .skeleton-bar { background: linear-gradient(90deg, #3c4043 25%, #4a4e51 37%, #3c4043 63%); background-size: 400% 100%; } :host(.theme-system) .ds-key-config-card { background: #303134; border-color: #3c4043; } :host(.theme-system) .ds-key-config-header { color: #f28b82; } :host(.theme-system) .ds-key-config-header a, :host(.theme-system) .config-label-row a, :host(.theme-system) .reset-prompt-link, :host(.theme-system) .config-title a{ color: #cfd3d6; } :host(.theme-system) .config-title span span { color: #cfd3d6; } :host(.theme-system) .ds-key-config-header a:hover, :host(.theme-system) .config-label-row a:hover, :host(.theme-system) .reset-prompt-link:hover, :host(.theme-system) .config-title a:hover{ color: #f9fafb; } :host(.theme-system) .key-input-field { background: #292a2d; border-color: #61666b; color: #f9fafb; } :host(.theme-system) .key-input-field:focus { border-color: #f9fafb; } :host(.theme-system) .toggle-pwd-btn { color: #cfd3d6; } :host(.theme-system) .toggle-pwd-btn:hover { color: #f9fafb; } :host(.theme-system) .save-key-btn { background: #e8eaed; color: #0f1115; } :host(.theme-system) .save-key-btn:hover { background: #ffffff; } :host(.theme-system) .tts-action-btn { border-color: #61666b; color: #e8eaed; } :host(.theme-system) .tts-action-btn:hover { background: #3c4043; } :host(.theme-system) .config-checkbox-label, :host(.theme-system) .config-radio-label { color: #adb2b8; } :host(.theme-system) .cancel-config-btn { border-color:#61666b; color: #f9fafb; } :host(.theme-system) .cancel-config-btn:hover { background: rgba(255,255,255,.08); } :host(.theme-system) .save-config-btn { background: #e8eaed; color: #0f1115; } :host(.theme-system) .save-config-btn:hover { background: #ffffff; } :host(.theme-system) .config-scroll-content::-webkit-scrollbar-thumb { background: rgba(200, 200, 200, calc(0.3 * var(--scrollbar-opacity, 1))); } :host(.theme-system) .config-scroll-content::-webkit-scrollbar-thumb:hover { background: rgba(200, 200, 200, 0.6); } :host(.theme-system) .zdw { background: #303134; } :host(.theme-system) .zdww { border-color: #3c4043; } :host(.theme-system) .prompts-content { background: #292a2d; } :host(.theme-system) .cf-hint, :host(.theme-system) .rd-config-label { color: #adb2b8; } } ${extraStyle} `; const EYE_SVG_OPEN = ``; const EYE_SVG_CLOSED = ``; const NOTEBOOK_ICON_SVG = ``; const NOTEBOOK_ICON_SVG_ADD = ``; const TRASH_ICON_SVG = ``; GM_registerMenuCommand("⚙️ 页面配置项", () => { const selectionRect = getSelectionRect() || { left: (window.innerWidth - 380) / 2, top: (window.innerHeight - 495) / 2, width: 380, height: 495 }; renderConfigPage(selectionRect); }); function applyThemeToPopup() { if (!popup) return; const theme = getThemeMode(); popup.classList.remove('theme-light', 'theme-dark', 'theme-system'); if (theme === 'light') { popup.classList.add('theme-light'); } else if (theme === 'dark') { popup.classList.add('theme-dark'); } else { popup.classList.add('theme-system'); } } function createCustomPopoverHtml(id, options, currentValue) { const currentObj = options.find(o => o.value === currentValue) || options[0]; const arrowSvg = ``; const checkSvg = ``; const optionsHtml = options.map(opt => `