1<!DOCTYPE html> 2<html lang="zh-CN"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>忴忏¸-é¦é¡µ</title> 7 <link href="/static/favicon.ico" rel="shortcut icon"> 8 <style> 9 /* --- 1. åºç¡ãåä½åå ¨å±æ ·å¼ --- */ 10 html { 11 box-sizing: border-box; 12 font-size: clamp(14px, 1.6vw, 18px); 13 overflow-x: hidden; 14 } 15 *, *:before, *:after { 16 box-sizing: inherit; 17 } 18 /* Hide all scrollbars globally */ 19 *::-webkit-scrollbar { display: none; } 20 * { scrollbar-width: none; } 21 22 body { 23 background: #333333; 24 font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; 25 margin: 0; 26 padding: 0; 27 color: #DDDDDD; 28 line-height: 1.7; 29 position: relative; 30 min-height: 100vh; 31 overflow-x: hidden; 32 } 33 a { 34 text-decoration: none; 35 color: #D1C4E9; /* æåç龿¥é¢è² */ 36 } 37 a:hover { 38 text-decoration: underline; 39 } 40 41 /* --- 2. 卿è¾å èæ¯ (from author.html) --- */ 42 .glow { 43 position: fixed; 44 border-radius: 50%; 45 opacity: 0.3; 46 filter: blur(5em); 47 z-index: -1; 48 pointer-events: none; 49 } 50 .glow-1 { 51 width: 15em; 52 height: 15em; 53 background: #995577; 54 top: 10%; 55 left: 15%; 56 animation: float 20s infinite alternate; 57 } 58 .glow-2 { 59 width: 20em; 60 height: 20em; 61 background: #559977; 62 top: 40%; 63 right: 10%; 64 animation: float 25s infinite alternate -5s; 65 } 66 @keyframes float { 67 0% { transform: translate(0, 0); } 68 50% { transform: translate(10em, 20em) scale(1.5); } 69 100% { transform: translate(-10em, -10em); } 70 } 71 72 /* --- 3. æ°å¢: â䏿¢â å¸å± (from index.html) --- */ 73 .hub-container { 74 display: flex; 75 min-height: 100vh; 76 max-width: 100%; 77 overflow-x: hidden; 78 } 79 80 /* --- 4. æ°å¢: 左侧边æ (from index.html) --- */ 81 .hub-sidebar { 82 width: 240px; 83 background: #222222; 84 position: fixed; 85 top: 0; 86 left: 0; 87 bottom: 0; 88 padding: 1.5em; 89 display: flex; /* é»è®¤ display:flexï¼ç¨äº .desktop-only */ 90 flex-direction: column; 91 justify-content: space-between; 92 z-index: 1000; 93 overflow-y: auto; 94 } 95 96 .sidebar-header .site-title { 97 font-size: 1.8em; 98 font-weight: bold; 99 color: #FFFFFF; 100 margin: 0.0em -0.2em 0.5em -0.2em; 101 } 102 .sidebar-header .site-title a:hover { 103 text-decoration: underline; 104 } 105 106 /* æ°å¢ï¼æ é¢å¾çæ ·å¼ */ 107 .site-title-image { 108 width: 100%; 109 max-width: 190px; /* æ¡é¢ä¾§è¾¹æ çæå¤§å®½åº¦ */ 110 height: auto; 111 display: block; /* å è®¸å± ä¸ç */ 112 } 113 114 /* æ°å¢ï¼æ é¢å¾çæ ·å¼ */ 115 .site-title-image-2 { 116 background-color: #ccc; 117 border-radius: 10px; 118 width: 100%; 119 max-width: 220px; /* æ¡é¢ä¾§è¾¹æ çæå¤§å®½åº¦ */ 120 height: auto; 121 display: block; /* å è®¸å± ä¸ç */ 122 } 123 124 .main-nav ul { 125 list-style: none; 126 padding: 0; 127 margin: 0; 128 } 129 .main-nav li a { 130 display: block; 131 /* 缩å°è¡è·ï¼åå° padding å margin */ 132 padding: 0.3em 0.5em; 133 color: #BBBBBB; 134 border-radius: 8px; 135 margin-bottom: 0.3em; /* 缩å°è¡è· */ 136 font-size: 0.8em; 137 transition: background-color 0.2s, color 0.2s, transform 0.2s; 138 } 139 /* æ°å¢ï¼ç°ä»£åçæ¬åå¾®äº¤äº */ 140 .main-nav li a:hover, 141 .main-nav li a.active { 142 background: rgba(255, 255, 255, 0.05); /* 5% ç½è²éæå± */ 143 color: #FFFFFF; 144 transform: translateX(3px); /* 轻微å³ç§» */ 145 } 146 147 .sidebar-footer { 148 font-size: 0.7em; 149 color: #888888; 150 } 151 .sidebar-footer p { 152 margin: 0.25em 0; 153 } 154 .sidebar-footer a { 155 color: #AAAAAA; 156 text-decoration: underline; 157 } 158 .sidebar-footer span { 159 margin-right: 0.5em; 160 display: inline-block; 161 } 162 163 /* --- 5. æ°å¢: å³ä¾§è¾¹æ --- */ 164 .hub-right-sidebar { 165 font-size: 0.6em; 166 width: 240px; 167 background: #222222; 168 position: fixed; 169 top: 0; 170 right: 0; 171 bottom: 0; 172 padding: 1.5em; 173 display: flex; 174 flex-direction: column; 175 gap: 2em; /* 模åé´è· */ 176 z-index: 1000; 177 overflow-y: auto; 178 color: #BBBBBB; 179 } 180 .sidebar-section-title { 181 font-size: 1.2em; 182 font-weight: bold; 183 color: #FFFFFF; 184 margin: 0 0 0.8em 0; 185 padding-bottom: 0.3em; 186 border-bottom: 1px solid #444; 187 } 188 .sidebar-text { 189 font-size: 1.0em; 190 line-height: 1.6; 191 color: #BBBBBB; 192 } 193 .sidebar-text p { 194 margin: 0 0 1em 0; 195 } 196 197 /* --- 6. ä¿®æ¹: 主å å®¹åº --- */ 198 .hub-main-content { 199 /* æ¡é¢ç«¯: å·¦å³çç½ */ 200 margin: 0 240px 0 240px; 201 padding: 2em; 202 width: auto; 203 flex-grow: 1; 204 } 205 206 /* --- 7. åæå¡çæ ·å¼ (æ ä¿®æ¹) --- */ 207 .main-container { 208 width: 100%; 209 max-width: 48em; 210 margin: 0 auto; 211 overflow-x: hidden; 212 } 213 .intro-widget { 214 background: rgba(255, 255, 255, 0.9); 215 color: #1c1917; 216 position: relative; 217 z-index: 10; 218 @supports (backdrop-filter: blur(5px)) or (-webkit-backdrop-filter: blur(5px)) { 219 backdrop-filter: blur(5px); 220 -webkit-backdrop-filter: blur(5px); 221 } 222 border-radius: 1.5em; 223 padding: 1.5em; 224 margin-bottom: 1.5em; 225 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 226 transition: all 0.2s; 227 } 228 .intro-widget:hover { 229 transform: translateY(-4px); 230 box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15); 231 } 232 .image-widget { 233 padding: 0; 234 overflow: hidden; 235 transition: all 0.2s; 236 } 237 .image-widget:hover { 238 transform: translateY(-4px); 239 box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15); 240 } 241 .intro-image { 242 width: 100%; 243 height: auto; 244 display: block; 245 } 246 /* --- æ ç¾é¡µç³»ç» (æ ä¿®æ¹) --- */ 247 .tab-bar { 248 display: flex; 249 justify-content: center; 250 gap: 1em; 251 margin-bottom: 1.5em; 252 padding-bottom: 0.5em; 253 border-bottom: 1px solid rgba(0, 0, 0, 0.1); 254 flex-wrap: wrap; 255 } 256 .tab-btn { 257 font-family: 'Noto Sans SC', sans-serif; 258 font-size: 1em; 259 font-weight: 500; 260 padding: 0.5em 0.2em; 261 border: none; 262 background: none; 263 cursor: pointer; 264 color: #57534e; 265 border-bottom: 3px solid transparent; 266 transition: color 0.2s, border-color 0.2s; 267 } 268 .tab-btn.active { 269 font-weight: 700; 270 color: #1c1917; 271 border-bottom-color: #995577; 272 } 273 .tab-btn:hover:not(.active) { 274 color: #1c1917; 275 } 276 .tab-content-container { padding-top: 0em; } 277 .tab-content { display: none; text-align: justify; } 278 .tab-content.active { display: block; } 279 .tab-content p { margin: 0 0 1em 0; } 280 .tab-content h3 { 281 font-size: 1.2em; 282 color: #995577; 283 margin: 0.5em 0 0.5em 0; 284 padding-bottom: 0.25em; 285 border-bottom: 1px solid rgba(0, 0, 0, 0.05); 286 } 287 /* --- é¢ç空é´å¡ç (æ ä¿®æ¹) --- */ 288 .placeholder-widget h3 { 289 font-size: 1.3em; 290 color: #1c1917; 291 margin: 0 0 1em 0; 292 } 293 .placeholder-widget .placeholder-text { 294 color: #57534e; 295 font-style: italic; 296 } 297 .placeholder-widget hr { 298 border: 0; 299 height: 1px; 300 background: rgba(0, 0, 0, 0.1); 301 margin: 1.5em 0; 302 } 303 304 /* --- 8. æ°å¢: ååºå¼å¸å± (from index.html) --- */ 305 .mobile-only { 306 display: none; 307 } 308 .medium-footer { /* éèä¸å±é¡µè */ 309 display: none; 310 } 311 .desktop-only { 312 display: flex; /* .hub-sidebar é»è®¤ */ 313 flex-direction: column; 314 } 315 316 .mobile-header { 317 padding: 0; 318 } 319 .mobile-header-bar { 320 display: flex; 321 align-items: center; 322 gap: 12px; 323 position: fixed; 324 top: 0; 325 left: 0; 326 right: 0; 327 z-index: 999; 328 background: rgba(30,30,30,0.7); 329 backdrop-filter: blur(12px); 330 -webkit-backdrop-filter: blur(12px); 331 padding: 12px 12px; 332 } 333 .mobile-menu-btn { 334 display: flex; align-items: center; justify-content: center; 335 width: 40px; height: 40px; 336 border-radius: 10px; 337 background: rgba(255,255,255,0.08); 338 color: #aaa; 339 border: none; 340 cursor: pointer; 341 flex-shrink: 0; 342 } 343 .mobile-menu-btn:active { background: rgba(255,255,255,0.15); color: #fff; } 344 .mobile-menu-btn-sm { 345 width: 32px; height: 32px; 346 background: none; 347 } 348 .mobile-title-img { 349 max-height: 40px; 350 width: auto; 351 display: block; 352 } 353 .drawer-overlay { 354 position: fixed; inset: 0; 355 background: rgba(0,0,0,0.45); 356 z-index: 997; 357 opacity: 0; 358 pointer-events: none; 359 transition: opacity 0.2s; 360 } 361 .drawer-overlay.open { 362 opacity: 1; 363 pointer-events: auto; 364 } 365 .mobile-drawer { 366 position: fixed; 367 top: 64px; bottom: 0; 368 z-index: 998; 369 overflow-y: auto; 370 display: flex; 371 flex-direction: column; 372 transition: transform 0.25s ease-out; 373 background: rgba(30,30,30,0.8); 374 backdrop-filter: blur(16px); 375 -webkit-backdrop-filter: blur(16px); 376 } 377 .mobile-drawer-left { 378 left: 0; 379 border-radius: 0 12px 12px 0; 380 transform: translateX(-100%); 381 width: 35%; 382 max-width: 280px; 383 } 384 .mobile-drawer-right { 385 right: 0; 386 border-radius: 12px 0 0 12px; 387 transform: translateX(100%); 388 width: 65%; 389 max-width: 360px; 390 } 391 .mobile-drawer.open { 392 transform: translateX(0); 393 } 394 .drawer-head { 395 display: flex; 396 align-items: center; 397 justify-content: space-between; 398 padding: 14px 14px 10px; 399 border-bottom: 1px solid rgba(255,255,255,0.08); 400 flex-shrink: 0; 401 font-size: 13px; 402 font-weight: 600; 403 color: #ccc; 404 } 405 .drawer-close { 406 background: none; border: none; color: #888; cursor: pointer; 407 display: flex; align-items: center; justify-content: center; 408 width: 28px; height: 28px; border-radius: 6px; 409 }
410 .drawer-close:active { background: rgba(255,255,255,0.1); } 411 .drawer-nav { 412 display: flex; 413 flex-direction: column; 414 gap: 2px; 415 padding: 8px 10px; 416 flex: 1; 417 } 418 .drawer-nav a { 419 display: block; 420 padding: 10px 12px; 421 color: #bbb; 422 text-decoration: none; 423 border-radius: 8px; 424 font-size: 14px; 425 transition: background 0.15s; 426 } 427 .drawer-nav a:active { background: rgba(255,255,255,0.1); color: #fff; } 428 .drawer-user { 429 display: flex; align-items: center; gap: 10px; 430 padding: 12px 14px; 431 border-top: 1px solid rgba(255,255,255,0.08); 432 font-size: 13px; 433 flex-shrink: 0; 434 } 435 .drawer-user a { color: #aaa; text-decoration: none; } 436 .drawer-info { 437 padding: 12px 14px; 438 font-size: 13px; 439 color: #999; 440 line-height: 1.8; 441 } 442 .drawer-info a { color: #aaa; text-decoration: none; } 443 .drawer-info h4 { 444 color: #ccc; 445 font-size: 12px; 446 font-weight: 600; 447 margin: 14px 0 4px; 448 text-transform: uppercase; 449 letter-spacing: 0.5px; 450 } 451 .drawer-info h4:first-child { margin-top: 0; } 452 .drawer-info p { margin: 2px 0; } 453 .mobile-user-dropdown { 454 position: fixed; 455 top: 64px; 456 right: 56px; 457 background: rgba(30,30,30,0.9); 458 backdrop-filter: blur(16px); 459 -webkit-backdrop-filter: blur(16px); 460 border-radius: 10px; 461 padding: 6px 0; 462 z-index: 1000; 463 min-width: 140px; 464 box-shadow: 0 8px 24px rgba(0,0,0,0.3); 465 opacity: 0; 466 transform: translateY(-8px); 467 pointer-events: none; 468 transition: opacity 0.15s, transform 0.15s; 469 } 470 .mobile-user-dropdown.open { 471 opacity: 1; 472 transform: translateY(0); 473 pointer-events: auto; 474 } 475 .mobile-user-dropdown-name { 476 padding: 8px 14px 6px; 477 font-size: 13px; 478 font-weight: 600; 479 color: #ddd; 480 border-bottom: 1px solid rgba(255,255,255,0.08); 481 margin-bottom: 4px; 482 } 483 .mobile-user-dropdown a { 484 display: block; 485 padding: 8px 14px; 486 color: #aaa; 487 text-decoration: none; 488 font-size: 13px; 489 } 490 .mobile-user-dropdown a:active { 491 background: rgba(255,255,255,0.08); 492 } 493 494 /* ç§»å¨ç«¯é¡µè (æ ä¿®æ¹) */ 495 .mobile-footer { 496 width: 100%; 497 padding: 1.5em 1em; 498 } 499 .mobile-footer .main-nav ul { 500 display: grid; 501 grid-template-columns: 1fr 1fr 1fr; 502 gap: 0.5em; 503 } 504 .mobile-footer .main-nav li { 505 text-align: center; 506 } 507 .mobile-footer .main-nav li a { 508 padding: 0.6em 0.25em; 509 color: #BBBBBB; 510 border-radius: 8px; 511 background: rgba(255, 255, 255, 0.05); 512 margin: 0; 513 } 514 .mobile-footer .main-nav li a:hover { 515 background: rgba(255, 255, 255, 0.1); 516 color: #FFFFFF; 517 } 518 .mobile-user-block { 519 border-top: 1px solid rgba(255,255,255,0.08); 520 margin-top: 0.8em; 521 padding: 0.8em 0.5em 0; 522 font-size: 0.85em; 523 text-align: center; 524 } 525 .mobile-user-row { 526 display: flex; 527 align-items: center; 528 justify-content: center; 529 gap: 6px; 530 margin-bottom: 4px; 531 } 532 .mobile-user-name { 533 color: #ddd; 534 text-decoration: none; 535 font-weight: 500; 536 } 537 .mobile-user-badge { 538 font-size: 10px; 539 background: rgba(153,85,119,0.3); 540 color: #cc99bb; 541 padding: 1px 5px; 542 border-radius: 2px; 543 } 544 .mobile-user-links a { 545 color: #888; 546 text-decoration: none; 547 font-size: 0.9em; 548 } 549 .mobile-user-links a:hover { color: #ccc; } 550 .mobile-user-sep { 551 color: #555; 552 margin: 0 4px; 553 } 554 .mobile-footer .sidebar-footer { 555 margin-top: 1.5em; 556 text-align: center; 557 } 558 .mobile-footer-section { 559 margin: 0 auto; 560 font-size: 0.8em; 561 color: #888888; 562 align-content: center; 563 text-align: center; 564 } 565 566 /* ãæ°å¢ãä¸å±é¡µèæ ·å¼ */ 567 .medium-footer { 568 background: none; 569 padding: 2em; /* é¡¶é¨çç½å¤ä¸ç¹ */ 570 margin-top: 2em; /* ä¸å 容å¡çéå¼ */ 571 color: #BBBBBB; 572 width: 100%; 573 } 574 /* (å¤ç¨ .sidebar-module æ ·å¼) */ 575 .medium-footer .sidebar-module { 576 max-width: 48em; /* éå¶å 容宽度ï¼å .main-container */ 577 margin: 0 auto 2em auto; /* å± ä¸å¹¶æ·»å 模åé´è· */ 578 } 579 .medium-footer .sidebar-section-title { 580 color: #FFFFFF; /* ç¡®ä¿æ é¢å¨æ·±è²èæ¯ä¸æ¯ç½è² */ 581 } 582 583 @media (max-width: 1280px) { 584 .hub-right-sidebar { /* éèå³ä¾§è¾¹æ (容å¨) */ 585 display: none; 586 } 587 .hub-main-content { /* 主å 容åº-åªç左侧margin */ 588 margin-left: 240px; 589 margin-right: 0; 590 } 591 .medium-footer { /* æ¾ç¤ºä¸å±é¡µè (å«èç³»æ¹å¼) */ 592 display: block; 593 } 594 } 595 596 /* C. ç§»å¨è®¾å¤ (<= 960px) */ 597 /* (æ¤å¤çè§åå°è¦ç B ä¸çè§å) */ 598 @media (max-width: 960px) { 599 .desktop-only { /* éèæææ¡é¢å ç´ (å æ¬å·¦ä¾§æ ) */ 600 display: none; 601 } 602 .mobile-only { /* æ¾ç¤ºç§»å¨ç«¯é¡µç/页è */ 603 display: block; 604 } 605 .hub-main-content { 606 margin: 0; 607 padding: 64px 3vw 0 3vw; 608 overflow-x: hidden; 609 } 610 .main-container { 611 width: 100%; 612 max-width: none; 613 margin-top: 1em; 614 } 615 .medium-footer { /* éèä¸å±é¡µè */ 616 display: none; 617 } 618 /* ç¡®ä¿ mobile-footer nav éç a 乿¯ block */ 619 .mobile-footer .main-nav li a { 620 display: block; 621 } 622 /* ç¡®ä¿ .intro-widget.placeholder-widget.mobile-only çæ */ 623 .intro-widget.placeholder-widget.mobile-only { 624 display: block !important; 625 } 626 } 627 @media (max-width: 350px) { 628 .mobile-header-bar { 629 gap: 7px; 630 padding-left: 7px; 631 padding-right: 7px; 632 } 633 .mobile-title-img { 634 max-width: 55px; 635 max-height: 32px; 636 } 637 } 638 /* --- Action button hover/active transitions --- */ 639 button, .btn-sm, .btn-xs, .btn-create, .btn-approve, .btn-reject, 640 .btn-revision, .ep-btn-save, .ep-btn-cancel, 641 [onclick][style*="cursor:pointer"], [onclick][style*="cursor: pointer"] { 642 transition: filter 0.15s, transform 0.12s, box-shadow 0.15s; 643 } 644 button:hover, .btn-sm:hover, .btn-xs:hover, .btn-create:hover, 645 .btn-approve:hover, .btn-reject:hover, .btn-revision:hover, 646 .ep-btn-save:hover, .ep-btn-cancel:hover { 647 filter: brightness(1.2); 648 transform: translateY(-1px); 649 box-shadow: 0 2px 6px rgba(0,0,0,0.25); 650 } 651 button:active, .btn-sm:active, .btn-xs:active, .btn-create:active, 652 .btn-approve:active, .btn-reject:active, .btn-revision:active, 653 .ep-btn-save:active, .ep-btn-cancel:active { 654 filter: brightness(0.9); 655 transform: translateY(0) scale(0.97); 656 box-shadow: none; 657 } 658 659 </style> 660 661<style> 662 /* --- 5. å°é¨ä»¶ (Widget) åºç¡æ ·å¼ --- */ 663 /* (注æ: base.html æ .intro-widget, è¿æ¯é¦é¡µä¸ç¨ç .widget) */ 664 .widget { 665 background: rgba(255, 255, 255, 0.9); 666 color: #1c1917; 667 font-size: 0.8em; 668 669 @supports (backdrop-filter: blur(5px)) or (-webkit-backdrop-filter: blur(5px)) { 670 backdrop-filter: blur(5px); 671 -webkit-backdrop-filter: blur(5px); 672 } 673 z-index: 1; 674 border-radius: 1.5em; 675 padding: 1.5em; 676 margin-bottom: 0; 677 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 678 transition: transform 0.2s, background-color 0.2s, box-shadow 0.2s; 679 } 680 681 .widget:hover { 682 transform: translateY(-4px); 683 box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15); 684 background-color: #EEEEEE; 685 } 686 687 /* --- æç´¢å°é¨ä»¶ --- */ 688 .search-widget { 689 position: relative; 690 line-height: 1.5; 691 z-index: 10; 692 margin-bottom: 1.5em; 693 } 694 .search-form-container { 695 display: flex; 696 align-items: center; 697 background: rgba(0, 0, 0, 0.05); 698 border-radius: 1em; 699 padding: 0.5em 1em; 700 position: relative; 701 z-index: 101; 702 border: 1px solid transparent; 703 transition: border-color 0.2s, background-color 0.2s; 704 } 705 .search-form-container:focus-within { 706 background: rgba(0, 0, 0, 0.02); 707 /* (å border-color: #F6F6E0; ) */ 708 border-color: rgba(0, 0, 0, 0.1); 709 } 710 711 .search-input { 712 flex-grow: 1; 713 border: none; 714 outline: none; 715 background: transparent; 716 font-size: 1em; 717 color: #1c1917; 718 min-width: 0; 719 } 720 .search-button { 721 background: transparent; 722 border: none; 723 cursor: pointer; 724 padding: 0; 725 flex-shrink: 0; 726 } 727 .search-button img { width: 1.8em; vertical-align: middle; opacity: 0.7; } 728 .search-button:hover img { opacity: 1; } 729 .search-button:active { transform: scale(0.95); } 730 731 .checkbox-container-switch { 732 font-family: 'Noto Sans SC', sans-serif; 733 display: flex; 734 flex-wrap: wrap; 735 gap: 1em 1.5em; 736 justify-content: center; 737 padding: 1em 0 0.5em 0; 738 color: #333; 739 font-size: 0.9em; 740 } 741 .checkbox-container-switch label { 742 display: flex; 743 align-items: center; 744 cursor: pointer; 745 } 746 .checkbox-container-switch input[type="checkbox"] { 747 opacity: 0; 748 width: 0; 749 height: 0; 750 position: absolute; 751 } 752 .switch-track { 753 position: relative; 754 display: inline-block; 755 width: 44px; 756 height: 24px; 757 background-color: #ccc; 758 border-radius: 12px; 759 transition: background-color 0.3s; 760 margin-right: 8px; 761 } 762 .switch-track::after { 763 content: ''; 764 position: absolute; 765 width: 18px; 766 height: 18px; 767 background-color: white; 768 border-radius: 50%; 769 top: 3px; 770 left: 3px; 771 transition: transform 0.3s; 772 box-shadow: 0 1px 3px rgba(0,0,0,0.1); 773 } 774 input:checked + .switch-track { 775 background-color: #559977; 776 } 777 input:checked + .switch-track::after { 778 transform: translateX(20px); 779 } 780 781 #autocomplete-suggestions { 782 display: none; 783 position: absolute; 784 top: 8.25em; 785 left: 1.5em; 786 right: 1.5em; 787 background-color: #FFFFFF; 788 border: 1px solid #FFFFFF; 789 border-radius: 0 0 12px 12px; 790 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); 791 z-index: 102; 792 max-height: 15em; 793 overflow-y: auto; 794 } 795 .suggestion-item { 796 padding: 0.8em 1.2em; 797 cursor: pointer; 798 color: #1c1917; 799 font-size: 0.9em; 800 border-top: 1px solid #dddddd; 801 z-index: 102; 802 position: relative; 803 } 804 .suggestion-item:hover, 805 .suggestion-item.active { 806 background-color: rgba(0, 0, 0, 0.05); /* (å #F6F6E0) */ 807 } 808 809 /* --- ç»è®¡å°é¨ä»¶ (ççåå¾è¡¨) --- */ 810 .stats-widget-grid { 811 display: flex; 812 align-items: center; 813 gap: 1.5em; 814 z-index: 100; 815 } 816
817 .chart-container { 818 flex-shrink: 0; 819 width: 150px; 820 height: 150px; 821 position: relative; 822 } 823 .donut-chart { 824 width: 100%; 825 height: 100%; 826 border-radius: 50%; 827 background: conic-gradient(#DDD4ED 0% 33.3%, #A8D8B9 33.3% 66.6%, #F3B9B0 66.6% 100%); 828 transition: transform 0.3s ease-in-out, box-shadow 0.3s; 829 } 830 .donut-chart:hover { 831 transform: scale(1.1); 832 box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15); 833 } 834 .donut-chart::before { 835 content: ''; 836 position: absolute; 837 left: 50%; 838 top: 50%; 839 transform: translate(-50%, -50%); 840 width: 65%; 841 height: 65%; 842 background: #F6F6F6; /* (ç¨ä¸ä¸ªç¨äº®çèæ¯è²) */ 843 border-radius: 50%; 844 } 845 .chart-legend { 846 flex-grow: 1; 847 list-style: none; 848 padding: 0; 849 margin: 0; 850 } 851 .legend-item { 852 display: flex; 853 align-items: center; 854 font-size: 1.1em; 855 margin-bottom: 0.8em; 856 } 857 .legend-color-box { 858 width: 20px; 859 height: 20px; 860 border-radius: 4px; 861 margin-right: 0.8em; 862 } 863 .legend-text { 864 flex-grow: 1; 865 color: #333; 866 text-underline-offset: 0.2em; 867 } 868 .legend-percent { 869 font-weight: bold; 870 font-size: 1.1em; 871 color: #1c1917; 872 } 873 874 /* --- 5.5. Masonry Grid Layout --- */ 875 .masonry-grid { 876 display: flex; 877 gap: 0.8em; 878 align-items: flex-start; 879 } 880 .masonry-col { 881 flex: 1; 882 min-width: 0; 883 display: flex; 884 flex-direction: column; 885 gap: 0.8em; 886 } 887 .masonry-grid > .widget { 888 break-inside: avoid; 889 margin-bottom: 1.5em; 890 } 891 .new-poem-widget > *:not(canvas) { 892 position: relative; 893 z-index: 1; 894 } 895 /* legacy compat */ 896 .widget-row { 897 display: flex; 898 gap: 1.5em; 899 align-items: flex-start; 900 margin-bottom: 1.5em; 901 } 902 .widget-row > .widget, 903 .widget-row > div.widget { 904 flex: 1; 905 min-width: 0; 906 margin-bottom: 0; 907 display: flex; 908 flex-direction: column; 909 } 910 .banner-widget { 911 padding: 0; 912 overflow: hidden; 913 display: none; 914 margin-bottom: 1.5em; 915 } 916 .banner-slider-container { 917 position: relative; 918 width: 100%; 919 overflow: hidden; 920 } 921 .banner-slides { 922 display: flex; 923 width: 100%; 924 transition: transform 0.5s ease-in-out; 925 } 926 .banner-slide { 927 display: block; 928 flex-shrink: 0; 929 width: 100%; 930 position: relative; 931 text-decoration: none; 932 color: white; 933 aspect-ratio: 21 / 9; 934 overflow: hidden; 935 background: #111; 936 } 937 .banner-image { 938 width: 100%; 939 height: 100%; 940 object-fit: cover; 941 display: block; 942 } 943 .banner-caption { 944 position: absolute; 945 bottom: 0; 946 left: 0; 947 right: 0; 948 padding: 1.5em 1.2em 1em; 949 background: linear-gradient(0deg, rgba(0, 0, 0, 0.65) 0%, rgba(0, 0, 0, 0) 100%); 950 font-size: 1.1em; 951 font-weight: 600; 952 white-space: nowrap; 953 overflow: hidden; 954 text-overflow: ellipsis; 955 } 956 .banner-nav { 957 position: absolute; 958 top: 50%; 959 transform: translateY(-50%); 960 background: rgba(0, 0, 0, 0.3); 961 color: white; 962 border: none; 963 border-radius: 50%; 964 cursor: pointer; 965 width: 2.5em; 966 height: 2.5em; 967 font-size: 1.2em; 968 display: flex; 969 align-items: center; 970 justify-content: center; 971 opacity: 0; 972 transition: opacity 0.3s; 973 } 974 .banner-slider-container:hover .banner-nav { 975 opacity: 0.8; 976 } 977 .banner-nav:hover { 978 opacity: 1; 979 background: rgba(0, 0, 0, 0.5); 980 } 981 #banner-prev { left: 0.8em; } 982 #banner-next { right: 0.8em; } 983 .banner-dots { 984 position: absolute; 985 bottom: 0.8em; 986 left: 50%; 987 transform: translateX(-50%); 988 display: flex; 989 gap: 0.5em; 990 } 991 .banner-dot { 992 width: 8px; 993 height: 8px !important; 994 padding: 0; 995 border: 1px solid #1c1917; 996 border-radius: 50%; 997 background: rgba(255, 255, 255, 0.5); 998 cursor: pointer; 999 transition: background-color 0.3s; 1000 } 1001 .banner-dot.active { 1002 background: rgba(255, 255, 255, 1); 1003 } 1004 1005 /* --- 6. æ¯æ¥ä¸è¯å°é¨ä»¶ --- */ 1006 .random-poem-widget { 1007 border-left-width: 5px; 1008 border-left-style: solid; 1009 border-left-color: #cfd0ff; 1010 padding-left: 1.5em; 1011 line-height: 1.8; 1012 position: relative; 1013 transition: transform 0.2s, background-color 0.2s, box-shadow 0.2s, border 0.5s; 1014 } 1015 .random-poem-widget:hover { 1016 border-left-width: 10px; 1017 border-right-width: 10px; 1018 border-right-style: solid; 1019 border-right-color: #cfd0ff; 1020 } 1021 .poem-header { 1022 display: flex; 1023 justify-content: space-between; 1024 align-items: center; 1025 margin-bottom: 1em; 1026 padding-bottom: 0.5em; 1027 border-bottom: 1px solid rgba(0, 0, 0, 0.1); 1028 } 1029 .poem-header-title { 1030 font-size: 1.1em; 1031 font-weight: bold; 1032 color: #555; 1033 } 1034 .poem-ai-button { 1035 background: none; 1036 border: none; 1037 color: #995577; 1038 font-weight: bold; 1039 text-decoration: underline; 1040 text-underline-offset: 0.2em; 1041 cursor: pointer; 1042 font-size: 0.9em; 1043 padding: 0; 1044 transition: opacity 0.2s; 1045 } 1046 .poem-ai-button:hover { opacity: 0.7; } 1047 .poem-content { 1048 font-size: 1.25em; 1049 white-space: pre-wrap; 1050 margin-bottom: 1.5em; 1051 line-height: 1.9; 1052 color: #1c1917; 1053 } 1054 .poem-footer { 1055 text-align: right; 1056 font-size: 1em; 1057 color: #444; 1058 } 1059 .poem-footer a { 1060 color: #1c1917; 1061 text-decoration: underline; 1062 text-underline-offset: 0.2em; 1063 } 1064 #interpretResult { 1065 display: none; 1066 font-size: 0.9em; 1067 padding: 1em; 1068 text-align: justify; 1069 line-height: 1.6; 1070 margin-top: 1.5em; 1071 background: rgba(0, 0, 0, 0.03); 1072 border-radius: 8px; 1073 border-top: 1px solid rgba(0, 0, 0, 0.05); 1074 } 1075 1076 /* --- 7. æ¸¸ææ¨¡åä¸å±æ ·å¼ --- */ 1077 .game-widget h3 { 1078 margin-top: 0; 1079 font-size: 1.5em; 1080 text-align: center; 1081 } 1082 .game-content { min-height: 150px; transition: opacity 0.3s ease; } 1083 .game-prompt { font-size: 1.2em; font-weight: bold; color: #333; margin-bottom: 0.5em; } 1084 .game-demo { 1085 font-family: 'lxgw', 'Noto Serif SC', serif; 1086 font-size: 1.1em; 1087 font-style: italic; 1088 background: rgba(0, 0, 0, 0.03); 1089 border-radius: 8px; 1090 padding: 1em; 1091 margin-bottom: 1.5em; 1092 white-space: pre-wrap; 1093 line-height: 1.7; 1094 } 1095 .game-area { display: grid; grid-template-columns: 1fr; gap: 0.8em; } 1096 .choice-button { 1097 display: block; 1098 width: 100%; 1099 padding: 0.5em; 1100 font-family: 'Noto Sans SC', sans-serif; 1101 font-size: 1em; 1102 background: rgba(0, 0, 0, 0.05); 1103 border: 2px solid transparent; 1104 border-radius: 8px; 1105 cursor: pointer; 1106 transition: all 0.2s ease; 1107 color: #1c1917; 1108 } 1109 .choice-button:hover:not(:disabled) { 1110 background: rgba(0, 0, 0, 0.08); 1111 border-color: rgba(0, 0, 0, 0.1); 1112 }
1113 .choice-button:disabled { cursor: not-allowed; opacity: 0.8; } 1114 .sortable-list { list-style: none; padding: 0; margin: 0; font-family: 'Noto Serif SC', serif; } 1115 .sortable-item { 1116 background: rgba(0, 0, 0, 0.05); 1117 padding: 1em; 1118 margin-bottom: 0.5em; 1119 border-radius: 8px; 1120 border: 2px solid #ccc; 1121 cursor: grab; 1122 transition: background-color 0.2s; 1123 user-select: none; 1124 } 1125 .sortable-item:hover { background: rgba(0, 0, 0, 0.08); } 1126 .sortable-item.sortable-ghost { opacity: 0.5; background: rgba(0, 0, 0, 0.1); } 1127 .sortable-item.sortable-chosen { cursor: grabbing; } 1128 1129 /* 游æåé¦åº */ 1130 .game-footer { 1131 display: flex; 1132 justify-content: flex-end; 1133 align-items: center; 1134 margin-top: 1.5em; 1135 flex-wrap: wrap; 1136 gap: 0.8em; 1137 } 1138 .game-feedback { 1139 font-size: 1.2em; 1140 font-weight: bold; 1141 width: 100%; 1142 margin-right: 0; 1143 } 1144 .game-feedback.correct { color: #559977; } 1145 .game-feedback.incorrect { color: #995577; } 1146 .game-button { 1147 font-family: 'Noto Sans SC', sans-serif; 1148 font-size: 0.9em; 1149 padding: 0.5em 1.5em; 1150 border-radius: 8px; 1151 border: 2px solid #995577; 1152 cursor: pointer; 1153 background: #995577; 1154 color: white; 1155 transition: background-color 0.2s; 1156 display: none; 1157 text-align: center; 1158 white-space: nowrap; 1159 flex-grow: 1; 1160 } 1161 .game-button:hover { background: #aA6688; } 1162 .game-button.secondary { background: none; border: 2px solid #995577; color: #995577; display: none; } 1163 .game-button.secondary:hover { background: rgba(153, 85, 119, 0.1); } 1164 .game-button.skip { background: none; border: 2px solid #888; color: #555; display: none; } 1165 .game-button.skip:hover { background: rgba(0, 0, 0, 0.1); } 1166 .game-loading { text-align: center; padding: 2em; font-size: 1.2em; color: #555; } 1167 .poem-ai-button.loading::after { 1168 content: '.'; 1169 animation: dots 1s steps(5, end) infinite; 1170 display: inline-block; 1171 width: 2em; 1172 text-align: left; 1173 } 1174 @keyframes dots { 1175 0% { content: '.'; } 20% { content: '..'; } 40% { content: '...'; } 1176 60% { content: '....'; } 80% { content: '.....'; } 100% { content: '......'; } 1177 } 1178 1179 /* --- 8. æ¦åå表å°é¨ä»¶ (List Widget) --- */ 1180 .widget-title { 1181 font-size: 1.5em; 1182 font-weight: 600; 1183 text-align: center; 1184 margin: 0 0 1em 0; 1185 color: #333; 1186 } 1187 .list-widget .poem-list-container { 1188 max-height: 30em; 1189 overflow-y: auto; 1190 margin: 0 -0.5em; 1191 padding: 0 0.5em; 1192 scrollbar-width: thin; 1193 scrollbar-color: #AAAAAA rgba(0, 0, 0, 0.05); 1194 } 1195 .list-widget .poem-list-container::-webkit-scrollbar { width: 8px; } 1196 .list-widget .poem-list-container::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.05); border-radius: 10px; } 1197 .list-widget .poem-list-container::-webkit-scrollbar-thumb { background-color: #AAAAAA; border-radius: 10px; border: 2px solid rgba(0, 0, 0, 0); background-clip: padding-box; } 1198 .list-widget .poem-list-container::-webkit-scrollbar-thumb:hover { background-color: #888888; } 1199 .list-widget .poem-list-item { 1200 display: flex; 1201 justify-content: space-between; 1202 align-items: center; 1203 text-decoration: none; 1204 color: #333; 1205 padding: 0.8em 1em; 1206 margin: 0.5em 0; 1207 border-radius: 8px; 1208 border-left-width: 6px; 1209 border-left-style: solid; 1210 background: rgba(0, 0, 0, 0.03); 1211 transition: all 0.2s; 1212 } 1213 .list-widget .poem-list-item:hover { 1214 background: rgba(0, 0, 0, 0.07); 1215 transform: translateX(3px); 1216 color: #1c1917; 1217 box-shadow: 0 4px 6px rgba(0,0,0,0.05); 1218 } 1219 .list-widget .poem-list-item .title { 1220 font-weight: 600; 1221 display: block; 1222 overflow: hidden; 1223 text-overflow: ellipsis; 1224 white-space: nowrap; 1225 } 1226 .list-widget .poem-list-item .meta { 1227 font-size: 0.8em; 1228 color: #888; 1229 flex-shrink: 0; 1230 white-space: nowrap; 1231 margin-left: 0.5em; 1232 overflow: hidden; 1233 text-overflow: ellipsis; 1234 white-space: nowrap; 1235 } 1236 .list-item-metric { 1237 font-size: 1.1em; 1238 font-weight: bold; 1239 color: #555; 1240 flex-shrink: 0; 1241 margin-left: 1em; 1242 font-feature-settings: "tnum"; /* ç宽æ°å */ 1243 } 1244 .empty-list-msg { 1245 text-align: center; 1246 color: #888; 1247 font-style: italic; 1248 padding: 2em 0; 1249 } 1250 1251 /* --- 9. ååºå¼è®¾è®¡ (å¹é base.html æç¹) --- */ 1252 1253 /* å 容ç¹å®çå°æç¹ */ 1254 @media (max-width: 600px) { 1255 .game-area { 1256 grid-template-columns: 1fr; 1257 } 1258 } 1259 1260 /* 忢å°ç§»å¨ç«¯ (ä¸ base.html ç 960px æç¹åæ¥) */ 1261 @media (max-width: 960px) { 1262 .widget { 1263 font-size: 1em; 1264 } 1265 #autocomplete-suggestions { 1266 top: 13em; 1267 } 1268 .stats-widget-grid { 1269 flex-direction: column; 1270 }
1271 .chart-container { 1272 width: 120px; 1273 height: 120px; 1274 } 1275 .chart-legend { 1276 width: 100%; 1277 } 1278 /* ãå ³é®ãåæ å¸å±å å */ 1279 .masonry-grid { 1280 display: block; 1281 } 1282 .masonry-col { 1283 display: block; 1284 } 1285 .masonry-col > .widget { 1286 margin-bottom: 1.5em; 1287 } 1288 .widget-row { 1289 display: block; 1290 margin-bottom: 0; 1291 } 1292 .widget-row > .widget, 1293 .widget-row > div.widget { 1294 margin-bottom: 1.5em; 1295 } 1296 1297 .checkbox-container-switch { 1298 flex-direction: column; 1299 align-items: flex-start; 1300 gap: 0.8em; 1301 } 1302 .checkbox-container-switch label .switch-track { 1303 width: 36px; 1304 height: 20px; 1305 border-radius: 10px; 1306 } 1307 .checkbox-container-switch label .switch-track::after { 1308 width: 16px; 1309 height: 16px; 1310 top: 2px; 1311 left: 2px; 1312 } 1313 .checkbox-container-switch label input:checked + .switch-track::after { 1314 transform: translateX(16px); 1315 } 1316 1317 .list-widget .poem-list-container { 1318 max-height: 300px; /* ç§»å¨ç«¯é«åº¦å°ä¸ç¹ */ 1319 } 1320 } 1321 1322 /* ---- åç²å¯¹è¯ ---- */ 1323 .duishi-side-btn { 1324 flex: 1; 1325 padding: 6px 0; 1326 border-radius: 8px; 1327 border: 1px solid #d8cce6; 1328 background: rgba(255, 255, 255, 0.7); 1329 color: #775599; 1330 font-family: 'lxgw', sans-serif; 1331 font-size: 0.9em; 1332 cursor: pointer; 1333 transition: all 0.15s ease; 1334 } 1335 .duishi-side-btn:hover { 1336 border-color: #775599; 1337 } 1338 .duishi-side-btn.active { 1339 background: #775599; 1340 border-color: #775599; 1341 color: #fff; 1342 } 1343 .duishi-input { 1344 flex: 1; 1345 padding: 8px 12px; 1346 border: 1px solid #ddd; 1347 border-radius: 8px; 1348 font-size: 1em; 1349 font-family: 'lxgw', sans-serif; 1350 color: #333; 1351 background: rgba(255, 255, 255, 0.8); 1352 box-sizing: border-box; 1353 min-width: 0; 1354 } 1355 .duishi-submit { 1356 padding: 8px 16px; 1357 background: #775599; 1358 color: #fff; 1359 border: none; 1360 border-radius: 8px; 1361 cursor: pointer; 1362 font-family: 'lxgw', sans-serif; 1363 font-size: 0.9em; 1364 white-space: nowrap; 1365 } 1366 .duishi-submit:disabled { 1367 opacity: 0.6; 1368 cursor: default; 1369 } 1370 .duishi-again { 1371 display: block; 1372 width: 100%; 1373 margin-top: 10px; 1374 padding: 7px 0; 1375 background: none; 1376 border: 1px solid #775599; 1377 border-radius: 8px; 1378 color: #775599; 1379 font-family: 'lxgw', sans-serif; 1380 font-size: 0.9em; 1381 cursor: pointer; 1382 } 1383 .duishi-again:hover { 1384 background: rgba(119, 85, 153, 0.08); 1385 } 1386 .duishi-line { 1387 line-height: 1.9; 1388 font-size: 1.05em; 1389 color: #444; 1390 } 1391 .duishi-line .duishi-line-author { 1392 color: #999; 1393 font-size: 0.8em; 1394 margin-left: 8px; 1395 white-space: nowrap; 1396 } 1397 .duishi-refresh { 1398 background: none; 1399 border: 1px solid #ccc; 1400 color: #888; 1401 font-size: 0.8em; 1402 padding: 2px 10px; 1403 border-radius: 12px; 1404 cursor: pointer; 1405 font-family: 'lxgw', sans-serif; 1406 } 1407 1408 /* ---- é å¯¹ç»æï¼å·¦å³å¯¹è¯æ°æ³¡ ---- */ 1409 .duishi-pair { 1410 display: flex; 1411 flex-direction: column; 1412 gap: 10px; 1413 } 1414 .duishi-side { 1415 display: flex; 1416 flex-direction: column; 1417 gap: 4px; 1418 max-width: 88%; 1419 } 1420 .duishi-side.up { 1421 align-items: flex-start; 1422 margin-right: auto; 1423 } 1424 .duishi-side.down { 1425 align-items: flex-end; 1426 margin-left: auto; 1427 } 1428 .duishi-badge { 1429 display: inline-block; 1430 padding: 0; 1431 background: none; 1432 border: none; 1433 border-radius: 0; 1434 color: #888; 1435 font-size: 0.75em; 1436 line-height: 1.6; 1437 white-space: nowrap; 1438 } 1439 /* ä½è æä»£è¡¨è²æ¶ï¼åªå¨åå左侧å 䏿¡ç«çº¿ */ 1440 .duishi-badge.has-color { 1441 border-left: 2px solid transparent; 1442 padding-left: 6px; 1443 } 1444 .duishi-bubble { 1445 padding: 7px 13px; 1446 border-radius: 12px; 1447 background: rgba(119, 85, 153, 0.10); 1448 color: #333; 1449 font-size: 1.1em; 1450 line-height: 1.75; 1451 word-break: break-word; 1452 } 1453</style> 1454 1455</head> 1456<body> 1457 <div class="glow glow-1"></div> 1458 <div class="glow glow-2"></div> 1459 1460 <div class="hub-container"> 1461 1462 <aside class="hub-sidebar desktop-only"> 1463 <div class="sidebar-header"> 1464 <h1 class="site-title"><a href="/"><img src="/static/pics/title.webp" alt="忴忏¸" class="site-title-image"></a></h1> 1465 <nav class="main-nav"> 1466 <ul> 1467 <li><a href="/">é¦é¡µ</a></li> 1468 <li><a href="/submit/">ä¸ä¼ ä½å</a></li> 1469 <li><a href="/random/">éæºé¡µé¢</a></li> 1470 <li><a href="/timeline/2026">æ¶é´çº¿</a></li> 1471 <li><a href="/stats/">æ°æ®ç»è®¡</a></li> 1472 <li><a href="/gamestats/">游æç»è®¡</a></li> 1473 <li><a href="/intro/">社å¢ä»ç»</a></li> 1474 <li><a href="http://cang.sjtuguoxue.space/">æ°åèå</a></li> 1475 <li><a href="/nanyang">æ°ååæ´é</a></li> 1476 <li><a href="/checker">æ ¼å¾æ ¡éª</a></li> 1477 <li><a href="https://music.163.com/m/playlist?id=14332525936" style="color: #993399; font-weight: bold;">ð¶ åè¯ä»¥æ</a></li> 1478 <li><a href="https://mp.weixin.qq.com/s/RoCImUNKl97QmY8PwFSB0A" style="font-weight: bold;">年度æ¥å</a></li> 1479 </ul> 1480 </nav> 1481 <div class="sidebar-user" style="margin-top:12px; padding:10px 0; border-top:1px solid rgba(255,255,255,0.08); font-size:13px;"> 1482 1483 <a href="/login/" style="color:#888; text-decoration:none;">ç»å½</a>
1484 <span style="color:#555; margin:0 4px;">·</span> 1485 <a href="https://onboarding.sjtuguoxue.space/join" style="color:#888; text-decoration:none;">注å</a> 1486 1487 </div> 1488 </div> 1489 <footer class="sidebar-footer"> 1490 <p>Copyright 2022-2026</p> 1491 <p><a href="https://lyy0323.space/writing/">ä¸åªæ¤°æ¤°</a></p> 1492 <p>访é®éï¼ 3607250</p> 1493 <p>沪ICPå¤2025137436å·</p> 1494 </footer> 1495</aside> 1496 <aside class="hub-right-sidebar desktop-only"> 1497 <section class="sidebar-module"> 1498 <h3 class="sidebar-section-title">èç³»æ¹å¼</h3> 1499 <div class="sidebar-text"> 1500 <p><a href="https://onboarding.sjtuguoxue.space/" style="color:#cc99bb;font-weight:bold;">忴忏¸Â·å å ¥æä»¬</a></p> 1501 <p>é®ç®±ï¼<a href="mailto:[email protected]">[email protected]</a></p> 1502 <p>QQï¼709881123</p> 1503 <p>å°åï¼ä¸æµ·å¸éµè¡åºä¸å·è·¯800å·</p> 1504 </div> 1505 <h1 class="site-title"><img src="/static/pics/name.webp" alt="åç" class="site-title-image-2"></h1> 1506</section> 1507 1508<section class="sidebar-module"> 1509 <h3 class="sidebar-section-title">常ç¨é¾æ¥</h3> 1510 <div class="sidebar-text"> 1511 <p><a href="http://sjtuguoxue.space/">sjtuguoxue.space</a></p> 1512 <p><a href="https://guoxue-sjtu.feishu.cn">é£ä¹¦ææ¡£</a></p> 1513 <p><img src="/static/pics/fangcun_logo.svg" style="height:1em;vertical-align:middle;margin-right:4px"><a href="https://write.sjtuguoxue.space">æ¹å¯¸Â·è¯è¯åä½</a></p> 1514 <p style="padding-left:1.2em"><a href="https://write.sjtuguoxue.space/download/android">Android ä¸è½½</a></p> 1515 <p><a href="https://yuan.sjtuguoxue.space">ææ¸æè®°</a></p> 1516 <p><a href="http://yuefu4anniv.sjtuguoxue.space/">弦æå载·ä¹åºé£é</a></p> 1517 </div> 1518</section> 1519 1520<section class="sidebar-module"> 1521 <h3 class="sidebar-section-title">æ°åªä½ç©éµ</h3> 1522 <div class="sidebar-text"> 1523 <p>å¾®ä¿¡å ¬ä¼å·ï¼ä¸æµ·äº¤å¤§å½å¦ç¤¾</p> 1524 <p>å°çº¢ä¹¦ï¼<a href="https://xhslink.com/m/2zltiBaeek6">交大å½å¦ç¤¾</a></p> 1525 <p>Bilibiliï¼<a href="https://space.bilibili.com/698232390">䏿µ·äº¤é大妿æ²é¨</a></p> 1526 </div> 1527</section> 1528 1529<section class="sidebar-module"> 1530 <h3 class="sidebar-section-title">åæ é¾æ¥</h3> 1531 <div class="sidebar-text"> 1532 <p><a href="https://club.jluzhouhang.cn/index.html">åæå¤§å¦å¨è¡è¯ç¤¾</a></p> 1533 </div> 1534</section> 1535</aside> 1536 1537 <main class="hub-main-content"> 1538 1539 <div class="mobile-header mobile-only"> 1540 <div class="mobile-header-bar"> 1541 <button class="mobile-menu-btn" onclick="toggleDrawer('left')" aria-label="èå"> 1542 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg> 1543 </button> 1544 <button class="mobile-menu-btn mobile-menu-btn-sm" onclick="history.go(-1)" aria-label="è¿å"> 1545 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg> 1546 </button> 1547 <a href="/" class="mobile-menu-btn mobile-menu-btn-sm" aria-label="主页"> 1548 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg> 1549 </a> 1550 <a href="/"><img src="/static/pics/title.webp" alt="忴忏¸" class="mobile-title-img"></a> 1551 <div style="flex:1;"></div> 1552 1553 <a href="/login/" class="mobile-menu-btn" aria-label="ç»å½" style="text-decoration:none;"> 1554 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/><polyline points="10 17 15 12 10 7"/><line x1="15" y1="12" x2="3" y2="12"/></svg> 1555 </a> 1556 1557 <button class="mobile-menu-btn" onclick="toggleDrawer('right')" aria-label="æ´å¤"> 1558 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="5" r="1.5" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.5" fill="currentColor" stroke="none"/><circle cx="12" cy="19" r="1.5" fill="currentColor" stroke="none"/></svg> 1559 </button> 1560 </div> 1561 1562 <!-- overlay -->
1563 <div class="drawer-overlay" id="drawer-overlay" onclick="closeDrawers()"></div> 1564 1565 <!-- left drawer: nav --> 1566 <div class="mobile-drawer mobile-drawer-left" id="drawer-left"> 1567 <nav class="drawer-nav"> 1568 <a href="/" onclick="closeDrawers()">é¦é¡µ</a> 1569 <a href="/submit/" onclick="closeDrawers()">ä¸ä¼ ä½å</a> 1570 <a href="/random/" onclick="closeDrawers()">éæºé¡µé¢</a> 1571 <a href="/timeline/2026" onclick="closeDrawers()">æ¶é´çº¿</a> 1572 <a href="/stats/" onclick="closeDrawers()">æ°æ®ç»è®¡</a> 1573 <a href="/gamestats/" onclick="closeDrawers()">游æç»è®¡</a> 1574 <a href="/intro/" onclick="closeDrawers()">社å¢ä»ç»</a> 1575 <a href="/nanyang" onclick="closeDrawers()">æ°ååæ´é</a> 1576 <a href="/checker" onclick="closeDrawers()">æ ¼å¾æ ¡éª</a> 1577 <a href="http://cang.sjtuguoxue.space/">æ°åèå</a> 1578 <a href="https://music.163.com/m/playlist?id=14332525936" style="color:#993399;">åè¯ä»¥æ</a> 1579 <a href="https://mp.weixin.qq.com/s/RoCImUNKl97QmY8PwFSB0A">年度æ¥å</a> 1580 </nav> 1581 </div> 1582 1583 <!-- right drawer: info -->
1584 <div class="mobile-drawer mobile-drawer-right" id="drawer-right"> 1585 <div class="drawer-info"> 1586 <h4>èç³»æ¹å¼</h4> 1587 <p><a href="https://onboarding.sjtuguoxue.space/" style="color:#cc99bb;font-weight:bold;">忴忏¸Â·å å ¥æä»¬</a></p> 1588 <p>é®ç®±ï¼<a href="mailto:[email protected]">[email protected]</a></p> 1589 <p>QQï¼709881123</p> 1590 <p>å°åï¼ä¸æµ·å¸éµè¡åºä¸å·è·¯800å·</p> 1591 1592 <h4>常ç¨é¾æ¥</h4> 1593 <p><a href="http://sjtuguoxue.space/">sjtuguoxue.space</a></p> 1594 <p><a href="https://guoxue-sjtu.feishu.cn">é£ä¹¦ææ¡£</a></p> 1595 <p><img src="/static/pics/fangcun_logo.svg" style="height:1em;vertical-align:middle;margin-right:4px"><a href="https://write.sjtuguoxue.space">æ¹å¯¸Â·è¯è¯åä½</a></p> 1596 <p style="padding-left:1.2em"><a href="https://write.sjtuguoxue.space/download/android">Android ä¸è½½</a></p> 1597 <p><a href="https://yuan.sjtuguoxue.space">ææ¸æè®°</a></p> 1598 1599 <h4>æ°åªä½ç©éµ</h4> 1600 <p>å¾®ä¿¡å ¬ä¼å·ï¼<a href="http://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MjM5NDgxODE2Ng==&scene=110#wechat_redirect">䏿µ·äº¤å¤§å½å¦ç¤¾</a></p> 1601 <p>å°çº¢ä¹¦ï¼<a href="https://xhslink.com/m/2zltiBaeek6">交大å½å¦ç¤¾</a></p> 1602 <p>Bilibiliï¼<a href="https://space.bilibili.com/698232390">䏿µ·äº¤é大妿æ²é¨</a></p> 1603 1604 <h4>åæ é¾æ¥</h4> 1605 <p><a href="https://club.jluzhouhang.cn/index.html">åæå¤§å¦å¨è¡è¯ç¤¾</a></p> 1606 1607 <div style="margin-top:16px; padding-top:12px; border-top:1px solid rgba(255,255,255,0.08); font-size:11px; color:#666;"> 1608 <p>Copyright 2022-2026 <a href="https://lyy0323.space/writing/" style="color:#888;">ä¸åªæ¤°æ¤°</a></p> 1609 <p>沪ICPå¤2025137436å·</p> 1610 </div> 1611 </div> 1612 </div> 1613</div>
1614<script> 1615function toggleDrawer(side){ 1616 closeUserDropdown(); 1617 var left=document.getElementById('drawer-left'); 1618 var right=document.getElementById('drawer-right'); 1619 var ov=document.getElementById('drawer-overlay'); 1620 if(side==='left'){ 1621 var opening=!left.classList.contains('open'); 1622 closeDrawers(); 1623 if(opening){left.classList.add('open');ov.classList.add('open');} 1624 } else { 1625 var opening=!right.classList.contains('open'); 1626 closeDrawers(); 1627 if(opening){right.classList.add('open');ov.classList.add('open');} 1628 } 1629} 1630function closeDrawers(){ 1631 document.getElementById('drawer-left').classList.remove('open'); 1632 document.getElementById('drawer-right').classList.remove('open'); 1633 document.getElementById('drawer-overlay').classList.remove('open'); 1634 closeUserDropdown(); 1635} 1636function toggleUserDropdown(e){ 1637 e.stopPropagation(); 1638 var dd=document.getElementById('mobile-user-dropdown'); 1639 var wasOpen = dd && dd.classList.contains('open'); 1640 closeDrawers(); 1641 if(dd && !wasOpen) dd.classList.add('open'); 1642} 1643function closeUserDropdown(){ 1644 var dd=document.getElementById('mobile-user-dropdown'); 1645 if(dd) dd.classList.remove('open'); 1646} 1647document.addEventListener('click',function(e){ 1648 var dd=document.getElementById('mobile-user-dropdown'); 1649 var btn=document.getElementById('mobile-user-btn'); 1650 if(dd && !dd.contains(e.target) && btn && !btn.contains(e.target)) closeUserDropdown(); 1651}); 1652</script>
1652 1653 1654 <div class="main-container"> 1655 1656<!-- <section class="intro-widget image-widget"> 1657 <img src="/static/pics/activities/25111501.png" alt="䏿µ·äº¤å¤§å½å¦ç¤¾ä»ç»" class="intro-image" 1658 onerror="this.style.display='none'"> 1659</section> --> 1660<section class="widget banner-widget" id="top-banner-widget"> 1661 <div class="banner-slider-container"> 1662 <div class="banner-slides" id="banner-slides-wrapper"> 1663 <!-- Slides å°ç± JS 卿æå ¥ --> 1664 </div> 1665 <button class="banner-nav" id="banner-prev" aria-label="ä¸ä¸å¼ ">â®</button> 1666 <button class="banner-nav" id="banner-next" aria-label="ä¸ä¸å¼ ">â¯</button> 1667 <div class="banner-dots" id="banner-dots-container"> 1668 <!-- Dots å°ç± JS 卿æå ¥ --> 1669 </div> 1670 </div> 1671</section> 1672<section class="widget search-widget"> 1673 <form action="/search/" method="POST" id="main-search-form"> 1674 <div class="search-form-container"> 1675 <input type="text" name="searchKey" class="search-input" id="search-input-field" autocomplete="off" value="" placeholder="æç´¢è¯è¯ãä½è ãæ ç¾(#)..."> 1676 <button class="search-button" type="submit" style="font-size: 1.2em;"> 1677 ð 1678 </button> 1679 </div> 1680 <div id="autocomplete-suggestions"></div> 1681 <div class="checkbox-container-switch"> 1682 <label><input type="checkbox" name="include_lyrics" ><span class="switch-track"></span> å å«ç°ä»£éµæ</label> 1683 <label><input type="checkbox" name="vector_search" ><span class="switch-track"></span> å å«è¯ä¹æ£ç´¢</label> 1684 <label><input type="checkbox" name="unviewed_first" ><span class="switch-track"></span> å°ä¼ä½åä¼å </label> 1685 </div> 1686 </form> 1687</section> 1688 1689 1690 1691 1692 1693 1694 1695 1696<!-- Tracking (must be before masonry scripts) -->
1697<script> 1698function trackEvent(type, target) { 1699 fetch('/api/track', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({type:type, target:target||''})}).catch(function(){}); 1700} 1701trackEvent('page_view', '/'); 1702 1703function drawBlob(canvas, color, seedOffset) { 1704 var ctx = canvas.getContext('2d'); 1705 var w = canvas.width, h = canvas.height; 1706 var bg = [255, 255, 255]; 1707 var cr = parseInt(color.slice(1,3),16), cg = parseInt(color.slice(3,5),16), cb = parseInt(color.slice(5,7),16); 1708 var seeds = [1.3, 2.1, 0.7, 1.9, 3.1, 0.4, 2.7, 1.5, 0.9]; 1709 var cx = w * 0.65, cy = h * 0.35, maxR = Math.min(w, h) * 0.7, layers = 7; 1710 ctx.clearRect(0, 0, w, h); 1711 for (var i = layers; i >= 0; i--) { 1712 var t = i / layers; 1713 var r = maxR * (0.45 + t * 0.55); 1714 var mix = t * 0.85; 1715 var fr = Math.round(cr * (1-mix) + bg[0] * mix); 1716 var fg = Math.round(cg * (1-mix) + bg[1] * mix); 1717 var fb = Math.round(cb * (1-mix) + bg[2] * mix); 1718 var pts = 8, angles = [], radii = []; 1719 for (var p = 0; p < pts; p++) { 1720 angles.push((p / pts) * Math.PI * 2); 1721 radii.push(r * (0.85 + 0.3 * Math.sin(seeds[p % 9] * (i+1) * 1.7 + p * 0.8 + (seedOffset||0)))); 1722 } 1723 ctx.beginPath(); 1724 for (var p = 0; p <= pts; p++) { 1725 var ai = p % pts; 1726 var ax = cx + Math.cos(angles[ai]) * radii[ai]; 1727 var ay = cy + Math.sin(angles[ai]) * radii[ai]; 1728 if (p === 0) { ctx.moveTo(ax, ay); continue; } 1729 var pi2 = (p-1) % pts; 1730 var a1 = angles[pi2] + 0.5/pts * Math.PI*2; 1731 var a2 = angles[ai] - 0.5/pts * Math.PI*2; 1732 ctx.bezierCurveTo( 1733 cx + Math.cos(a1)*radii[pi2]*1.05, cy + Math.sin(a1)*radii[pi2]*1.05, 1734 cx + Math.cos(a2)*radii[ai]*1.05, cy + Math.sin(a2)*radii[ai]*1.05, 1735 ax, ay 1736 ); 1737 } 1738 ctx.closePath(); 1739 ctx.fillStyle = 'rgb('+fr+','+fg+','+fb+')'; 1740 ctx.fill(); 1741 } 1742} 1743</script>
1743 1744 1745<div class="masonry-grid" id="masonry-grid"> 1746<div class="masonry-col" id="masonry-left"></div> 1747<div class="masonry-col" id="masonry-right"></div> 1748</div> 1749<div id="masonry-source" style="display:none;"> 1750 1751<!-- 0.5 åç²å¯¹è¯ï¼æäº¤å ¥å£ --> 1752<section class="widget new-poem-widget" id="duishi-entry-card" style="border-left: 5px solid #775599; position:relative; overflow:hidden;"> 1753 <canvas class="blob-bg" id="blob-duishi" width="200" height="200" style="position:absolute; top:-30px; right:-30px; width:200px; height:200px; pointer-events:none; opacity:0.5; z-index:0;"></canvas> 1754 <div style="position:relative; z-index:1;"> 1755 <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;"> 1756 <span style="font-weight:bold; font-size:1.1em; color:#775599;">åç²å¯¹è¯</span> 1757 </div> 1758 1759 <div id="duishi-form"> 1760 <div style="display:flex; gap:6px; margin-bottom:8px;"> 1761 <button type="button" class="duishi-side-btn active" data-side="up" onclick="duishiPickSide('up')">åä¸å¥</button> 1762 <button type="button" class="duishi-side-btn" data-side="down" onclick="duishiPickSide('down')">åä¸å¥</button> 1763 </div> 1764 <div style="display:flex; gap:6px;"> 1765 <input type="text" id="duishi-input" class="duishi-input" maxlength="24" autocomplete="off" placeholder="åä¸ä½ çä¸å¥ï¼2-10 个æ±åï¼"> 1766 <button type="button" id="duishi-submit-btn" class="duishi-submit" onclick="duishiSubmit()">æäº¤</button> 1767 </div> 1768 <div id="duishi-msg" style="color:#cc5555; font-size:0.85em; margin-top:4px; min-height:1.2em;"></div> 1769 </div> 1770 1771 <div id="duishi-result" style="display:none;"> 1772 <div id="duishi-waiting" style="display:none;"> 1773 <div style="color:#999; font-size:0.85em;" id="duishi-waiting-side">ä½ çä¸å¥</div> 1774 <div id="duishi-waiting-text" style="color:#444; font-size:1.15em; line-height:1.9; margin-bottom:8px;"></div> 1775 <div style="color:#775599; font-size:0.95em;">çå¾ å¿æçµçä¸â¦</div> 1776 </div> 1777 <div id="duishi-matched" style="display:none;"> 1778 <div style="color:#775599; font-size:0.9em; margin-bottom:8px;">å¿æçµçï¼é å°å¦</div> 1779 <div class="duishi-pair"> 1780 <div class="duishi-side up"> 1781 <span class="duishi-badge" id="duishi-up-badge"></span> 1782 <div class="duishi-bubble" id="duishi-up-text"></div> 1783 </div> 1784 <div class="duishi-side down"> 1785 <span class="duishi-badge" id="duishi-down-badge"></span> 1786 <div class="duishi-bubble" id="duishi-down-text"></div> 1787 </div> 1788 </div> 1789 </div> 1790 <button type="button" class="duishi-again" id="duishi-again" onclick="duishiAgain()">ååä¸ä¸ª</button> 1791 </div> 1792 1793 <div id="duishi-guest" style="display:none;"> 1794 <a href="/login/?next=/" onclick="trackEvent('duishi_login_prompt','duishi');" style="display:inline-block; padding:6px 18px; background:#775599; color:white; border-radius:6px; text-decoration:none; font-size:0.9em; font-family:'lxgw',sans-serif;">ç»å½åä¸</a> 1795 </div> 1796 </div> 1797</section> 1798 1799<!-- 0.6 åç²å¯¹è¯ï¼æè¿å¯¹è¯ --> 1800<section class="widget new-poem-widget" id="duishi-recent-card" style="border-left: 5px solid #775599; cursor:pointer; position:relative; overflow:hidden;"> 1801 <canvas class="blob-bg" id="blob-duishi-recent" width="200" height="200" style="position:absolute; top:-30px; right:-30px; width:200px; height:200px; pointer-events:none; opacity:0.5; z-index:0;"></canvas> 1802 <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; position:relative; z-index:1;"> 1803 <span style="font-weight:bold; font-size:1.1em; color:#775599;">æè¿å¯¹è¯</span> 1804 <button id="duishi-recent-refresh" class="duishi-refresh" onclick="event.stopPropagation(); trackEvent('card_refresh','duishi_recent'); refreshDuishiRecent(true);">æ¢ä¸æ¢</button> 1805 </div> 1806 <div style="position:relative; z-index:1;"> 1807 <div id="duishi-recent-up" style="color:#444; line-height:1.8; font-size:1.25em;">å è½½ä¸â¦</div> 1808 <div id="duishi-recent-detail" style="display:none; margin-top:10px; padding-top:10px; border-top:1px solid #eee;"> 1809 <div class="duishi-pair"> 1810 <div class="duishi-side up">
1811 <span class="duishi-badge" id="duishi-recent-up-badge"></span> 1812 <div class="duishi-bubble" id="duishi-recent-up-text"></div> 1813 </div> 1814 <div class="duishi-side down"> 1815 <span class="duishi-badge" id="duishi-recent-down-badge"></span> 1816 <div class="duishi-bubble" id="duishi-recent-down-text"></div> 1817 </div> 1818 </div> 1819 </div> 1820 </div> 1821</section> 1822 1823<!-- 0. åæ´éå¾ç¨¿ï¼éæ¶ï¼ --> 1824<section class="widget new-poem-widget" id="nanyang-call-card" style="border-left: 5px solid #ccaa44; position:relative; overflow:hidden; display:none;"> 1825 <canvas class="blob-bg" id="blob-nanyang-call" width="200" height="200" style="position:absolute; top:-30px; right:-30px; width:200px; height:200px; pointer-events:none; opacity:0.5; z-index:0;"></canvas> 1826 <div style="position:relative; z-index:1;"> 1827 <div style="font-weight:bold; font-size:1.1em; color:#ccaa44; margin-bottom:6px;">åæ´é åä¸ Â· å¾ç¨¿</div> 1828 <div style="color:#666; font-size:0.9em; line-height:1.6; margin-bottom:10px;">2026å¹´ãåæ´éãå¾ç¨¿å³æ¥èµ·è³5æ31æ¥éæ¶å¼æ¾ï¼è¯ææå¾ æ¨çä½åï¼</div> 1829 <a href="/nanyang-submit/" style="display:inline-block; padding:6px 18px; background:#ccaa44; color:white; border-radius:6px; text-decoration:none; font-size:0.9em; font-family:'lxgw',sans-serif;">ç«å³æäº¤</a> 1830 </div> 1831</section>
1832<script> 1833(function(){ 1834 var deadline = new Date('2026-05-31T23:59:59+08:00'); 1835 if (new Date() <= deadline) { 1836 var card = document.getElementById('nanyang-call-card'); 1837 if(card) card.style.display = ''; 1838 } 1839})(); 1840</script>
1840 1841 1842<!-- 1. æ°ä½æ¨è --> 1843 1844<section class="widget new-poem-widget" id="new-poem-card" style="border-left: 5px solid #bbffee; cursor:pointer; position:relative; overflow:hidden;"> 1845 <canvas class="blob-bg" id="blob-new" width="200" height="200" style="position:absolute; top:-30px; right:-30px; width:200px; height:200px; pointer-events:none; opacity:0.5; z-index:0;"></canvas> 1846 <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;"> 1847 <span style="font-weight:bold; font-size:1.1em; color:#995577;">æ°ä½æ¨è</span> 1848 <button id="new-poem-refresh" onclick="event.stopPropagation(); trackEvent('card_refresh','new_poem'); refreshNewPoem();" style="background:none; border:1px solid #ccc; color:#888; font-size:0.8em; padding:2px 10px; border-radius:12px; cursor:pointer; font-family:'lxgw',sans-serif;">æ¢ä¸æ¢</button> 1849 </div> 1850 <div id="new-poem-sentence" style="color:#444; line-height:1.8; font-size:1.25em;">åæªè§ï¼æ¢¦æªéï¼ææªç ã</div> 1851 <div id="new-poem-detail" style="display:none; margin-top:8px; padding-top:8px; border-top:1px solid #eee;"> 1852 <div style="margin-bottom:6px;"> 1853 <a id="new-poem-title-link" href="/poem/c1baed80" onclick="trackEvent('card_link',this.href)" style="color:#995577; font-weight:bold; text-decoration:none; font-size:1em;">æ°´è°æå¤´Â·ç¨ä¸å¡éµä½ä¸ç§æå ¶æç£</a> 1854 <span style="color:#999; font-size:0.9em;"> · 广å¯å± 士 · 2026.9.23</span> 1855 </div> 1856 <div id="new-poem-content" style="color:#666; font-size:1em; line-height:1.8; white-space:pre-line;">æ²é»ä»ä¹¡å°ï¼æä¹å ±ç§å¤©ãå æ¶ææï¼ä¸æå¤ç¬ä¾¿ç©·å¹´ã游è¿å èç¯è»ï¼é£åè½¦æ± äººæµ·ï¼ç²ä¹ ä¸ç¥å¯ãæ æ°æé±¼å½±ï¼æ³å¨æ°´äºé´ã 1857åæªè§ï¼æ¢¦æªéï¼ææªç ãéä¸ç¸çï¼æ¸æ¬¡æ´ææ¸æåãä¼¼æ¯å声é è²ï¼ä¼¼æåæ ç´æï¼äºæ¤èæ±å ¨ã妿å¢åäºï¼ä½å¿ æ´å©µå¨ã</div> 1858 </div> 1859</section> 1860 1861 1862<!-- 2. å¤å ¸æ¨è --> 1863<section class="widget new-poem-widget" id="classic-rec-card" style="border-left: 5px solid #557799; cursor:pointer; position:relative; overflow:hidden;"> 1864 <canvas class="blob-bg" id="blob-classic" width="200" height="200" style="position:absolute; top:-30px; right:-30px; width:200px; height:200px; pointer-events:none; opacity:0.5; z-index:0;"></canvas> 1865 <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;"> 1866 <span style="font-weight:bold; font-size:1.1em; color:#557799;">å¤å ¸æ¨è</span> 1867 <button id="classic-rec-refresh" onclick="event.stopPropagation(); trackEvent('card_refresh','classic_rec'); refreshClassicRec();" style="background:none; border:1px solid #ccc; color:#888; font-size:0.8em; padding:2px 10px; border-radius:12px; cursor:pointer; font-family:'lxgw',sans-serif;">æ¢ä¸æ¢</button> 1868 </div> 1869 <div id="classic-rec-sentence" style="color:#444; line-height:1.8; font-size:1.25em;">å è½½ä¸...</div> 1870 <div id="classic-rec-detail" style="display:none; margin-top:8px; padding-top:8px; border-top:1px solid #eee;"> 1871 <div style="margin-bottom:6px;"> 1872 <span id="classic-rec-title" style="color:#557799; font-weight:bold; font-size:1em;"></span> 1873 <span id="classic-rec-meta" style="color:#999; font-size:0.9em;"></span> 1874 </div> 1875 <div id="classic-rec-content" style="color:#666; font-size:1em; line-height:1.8; white-space:pre-line;"></div> 1876 </div> 1877 <div id="classic-rec-reason" style="display:none; margin-top:8px; padding-top:8px; border-top:1px solid #eee;"> 1878 <span style="color:#999; font-size:0.85em;" id="classic-rec-reason-text"></span> 1879 </div> 1880</section> 1881 1882<!-- 2.5 é£è±ä»¤ --> 1883<section class="widget new-poem-widget" id="feihua-card" style="border-left: 5px solid #ccaa44; position:relative; overflow:hidden;"> 1884 <canvas class="blob-bg" id="blob-feihua" width="200" height="200" style="position:absolute; top:-30px; right:-30px; width:200px; height:200px; pointer-events:none; opacity:0.5; z-index:0;"></canvas> 1885 <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px; position:relative; z-index:1;">
1886 <span style="font-weight:bold; font-size:1.1em; color:#ccaa44;">é£è±ä»¤ · 仿¥ï¼<span id="fh-char-display"></span></span> 1887 </div> 1888 <div id="fh-history" style="position:relative; z-index:1;"></div> 1889 <div id="fh-current" style="position:relative; z-index:1;"> 1890 <div id="fh-pos-label" style="color:#888; font-size:0.9em; margin-bottom:6px;"></div> 1891 <div style="display:flex; gap:6px;"> 1892 <input type="text" id="fh-input" placeholder="" style="flex:1; padding:8px 12px; border:1px solid #ddd; border-radius:8px; font-size:1em; font-family:'lxgw',sans-serif; color:#333; background:rgba(255,255,255,0.8);"> 1893 <button id="fh-submit" onclick="fhSubmit()" style="padding:8px 16px; background:#ccaa44; color:white; border:none; border-radius:8px; cursor:pointer; font-family:'lxgw',sans-serif; font-size:0.9em;">æäº¤</button> 1894 </div> 1895 <div id="fh-msg" style="color:#cc5555; font-size:0.85em; margin-top:4px; min-height:1.2em;"></div> 1896 </div> 1897 <div id="fh-done" style="display:none; text-align:center; padding:12px 0; color:#559977; font-weight:bold; font-size:1.1em; position:relative; z-index:1;"> 1898 éå ³ï¼ 1899 </div> 1900</section> 1901 1902<!-- 3. 仿¥åå² --> 1903 1904<section class="widget new-poem-widget" id="today-history-card" style="border-left: 5px solid #559977; cursor:pointer; position:relative; overflow:hidden;"> 1905 <canvas class="blob-bg" id="blob-history" width="200" height="200" style="position:absolute; top:-30px; right:-30px; width:200px; height:200px; pointer-events:none; opacity:0.5; z-index:0;"></canvas> 1906 <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;"> 1907 <span style="font-weight:bold; font-size:1.1em; color:#559977;">仿¥åå²</span> 1908 1909 <button id="history-refresh" onclick="event.stopPropagation(); trackEvent('card_refresh','today_history'); refreshHistory();" style="background:none; border:1px solid #ccc; color:#888; font-size:0.8em; padding:2px 10px; border-radius:12px; cursor:pointer; font-family:'lxgw',sans-serif;">æ¢ä¸æ¢</button> 1910 1911 </div> 1912 <div id="history-sentence" style="color:#444; line-height:1.8; font-size:1.25em;">è¡æ´ªéå ®é¿ä¿ï¼çµä¸æ¥å ®æ°¸åç¥äºå¤§èã</div> 1913 <div id="history-detail" style="display:none; margin-top:8px; padding-top:8px; border-top:1px solid #eee;"> 1914 <div style="margin-bottom:6px;"> 1915 <a id="history-title-link" href="/poem/abe19687" onclick="trackEvent('card_link',this.href)" style="color:#559977; font-weight:bold; text-decoration:none; font-size:1em;">ä¸å</a> 1916 <span id="history-meta" style="color:#999; font-size:0.9em;"> · æ¿é · 2025å¹´</span> 1917 </div> 1918 <div id="history-content" style="color:#666; font-size:1em; line-height:1.8; white-space:pre-line;">æ¥åºå ®è¥¿æï¼æ¥æ²¡å ®æ¶æ¡ã 1919éä¹æå ®æ 迹ï¼å é¾å¥èµ°å ®è«è«ã 1920åé£å ®å°åï¼ç²¾çµè¦å ®å¸¸ç¾ã 1921æ®ææå ®è¢¤å»¶ï¼ç»æå ®ä¸çã 1922天éé¢å¼å ³ï¼åäºé¨å ®æµ©æã 1923ç¼æ¥¼æ¯çæ§ï¼æéåé«éª§ã 1924ç¿ çåå ®ç¯ä½©ï¼ç®è¯å ®ç³ç ã 1925ç´ å¨¥å ®é女ï¼è¢èç¿©å ®è¿ç¶æµã 1926åéé²å ®æ¢éï¼æ¾å¸è¾äºå «æã 1927éæº¶æº¶å ®è¡æ³¢ï¼äºååå ®å¼å¼ ã 1928æ·«éå ®æéï¼äº¤ç¢§æ¡å ®éæ£ ã 1929é¸ å¼å ®èé¼ï¼å¤©ççå ®è¿æ¥å ã 1930æ¸¸å ´å ®æå°½ï¼ä»è¢éä¸¾å ®æè³ã 1931ç¼è¾å ®åå¾¡ï¼æè½¨æå ®ä¸åéã 1932å¤©ä¹ æèå¤ç¥ï¼è³ç®å¨±å ®ä¸çã 1933è¡æ´ªéå ®é¿ä¿ï¼çµä¸æ¥å ®æ°¸åç¥äºå¤§èã</div> 1934 </div> 1935</section> 1936 1937 1938<!-- 4. æ¯æ¥ä¸è¯ --> 1939<section class="widget random-poem-widget" style="border-left-color: #cfd0ff;"> 1940 <div class="poem-header"> 1941 <span class="poem-header-title">æ¯æ¥ä¸è¯ 2026-09-26</span> 1942 <button id="showInterpret" data-poem-id="706d58dd" class="poem-ai-button" onclick="trackEvent('interpret_click','706d58dd')"> 1943 æ¥ç AI èµæ 1944 </button> 1945 </div> 1946 <div class="poem-content">æ¥æ¨±ä¸ææ°´éå¹²ã</div> 1947 <div class="poem-footer"> 1948 ââæé¨å¤ ã<a href="/poem/706d58dd" onclick="trackEvent('card_link',this.href)">æ°´ç¯æµ 梦ä¸é¦æ¬¡ä¹å±±ç¢§åéµï¼å ¶ä¸ï¼</a>ã 2025.3.19 1949 </div> 1950 <div id="interpretResult" style="display: none;"></div> 1951</section> 1952 1953<!-- 5. å°æ¸¸æ --> 1954<section class="widget game-widget"> 1955 <h3>çä¸ç</h3> 1956 <div id="game-container"> 1957 <div class="game-loading">æ£å¨å è½½é¢ç®...</div> 1958 <div class="game-content" style="display: none;"> 1959 <p class="game-prompt"></p> 1960 <div class="game-demo" style="display: none;"></div> 1961 <div class="game-area"></div> 1962 <div class="game-footer"> 1963 <div class="game-feedback"></div> 1964 <a id="view-poem-btn" class="game-button secondary" target="_blank" href="#" onclick="trackEvent('game_action','view_poem')">æ¥çä½å</a> 1965 <button id="skip-game-btn" class="game-button skip" onclick="trackEvent('game_action','skip')">è·³è¿</button> 1966 <button id="next-game-btn" class="game-button" onclick="trackEvent('game_action','next')">ä¸ä¸é¢</button> 1967 <button id="submit-sort-btn" class="game-button" onclick="trackEvent('game_action','submit')">æäº¤çæ¡</button> 1968 </div> 1969 </div> 1970 </div> 1971</section> 1972 1973<!-- 6. ç»è®¡é¢æ¿ --> 1974<section class="widget stats-widget"> 1975 <div class="stats-widget-grid"> 1976 <div class="chart-container"> 1977 <div class="donut-chart" style="background: conic-gradient(#995577 0% 57.11139896373057%, #559977 57.11139896373057% 97.91450777202073%, #557799 97.91450777202073% 100%);"></div> 1978 </div> 1979 <ul class="chart-legend"> 1980 <li class="legend-item">
1981 <span class="legend-color-box" style="background-color: #995577;"></span> 1982 <span class="legend-text"><a href="/class/1" onclick="trackEvent('stats_click','class/1')" style="color: #333; text-decoration: underline;">è¯</a> (4409)</span> 1983 <span class="legend-percent">57.1%</span> 1984 </li> 1985 <li class="legend-item"> 1986 <span class="legend-color-box" style="background-color: #559977;"></span> 1987 <span class="legend-text"><a href="/class/2" onclick="trackEvent('stats_click','class/2')" style="color: #333; text-decoration: underline;">è¯</a> (3150)</span> 1988 <span class="legend-percent">40.8%</span> 1989 </li> 1990 <li class="legend-item"> 1991 <span class="legend-color-box" style="background-color: #557799;"></span> 1992 <span class="legend-text"><a href="/class/3" onclick="trackEvent('stats_click','class/3')" style="color: #333; text-decoration: underline;">ç°ä»£éµæ</a> (161)</span> 1993 <span class="legend-percent">2.1%</span> 1994 </li> 1995 </ul> 1996 </div> 1997</section> 1998 1999<!-- 7. é£åæ¦ --> 2000<section class="widget list-widget"> 2001 <h3 class="widget-title">ä½åé£åæ¦ (7æ¥)</h3> 2002 <div class="poem-list-container"> 2003 2004 <a href="/poem/5619f72f" class="poem-list-item" style="border-left-color: #e2e6d5;" onclick="trackEvent('trending_click','5619f72f')"> 2005 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2006 <span class="title">仿ä¹åºå夿¥æÂ·æ¥é¨ï¼å ¶ä¸ï¼</span> 2007 <span class="meta">/ å°æ </span> 2008 </div> 2009 <span class="list-item-metric">87+</span> 2010 </a> 2011 2012 <a href="/poem/f909384f" class="poem-list-item" style="border-left-color: #ccddff;" onclick="trackEvent('trending_click','f909384f')"> 2013 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2014 <span class="title">èºå¼åºÂ·æç§</span> 2015 <span class="meta">/ å°¤å ±</span> 2016 </div> 2017 <span class="list-item-metric">86+</span> 2018 </a> 2019 2020 <a href="/poem/4c731e2c" class="poem-list-item" style="border-left-color: #00ffff;" onclick="trackEvent('trending_click','4c731e2c')"> 2021 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2022 <span class="title">å¤å·</span> 2023 <span class="meta">/ å¯è±</span> 2024 </div> 2025 <span class="list-item-metric">78+</span> 2026 </a> 2027 2028 <a href="/poem/47aebd33" class="poem-list-item" style="border-left-color: #397467;" onclick="trackEvent('trending_click','47aebd33')"> 2029 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2030 <span class="title">å °éµç·å临æ¥</span> 2031 <span class="meta">/ åå¢å人</span> 2032 </div> 2033 <span class="list-item-metric">73+</span> 2034 </a> 2035 2036 <a href="/poem/c3b8f234" class="poem-list-item" style="border-left-color: #a31eeb;" onclick="trackEvent('trending_click','c3b8f234')"> 2037 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2038 <span class="title">ååè¡</span> 2039 <span class="meta">/ 徿¸</span> 2040 </div> 2041 <span class="list-item-metric">62+</span> 2042 </a> 2043 2044 <a href="/poem/5b3f086e" class="poem-list-item" style="border-left-color: #00ffff;" onclick="trackEvent('trending_click','5b3f086e')"> 2045 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2046 <span class="title">æµ·</span> 2047 <span class="meta">/ å¯è±</span> 2048 </div> 2049 <span class="list-item-metric">60+</span> 2050 </a> 2051 2052 <a href="/poem/15ff4b66" class="poem-list-item" style="border-left-color: #a31eeb;" onclick="trackEvent('trending_click','15ff4b66')"> 2053 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;">
2054 <span class="title">ééé䏿®è³æ¥æ¶</span> 2055 <span class="meta">/ 徿¸</span> 2056 </div> 2057 <span class="list-item-metric">53+</span> 2058 </a> 2059 2060 <a href="/poem/ac68dc16" class="poem-list-item" style="border-left-color: #a31eeb;" onclick="trackEvent('trending_click','ac68dc16')"> 2061 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2062 <span class="title">覽å¤</span> 2063 <span class="meta">/ 徿¸</span> 2064 </div> 2065 <span class="list-item-metric">52+</span> 2066 </a> 2067 2068 <a href="/poem/1d5763b1" class="poem-list-item" style="border-left-color: #a31eeb;" onclick="trackEvent('trending_click','1d5763b1')"> 2069 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2070 <span class="title">å¾è»è¡</span> 2071 <span class="meta">/ 徿¸</span> 2072 </div> 2073 <span class="list-item-metric">49+</span> 2074 </a> 2075 2076 <a href="/poem/0f1c52d2" class="poem-list-item" style="border-left-color: #000000;" onclick="trackEvent('trending_click','0f1c52d2')"> 2077 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2078 <span class="title">ç¬å®¶ åéå</span> 2079 <span class="meta">/ çå°ç½</span> 2080 </div> 2081 <span class="list-item-metric">49+</span> 2082 </a> 2083 2084 </div> 2085</section> 2086 2087<!-- 8. ç鍿¦ --> 2088<section class="widget list-widget"> 2089 <h3 class="widget-title">çé¨ä½åæ¦ (æ»)</h3> 2090 <div class="poem-list-container"> 2091 2092 <a href="/poem/4cab65dc" class="poem-list-item" style="border-left-color: #bb99dd;" onclick="trackEvent('popular_click','4cab65dc')"> 2093 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2094 <span class="title">æ¬¡éµæ³æ³ææ±ååå ¶ä¸</span> 2095 <span class="meta">/ ç¥æ°´</span> 2096 </div> 2097 <span class="list-item-metric">2255</span> 2098 </a> 2099 2100 <a href="/poem/91cf6e33" class="poem-list-item" style="border-left-color: #1c5f4f;" onclick="trackEvent('popular_click','91cf6e33')"> 2101 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2102 <span class="title">æè¯ä¹åä¹</span> 2103 <span class="meta">/ æ¿é</span> 2104 </div> 2105 <span class="list-item-metric">2151</span> 2106 </a> 2107 2108 <a href="/poem/8a8cdf3a" class="poem-list-item" style="border-left-color: #8ccd91;" onclick="trackEvent('popular_click','8a8cdf3a')"> 2109 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2110 <span class="title">å¤åå½å¯</span> 2111 <span class="meta">/ ææ£ææ</span> 2112 </div> 2113 <span class="list-item-metric">2102</span> 2114 </a> 2115 2116 <a href="/poem/2ca75a70" class="poem-list-item" style="border-left-color: #1c5f3e;" onclick="trackEvent('popular_click','2ca75a70')"> 2117 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2118 <span class="title">æ é¢</span> 2119 <span class="meta">/ å¸ä¹</span> 2120 </div> 2121 <span class="list-item-metric">2079</span> 2122 </a> 2123 2124 <a href="/poem/6f8ec919" class="poem-list-item" style="border-left-color: #ecd5e6;" onclick="trackEvent('popular_click','6f8ec919')"> 2125 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2126 <span class="title">鹧鸪天·æå</span> 2127 <span class="meta">/ 令å</span> 2128 </div> 2129 <span class="list-item-metric">2073</span> 2130 </a> 2131 2132 <a href="/poem/a49b7763" class="poem-list-item" style="border-left-color: #1c5f4f;" onclick="trackEvent('popular_click','a49b7763')"> 2133 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;">
2134 <span class="title">å¤ä½</span> 2135 <span class="meta">/ æ¿é</span> 2136 </div> 2137 <span class="list-item-metric">2064</span> 2138 </a> 2139 2140 <a href="/poem/2c3d280e" class="poem-list-item" style="border-left-color: #1c5f4f;" onclick="trackEvent('popular_click','2c3d280e')"> 2141 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2142 <span class="title">鹧鸪天·æ¥èè¿éµå ¼èµ </span> 2143 <span class="meta">/ æ¿é</span> 2144 </div> 2145 <span class="list-item-metric">2040</span> 2146 </a> 2147 2148 <a href="/poem/48f5dea4" class="poem-list-item" style="border-left-color: #e2e2d1;" onclick="trackEvent('popular_click','48f5dea4')"> 2149 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2150 <span class="title">浣溪æ²</span> 2151 <span class="meta">/ 溪ç</span> 2152 </div> 2153 <span class="list-item-metric">2003</span> 2154 </a> 2155 2156 <a href="/poem/0d46efd2" class="poem-list-item" style="border-left-color: #bb99dd;" onclick="trackEvent('popular_click','0d46efd2')"> 2157 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2158 <span class="title">ä¸å¾</span> 2159 <span class="meta">/ ç¥æ°´</span> 2160 </div> 2161 <span class="list-item-metric">1965</span> 2162 </a> 2163 2164 <a href="/poem/2b0d96a8" class="poem-list-item" style="border-left-color: #bb99dd;" onclick="trackEvent('popular_click','2b0d96a8')"> 2165 <div style="min-width: 0; margin-right: 0.5em; overflow: hidden;"> 2166 <span class="title">æç ´æµ£æºªæ² ä¸å¤ç¬è¡ï¼æç§é£èµ·å ®</span> 2167 <span class="meta">/ ç¥æ°´</span> 2168 </div> 2169 <span class="list-item-metric">1944</span> 2170 </a> 2171 2172 </div> 2173</section> 2174 2175</div><!-- end masonry-source --> 2176 2177<!-- Masonry distribution (widgets only, no scripts) -->
2178<script> 2179(function(){ 2180 var src = document.getElementById('masonry-source'); 2181 var grid = document.getElementById('masonry-grid'); 2182 var left = document.getElementById('masonry-left'); 2183 var right = document.getElementById('masonry-right'); 2184 var widgets = Array.from(src.querySelectorAll(':scope > .widget')); 2185 var isMobile = window.innerWidth <= 960; 2186 if (isMobile) { 2187 widgets.forEach(function(el){ grid.appendChild(el); }); 2188 left.remove(); 2189 right.remove(); 2190 } else { 2191 var leftH = 0, rightH = 0; 2192 widgets.forEach(function(el) { 2193 var col = leftH <= rightH ? left : right; 2194 col.appendChild(el); 2195 leftH = left.offsetHeight; 2196 rightH = right.offsetHeight; 2197 }); 2198 } 2199 src.remove(); 2200})(); 2201</script>
2201 2202 2203<!-- Card init scripts (run after masonry distribution) -->
2204<script> 2205// Draw blobs on recommendation cards 2206 2207var bc1 = document.getElementById('blob-new'); 2208if(bc1) drawBlob(bc1, '#995577', 0); 2209 2210var bc2 = document.getElementById('blob-classic'); 2211if(bc2) drawBlob(bc2, '#557799', 2.1); 2212var bc4 = document.getElementById('blob-feihua'); 2213if(bc4) drawBlob(bc4, '#ccaa44', 6.7); 2214var bc5 = document.getElementById('blob-nanyang-call'); 2215if(bc5) drawBlob(bc5, '#ccaa44', 8.2); 2216var bc6 = document.getElementById('blob-duishi'); 2217if(bc6) drawBlob(bc6, '#775599', 5.4); 2218var bc7 = document.getElementById('blob-duishi-recent'); 2219if(bc7) drawBlob(bc7, '#775599', 9.1); 2220 2221var bc3 = document.getElementById('blob-history'); 2222if(bc3) drawBlob(bc3, '#559977', 4.3); 2223 2224 2225(function(){ 2226 var card = document.getElementById('new-poem-card'); 2227 var detail = document.getElementById('new-poem-detail'); 2228 if (!card) return; 2229 fetch('/api/record_view/', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({poem_id: "c1baed80"})}); 2230 card.addEventListener('click', function(e){ 2231 if (!e.target.closest('a') && !e.target.closest('button')) { 2232 var opening = detail.style.display === 'none'; 2233 detail.style.display = opening ? '' : 'none'; 2234 if(opening) trackEvent('card_expand','new_poem'); 2235 } 2236 }); 2237})(); 2238 2239 2240function refreshNewPoem(){ 2241 fetch('/api/new_poem').then(function(r){return r.json()}).then(function(d){ 2242 if(!d.id) return; 2243 document.getElementById('new-poem-sentence').textContent = d.sentence; 2244 document.getElementById('new-poem-title-link').textContent = d.title; 2245 document.getElementById('new-poem-title-link').href = '/poem/' + d.id; 2246 document.getElementById('new-poem-title-link').nextElementSibling.textContent = ' · ' + d.author + ' · ' + d.date; 2247 document.getElementById('new-poem-content').textContent = d.content; 2248 document.getElementById('new-poem-detail').style.display = 'none'; 2249 document.getElementById('new-poem-card').style.borderLeftColor = d.color; 2250 fetch('/api/record_view/', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({poem_id: d.id})}); 2251 }); 2252} 2253 2254(function(){ 2255 var card = document.getElementById('classic-rec-card'); 2256 var detail = document.getElementById('classic-rec-detail'); 2257 var reason = document.getElementById('classic-rec-reason'); 2258 var expanded = false; 2259 card.addEventListener('click', function(e){ 2260 if(e.target.closest('button') || e.target.closest('a')) return; 2261 expanded = !expanded; 2262 detail.style.display = expanded ? '' : 'none'; 2263 reason.style.display = expanded ? '' : 'none'; 2264 if(expanded) trackEvent('card_expand','classic_rec'); 2265 }); 2266 var cached = localStorage.getItem('classicRec'); 2267 if(cached){ try { renderClassicRec(JSON.parse(cached)); } catch(e){ loadClassicRec(); } } 2268 else { loadClassicRec(); } 2269})(); 2270function loadClassicRec(){ 2271 fetch('/api/classic_recommend').then(function(r){return r.json()}).then(function(d){ 2272 if(!d.ok) return; 2273 localStorage.setItem('classicRec', JSON.stringify(d)); 2274 renderClassicRec(d); 2275 }).catch(function(){ document.getElementById('classic-rec-sentence').textContent = 'ææ æ¨è'; }); 2276} 2277function renderClassicRec(d){ 2278 document.getElementById('classic-rec-sentence').textContent = d.sentence; 2279 document.getElementById('classic-rec-title').textContent = 'ã' + d.title + 'ã'; 2280 document.getElementById('classic-rec-meta').textContent = ' · ' + (d.dynasty||'') + ' · ' + d.author; 2281 document.getElementById('classic-rec-content').textContent = d.content; 2282 document.getElementById('classic-rec-reason-text').innerHTML = 2283 'å' + d.source_author + 'ã<a href="/poem/' + d.source_id + '" onclick="trackEvent(\'card_link\',this.href)" style="color:#995577; text-decoration:none;">' + d.source_title + '</a>ãç¸ä¼¼åº¦ ' + d.score; 2284 document.getElementById('classic-rec-detail').style.display = 'none'; 2285 document.getElementById('classic-rec-reason').style.display = 'none'; 2286} 2287function refreshClassicRec(){
2288 localStorage.removeItem('classicRec'); 2289 document.getElementById('classic-rec-sentence').textContent = 'å è½½ä¸...'; 2290 document.getElementById('classic-rec-detail').style.display = 'none'; 2291 document.getElementById('classic-rec-reason').style.display = 'none'; 2292 loadClassicRec(); 2293} 2294 2295 2296(function(){ 2297 var card = document.getElementById('today-history-card'); 2298 var detail = document.getElementById('history-detail'); 2299 if (!card) return; 2300 card.addEventListener('click', function(e){ 2301 if(e.target.closest('button') || e.target.closest('a')) return; 2302 var opening = detail.style.display === 'none'; 2303 detail.style.display = opening ? '' : 'none'; 2304 if(opening) trackEvent('card_expand','today_history'); 2305 }); 2306})(); 2307 2308function refreshHistory(){ 2309 fetch('/api/today_history').then(function(r){return r.json()}).then(function(d){ 2310 if(!d.ok && !d.id) return; 2311 document.getElementById('history-sentence').textContent = d.sentence; 2312 document.getElementById('history-title-link').textContent = d.title; 2313 document.getElementById('history-title-link').href = '/poem/' + d.id; 2314 document.getElementById('history-meta').textContent = ' · ' + d.author + ' · ' + d.year + 'å¹´'; 2315 document.getElementById('history-content').textContent = d.content; 2316 document.getElementById('history-detail').style.display = 'none'; 2317 document.getElementById('today-history-card').style.borderLeftColor = d.color; 2318 }); 2319} 2320 2321// --- é£è±ä»¤ --- 2322(function(){ 2323 var chars = ['æ','è±','é£','éª','æ¥','ç§','å±±','æ°´','äº','é¨','é ','梦','å¿','å¤','天','红','é','ç½','é¿','å½']; 2324 var today = new Date(Date.now() + 8*3600000); 2325 var dayKey = today.toISOString().slice(0,10); 2326 var todayChar = chars[parseInt(dayKey.replace(/-/g,'')) % chars.length]; 2327 var storeKey = 'feihua_' + dayKey; 2328 2329 document.getElementById('fh-char-display').textContent = 'â' + todayChar + 'â'; 2330 document.getElementById('fh-input').placeholder = '请è¾å ¥å«â' + todayChar + 'âçä¸è¨è¯å¥...'; 2331 2332 var state = JSON.parse(localStorage.getItem(storeKey) || 'null'); 2333 if (!state || state.char !== todayChar) { 2334 state = { char: todayChar, currentPos: 1, history: [] }; 2335 } 2336 saveState(); 2337 render(); 2338 2339 function saveState() { localStorage.setItem(storeKey, JSON.stringify(state)); } 2340 2341 function render() { 2342 var histEl = document.getElementById('fh-history'); 2343 histEl.innerHTML = '';
2344 state.history.forEach(function(h) { 2345 histEl.innerHTML += '<div style="margin-bottom:6px; padding:4px 0; border-bottom:1px solid #f0f0f0;">' 2346 + '<div style="color:#333; font-size:0.95em;">第' + h.pos + 'ä½ Â· <span style="color:#995577;">ä½ ï¼</span>' + h.userText + '</div>' 2347 + '<div style="color:#666; font-size:0.9em; margin-top:2px; display:flex; align-items:center; gap:6px;"><img src="/static/pics/tianquan_avatar.webp" style="width:20px;height:20px;border-radius:50%;flex-shrink:0;"><span><span style="color:#557799;">天æï¼</span>' + h.sysText + ' ââ ' + h.sysAuthor + 'ã' + h.sysTitle + 'ã</span></div>' 2348 + '</div>'; 2349 }); 2350 if (state.currentPos > 7) { 2351 document.getElementById('fh-current').style.display = 'none'; 2352 document.getElementById('fh-done').style.display = ''; 2353 } else { 2354 document.getElementById('fh-current').style.display = ''; 2355 document.getElementById('fh-done').style.display = 'none'; 2356 document.getElementById('fh-pos-label').textContent = '第 ' + state.currentPos + ' ä½'; 2357 } 2358 } 2359 2360 window.fhSubmit = function() { 2361 var input = document.getElementById('fh-input').value.trim(); 2362 var msg = document.getElementById('fh-msg'); 2363 msg.textContent = ''; 2364 if (!input) { msg.textContent = '请è¾å ¥è¯å¥'; return; } 2365 2366 var clean = input.replace(/[ï¼ãï¼ï¼ãï¼ï¼""''ï¼ï¼\s]/g, ''); 2367 if (clean.length !== 5 && clean.length !== 7) { 2368 msg.textContent = '请è¾å ¥äºè¨æä¸è¨è¯å¥ï¼' + clean.length + 'åï¼'; 2369 return; 2370 } 2371 var pos = state.currentPos; 2372 if (clean.charAt(pos - 1) !== todayChar) { 2373 msg.textContent = 'â' + todayChar + 'âåºå¨ç¬¬' + pos + 'ä½ï¼å½å第' + pos + '使¯â' + clean.charAt(pos-1) + 'â'; 2374 return; 2375 } 2376 2377 msg.textContent = 'éªè¯ä¸...'; 2378 msg.style.color = '#888'; 2379 document.getElementById('fh-submit').disabled = true; 2380 2381 fetch('https://shi.sjtuguoxue.space/api/search/text?q=' + encodeURIComponent(clean) + '&limit=1') 2382 .then(function(r){ return r.json(); }) 2383 .then(function(d) { 2384 if (!d.total || d.total === 0) { 2385 msg.style.color = '#cc5555'; 2386 msg.textContent = 'æªæ¾å°æ¤è¯å¥ï¼è¯·æ£æ¥ææ¢ä¸å¥'; 2387 document.getElementById('fh-submit').disabled = false; 2388 return; 2389 } 2390 msg.textContent = 'æ£ç¡®ï¼å¤©ææ¥æ...'; 2391 msg.style.color = '#559977'; 2392 trackEvent('card_expand', 'feihua'); 2393 2394 fetch('https://shi.sjtuguoxue.space/api/feihua?char=' + encodeURIComponent(todayChar) + '&position=' + pos + '&length=7&limit=10') 2395 .then(function(r){ return r.json(); }) 2396 .then(function(fd) { 2397 var sysResult = fd.results && fd.results.length > 0 2398 ? fd.results[Math.floor(Math.random() * fd.results.length)] 2399 : { text: '(å¤©ææ å¥å¯åº)', author: '', title: '' }; 2400 2401 state.history.push({ 2402 pos: pos, 2403 userText: input, 2404 sysText: sysResult.text, 2405 sysAuthor: sysResult.author || 'ä½å', 2406 sysTitle: sysResult.title || '' 2407 }); 2408 state.currentPos = pos + 1; 2409 saveState(); 2410 render(); 2411 document.getElementById('fh-input').value = ''; 2412 document.getElementById('fh-submit').disabled = false; 2413 msg.textContent = ''; 2414 }); 2415 }) 2416 .catch(function() { 2417 msg.style.color = '#cc5555'; 2418 msg.textContent = 'ç½ç»é误ï¼è¯·éè¯'; 2419 document.getElementById('fh-submit').disabled = false;
2420 }); 2421 }; 2422 2423 document.getElementById('fh-input').addEventListener('keydown', function(e) { 2424 if (e.key === 'Enter') fhSubmit(); 2425 }); 2426})(); 2427</script>
2427 2428 2429 2430 </div> 2431 2432 <footer class="mobile-footer mobile-only"> 2433 <div class="sidebar-footer"> 2434 <p>Copyright 2022-2026 <a href="https://lyy0323.space/writing/">ä¸åªæ¤°æ¤°</a> ï½ è®¿é®éï¼ 3607250</p> 2435 <p>沪ICPå¤2025137436å·</p> 2436 </div> 2437</footer> 2438 2439 <footer class="medium-footer"> 2440 <section class="sidebar-module"> 2441 <h3 class="sidebar-section-title">èç³»æ¹å¼</h3> 2442 <div class="sidebar-text"> 2443 <p><a href="https://onboarding.sjtuguoxue.space/" style="color:#cc99bb;font-weight:bold;">忴忏¸Â·å å ¥æä»¬</a></p> 2444 <p>é®ç®±ï¼<a href="mailto:[email protected]">[email protected]</a></p> 2445 <p>QQï¼709881123</p> 2446 <p>å°åï¼ä¸æµ·å¸éµè¡åºä¸å·è·¯800å·</p> 2447 </div> 2448 <h1 class="site-title"><img src="/static/pics/name.webp" alt="åç" class="site-title-image-2"></h1> 2449</section> 2450 2451<section class="sidebar-module"> 2452 <h3 class="sidebar-section-title">常ç¨é¾æ¥</h3> 2453 <div class="sidebar-text"> 2454 <p><a href="http://sjtuguoxue.space/">sjtuguoxue.space</a></p> 2455 <p><a href="https://guoxue-sjtu.feishu.cn">é£ä¹¦ææ¡£</a></p> 2456 <p><img src="/static/pics/fangcun_logo.svg" style="height:1em;vertical-align:middle;margin-right:4px"><a href="https://write.sjtuguoxue.space">æ¹å¯¸Â·è¯è¯åä½</a></p> 2457 <p style="padding-left:1.2em"><a href="https://write.sjtuguoxue.space/download/android">Android ä¸è½½</a></p> 2458 <p><a href="https://yuan.sjtuguoxue.space">ææ¸æè®°</a></p> 2459 <p><a href="http://yuefu4anniv.sjtuguoxue.space/">弦æå载·ä¹åºé£é</a></p> 2460 </div> 2461</section> 2462 2463<section class="sidebar-module"> 2464 <h3 class="sidebar-section-title">æ°åªä½ç©éµ</h3> 2465 <div class="sidebar-text"> 2466 <p>å¾®ä¿¡å ¬ä¼å·ï¼ä¸æµ·äº¤å¤§å½å¦ç¤¾</p> 2467 <p>å°çº¢ä¹¦ï¼<a href="https://xhslink.com/m/2zltiBaeek6">交大å½å¦ç¤¾</a></p> 2468 <p>Bilibiliï¼<a href="https://space.bilibili.com/698232390">䏿µ·äº¤é大妿æ²é¨</a></p> 2469 </div> 2470</section> 2471 2472<section class="sidebar-module"> 2473 <h3 class="sidebar-section-title">åæ é¾æ¥</h3> 2474 <div class="sidebar-text"> 2475 <p><a href="https://club.jluzhouhang.cn/index.html">åæå¤§å¦å¨è¡è¯ç¤¾</a></p> 2476 </div> 2477</section> 2478 </footer> 2479 2480 </main> 2481 2482 </div> 2483 2484 2485
2486<script> 2487 function streamText(element, text, speed = 15) { 2488 element.innerHTML = ''; 2489 element.style.display = 'block'; 2490 let currentText = ''; 2491 let i = 0; 2492 const formattedText = text.replace(/\\t/g, '\t').replace(/\\n/g, '\n').replace(/\\"/g, '"'); 2493 const intervalId = setInterval(() => { 2494 if (i < formattedText.length) { 2495 currentText += formattedText[i]; 2496 element.innerHTML = currentText 2497 .replace(/(?:\r\n|\r|\n)/g, '<br>') 2498 .replace(/\t/g, '  '); 2499 i++; 2500 } else { 2501 clearInterval(intervalId); 2502 } 2503 }, speed); 2504 } 2505 document.body.addEventListener('click', function(e) { 2506 if (e.target && e.target.id == 'showInterpret') { 2507 const button = e.target; 2508 const poemId = button.getAttribute('data-poem-id'); 2509 const resultEl = document.getElementById('interpretResult'); 2510 if (button.disabled || !resultEl) return; 2511 button.disabled = true; 2512 button.textContent = 'AI èµæçæä¸'; 2513 button.classList.add('loading'); 2514 const originalText = 'æ¥ç AI èµæ'; 2515 setTimeout(() => { 2516 button.textContent = 'å è½½ä¸'; 2517 fetch('/api/interpret/' + poemId) 2518 .then(response => { 2519 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); 2520 return response.json(); 2521 }) 2522 .then(data => { 2523 if (data.ok && data.result) { 2524 button.style.display = 'none'; 2525 streamText(resultEl, data.result, 15); 2526 } else { 2527 throw new Error(data.error || 'ææ èµæ'); 2528 } 2529 }) 2530 .catch(err => { 2531 console.error('Error fetching or processing interpretation:', err); 2532 if(resultEl) { 2533 resultEl.innerHTML = 'èµæå 载失败ï¼è¯·ç¨ååè¯ãå¯è½æ¯ä»å¤©çèµæå°æªçæã'; 2534 resultEl.style.display = 'block'; 2535 } 2536 button.disabled = false; 2537 button.textContent = originalText; 2538 button.classList.remove('loading'); 2539 }); 2540 }, 3000); 2541 } 2542 }); 2543</script>
2543 2544
2545<script> 2546document.addEventListener('DOMContentLoaded', function() { 2547 const bannerWidget = document.getElementById('top-banner-widget'); 2548 const slidesWrapper = document.getElementById('banner-slides-wrapper'); 2549 const dotsContainer = document.getElementById('banner-dots-container'); 2550 const prevButton = document.getElementById('banner-prev'); 2551 const nextButton = document.getElementById('banner-next'); 2552 2553 let slidesData = []; 2554 let currentIndex = 0; 2555 let slideInterval; 2556 const AUTO_PLAY_DELAY = 5000; // 5 ç§ 2557 2558 async function fetchBannerData() { 2559 try { 2560 // å设 JSON æä»¶æ¾å¨ static ç®å½ä¸ 2561 // 注æ: è¯·ç¡®ä¿ /static/banner_data.json è·¯å¾æ¯æ£ç¡®ç 2562 const response = await fetch("/static/banner_data.json?v=1790307595"); 2563 if (!response.ok) { 2564 throw new Error(`HTTP error! status: ${response.status}`); 2565 } 2566 const data = await response.json(); 2567 2568 // --- ä¿®æ¹å¼å§: è¿æ»¤è¿æçæ´»å¨ --- 2569 // è·å YYYY-MM-DD æ ¼å¼çä»å¤©æ¥æ 2570 const today = new Date().toISOString().split('T')[0]; 2571 2572 const validData = data.filter(item => { 2573 // å¦ææ²¡æ 'activityDate' åæ®µ (èæ°æ®)ï¼åå§ç»æ¾ç¤º 2574 if (!item.activityDate) { 2575 return true; 2576 } 2577 // å¦æææ¥æï¼åæ¯è¾æ¥æ 2578 return item.activityDate >= today; 2579 }); 2580 // --- ä¿®æ¹ç»æ --- 2581 2582 // æ ¡éªæ°æ® (使ç¨è¿æ»¤åç validData) 2583 if (Array.isArray(validData) && validData.length >= 2) { 2584 slidesData = validData; // 使ç¨è¿æ»¤åç validData 2585 initBanner(); 2586 } else { 2587 console.warn('Banner data is invalid or has less than 2 items (after filtering). Banner will be hidden.'); 2588 bannerWidget.style.display = 'none'; 2589 } 2590 } catch (error) { 2591 console.error('Failed to fetch banner data:', error); 2592 bannerWidget.style.display = 'none'; // å 载失败æ¶éè 2593 } 2594 } 2595 2596 function initBanner() { 2597 // 卿å建 slides å dots 2598 slidesWrapper.innerHTML = ''; 2599 dotsContainer.innerHTML = ''; 2600 2601 slidesData.forEach((item, index) => { 2602 // å建 Slide 2603 const slide = document.createElement('a'); 2604 slide.href = item.targetUrl; 2605 slide.className = 'banner-slide'; 2606 slide.target = '_blank'; 2607 slide.rel = 'noopener noreferrer'; 2608 slide.onclick = function(){ if(typeof trackEvent==='function') trackEvent('banner_click', item.caption); }; 2609 2610 slide.innerHTML = ` 2611 <img src="${item.imageUrl}" class="banner-image" alt="${item.caption}" onerror="this.parentElement.style.display='none'"> 2612 <div class="banner-caption">${item.caption}</div> 2613 `; 2614 slidesWrapper.appendChild(slide); 2615 2616 // å建 Dot 2617 const dot = document.createElement('button'); 2618 dot.className = 'banner-dot'; 2619 dot.setAttribute('aria-label', `Go to slide ${index + 1}`); 2620 dot.addEventListener('click', () => { 2621 goToSlide(index); 2622 resetInterval(); 2623 }); 2624 dotsContainer.appendChild(dot); 2625 }); 2626 2627 // æ·»å äºä»¶çå¬ 2628 prevButton.addEventListener('click', () => { 2629 goToSlide(currentIndex - 1); 2630 resetInterval(); 2631 }); 2632 nextButton.addEventListener('click', () => { 2633 goToSlide(currentIndex + 1); 2634 resetInterval(); 2635 }); 2636 2637 // è§¦æ¸æ»å¨æ¯æ (ç®åç) 2638 let touchStartX = 0; 2639 bannerWidget.addEventListener('touchstart', (e) => touchStartX = e.touches[0].clientX); 2640 bannerWidget.addEventListener('touchend', (e) => { 2641 let touchEndX = e.changedTouches[0].clientX; 2642 if (touchStartX - touchEndX > 50) { // åå·¦æ» 2643 nextButton.click(); 2644 } else if (touchEndX - touchStartX > 50) { // å峿» 2645 prevButton.click(); 2646 } 2647 }); 2648 2649 2650 // åå§å 2651 bannerWidget.style.display = 'block'; // æ¾ç¤º Banner 2652 goToSlide(0); // æ¾ç¤ºç¬¬ä¸å¼ 2653 startInterval(); // å¼å§èªå¨ææ¾ 2654 } 2655 2656 function goToSlide(index) { 2657 const totalSlides = slidesData.length; 2658 // å¾ªç¯ææ¾ 2659 if (index < 0) { 2660 currentIndex = totalSlides - 1; 2661 } else if (index >= totalSlides) { 2662 currentIndex = 0; 2663 } else { 2664 currentIndex = index; 2665 } 2666 2667 // ç§»å¨ slides 2668 slidesWrapper.style.transform = `translateX(-${currentIndex * 100}%)`; 2669 2670 // æ´æ° dots 2671 const dots = dotsContainer.querySelectorAll('.banner-dot');
2672 dots.forEach((dot, i) => { 2673 dot.classList.toggle('active', i === currentIndex); 2674 }); 2675 } 2676 2677 function startInterval() { 2678 clearInterval(slideInterval); // æ¸ é¤æ§ç 2679 slideInterval = setInterval(() => { 2680 goToSlide(currentIndex + 1); 2681 }, AUTO_PLAY_DELAY); 2682 } 2683 2684 function resetInterval() { 2685 startInterval(); // é置计æ¶å¨ 2686 } 2687 2688 // å¼å§å è½½æ°æ® 2689 fetchBannerData(); 2690}); 2691</script>
2691 2692
2693<script> 2694document.addEventListener('DOMContentLoaded', function() { 2695 const searchInput = document.getElementById('search-input-field'); 2696 const suggestionsBox = document.getElementById('autocomplete-suggestions'); 2697 const searchForm = document.getElementById('main-search-form'); 2698 let debounceTimer; 2699 searchInput.addEventListener('input', function() { 2700 const prefix = this.value; 2701 clearTimeout(debounceTimer); 2702 if (prefix.length === 0) { 2703 hideSuggestions(); 2704 return; 2705 } 2706 debounceTimer = setTimeout(() => { 2707 fetchSuggestions(prefix); 2708 }, 300); 2709 }); 2710 async function fetchSuggestions(prefix) { 2711 try { 2712 const response = await fetch(`/api/autocomplete?q=${encodeURIComponent(prefix)}`); 2713 if (!response.ok) throw new Error('Network error'); 2714 const suggestions = await response.json(); 2715 showSuggestions(suggestions); 2716 } catch (error) { 2717 console.error('Error fetching autocomplete:', error); 2718 hideSuggestions(); 2719 } 2720 } 2721 function showSuggestions(suggestions) { 2722 if (suggestions.length === 0) { 2723 hideSuggestions(); 2724 return; 2725 } 2726 suggestionsBox.innerHTML = ''; 2727 suggestions.forEach(text => { 2728 const item = document.createElement('div'); 2729 item.className = 'suggestion-item'; 2730 item.textContent = text; 2731 item.addEventListener('click', function() { 2732 searchInput.value = text; 2733 hideSuggestions(); 2734 searchForm.submit(); 2735 }); 2736 suggestionsBox.appendChild(item); 2737 }); 2738 suggestionsBox.style.display = 'block'; 2739 } 2740 function hideSuggestions() { 2741 suggestionsBox.style.display = 'none'; 2742 } 2743 document.addEventListener('click', function(e) { 2744 if (!e.target.closest('.search-widget')) { 2745 hideSuggestions(); 2746 } 2747 }); 2748 searchInput.addEventListener('keydown', function(e) { 2749 if (e.key === 'Enter' && suggestionsBox.style.display === 'block') { 2750 if (suggestionsBox.children.length > 0) { 2751 e.preventDefault(); 2752 } 2753 } 2754 }); 2755}); 2756</script>
2756 2757
2758<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
2758 2759
2760<script> 2761document.addEventListener('DOMContentLoaded', function() { 2762 const gameContainer = document.getElementById('game-container'); 2763 if (!gameContainer) return; 2764 const loadingEl = gameContainer.querySelector('.game-loading'); 2765 const contentEl = gameContainer.querySelector('.game-content'); 2766 const promptEl = gameContainer.querySelector('.game-prompt'); 2767 const demoEl = gameContainer.querySelector('.game-demo'); 2768 const gameArea = gameContainer.querySelector('.game-area'); 2769 const feedbackEl = gameContainer.querySelector('.game-feedback'); 2770 const nextBtn = document.getElementById('next-game-btn'); 2771 const submitSortBtn = document.getElementById('submit-sort-btn'); 2772 const viewPoemBtn = document.getElementById('view-poem-btn'); 2773 const skipBtn = document.getElementById('skip-game-btn'); 2774 let currentGameData = null; 2775 let isAnswered = false; 2776 2777 function logGameAnswer(logData) { 2778 fetch('/api/log_game_answer', { 2779 method: 'POST', 2780 headers: {'Content-Type': 'application/json'}, 2781 body: JSON.stringify(logData) 2782 }) 2783 .then(response => { 2784 if (!response.ok) console.error('æ¸¸ææ¥å¿è®°å½å¤±è´¥ (API ååºé 200)ã'); 2785 }) 2786 .catch(error => console.error('æ¸¸ææ¥å¿è®°å½å¤±è´¥ (ç½ç»é误):', error)); 2787 } 2788 2789 async function fetchGame() { 2790 isAnswered = false; 2791 contentEl.style.display = 'none'; 2792 loadingEl.style.display = 'block'; 2793 feedbackEl.textContent = ''; 2794 nextBtn.style.display = 'none'; 2795 submitSortBtn.style.display = 'none'; 2796 viewPoemBtn.style.display = 'none'; 2797 skipBtn.style.display = 'none'; 2798 2799 const storedGame = localStorage.getItem('unansweredGame'); 2800 if (storedGame) { 2801 console.log("ä» localStorage å è½½æªçé¢ç®ã"); 2802 currentGameData = JSON.parse(storedGame); 2803 renderGame(currentGameData); 2804 return; 2805 } 2806 2807 console.log("localStorage 为空ï¼ä» API è·åæ°é¢ç®ã"); 2808 try { 2809 const response = await fetch('/api/game_question/'); 2810 if (!response.ok) throw new Error('Network error'); 2811 const data = await response.json(); 2812 if (data.ok && data.content) { 2813 localStorage.setItem('unansweredGame', JSON.stringify(data.content)); 2814 currentGameData = data.content; 2815 renderGame(currentGameData); 2816 } else { 2817 throw new Error('API ååºæ ¼å¼ä¸æ£ç¡®'); 2818 } 2819 } catch (error) { 2820 console.error('å è½½é¢ç®å¤±è´¥:', error); 2821 loadingEl.textContent = 'å è½½é¢ç®å¤±è´¥ï¼è¯·å·æ°ã'; 2822 } 2823 } 2824 2825 function renderGame(data) { 2826 promptEl.textContent = data.prompt; 2827 if (data.demo) { 2828 demoEl.textContent = data.demo; 2829 demoEl.style.display = 'block'; 2830 } else { 2831 demoEl.style.display = 'none'; 2832 } 2833 if (data.type === 'multiple_choice') { 2834 renderMultipleChoice(data.choices); 2835 } else if (data.type === 'sorting') { 2836 renderSortingGame(data.choices); 2837 } 2838 loadingEl.style.display = 'none'; 2839 contentEl.style.display = 'block'; 2840 } 2841 2842 function renderMultipleChoice(choices) { 2843 gameArea.innerHTML = ''; 2844 gameArea.style.gridColumn = ''; 2845 choices.forEach(text => { 2846 const btn = document.createElement('button'); 2847 btn.className = 'choice-button'; 2848 btn.textContent = text; 2849 btn.addEventListener('click', () => { 2850 handleChoiceClick(btn, text, currentGameData.goal); 2851 }); 2852 gameArea.appendChild(btn); 2853 }); 2854 skipBtn.style.display = 'none'; 2855 submitSortBtn.style.display = 'none'; 2856 } 2857 2858 function handleChoiceClick(clickedButton, selectedChoice, correctChoice) { 2859 if (isAnswered) return; 2860 isAnswered = true; 2861 localStorage.removeItem('unansweredGame'); 2862 const isCorrect = (selectedChoice === correctChoice); 2863 if (isCorrect) { 2864 feedbackEl.textContent = 'åçæ£ç¡®ï¼'; 2865 feedbackEl.className = 'game-feedback correct'; 2866 } else { 2867 feedbackEl.textContent = `åçéè¯¯ãæ£ç¡®çæ¡: ${correctChoice}`; 2868 feedbackEl.className = 'game-feedback incorrect'; 2869 } 2870 logGameAnswer({ 2871 content: currentGameData, 2872 is_correct: isCorrect ? 1 : 0, 2873 attempt_count: 1 2874 }); 2875 gameArea.querySelectorAll('.choice-button').forEach(btn => { 2876 btn.disabled = true; 2877 if (btn.textContent === correctChoice) { 2878 btn.style.borderColor = '#559977'; 2879 btn.style.backgroundColor = 'rgba(85, 153, 119, 0.2)'; 2880 } else if (btn === clickedButton) { 2881 btn.style.borderColor = '#995577'; 2882 btn.style.backgroundColor = 'rgba(153, 85, 119, 0.2)'; 2883 } 2884 }); 2885 if (currentGameData.poem_id) { 2886 viewPoemBtn.href = `/poem/${currentGameData.poem_id}`; 2887 viewPoemBtn.style.display = 'inline-block'; 2888 } 2889 nextBtn.style.display = 'inline-block'; 2890 } 2891
2892 function renderSortingGame(choices) { 2893 gameArea.innerHTML = ''; 2894 const list = document.createElement('ul'); 2895 list.className = 'sortable-list'; 2896 list.id = 'sortable-list'; 2897 choices.forEach(text => { 2898 const item = document.createElement('li'); 2899 item.className = 'sortable-item'; 2900 item.textContent = text; 2901 list.appendChild(item); 2902 }); 2903 gameArea.appendChild(list); 2904 skipBtn.style.display = 'inline-block'; 2905 submitSortBtn.style.display = 'inline-block'; 2906 new Sortable(list, { 2907 animation: 150, 2908 forceFallback: true, 2909 onStart: function () { 2910 if (window.innerWidth <= 768) document.body.style.overflow = 'hidden'; 2911 }, 2912 onEnd: function () { 2913 if (window.innerWidth <= 768) document.body.style.overflow = 'auto'; 2914 } 2915 }); 2916 } 2917 2918 function handleSubmitSort() { 2919 if (isAnswered) return; 2920 isAnswered = true; 2921 skipBtn.style.display = 'none' 2922 localStorage.removeItem('unansweredGame'); 2923 const items = gameArea.querySelectorAll('.sortable-item'); 2924 const currentOrder = Array.from(items).map(item => item.textContent); 2925 const correctOrder = currentGameData.goal; 2926 let isCorrect = true; 2927 for (let i = 0; i < currentOrder.length; i++) { 2928 if (currentOrder[i] !== correctOrder[i]) { 2929 isCorrect = false; 2930 break; 2931 } 2932 } 2933 if (isCorrect) { 2934 feedbackEl.textContent = 'æåºæ£ç¡®ï¼'; 2935 feedbackEl.className = 'game-feedback correct'; 2936 } else { 2937 feedbackEl.textContent = 'æåºé误ã'; 2938 feedbackEl.className = 'game-feedback incorrect'; 2939 } 2940 logGameAnswer({ 2941 content: currentGameData, 2942 is_correct: isCorrect ? 1 : 0, 2943 attempt_count: 1 2944 }); 2945 const sortableList = document.getElementById('sortable-list'); 2946 const sortableInstance = Sortable.get(sortableList); 2947 if (sortableInstance) sortableInstance.destroy(); 2948 submitSortBtn.style.display = 'none'; 2949 if (currentGameData.poem_id) { 2950 viewPoemBtn.href = `/poem/${currentGameData.poem_id}`; 2951 viewPoemBtn.style.display = 'inline-block'; 2952 } 2953 nextBtn.style.display = 'inline-block'; 2954 } 2955 2956 function handleSkipGame() { 2957 if (isAnswered) return; 2958 isAnswered = true; 2959 logGameAnswer({ 2960 content: currentGameData, 2961 is_correct: -1, 2962 attempt_count: 0 2963 }); 2964 localStorage.removeItem('unansweredGame'); 2965 fetchGame(); 2966 } 2967 2968 nextBtn.addEventListener('click', fetchGame); 2969 submitSortBtn.addEventListener('click', handleSubmitSort); 2970 skipBtn.addEventListener('click', handleSkipGame); 2971 2972 fetchGame(); 2973}); 2974</script>
2974 2975
2976<script> 2977// ---- åç²å¯¹è¯ ---- 2978// é å¯¹ç»æç»ä¸æ¸²æï¼ä¸å¥å¨å·¦ãä¸å¥å¨å³ï¼ä½è å badge è´´å¨åèªæ°æ³¡ä¸æ¹ï¼ 2979// badge é»è®¤æ è¾¹æ¡ï¼ä» å½ä½è æä»£è¡¨è²æ¶å¨åå左侧å 䏿¡ç«çº¿ã 2980window.duishiFillBadge = function(el, penName, color) { 2981 if (!el) { return; } 2982 el.textContent = penName || 'ä½å'; 2983 if (color) { 2984 el.className = 'duishi-badge has-color'; 2985 el.style.borderLeftColor = color; 2986 } else { 2987 el.className = 'duishi-badge'; 2988 el.style.borderLeftColor = ''; 2989 } 2990}; 2991 2992window.duishiFillPair = function(prefix, pair) { 2993 window.duishiFillBadge(document.getElementById(prefix + '-up-badge'), pair.upPenName, pair.upColor); 2994 window.duishiFillBadge(document.getElementById(prefix + '-down-badge'), pair.downPenName, pair.downColor); 2995 var upText = document.getElementById(prefix + '-up-text'); 2996 var downText = document.getElementById(prefix + '-down-text'); 2997 if (upText) { upText.textContent = pair.upText; } 2998 if (downText) { downText.textContent = pair.downText; } 2999}; 3000 3001(function(){ 3002 var entry = document.getElementById('duishi-entry-card'); 3003 if (!entry) return; 3004 3005 var LOGGED_IN = false; 3006 var STATE_KEY = 'duishiState'; 3007 3008 var formEl = document.getElementById('duishi-form'); 3009 var resultEl = document.getElementById('duishi-result'); 3010 var guestEl = document.getElementById('duishi-guest'); 3011 var waitingEl = document.getElementById('duishi-waiting'); 3012 var matchedEl = document.getElementById('duishi-matched'); 3013 var msgEl = document.getElementById('duishi-msg'); 3014 var inputEl = document.getElementById('duishi-input'); 3015 var submitBtn = document.getElementById('duishi-submit-btn'); 3016 3017 var side = 'up'; 3018 3019 function readState() { 3020 try { return JSON.parse(localStorage.getItem(STATE_KEY) || 'null'); } 3021 catch (e) { return null; } 3022 } 3023 function writeState(state) { 3024 try { localStorage.setItem(STATE_KEY, JSON.stringify(state)); } catch (e) {} 3025 } 3026 function clearState() { 3027 try { localStorage.removeItem(STATE_KEY); } catch (e) {} 3028 } 3029 function sideLabel(value) { return value === 'down' ? 'ä¸å¥' : 'ä¸å¥'; } 3030 3031 function showForm() { 3032 formEl.style.display = ''; 3033 resultEl.style.display = 'none'; 3034 } 3035 3036 function renderWaiting(pair) { 3037 formEl.style.display = 'none'; 3038 resultEl.style.display = ''; 3039 waitingEl.style.display = ''; 3040 matchedEl.style.display = 'none'; 3041 var mine = pair.mySide === 'down' ? 'down' : 'up'; 3042 document.getElementById('duishi-waiting-side').textContent = 'ä½ ç' + sideLabel(mine); 3043 document.getElementById('duishi-waiting-text').textContent = 3044 mine === 'down' ? pair.downText : pair.upText; 3045 } 3046 3047 function renderMatched(pair) { 3048 formEl.style.display = 'none'; 3049 resultEl.style.display = ''; 3050 waitingEl.style.display = 'none'; 3051 matchedEl.style.display = ''; 3052 window.duishiFillPair('duishi', pair); 3053 } 3054 3055 function applyState(state) { 3056 if (!state || !state.pair) { showForm(); return; } 3057 if (state.pair.matched) { renderMatched(state.pair); } else { renderWaiting(state.pair); } 3058 } 3059 3060 window.duishiPickSide = function(next) { 3061 side = next === 'down' ? 'down' : 'up'; 3062 var btns = entry.querySelectorAll('.duishi-side-btn'); 3063 for (var i = 0; i < btns.length; i++) { 3064 var isActive = btns[i].getAttribute('data-side') === side; 3065 btns[i].className = 'duishi-side-btn' + (isActive ? ' active' : ''); 3066 } 3067 inputEl.placeholder = 'åä¸ä½ ç' + sideLabel(side) + 'ï¼2-10 个æ±åï¼'; 3068 msgEl.textContent = ''; 3069 }; 3070 3071 window.duishiSubmit = function() { 3072 var text = (inputEl.value || '').trim(); 3073 msgEl.textContent = ''; 3074 if (!text) { msgEl.textContent = '请å åä¸å¥è¯'; return; } 3075 3076 submitBtn.disabled = true; 3077 fetch('/api/duishi/submit', { 3078 method: 'POST', 3079 headers: {'Content-Type': 'application/json'}, 3080 body: JSON.stringify({side: side, text: text}) 3081 }).then(function(r){ 3082 return r.json().then(function(body){ return {status: r.status, body: body}; }) 3083 .catch(function(){ return {status: r.status, body: null}; }); 3084 }).then(function(res){ 3085 submitBtn.disabled = false;
3086 var body = res.body; 3087 if (!body || !body.ok) { 3088 if (res.status === 401) { showGuest(); return; } 3089 msgEl.textContent = (body && body.message) || 'æäº¤å¤±è´¥ï¼è¯·éè¯'; 3090 return; 3091 } 3092 trackEvent('duishi_submit', side); 3093 var state = {side: side, pair: body.pair}; 3094 writeState(state); 3095 inputEl.value = ''; 3096 applyState(state); 3097 }).catch(function(){ 3098 submitBtn.disabled = false; 3099 msgEl.textContent = 'ç½ç»é误ï¼è¯·éè¯'; 3100 }); 3101 }; 3102 3103 window.duishiAgain = function() { 3104 trackEvent('duishi_again', 'duishi'); 3105 clearState(); 3106 inputEl.value = ''; 3107 msgEl.textContent = ''; 3108 showForm(); 3109 }; 3110 3111 function showGuest() { 3112 guestEl.style.display = ''; 3113 formEl.style.display = 'none'; 3114 resultEl.style.display = 'none'; 3115 } 3116 3117 if (!LOGGED_IN) { 3118 showGuest(); 3119 } else { 3120 var state = readState(); 3121 showForm(); 3122 if (state && state.pair && state.pair.id) { 3123 fetch('/api/duishi/me/latest?id=' + encodeURIComponent(state.pair.id)) 3124 .then(function(r){ return r.json(); }) 3125 .then(function(data){ 3126 if (!data || !data.ok) { return; } 3127 var current = readState(); 3128 if (!current || !current.pair || current.pair.id !== state.pair.id) return; 3129 if (data.pair) { 3130 var next = {side: state.side, pair: data.pair}; 3131 writeState(next); 3132 applyState(next); 3133 } else { 3134 // ç¼åçè®°å½å·²éèï¼æä¸å±äºå½åç»å½ç¨æ·ã 3135 clearState(); 3136 showForm(); 3137 } 3138 }).catch(function(){}); 3139 } 3140 inputEl.addEventListener('keydown', function(e) { 3141 if (e.key === 'Enter') { window.duishiSubmit(); } 3142 }); 3143 } 3144})(); 3145 3146// ---- æè¿å¯¹è¯ ---- 3147(function(){ 3148 var card = document.getElementById('duishi-recent-card'); 3149 if (!card) return; 3150 3151 var upEl = document.getElementById('duishi-recent-up'); 3152 var detailEl = document.getElementById('duishi-recent-detail'); 3153 var currentId = null; 3154 var expanded = false; 3155 3156 function render(pair) { 3157 if (!pair) { 3158 currentId = null; 3159 upEl.textContent = 'è¿æ²¡æäººå¯¹ä¸ï¼æ¥å第ä¸ä¸ªå§'; 3160 detailEl.style.display = 'none'; 3161 return; 3162 } 3163 currentId = pair.id; 3164 expanded = false; 3165 upEl.textContent = pair.upText + 'â¦â¦'; 3166 detailEl.style.display = 'none'; 3167 window.duishiFillPair('duishi-recent', pair); 3168 } 3169 3170 window.refreshDuishiRecent = function() { 3171 var url = '/api/duishi/recent' + (currentId ? '?exclude=' + encodeURIComponent(currentId) : ''); 3172 fetch(url).then(function(r){ return r.json(); }).then(function(data){ 3173 if (data && data.ok) { render(data.pair); } 3174 }).catch(function(){}); 3175 }; 3176 3177 card.addEventListener('click', function(e){ 3178 if (e.target.closest('button') || e.target.closest('a')) return; 3179 if (!currentId) return; 3180 expanded = !expanded; 3181 detailEl.style.display = expanded ? '' : 'none'; 3182 if (expanded) { 3183 trackEvent('card_expand', 'duishi_recent'); 3184 fetch('/api/duishi/record_view', { 3185 method: 'POST', 3186 headers: {'Content-Type': 'application/json'}, 3187 body: JSON.stringify({id: currentId}) 3188 }).catch(function(){}); 3189 } 3190 }); 3191 3192 refreshDuishiRecent(); 3193})(); 3194</script>
3194 3195 3196 3197</body> 3198</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.