Demo
@@ -0,0 +1,555 @@
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Noto Sans SC',sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;background:#EDEBE6;color:#3A3632;overflow-x:hidden}
|
||||
/* 网格噪点背景纹理 — 模拟纸张/教科书的微质感 */
|
||||
body::before{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;background-image:radial-gradient(circle,#C8C4BD .5px,transparent .5px);background-size:18px 18px;opacity:.35}
|
||||
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#C8C4BD;border-radius:3px}
|
||||
.app-wrapper{max-width:1440px;margin:0 auto;height:100vh;display:flex;flex-direction:column;position:relative;background:#F7F5F1}
|
||||
.mobile-topbar{display:none;align-items:center;padding:12px 16px;background:#FDFCFA;border-bottom:1px solid #D6D1CA;z-index:30}
|
||||
.mobile-topbar .hamburger{width:36px;height:36px;border-radius:8px;border:1px solid #D6D1CA;background:#FDFCFA;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:18px;color:#8A8580}
|
||||
.mobile-topbar .topbar-logo{display:flex;align-items:center;gap:8px;margin-left:12px}
|
||||
.mobile-topbar .topbar-logo-icon{width:32px;height:32px;border-radius:8px;background:linear-gradient(135deg,#808C95,#9AA5AE);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:16px;color:#fff}
|
||||
.mobile-topbar .topbar-logo-text{font-size:16px;font-weight:700;color:#3A3632}
|
||||
.app-layout{display:flex;flex:1;overflow:hidden}
|
||||
|
||||
/* Sidebar — 直角 + 细线 */
|
||||
.sidebar{width:240px;min-width:240px;background:#FDFCFA;border-right:1px solid #D6D1CA;display:flex;flex-direction:column;z-index:20;transition:transform .3s;border-radius:0}
|
||||
.sidebar-logo{padding:20px 20px 16px;display:flex;align-items:center;gap:12px}
|
||||
.sidebar-logo-icon{width:42px;height:42px;border-radius:8px;background:linear-gradient(135deg,#808C95,#9AA5AE);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:20px;color:#fff}
|
||||
.sidebar-logo-text{font-size:18px;font-weight:700;color:#3A3632}
|
||||
.sidebar-search{padding:0 16px 12px}
|
||||
.sidebar-search input{width:100%;padding:8px 12px 8px 32px;border-radius:6px;border:1px solid #D6D1CA;font-size:12px;outline:none;background:#F7F5F1 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='%23ABA6A1'%3E%3Cpath d='M10.5 9.5l3 3-1.5 1.5-3-3A5.5 5.5 0 117 12.5a5.5 5.5 0 013.5-3z'/%3E%3C/svg%3E") 10px center no-repeat;transition:border-color .2s}
|
||||
.sidebar-search input:focus{border-color:#808C95;background-color:#FDFCFA}
|
||||
.sidebar-nav{flex:1;padding:8px 12px;display:flex;flex-direction:column;gap:3px;overflow-y:auto}
|
||||
.nav-item{display:flex;align-items:center;gap:12px;padding:10px 14px;border-radius:6px;cursor:pointer;font-size:14px;font-weight:500;color:#8A8580;transition:all .15s;border:none;background:none;width:100%;text-align:left}
|
||||
.nav-item:hover{background:#EAE7E2;color:#808C95}
|
||||
.nav-item.active{background:#E6E9EC;color:#657078;font-weight:600}
|
||||
.nav-item .nav-icon{font-size:17px;width:24px;text-align:center;flex-shrink:0}
|
||||
.sidebar-footer{padding:16px;border-top:1px solid #EAE7E2;display:flex;align-items:center;gap:10px}
|
||||
.sidebar-avatar{width:36px;height:36px;border-radius:50% 50% 50% 50% / 60% 60% 40% 40%;box-shadow:inset 0 -3px 0 rgba(0,0,0,.1);display:flex;align-items:center;justify-content:center;font-size:16px;color:#fff;font-weight:700;flex-shrink:0}
|
||||
.sidebar-user-name{font-size:13px;font-weight:600;color:#3A3632}
|
||||
.sidebar-user-id{font-size:11px;color:#ABA6A1}
|
||||
|
||||
/* Main */
|
||||
.main-content{flex:1;overflow-y:auto;padding:28px 36px}
|
||||
.main-content-inner{max-width:1000px}
|
||||
.page-header{margin-bottom:24px}
|
||||
.page-title{font-size:24px;font-weight:700;color:#3A3632}
|
||||
.page-subtitle{font-size:13px;color:#ABA6A1;margin-top:4px}
|
||||
.page{display:none}.page.active{display:block}
|
||||
|
||||
/* Cards — 8px圆角 + 纯细线无阴影 */
|
||||
.video-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
|
||||
@media(max-width:1200px){.video-grid{grid-template-columns:repeat(2,1fr)}}
|
||||
@media(max-width:640px){.video-grid{grid-template-columns:1fr}}
|
||||
.video-card{background:#FDFCFA;border-radius:8px;overflow:hidden;cursor:pointer;transition:all .15s;border:1px solid #D6D1CA}
|
||||
.video-card:hover{border-color:#808C95;background:#F0EEEA}
|
||||
.video-card-cover{aspect-ratio:16/9;display:flex;align-items:center;justify-content:center;font-size:36px;position:relative}
|
||||
.video-card-duration{position:absolute;bottom:8px;right:8px;background:rgba(58,54,50,.65);color:#fff;font-size:11px;padding:2px 8px;border-radius:4px;font-weight:500}
|
||||
.video-card-body{padding:14px}
|
||||
.video-card-title{font-size:14px;font-weight:600;line-height:1.4;color:#3A3632;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin-bottom:8px}
|
||||
.video-card-meta{display:flex;align-items:center;gap:6px;font-size:12px;color:#ABA6A1}
|
||||
.video-card-tag{font-size:11px;padding:3px 8px;border-radius:4px;background:#EAE7E2;color:#808C95;font-weight:500}
|
||||
.video-card-author{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.knowledge-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
|
||||
@media(max-width:640px){.knowledge-grid{grid-template-columns:1fr}}
|
||||
.knowledge-card{background:#FDFCFA;border-radius:8px;overflow:hidden;cursor:pointer;transition:all .15s;border:1px solid #D6D1CA}
|
||||
.knowledge-card:hover{border-color:#808C95}
|
||||
.knowledge-card-visual{height:120px;display:flex;align-items:center;justify-content:center;font-size:42px;position:relative;overflow:hidden}
|
||||
.knowledge-card-question{position:absolute;bottom:12px;left:16px;right:16px;font-size:14px;font-weight:700;color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.3);line-height:1.3}
|
||||
.knowledge-card-body{padding:14px}
|
||||
.knowledge-card-title{font-size:15px;font-weight:700;color:#3A3632;margin-bottom:6px}
|
||||
.knowledge-card-desc{font-size:12px;color:#8A8580;line-height:1.5;margin-bottom:8px}
|
||||
.knowledge-card-time{font-size:11px;color:#ABA6A1}
|
||||
|
||||
/* Story/Sim */
|
||||
.story-container,.sim-container{background:#FDFCFA;border-radius:8px;padding:32px;border:1px solid #D6D1CA;min-height:400px}
|
||||
.story-stats{display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap}
|
||||
.story-stat{font-size:13px;padding:6px 14px;border-radius:8px;color:#fff;font-weight:600}
|
||||
.story-text{font-size:15px;line-height:1.8;color:#5A5550;min-height:120px;white-space:pre-line}
|
||||
.story-character{font-weight:700;color:#808C95;margin-bottom:8px;font-size:14px}
|
||||
.story-choices{display:flex;flex-direction:column;gap:10px;margin-top:20px}
|
||||
.story-choice{background:#F7F5F1;border:1px solid #D6D1CA;border-radius:8px;padding:14px 18px;font-size:14px;font-weight:600;cursor:pointer;transition:all .15s;text-align:left;color:#3A3632}
|
||||
.story-choice:hover{border-color:#808C95;background:#EAE7E2}
|
||||
.sim-stats{display:flex;gap:16px;margin-bottom:20px}
|
||||
.sim-stat{flex:1;background:#F7F5F1;border:1px solid #EAE7E2;border-radius:8px;padding:14px;text-align:center}
|
||||
.sim-stat-val{font-size:28px;font-weight:800;color:#3A3632}
|
||||
.sim-stat-label{font-size:11px;color:#8A8580;margin-top:4px}
|
||||
|
||||
/* Filters */
|
||||
.filter-bar{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px}
|
||||
.filter-btn{padding:7px 14px;border-radius:6px;border:1px solid #D6D1CA;background:#FDFCFA;font-size:13px;font-weight:500;color:#8A8580;cursor:pointer;transition:all .15s;white-space:nowrap}
|
||||
.filter-btn:hover{border-color:#808C95;color:#808C95}
|
||||
.filter-btn.active{background:#808C95;border-color:#808C95;color:#fff;font-weight:600}
|
||||
|
||||
/* Search results */
|
||||
.search-results{display:none;position:absolute;top:100%;left:0;right:0;background:#FDFCFA;border:1px solid #D6D1CA;border-radius:6px;z-index:40;max-height:300px;overflow-y:auto;margin-top:4px}
|
||||
.search-results.show{display:block}
|
||||
.search-result-item{padding:10px 16px;cursor:pointer;border-bottom:1px solid #EAE7E2;font-size:13px;display:flex;align-items:center;gap:8px;color:#3A3632}
|
||||
.search-result-item:hover{background:#EAE7E2}
|
||||
.search-result-item:last-child{border-bottom:none}
|
||||
.search-result-type{font-size:10px;padding:2px 6px;border-radius:3px;background:#E6E9EC;color:#657078;font-weight:600;flex-shrink:0}
|
||||
|
||||
/* AI Toggle + Float merge — 合并为一个扇形展开按钮 */
|
||||
.float-hub{position:fixed;bottom:24px;right:24px;z-index:25;display:flex;flex-direction:column;align-items:flex-end;gap:8px}
|
||||
.float-hub-btn{width:48px;height:48px;border-radius:50%;border:none;background:#808C95;color:#fff;font-size:20px;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center}
|
||||
.float-hub-btn:hover{background:#657078;transform:scale(1.05)}
|
||||
.float-hub-expand{display:none;flex-direction:column;align-items:flex-end;gap:8px}
|
||||
.float-hub.open .float-hub-expand{display:flex}
|
||||
.float-hub-child{width:44px;height:44px;border-radius:50%;border:none;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;transition:all .2s;position:relative;text-decoration:none}
|
||||
.float-hub-child:hover{transform:scale(1.08)}
|
||||
.float-hub-child.ai{background:#A8907E;color:#fff}
|
||||
.float-hub-child.mbti{background:linear-gradient(135deg,#8A9BA8,#A0B1BD);color:#fff}
|
||||
.float-hub-label{position:absolute;right:52px;white-space:nowrap;font-size:11px;font-weight:600;color:#8A8580;background:#FDFCFA;border:1px solid #D6D1CA;padding:4px 10px;border-radius:4px;pointer-events:none;opacity:0;transition:opacity .2s}
|
||||
.float-hub-child:hover .float-hub-label{opacity:1}
|
||||
|
||||
/* AI Panel */
|
||||
.ai-panel{position:fixed;top:0;right:0;bottom:0;width:360px;background:#FDFCFA;border-left:1px solid #D6D1CA;z-index:30;transform:translateX(100%);transition:transform .3s;display:flex;flex-direction:column}
|
||||
.ai-panel.open{transform:translateX(0)}
|
||||
.ai-panel-header{padding:16px;border-bottom:1px solid #EAE7E2;display:flex;align-items:center;justify-content:space-between;font-weight:600;font-size:14px;color:#3A3632}
|
||||
.ai-panel-close{width:32px;height:32px;border-radius:50%;border:none;background:#EAE7E2;cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;color:#8A8580}
|
||||
.ai-panel-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}
|
||||
.ai-panel-input-area{padding:12px 16px;border-top:1px solid #EAE7E2;display:flex;gap:8px}
|
||||
.ai-panel-input-area input{flex:1;padding:10px 14px;border-radius:8px;border:1px solid #D6D1CA;font-size:13px;outline:none;background:#F7F5F1}
|
||||
.ai-panel-input-area input:focus{border-color:#808C95}
|
||||
.ai-send-btn{min-width:40px;height:40px;border-radius:50%;border:none;background:#808C95;color:#fff;font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center}
|
||||
|
||||
.chat-msg{display:flex;animation:fadeIn .3s ease}
|
||||
@keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
|
||||
.chat-msg.assistant{justify-content:flex-start}.chat-msg.user{justify-content:flex-end}
|
||||
.chat-bubble{max-width:85%;padding:10px 14px;border-radius:8px;font-size:13px;line-height:1.6;word-break:break-word}
|
||||
.chat-msg.assistant .chat-bubble{background:#EAE7E2;color:#3A3632;border-bottom-left-radius:2px}
|
||||
.chat-msg.user .chat-bubble{background:#808C95;color:#fff;border-bottom-right-radius:2px}
|
||||
|
||||
/* Modal — 16px圆角 */
|
||||
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(58,54,50,.4);z-index:100;justify-content:center;align-items:center}
|
||||
.modal-overlay.show{display:flex}
|
||||
.modal-content{background:#FDFCFA;border-radius:16px;padding:28px;max-width:560px;width:90vw;max-height:80vh;overflow-y:auto;border:1px solid #D6D1CA;animation:modalIn .2s ease}
|
||||
@keyframes modalIn{from{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}
|
||||
.modal-close-btn{position:absolute;top:16px;right:16px;width:32px;height:32px;border-radius:50%;border:none;background:#EAE7E2;cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;color:#8A8580}
|
||||
|
||||
.fav-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
|
||||
@media(max-width:1200px){.fav-grid{grid-template-columns:repeat(2,1fr)}}
|
||||
@media(max-width:640px){.fav-grid{grid-template-columns:1fr}}
|
||||
.fav-card{background:#FDFCFA;border-radius:8px;padding:16px;border:1px solid #D6D1CA;position:relative}
|
||||
.fav-card-remove{position:absolute;top:10px;right:10px;width:28px;height:28px;border-radius:50%;border:none;background:#F5EAEA;color:#C49595;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center}
|
||||
|
||||
.spinner{width:32px;height:32px;border:3px solid #EAE7E2;border-top-color:#808C95;border-radius:50%;animation:spin .7s linear infinite;margin:40px auto}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
.section-title{font-size:16px;font-weight:700;margin-bottom:16px;color:#3A3632}
|
||||
|
||||
/* Banner */
|
||||
.banner{background:linear-gradient(135deg,#808C95,#9AA5AE);border-radius:8px;padding:28px 32px;color:#fff;margin-bottom:24px;position:relative;overflow:hidden}
|
||||
.banner::after{content:"";position:absolute;top:-30px;right:-30px;width:120px;height:120px;border-radius:50%;background:rgba(255,255,255,.08)}
|
||||
.banner-title{font-size:22px;font-weight:800;margin-bottom:8px;position:relative;z-index:1}
|
||||
.banner-text{font-size:14px;opacity:.85;position:relative;z-index:1}
|
||||
|
||||
/* ========== Desmos-style Home Page ========== */
|
||||
.ds-hero{text-align:center;padding:40px 20px 20px}
|
||||
.ds-hero-title{font-size:28px;font-weight:800;color:#3A3632;margin-bottom:8px;letter-spacing:-0.3px}
|
||||
.ds-hero-sub{font-size:14px;color:#8A8580;margin-bottom:20px}
|
||||
.ds-hero-canvas{display:block;margin:0 auto 20px;border-radius:8px;background:#FDFCFA;border:1px solid #D6D1CA;max-width:100%;height:auto}
|
||||
.ds-hero-cta{padding:12px 32px;border-radius:8px;border:none;background:#A8907E;color:#fff;font-size:15px;font-weight:700;cursor:pointer;transition:all .15s}
|
||||
.ds-hero-cta:hover{background:#927A6A;transform:translateY(-1px)}
|
||||
.ds-section-title{font-size:18px;font-weight:700;color:#3A3632;margin:28px 0 16px}
|
||||
.ds-card-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:8px}
|
||||
.ds-card-grid-3{grid-template-columns:repeat(3,1fr)}
|
||||
@media(max-width:640px){.ds-card-grid,.ds-card-grid-3{grid-template-columns:1fr}}
|
||||
.ds-feature-card{background:#FDFCFA;border-radius:8px;padding:24px;border:1px solid #D6D1CA;cursor:pointer;transition:all .15s;text-align:center}
|
||||
.ds-feature-card:hover{border-color:#808C95;background:#F0EEEA}
|
||||
.ds-feature-card-icon{font-size:36px;margin-bottom:10px}
|
||||
.ds-feature-card-name{font-size:15px;font-weight:700;color:#3A3632;margin-bottom:4px}
|
||||
.ds-feature-card-desc{font-size:12px;color:#8A8580;line-height:1.5}
|
||||
.ds-footer-note{padding:24px 0 8px;text-align:center;font-size:12px;color:#ABA6A1;border-top:1px solid #EAE7E2;margin-top:24px}
|
||||
|
||||
/* ========== Math Life Hub ========== */
|
||||
.ml-theme-grid{margin-bottom:28px}
|
||||
.ml-theme-card{position:relative}
|
||||
.ml-theme-card.ml-theme-coming{cursor:default;opacity:.72}
|
||||
.ml-theme-card.ml-theme-coming:hover{background:#FDFCFA;border-color:#D6D1CA}
|
||||
.ml-coming-badge{display:inline-block;margin-top:10px;padding:3px 10px;border-radius:999px;background:#EAE7E2;color:#8A8580;font-size:11px;font-weight:600}
|
||||
.ml-more{margin-top:28px;padding:22px 0 8px;border-top:1px solid #EAE7E2}
|
||||
.ml-more-title{font-size:16px;font-weight:700;color:#3A3632;margin-bottom:6px}
|
||||
.ml-more-sub{font-size:12px;color:#8A8580;margin-bottom:16px}
|
||||
.ml-more-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
|
||||
@media(max-width:900px){.ml-more-grid{grid-template-columns:repeat(2,1fr)}}
|
||||
@media(max-width:640px){.ml-more-grid{grid-template-columns:1fr}}
|
||||
.ml-more-card{border:1px dashed #D6D1CA;border-radius:8px;padding:14px;text-align:center;background:#FDFCFA}
|
||||
.ml-more-icon{font-size:26px;margin-bottom:6px}
|
||||
.ml-more-name{font-size:13px;font-weight:600;color:#3A3632;margin-bottom:6px}
|
||||
.ml-more-tag{font-size:11px;color:#ABA6A1}
|
||||
.ml-player-header{display:flex;align-items:center;gap:12px;margin-bottom:18px;flex-wrap:wrap}
|
||||
.ml-back-btn{padding:8px 14px;border:1px solid #D6D1CA;border-radius:6px;background:#FDFCFA;color:#657078;font-size:12px;cursor:pointer}
|
||||
.ml-back-btn:hover{background:#EAE7E2}
|
||||
.ml-player-title{font-size:18px;font-weight:700;color:#3A3632}
|
||||
.ml-player-sub{font-size:12px;color:#8A8580;flex:1;min-width:180px}
|
||||
.ml-stats{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:16px}
|
||||
.ml-stat{display:flex;align-items:center;gap:8px;background:#F7F5F1;border:1px solid #EAE7E2;border-radius:8px;padding:8px 12px;font-size:12px}
|
||||
.ml-stat b{font-size:16px}
|
||||
.ml-stat span{color:#8A8580}
|
||||
.ml-stat-icon{width:26px;height:26px;border-radius:6px;color:#fff;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;flex-shrink:0}
|
||||
.stats5{margin-top:12px;padding:10px 12px;background:#F7F5F1;border:1px solid #EAE7E2;border-radius:8px;text-align:left}
|
||||
.stats5-row{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:10px}
|
||||
.stats5-row:last-child{margin-bottom:0}
|
||||
.stats5-row span{width:32px;color:#8A8580;flex-shrink:0}
|
||||
.stats5-track{flex:1;height:5px;background:#E8E5E0;border-radius:999px;overflow:hidden}
|
||||
.stats5-track i{display:block;height:100%;background:linear-gradient(90deg,#A8907E,#C47F3A);border-radius:999px}
|
||||
.stats5-row b{width:24px;text-align:right;color:#3A3632;font-weight:700}
|
||||
.ml-more-desc{font-size:11px;color:#8A8580;line-height:1.4;margin-bottom:6px;padding:0 6px}
|
||||
.ml-alumni{margin-top:28px;padding:22px 0 8px;border-top:1px solid #EAE7E2}
|
||||
.ml-alumni-head{display:flex;justify-content:space-between;align-items:flex-start;gap:14px;flex-wrap:wrap;margin-bottom:16px}
|
||||
.ml-alumni-title{font-size:16px;font-weight:700;color:#3A3632;margin-bottom:6px}
|
||||
.ml-alumni-sub{font-size:12px;color:#8A8580}
|
||||
.ml-alumni-preview-btn{padding:8px 16px;border-radius:999px;border:1.5px solid #A8907E;background:#FDFCFA;color:#A8907E;font-size:12px;font-weight:700;cursor:pointer;transition:all .15s}
|
||||
.ml-alumni-preview-btn:hover{background:#A8907E;color:#fff}
|
||||
.ml-alumni-entry{display:flex;align-items:center;gap:14px;width:100%;margin-top:26px;padding:18px 20px;border:1.5px solid #A8907E;border-radius:8px;background:#FDFCFA;cursor:pointer;transition:all .15s;font:inherit;text-align:left}
|
||||
.ml-alumni-entry:hover{background:#FBF8F3;box-shadow:0 4px 14px rgba(0,0,0,.06)}
|
||||
.ml-alumni-entry-icon{width:46px;height:46px;border-radius:50%;background:#EAE7E2;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0}
|
||||
.ml-alumni-entry-body{flex:1;min-width:0}
|
||||
.ml-alumni-entry-body b{display:block;font-size:15px;color:#3A3632;margin-bottom:3px}
|
||||
.ml-alumni-entry-body small{font-size:12px;color:#8A8580}
|
||||
.ml-alumni-entry-arrow{font-size:18px;color:#A8907E;font-weight:700}
|
||||
.ml-alumni-list-head{margin:18px 0 14px}
|
||||
#alumniCards{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
|
||||
.ml-alumni-card{display:flex;flex-direction:column;align-items:center;gap:6px;width:100%;padding:16px 12px;border:1px solid #D6D1CA;border-radius:8px;background:#FDFCFA;cursor:pointer;text-align:center;transition:all .15s;font:inherit}
|
||||
.ml-alumni-card:hover{border-color:#A8907E;box-shadow:0 4px 14px rgba(0,0,0,.07);transform:translateY(-2px)}
|
||||
.ml-alumni-card .ml-alumni-avatar{width:46px;height:46px;font-size:22px}
|
||||
.ml-alumni-skill-tags{display:flex;flex-wrap:wrap;gap:4px;justify-content:center}
|
||||
.ml-alumni-skill-tag{font-size:10px;padding:2px 7px;border-radius:999px;background:#F0EDE8;color:#8A8580;font-weight:600}
|
||||
.ml-alumni-dim-row{display:flex;gap:6px;font-size:10px;color:#8A8580;flex-wrap:wrap;justify-content:center}
|
||||
.ml-alumni-card-cta{font-size:11px;font-weight:700;color:#A8907E;margin-top:4px}
|
||||
.ml-story-linear{max-width:720px;margin:0 auto;padding:18px 0}
|
||||
.ml-story-progress{height:6px;background:#EAE7E2;border-radius:999px;overflow:hidden;margin-bottom:18px}
|
||||
.ml-story-progress i{display:block;height:100%;background:linear-gradient(90deg,#A8907E,#C47F3A);border-radius:999px;transition:width .3s}
|
||||
.ml-story-stage-name{font-size:15px;font-weight:700;color:#3A3632;margin-bottom:12px}
|
||||
.ml-story-scene{font-size:15px;line-height:1.9;color:#3A3632;background:#F7F5F1;border:1px solid #EAE7E2;border-radius:8px;padding:18px 20px;margin-bottom:14px}
|
||||
.ml-story-effect{font-size:12px;color:#8A8580;background:#FBF8F3;border:1px solid #EAE7E2;border-radius:8px;padding:10px 14px;margin-bottom:14px}
|
||||
.ml-story-next{display:block;margin-left:auto;padding:10px 20px;border-radius:8px;border:1.5px solid #A8907E;background:#A8907E;color:#fff;font-size:13px;font-weight:700;cursor:pointer}
|
||||
.ml-story-next:hover{background:#9A7D69}
|
||||
.ml-story-ending{font-size:14px;color:#3A3632;background:#F7F5F1;border:1px dashed #D6D1CA;border-radius:8px;padding:18px;margin-bottom:14px;line-height:1.8}
|
||||
.ml-story-timeline{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:16px}
|
||||
.ml-story-stage{background:#F7F5F1;border:1px solid #EAE7E2;border-radius:8px;padding:14px;text-align:center}
|
||||
.ml-story-stage span{width:24px;height:24px;border-radius:50%;background:#A8907E;color:#fff;font-size:12px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;margin-bottom:6px}
|
||||
.ml-story-stage b{display:block;font-size:13px;color:#3A3632;margin-bottom:4px}
|
||||
.ml-story-stage small{font-size:10px;color:#ABA6A1}
|
||||
.ml-alumni-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}
|
||||
.ml-alumni-slot{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;border:1px dashed #D6D1CA;border-radius:8px;padding:16px 10px;background:#FDFCFA;text-align:center;min-height:122px}
|
||||
.ml-alumni-slot.join{cursor:pointer;color:#8A8580;font:inherit}
|
||||
.ml-alumni-slot.join:hover{border-color:#A8907E;color:#A8907E;background:#FBF8F3}
|
||||
.ml-alumni-avatar{width:40px;height:40px;border-radius:50%;background:#EAE7E2;display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
|
||||
.ml-alumni-code{font-size:10px;color:#ABA6A1;letter-spacing:.5px}
|
||||
.ml-alumni-name{font-size:13px;font-weight:600;color:#3A3632}
|
||||
.ml-alumni-meta{font-size:10px;color:#ABA6A1;line-height:1.4}
|
||||
.ml-alumni-pipeline{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
|
||||
.ml-pipeline-step{display:flex;align-items:center;gap:8px;background:#F7F5F1;border:1px solid #EAE7E2;border-radius:8px;padding:10px 12px;font-size:12px;color:#8A8580}
|
||||
.ml-pipeline-step span{width:22px;height:22px;border-radius:50%;background:#A8907E;color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||||
.ml-pipeline-step b{font-size:12px;color:#3A3632}
|
||||
.ml-alumni-scaffold{padding:20px 0}
|
||||
.ml-scaffold-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px}
|
||||
.ml-alumni-empty{border:1px dashed #D6D1CA;border-radius:8px;padding:28px;text-align:center;color:#8A8580;font-size:13px;background:#FDFCFA}
|
||||
.ml-alumni-draft{display:flex;align-items:center;gap:10px;border:1px solid #EAE7E2;border-radius:8px;padding:12px 14px;background:#FDFCFA;margin-bottom:10px;font-size:13px;color:#3A3632}
|
||||
@media(max-width:900px){.ml-alumni-grid{grid-template-columns:repeat(2,1fr)}.ml-alumni-pipeline{grid-template-columns:repeat(2,1fr)}.ml-scaffold-grid{grid-template-columns:repeat(2,1fr)}#alumniCards{grid-template-columns:repeat(2,1fr)}}
|
||||
@media(max-width:560px){.ml-alumni-grid{grid-template-columns:1fr 1fr}.ml-alumni-slot{min-height:108px;padding:12px 8px}#alumniCards{grid-template-columns:1fr 1fr}.ml-story-timeline{grid-template-columns:repeat(2,1fr)}}
|
||||
|
||||
/* ========== Numberphile-style Discover Page ========== */
|
||||
.np-greeting{font-size:14px;color:#8A8580;margin-bottom:24px;line-height:1.6}
|
||||
.np-video-list{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
|
||||
@media(max-width:1200px){.np-video-list{grid-template-columns:repeat(2,1fr)}}
|
||||
@media(max-width:640px){.np-video-list{grid-template-columns:1fr;gap:16px}}
|
||||
.np-card{background:#FDFCFA;border-radius:8px;overflow:hidden;border:1px solid #D6D1CA;cursor:pointer;transition:all .15s;display:flex;flex-direction:column}
|
||||
.np-card:hover{border-color:#808C95}
|
||||
.np-cover{position:relative;aspect-ratio:16/9;overflow:hidden;border-radius:8px 8px 0 0}
|
||||
.np-cover-inner{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;transition:transform .35s ease}
|
||||
.np-cover-icon{font-size:40px;opacity:.3}
|
||||
.np-cover-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
||||
.np-card:hover .np-cover-inner{transform:scale(1.05)}
|
||||
.np-cover-duration{position:absolute;top:8px;left:8px;background:rgba(58,54,50,.55);backdrop-filter:blur(4px);color:#fff;font-size:11px;font-weight:500;padding:2px 8px;border-radius:4px;letter-spacing:.3px;z-index:2}
|
||||
.np-cover-play{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:48px;height:48px;border-radius:50%;background:rgba(58,54,50,.4);backdrop-filter:blur(4px);color:#fff;font-size:16px;display:flex;align-items:center;justify-content:center;padding-left:3px;opacity:0;transition:opacity .25s;pointer-events:none;z-index:2}
|
||||
.np-card:hover .np-cover-play{opacity:1}
|
||||
.np-cover-fav{position:absolute;top:8px;right:8px;width:30px;height:30px;border-radius:50%;border:none;background:rgba(58,54,50,.3);backdrop-filter:blur(4px);font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s;z-index:2}
|
||||
.np-card:hover .np-cover-fav,.np-cover-fav.faved{opacity:1}
|
||||
.np-body{padding:12px 14px 14px;display:flex;flex-direction:column;flex:1}
|
||||
.np-card-title{font-size:14px;font-weight:600;line-height:1.45;color:#3A3632;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin-bottom:8px;min-height:40px}
|
||||
.np-stats{display:flex;align-items:center;justify-content:space-between;font-size:12px;color:#ABA6A1;margin-bottom:8px}
|
||||
.np-stat{display:flex;align-items:center;gap:4px}
|
||||
.np-uploader{display:flex;align-items:center;gap:8px;margin-top:auto}
|
||||
.np-up-avatar{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;color:#fff;font-weight:600;flex-shrink:0}
|
||||
.np-up-name{font-size:12px;color:#8A8580;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.np-load-hint{grid-column:1/-1;text-align:center;font-size:12px;color:#ABA6A1;padding:16px 0 4px}
|
||||
|
||||
/* ========== Five-Dimension Ability Map ========== */
|
||||
#dimMap{margin-bottom:18px}
|
||||
.dim-map-head{display:flex;justify-content:space-between;align-items:flex-start;gap:14px;flex-wrap:wrap;margin-bottom:14px}
|
||||
.dim-map-title{font-size:18px;font-weight:700;color:#3A3632}
|
||||
.dim-map-sub{font-size:12px;color:#8A8580;margin-top:4px}
|
||||
.dim-map-legend{display:flex;flex-wrap:wrap;gap:8px;padding-top:2px}
|
||||
.dim-map-legend-item{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:#8A8580}
|
||||
.dim-map-legend-item i{width:8px;height:8px;border-radius:50%;background:var(--dim-color)}
|
||||
.dim-map-board{position:relative;height:360px;background:linear-gradient(rgba(58,54,50,.045) 1px,transparent 1px),linear-gradient(90deg,rgba(58,54,50,.045) 1px,transparent 1px),#F7F5F1;background-size:40px 40px;border:1px dashed #D6D1CA;border-radius:8px;overflow:hidden}
|
||||
.dim-map-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
|
||||
.dim-map-svg line{stroke:#C8C4BD;stroke-width:.45;stroke-dasharray:2.5 3}
|
||||
.dim-map-svg .dim-map-spoke{stroke:#D6D1CA;stroke-width:.3}
|
||||
.dim-map-center{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:76px;height:76px;border-radius:50%;background:#FDFCFA;border:1.5px solid #A8907E;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;color:#3A3632;font-size:11px;box-shadow:0 3px 10px rgba(0,0,0,.08);z-index:2}
|
||||
.dim-map-gourd{width:30px;height:42px;border-radius:15px 15px 16px 16px / 20px 20px 24px 24px;background:linear-gradient(160deg,#8A9BA8,#65707A);color:#fff;font-size:16px;font-weight:800;display:flex;align-items:center;justify-content:center}
|
||||
.dim-map-node{position:absolute;transform:translate(-50%,-50%);min-width:104px;padding:10px 12px;border-radius:8px;background:#FDFCFA;border:1.5px solid var(--dim-color);box-shadow:0 3px 10px rgba(0,0,0,.06);cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:3px;transition:all .18s;z-index:3}
|
||||
.dim-map-node:hover,.dim-map-node.active{transform:translate(-50%,-50%) scale(1.06);box-shadow:0 6px 18px rgba(58,54,50,.14)}
|
||||
.dim-node-icon{font-size:22px;line-height:1}
|
||||
.dim-node-name{font-size:13px;font-weight:700;color:#3A3632;white-space:nowrap}
|
||||
.dim-node-value{font-size:12px;font-weight:800;color:var(--dim-color);min-width:30px;height:20px;border-radius:10px;background:rgba(58,54,50,.06);display:flex;align-items:center;justify-content:center}
|
||||
.dim-map-foot{text-align:center;font-size:11px;color:#ABA6A1;margin-top:10px}
|
||||
.dim-tabs{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}
|
||||
.dim-tab{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:999px;border:1.5px solid #D6D1CA;background:#FDFCFA;color:#3A3632;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s}
|
||||
.dim-tab:hover{border-color:var(--dim-color,#A8907E);color:var(--dim-color,#A8907E)}
|
||||
.dim-tab.active{background:var(--dim-color,#A8907E);border-color:var(--dim-color,#A8907E);color:#fff}
|
||||
.dim-tab span{font-size:11px;opacity:.78;font-weight:700}
|
||||
.np-dim-badge{display:inline-flex;align-items:center;gap:4px;width:fit-content;font-size:11px;font-weight:700;color:var(--dim-color);background:rgba(58,54,50,.05);border:1px solid rgba(58,54,50,.12);border-radius:6px;padding:3px 8px;margin-bottom:8px}
|
||||
.video-modal-top{margin-bottom:10px}
|
||||
.video-modal-dim{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:700;color:var(--dim-color);background:rgba(58,54,50,.05);border:1px solid rgba(58,54,50,.12);border-radius:6px;padding:3px 8px;margin-bottom:8px}
|
||||
.video-modal-title{font-size:18px;font-weight:700;color:#3A3632;line-height:1.4}
|
||||
.video-modal-player{aspect-ratio:16/9;background:#111;border-radius:8px;margin:12px 0;overflow:hidden;display:flex;align-items:center;justify-content:center}
|
||||
.video-modal-player iframe{width:100%;height:100%;border:0}
|
||||
.video-modal-player.empty{background:#F1F5F9;color:#CBD5E1;font-size:48px}
|
||||
.video-modal-desc{font-size:14px;color:#64748B;line-height:1.7;margin-bottom:8px}
|
||||
.video-modal-meta{display:flex;gap:8px;flex-wrap:wrap;font-size:12px;color:#94A3B8}
|
||||
.video-claim-box{margin-top:14px;padding-top:14px;border-top:1px solid #EAE7E2}
|
||||
.video-claim-btn{width:100%;padding:12px;border-radius:8px;border:1.5px solid var(--dim-color,#5B6AF0);background:var(--dim-color,#5B6AF0);color:#fff;font-size:14px;font-weight:700;cursor:pointer}
|
||||
.video-claim-btn:hover{opacity:.9}
|
||||
.video-claim-btn.claimed{background:#E8E5E0;border-color:#D6D1CA;color:#8A8580;cursor:default;opacity:1}
|
||||
.video-claim-hint{font-size:12px;color:#ABA6A1;line-height:1.6}
|
||||
@media(max-width:760px){
|
||||
.dim-map-board{height:430px}
|
||||
.dim-map-node{min-width:76px;padding:6px}
|
||||
.dim-node-name{font-size:11px}
|
||||
.dim-node-value{font-size:10px;height:18px;min-width:24px}
|
||||
.dim-map-legend{display:none}
|
||||
}
|
||||
|
||||
/* Profile */
|
||||
.profile-card{background:linear-gradient(135deg,#808C95,#9AA5AE);border-radius:8px;padding:28px;color:#fff;display:flex;align-items:center;gap:16px;margin-bottom:20px;flex-wrap:wrap}
|
||||
.profile-avatar-lg{width:72px;height:72px;border-radius:50% 50% 50% 50% / 60% 60% 40% 40%;box-shadow:inset 0 -3px 0 rgba(0,0,0,.08);background:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;font-size:32px;font-weight:800;flex-shrink:0}
|
||||
.profile-name-lg{font-size:22px;font-weight:800;margin-bottom:4px}
|
||||
.profile-id-lg{font-size:13px;opacity:.8}
|
||||
.profile-stats-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:20px}
|
||||
.profile-stat-card{background:#FDFCFA;border-radius:8px;padding:20px;text-align:center;border:1px solid #D6D1CA}
|
||||
.profile-stat-card .num{font-size:32px;font-weight:800;color:#808C95}
|
||||
.profile-stat-card .label{font-size:12px;color:#8A8580;margin-top:4px}
|
||||
|
||||
/* Pet display on home */
|
||||
.pet-card{background:linear-gradient(135deg,#8A9BA8,#9AADBA);border-radius:8px;padding:24px 28px;color:#fff;display:flex;align-items:center;gap:20px;margin-bottom:24px;position:relative;overflow:hidden}
|
||||
.pet-card::after{content:"";position:absolute;top:-20px;right:-20px;width:100px;height:100px;border-radius:50%;background:rgba(255,255,255,.1)}
|
||||
.pet-avatar{width:80px;height:80px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:40px;font-weight:800;background:rgba(255,255,255,.2);flex-shrink:0;position:relative;z-index:1}
|
||||
.pet-info{position:relative;z-index:1}
|
||||
.pet-name{font-size:20px;font-weight:800;margin-bottom:4px}
|
||||
.pet-type{font-size:13px;opacity:.85;margin-bottom:6px}
|
||||
.pet-exp-bar{height:6px;background:rgba(255,255,255,.25);border-radius:3px;overflow:hidden;width:200px}
|
||||
.pet-exp-fill{height:100%;background:#FDFCFA;border-radius:3px;transition:width .5s /* 注意:JS 中使用 requestAnimationFrame 实现跳动动画,覆盖此 transition */};
|
||||
.pet-level{font-size:12px;margin-top:4px;opacity:.8}
|
||||
.pet-dims{display:grid;grid-template-columns:repeat(5,minmax(96px,1fr));gap:8px;margin:12px 0;max-width:680px}
|
||||
.pet-dim{display:grid;grid-template-columns:18px 30px 1fr 26px;align-items:center;gap:6px;background:rgba(255,255,255,.14);border-radius:6px;padding:6px 8px;font-size:10px}
|
||||
.pet-dim-name{color:rgba(255,255,255,.88);white-space:nowrap}
|
||||
.pet-dim-track{height:5px;background:rgba(255,255,255,.25);border-radius:999px;overflow:hidden}
|
||||
.pet-dim-track i{display:block;height:100%;border-radius:999px}
|
||||
.pet-dim b{text-align:right;color:#fff;font-variant-numeric:tabular-nums}
|
||||
@media(max-width:900px){.pet-dims{grid-template-columns:repeat(2,1fr)}}
|
||||
@media(max-width:560px){.pet-dims{grid-template-columns:1fr 1fr}.pet-card{align-items:flex-start}}
|
||||
|
||||
/* Toolbox */
|
||||
.tool-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:24px}
|
||||
@media(max-width:640px){.tool-grid{grid-template-columns:1fr}}
|
||||
.tool-card{background:#FDFCFA;border-radius:8px;padding:20px;border:1px solid #D6D1CA;cursor:pointer;transition:all .15s;text-align:center}
|
||||
.tool-card:hover{border-color:#808C95;background:#F0EEEA}
|
||||
.tool-card-icon{font-size:36px;margin-bottom:10px}
|
||||
.tool-card-name{font-size:15px;font-weight:700;color:#3A3632;margin-bottom:4px}
|
||||
.tool-card-desc{font-size:12px;color:#8A8580}
|
||||
.tool-modal-body{padding:0;min-height:300px}
|
||||
|
||||
/* Mental math game */
|
||||
.math-race .question{font-size:28px;font-weight:800;text-align:center;padding:20px;color:#3A3632}
|
||||
.math-race .answer-area{display:flex;gap:10px;padding:0 20px 20px;justify-content:center}
|
||||
.math-race input{width:120px;padding:12px;border:1px solid #D6D1CA;border-radius:6px;font-size:20px;text-align:center;outline:none}
|
||||
.math-race input:focus{border-color:#808C95}
|
||||
.math-race .result{text-align:center;padding:10px;font-weight:700}
|
||||
.math-race .stats{display:flex;justify-content:center;gap:30px;padding:10px;font-size:13px;color:#8A8580}
|
||||
.math-race .timer-bar{height:4px;background:#EAE7E2;margin:0 20px 20px;border-radius:2px;overflow:hidden}
|
||||
.math-race .timer-fill{height:100%;background:#8AA68A;transition:width .1s linear}
|
||||
|
||||
/* Symbol lookup */
|
||||
.symbol-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;padding:16px}
|
||||
.symbol-btn{padding:10px;border:1px solid #D6D1CA;border-radius:6px;background:#FDFCFA;cursor:pointer;text-align:center;font-weight:600;transition:all .15s;font-size:13px}
|
||||
.symbol-btn:hover{border-color:#808C95;background:#EAE7E2}
|
||||
.symbol-detail{padding:0 20px 20px;text-align:center;font-size:13px;color:#8A8580;line-height:1.6;min-height:60px}
|
||||
|
||||
/* Function grapher */
|
||||
.graph-container{padding:16px;text-align:center}
|
||||
.graph-container canvas{border:1px solid #D6D1CA;border-radius:8px;display:block;margin:0 auto}
|
||||
.graph-inputs{display:flex;gap:8px;padding:0 16px 16px;justify-content:center}
|
||||
.graph-inputs input{padding:8px 12px;border:1px solid #D6D1CA;border-radius:6px;font-size:13px;outline:none;width:200px;background:#F7F5F1}
|
||||
.graph-inputs input:focus{border-color:#808C95}
|
||||
.graph-inputs button{padding:8px 16px;border-radius:6px;border:none;background:#808C95;color:#fff;font-size:13px;cursor:pointer;transition:all .15s}
|
||||
.graph-inputs button:hover{background:#657078}
|
||||
|
||||
/* Sidebar overlay */
|
||||
.sidebar-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:25}
|
||||
|
||||
/* Responsive */
|
||||
@media(max-width:1366px){.app-wrapper{max-width:100%}.main-content{padding:24px 28px}}
|
||||
@media(max-width:768px){
|
||||
body{overflow-x:hidden}.app-wrapper{max-width:100%;height:100dvh}
|
||||
.mobile-topbar{display:flex}
|
||||
.sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;transform:translateX(-100%);z-index:30;border-right:1px solid #D6D1CA;border-radius:0}
|
||||
.sidebar.open{transform:translateX(0)}.sidebar-overlay.active{display:block}
|
||||
.main-content{padding:20px 16px}.main-content-inner{max-width:100%}
|
||||
.video-grid{grid-template-columns:1fr}.fav-grid{grid-template-columns:1fr}.knowledge-grid{grid-template-columns:1fr}
|
||||
.story-container,.sim-container{padding:20px;border-radius:8px}.banner{padding:20px}.banner-title{font-size:18px}
|
||||
.ai-panel{width:100%;max-width:100%}
|
||||
}
|
||||
|
||||
/* ========== UI Polish ========== */
|
||||
.nav-icon svg{width:17px;height:17px;display:block;margin:0 auto;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||||
/* 语言切换按钮 */
|
||||
.lang-switch{display:flex;gap:2px;margin-left:auto}
|
||||
.lang-btn{padding:2px 6px;border-radius:4px;border:1px solid #D6D1CA;background:transparent;font-size:11px;font-weight:600;color:#ABA6A1;cursor:pointer;transition:all .15s;min-width:28px}
|
||||
.lang-btn.active{background:#808C95;color:#fff;border-color:#808C95}
|
||||
.lang-btn:hover:not(.active){color:#808C95;border-color:#808C95}
|
||||
.lang-switch.mobile-lang{margin-left:auto}
|
||||
/* 交互 */
|
||||
::selection{background:#E6E9EC;color:#657078}
|
||||
button:focus-visible,input:focus-visible,[tabindex]:focus-visible{outline:2px solid #808C95;outline-offset:2px}
|
||||
.nav-item:active,.filter-btn:active,.ds-hero-cta:active,.symbol-btn:active,.graph-inputs button:active,.ai-send-btn:active{transform:scale(.97)}
|
||||
.ds-feature-card:active,.tool-card:active{transform:translateY(-1px) scale(.98)}
|
||||
::-webkit-scrollbar-thumb:hover{background:#ABA6A1}
|
||||
.sim-stat-val,.profile-stat-card .num,.math-race .stats,.np-cover-duration,.pet-type{font-variant-numeric:tabular-nums}
|
||||
.page-title,.ds-section-title,.section-title{letter-spacing:-0.01em}
|
||||
.sidebar-search input:focus,.math-race input:focus,.graph-inputs input:focus,.ai-panel-input-area input:focus{box-shadow:0 0 0 3px rgba(128,140,149,.12)}
|
||||
@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
||||
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
|
||||
|
||||
/* ========== Skeleton Screen (骨架屏) ========== */
|
||||
@keyframes skeletonShimmer{0%{background-position:-400px 0}100%{background-position:400px 0}}
|
||||
.skeleton{background:linear-gradient(90deg,#EAE7E2 25%,#DED9D2 37%,#EAE7E2 63%);background-size:400px 100%;animation:skeletonShimmer 1.5s ease-in-out infinite;border-radius:4px}
|
||||
.skeleton-card{width:100%;height:180px;border-radius:8px;border:1px solid #EAE7E2;padding:14px;display:flex;flex-direction:column;gap:10px}
|
||||
.skeleton-line{height:12px;border-radius:3px}
|
||||
.skeleton-line.w80{width:80%}.skeleton-line.w60{width:60%}.skeleton-line.w40{width:40%}
|
||||
.skeleton-avatar{width:28px;height:28px;border-radius:50%}
|
||||
|
||||
/* ========== Toast ========== */
|
||||
.toast-container{position:fixed;top:20px;right:20px;z-index:200;display:flex;flex-direction:column;gap:8px;pointer-events:none}
|
||||
.toast{background:#3A3632;color:#FDFCFA;padding:10px 18px;border-radius:6px;font-size:13px;font-weight:500;animation:toastIn .3s ease,toastOut .3s ease 2s forwards;max-width:280px;box-shadow:0 2px 8px rgba(58,54,50,.15)}
|
||||
@keyframes toastIn{from{opacity:0;transform:translateX(20px)}to{opacity:1;transform:translateX(0)}}
|
||||
@keyframes toastOut{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(20px)}}
|
||||
|
||||
/* ========== 粒子爆发动画 ========== */
|
||||
@keyframes particleBurst{0%{opacity:1;transform:translate(0,0) scale(1)}100%{opacity:0;transform:translate(var(--px),var(--py)) scale(0)}}
|
||||
.particle{position:fixed;pointer-events:none;z-index:300;animation:particleBurst .7s ease-out forwards}
|
||||
|
||||
/* ========== MathBTI sidebar promocard (醒目入口) ========== */
|
||||
.sidebar-mbti-card{margin:0 12px 8px;padding:14px;border-radius:6px;background:linear-gradient(135deg,#8A9BA8,#99A9B5);color:#fff;font-size:12px;font-weight:600;cursor:pointer;transition:all .15s;line-height:1.5}
|
||||
.sidebar-mbti-card:hover{background:linear-gradient(135deg,#7A8B9A,#8A9AA8);transform:scale(1.01)}
|
||||
.sidebar-mbti-card .mbti-card-icon{font-size:20px;margin-bottom:4px}
|
||||
.sidebar-mbti-card .mbti-card-arrow{float:right;opacity:.7;font-size:14px}
|
||||
|
||||
/* ========== 葫芦元素 ========== */
|
||||
.sidebar-logo-icon,.topbar-logo-icon{border-radius:50% 50% 50% 50% / 60% 60% 40% 40%;box-shadow:inset 0 -3px 0 rgba(0,0,0,.1)}
|
||||
.vine-divider{height:1px;background:linear-gradient(90deg,transparent,#C8C4BD 15%,#A8B886 50%,#C8C4BD 85%,transparent);margin:20px 0;position:relative}
|
||||
.vine-divider::after{content:"🏮";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-size:14px;background:#F7F5F1;padding:0 8px}
|
||||
#toastBox .toast{border-radius:50% 50% 50% 50% / 60% 60% 40% 40%;padding:12px 24px}
|
||||
.skeleton-card{border-radius:50% 50% 50% 50% / 60% 60% 40% 40%;padding:20px 16px 16px}
|
||||
.skeleton-card::before{content:"";display:block;width:40px;height:16px;background:linear-gradient(90deg,#E8E5E0 25%,#F0EEEA 50%,#E8E5E0 75%);background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:8px;margin-bottom:12px}
|
||||
|
||||
/* ========== 抽卡按钮 ========== */
|
||||
.float-hub-child.cards{background:#A8907E}
|
||||
.float-hub-child.cards:hover{background:#B8956E}
|
||||
.draw-btn{display:inline-flex;align-items:center;gap:8px;padding:10px 24px;border-radius:50% 50% 50% 50% / 60% 60% 40% 40%;border:none;background:linear-gradient(135deg,#A8907E,#C47F3A);color:#fff;font-size:14px;font-weight:700;cursor:pointer;transition:all .2s;box-shadow:0 3px 12px rgba(164,126,94,.3)}
|
||||
.draw-btn:hover{transform:scale(1.03);box-shadow:0 6px 20px rgba(164,126,94,.45)}
|
||||
.draw-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none}
|
||||
|
||||
/* ========== 知识页抽卡进度条 ========== */
|
||||
.draw-progress-bar{background:#F7F5F1;border-radius:8px;padding:14px 18px;border:1px solid #D6D1CA;display:flex;align-items:center;gap:12px;font-size:13px;color:#8A8580;margin-top:16px;cursor:pointer;transition:all .15s}
|
||||
.draw-progress-bar:hover{border-color:#A8907E;background:#FDF8F4}
|
||||
.draw-progress-track{flex:1;height:8px;background:#E8E5E0;border-radius:4px;overflow:hidden}
|
||||
.draw-progress-fill{height:100%;background:linear-gradient(90deg,#A8907E,#C47F3A);border-radius:4px;transition:width .4s cubic-bezier(.34,1.56,.64,1)}
|
||||
.draw-progress-icon{font-size:20px;flex-shrink:0}
|
||||
.draw-progress-ready{background:#FDF4EC;border-color:#C47F3A;animation:pulse-glow 2s infinite}
|
||||
@keyframes pulse-glow{0%,100%{box-shadow:0 0 0 0 rgba(196,127,58,.2)}50%{box-shadow:0 0 0 6px rgba(196,127,58,0)}}
|
||||
|
||||
/* ========== 葫芦摇晃 + 卡片翻转动画 ========== */
|
||||
@keyframes gourd-shake{0%,100%{transform:rotate(0)}10%{transform:rotate(-8deg)}20%{transform:rotate(8deg)}30%{transform:rotate(-6deg)}40%{transform:rotate(6deg)}50%{transform:rotate(-3deg)}60%{transform:rotate(3deg)}70%{transform:rotate(-1deg)}80%{transform:rotate(1deg)}}
|
||||
.gourd-shaking{animation:gourd-shake .8s ease-in-out}
|
||||
@keyframes gourd-bounce{0%{transform:scale(1.2)}50%{transform:scale(.9)}70%{transform:scale(1.05)}100%{transform:scale(1)}}
|
||||
@keyframes card-flip{0%{transform:perspective(600px) rotateY(0)}50%{transform:perspective(600px) rotateY(90deg)}100%{transform:perspective(600px) rotateY(0)}}
|
||||
.card-flip-in{animation:card-flip .6s ease-in-out}
|
||||
|
||||
/* ========== 抽卡弹窗 ========== */
|
||||
.gacha-overlay{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:500;display:flex;align-items:center;justify-content:center;animation:fadeIn .25s}
|
||||
.gacha-modal{background:#FDFCFA;border-radius:16px;padding:28px 24px;max-width:360px;width:90%;text-align:center;position:relative;animation:gourd-bounce .5s ease-out}
|
||||
.gacha-gourd{font-size:72px;display:block;margin:0 auto 16px;cursor:pointer;user-select:none;transition:all .2s;filter:drop-shadow(0 4px 8px rgba(0,0,0,.15))}
|
||||
.gacha-gourd:hover{transform:scale(1.08)}
|
||||
.gacha-title{font-size:20px;font-weight:700;color:#3A3632;margin-bottom:4px}
|
||||
.gacha-sub{font-size:12px;color:#ABA6A1;margin-bottom:12px}
|
||||
.gacha-draws{font-size:13px;color:#8A8580;margin-bottom:8px}
|
||||
.gacha-draws b{color:#A8907E;font-size:16px}
|
||||
.gacha-close{position:absolute;top:12px;right:16px;background:none;border:none;font-size:20px;color:#ABA6A1;cursor:pointer;padding:4px}
|
||||
.gacha-card-reveal{margin-top:16px;animation:gourd-bounce .5s ease-out}
|
||||
.gacha-card-portrait{width:100px;height:100px;border-radius:50%;margin:0 auto 12px;display:flex;align-items:center;justify-content:center;font-size:48px;overflow:hidden;border:3px solid var(--clan-color,#A8907E)}
|
||||
.gacha-card-portrait img{width:100%;height:100%;object-fit:cover}
|
||||
.gacha-card-name{font-size:18px;font-weight:700;color:#3A3632;margin-bottom:2px}
|
||||
.gacha-card-type{font-size:12px;color:#ABA6A1;margin-bottom:4px}
|
||||
.gacha-card-clan{font-size:12px;padding:3px 12px;border-radius:12px;display:inline-block;margin-bottom:8px}
|
||||
.gacha-card-tagline{font-size:13px;color:#8A8580;font-style:italic;margin-bottom:12px}
|
||||
.gacha-card-dupe{font-size:12px;color:#C49595;margin-bottom:8px;font-style:italic}
|
||||
.gacha-card-detail-btn{padding:6px 18px;border-radius:20px;border:1px solid #D6D1CA;background:#FDFCFA;color:#8A8580;font-size:12px;cursor:pointer;transition:all .15s}
|
||||
.gacha-card-detail-btn:hover{border-color:#A8907E;color:#A8907E}
|
||||
|
||||
/* ========== 卡片图鉴条 (首页) ========== */
|
||||
.card-gallery-wrap{position:relative;margin:8px 0 20px}
|
||||
.card-gallery-scroll{display:flex;gap:10px;overflow-x:auto;padding:4px 2px 8px;scrollbar-width:none}
|
||||
.card-gallery-scroll::-webkit-scrollbar{display:none}
|
||||
.card-gallery-item{flex-shrink:0;width:72px;text-align:center;cursor:pointer;transition:all .15s}
|
||||
.card-gallery-item:hover{transform:translateY(-3px)}
|
||||
.card-gallery-item .cg-icon{width:56px;height:56px;border-radius:50%;margin:0 auto 4px;display:flex;align-items:center;justify-content:center;font-size:24px;border:2px solid transparent;transition:all .2s;overflow:hidden;position:relative}
|
||||
.card-gallery-item .cg-icon img{width:100%;height:100%;object-fit:cover;border-radius:50%;display:block}
|
||||
.card-gallery-item:hover .cg-icon{border-color:var(--clan-color,#A8907E);box-shadow:0 3px 12px rgba(0,0,0,.1)}
|
||||
.card-gallery-item .cg-name{font-size:10px;color:#8A8580;line-height:1.3;max-width:72px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.card-gallery-empty{text-align:center;padding:20px;color:#ABA6A1;font-size:13px;border:1px dashed #D6D1CA;border-radius:8px}
|
||||
.card-gallery-empty .cg-empty-icon{font-size:28px;display:block;margin-bottom:6px}
|
||||
|
||||
/* ========== 卡片详情弹窗 ========== */
|
||||
.card-detail-overlay{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:510;display:flex;align-items:center;justify-content:center;animation:fadeIn .25s}
|
||||
.card-detail-modal{background:#FDFCFA;border-radius:16px;padding:0;max-width:400px;width:90%;overflow:hidden;position:relative;animation:gourd-bounce .5s ease-out}
|
||||
.card-detail-header{padding:32px 24px 20px;text-align:center;position:relative}
|
||||
.card-detail-header .card-detail-bg{position:absolute;inset:0;opacity:.12}
|
||||
.card-detail-portrait{width:80px;height:80px;border-radius:50%;margin:0 auto 12px;display:flex;align-items:center;justify-content:center;font-size:40px;overflow:hidden;border:3px solid #FDFCFA;position:relative;z-index:1;box-shadow:0 3px 12px rgba(0,0,0,.1)}
|
||||
.card-detail-name{font-size:22px;font-weight:700;color:#3A3632;position:relative;z-index:1;margin-bottom:2px}
|
||||
.card-detail-binary{font-size:12px;color:#ABA6A1;letter-spacing:2px;position:relative;z-index:1;margin-bottom:6px}
|
||||
.card-detail-clan{display:inline-block;padding:4px 14px;border-radius:12px;font-size:12px;font-weight:600;position:relative;z-index:1;margin-bottom:6px}
|
||||
.card-detail-tagline{font-size:13px;color:#8A8580;font-style:italic;position:relative;z-index:1}
|
||||
.card-detail-body{padding:20px 24px 24px;border-top:1px solid #EAE7E2}
|
||||
.card-detail-section{margin-bottom:16px}
|
||||
.card-detail-section-title{font-size:12px;font-weight:700;color:#ABA6A1;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}
|
||||
.card-detail-section p{font-size:14px;color:#5A5652;line-height:1.7}
|
||||
.card-detail-fact{background:#F7F5F1;border-radius:10px;padding:14px;display:flex;gap:10px;align-items:flex-start;font-size:13px;color:#8A8580;line-height:1.6}
|
||||
.card-detail-fact .fact-emoji{font-size:18px;flex-shrink:0}
|
||||
.card-detail-obtain{background:#F7F5F1;border-radius:10px;padding:12px 14px;font-size:12px;color:#ABA6A1;display:flex;align-items:center;gap:8px;margin-top:12px}
|
||||
.card-detail-obtain .obtain-icon{font-size:16px}
|
||||
.card-detail-close{position:absolute;top:12px;right:16px;z-index:2;background:rgba(255,255,255,.8);border:none;border-radius:50%;width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:16px;color:#8A8580;cursor:pointer}
|
||||
|
||||
@keyframes gacha-particle-key{0%{opacity:1;transform:translate(0,0) scale(1)}100%{opacity:0;transform:translate(var(--gpx),var(--gpy)) scale(0) rotate(360deg)}}
|
||||
.gacha-particle{position:fixed;pointer-events:none;z-index:520;width:10px;height:10px;border-radius:50%;animation:gacha-particle-key 1s ease-out forwards}
|
||||
|
||||
.knowledge-card-read{position:absolute;top:8px;right:8px;background:rgba(255,255,255,.85);border-radius:50%;width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:14px;z-index:2}
|
||||
.knowledge-card-read.unread{opacity:.4}
|
||||
|
||||
/* ========== 卡册页网格 ========== */
|
||||
.cards-collection-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
|
||||
@media(max-width:1000px){.cards-collection-grid{grid-template-columns:repeat(3,1fr)}}
|
||||
@media(max-width:640px){.cards-collection-grid{grid-template-columns:repeat(2,1fr)}}
|
||||
.cc-card{background:#FDFCFA;border:1px solid #D6D1CA;border-radius:8px;padding:16px 12px;text-align:center;transition:all .15s}
|
||||
.cc-card.owned{cursor:pointer}
|
||||
.cc-card.owned:hover{border-color:var(--clan-color,#A8907E);box-shadow:0 3px 12px rgba(0,0,0,.08);transform:translateY(-2px)}
|
||||
.cc-card.locked{opacity:.45;filter:grayscale(.5)}
|
||||
.cc-card-icon{width:52px;height:52px;border-radius:50%;margin:0 auto 8px;display:flex;align-items:center;justify-content:center;font-size:24px;overflow:hidden;position:relative}
|
||||
.cc-card-icon img{width:100%;height:100%;object-fit:cover;border-radius:50%;display:block}
|
||||
.cc-card.locked .cc-card-icon::after{content:'🔒';position:absolute;right:-1px;bottom:-1px;width:18px;height:18px;line-height:18px;font-size:11px;background:rgba(255,255,255,.9);border-radius:50%;box-shadow:0 1px 4px rgba(0,0,0,.15);color:#8A8580}
|
||||
.cc-card-icon.locked-icon{background:#E8E5E0!important;color:#C8C4BD}
|
||||
.cc-card-name{font-size:13px;font-weight:600;color:#3A3632;margin-bottom:2px}
|
||||
.cc-card-type{font-size:10px;color:#ABA6A1;letter-spacing:1px;margin-bottom:4px}
|
||||
.cc-card-clan{font-size:10px;padding:2px 8px;border-radius:8px;display:inline-block}
|
||||
@@ -0,0 +1,138 @@
|
||||
/* MathBTI — 数学人格测试 独立页面 */
|
||||
/* 移动端优先 · 卡片风 · 与数学脑主站统一 */
|
||||
|
||||
:root{--c-primary:#5B6AF0;--c-accent:#F97316;--c-text:#1E293B;--c-sub:#64748B;--c-muted:#94A3B8;--c-bg:#F8FAFC;--c-card:#FFF;--c-border:#E2E8F0;--shadow:0 4px 24px rgba(0,0,0,.06);--radius:16px;}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
html{font-size:16px;-webkit-text-size-adjust:100%}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;background:var(--c-bg);color:var(--c-text);line-height:1.6;min-height:100dvh;overflow-x:hidden}
|
||||
|
||||
/* ---- App Container ---- */
|
||||
.app{max-width:480px;margin:0 auto;padding:0 20px 40px;min-height:100dvh;display:flex;flex-direction:column;align-items:center;justify-content:center}
|
||||
.screen{display:none;width:100%;flex-direction:column;align-items:center;animation:fadeIn .35s ease}
|
||||
.screen.active{display:flex}
|
||||
@keyframes fadeIn{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
|
||||
|
||||
/* ---- Intro Screen ---- */
|
||||
.intro-header{text-align:center;margin-bottom:28px}
|
||||
.intro-logo{width:72px;height:72px;border-radius:50% 50% 50% 50% / 60% 60% 40% 40%;background:linear-gradient(135deg,var(--c-primary),#818CF8);color:#fff;font-size:36px;font-weight:700;display:flex;align-items:center;justify-content:center;margin:0 auto 20px;box-shadow:0 8px 32px rgba(91,106,240,.25),inset 0 -3px 0 rgba(0,0,0,.08)}
|
||||
.intro-title{font-size:28px;font-weight:800;color:var(--c-text);margin-bottom:8px}
|
||||
.intro-sub{font-size:15px;color:var(--c-sub);margin-bottom:12px}
|
||||
.intro-meta{font-size:13px;color:var(--c-muted);background:var(--c-bg);display:inline-block;padding:6px 14px;border-radius:20px}
|
||||
|
||||
.intro-desc{display:flex;flex-direction:column;gap:8px;align-items:center;margin-bottom:24px;font-size:14px;color:var(--c-sub)}
|
||||
.intro-desc span{background:var(--c-card);border:1px solid var(--c-border);border-radius:8px;padding:8px 16px;width:100%;text-align:center;max-width:320px}
|
||||
|
||||
.intro-axes{display:flex;flex-direction:column;gap:8px;margin-bottom:28px;width:100%;max-width:360px}
|
||||
.intro-axis-item{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;background:var(--c-card);border-radius:10px;border:1px solid var(--c-border)}
|
||||
.axis-label{font-size:13px;color:var(--c-sub);flex:1;text-align:center}
|
||||
.axis-emoji{font-size:18px}
|
||||
|
||||
.btn-start{width:100%;max-width:360px;padding:16px 0;background:linear-gradient(135deg,var(--c-primary),#818CF8);color:#fff;border:none;border-radius:14px;font-size:17px;font-weight:700;cursor:pointer;transition:all .2s;box-shadow:0 4px 16px rgba(91,106,240,.3)}
|
||||
.btn-start:hover{transform:translateY(-1px);box-shadow:0 6px 24px rgba(91,106,240,.4)}
|
||||
.btn-start:active{transform:scale(.98)}
|
||||
|
||||
/* ---- Quiz Screen ---- */
|
||||
.quiz-progress-wrap{width:100%;max-width:400px;margin-bottom:24px}
|
||||
.quiz-progress-bar{width:100%;height:6px;background:var(--c-border);border-radius:3px;overflow:hidden;margin-bottom:8px}
|
||||
.quiz-progress-fill{height:100%;background:linear-gradient(90deg,var(--c-primary),#818CF8);border-radius:3px;transition:width .3s ease}
|
||||
.quiz-progress-text{text-align:right;font-size:13px;color:var(--c-muted)}
|
||||
|
||||
.quiz-question{width:100%;max-width:400px;font-size:18px;font-weight:700;color:var(--c-text);margin-bottom:20px;line-height:1.5;text-align:center}
|
||||
|
||||
.quiz-options{width:100%;max-width:400px;display:flex;flex-direction:column;gap:10px;margin-bottom:20px}
|
||||
.quiz-option{display:flex;align-items:center;padding:14px 16px;background:var(--c-card);border:2px solid var(--c-border);border-radius:12px;cursor:pointer;transition:all .2s}
|
||||
.quiz-option input[type=radio]{display:none}
|
||||
.quiz-option:hover{border-color:var(--c-primary)30;background:#F5F7FF}
|
||||
.quiz-option.selected{border-color:var(--c-primary);background:#EEF0FF}
|
||||
.opt-text{font-size:15px;color:var(--c-text);line-height:1.4}
|
||||
|
||||
.btn-next{width:100%;max-width:400px;padding:14px 0;background:var(--c-primary);color:#fff;border:none;border-radius:12px;font-size:16px;font-weight:600;cursor:pointer;transition:all .2s}
|
||||
.btn-next:disabled{background:var(--c-border);color:var(--c-muted);cursor:not-allowed;box-shadow:none}
|
||||
.btn-next:not(:disabled):hover{background:#4F5EE0;transform:translateY(-1px)}
|
||||
|
||||
/* ---- Result Screen ---- */
|
||||
.result-header{text-align:center;margin-bottom:24px}
|
||||
.result-portrait-wrap{margin-bottom:16px}
|
||||
.result-portrait{width:96px;height:96px;border-radius:50%;margin:0 auto;box-shadow:0 4px 16px rgba(0,0,0,.08);overflow:hidden;position:relative;display:flex;align-items:center;justify-content:center}
|
||||
.result-icon-fallback{display:flex}
|
||||
.result-type-name{font-size:26px;font-weight:800;color:var(--c-text);margin-bottom:6px}
|
||||
.result-tagline{font-size:14px;color:var(--c-sub);font-style:italic;margin-bottom:10px}
|
||||
|
||||
.result-mathematician{display:flex;flex-direction:column;align-items:center;gap:6px;margin-bottom:4px}
|
||||
.clan-badge{display:inline-block;padding:4px 12px;border-radius:20px;font-size:13px;font-weight:600}
|
||||
.mathematician-name{font-size:12px;color:var(--c-muted)}
|
||||
|
||||
.result-keywords{display:flex;flex-wrap:wrap;justify-content:center;gap:6px;margin-bottom:20px}
|
||||
.kw-tag{padding:4px 12px;background:#EEF0FF;color:var(--c-primary);border-radius:20px;font-size:12px;font-weight:600}
|
||||
|
||||
.result-card{width:100%;max-width:400px;background:var(--c-card);border-radius:var(--radius);padding:20px;box-shadow:var(--shadow);margin-bottom:16px}
|
||||
.result-card-title{font-size:14px;font-weight:700;color:var(--c-sub);margin-bottom:10px;text-transform:uppercase;letter-spacing:.5px}
|
||||
.result-card-desc{font-size:14px;color:var(--c-text);line-height:1.7;margin-bottom:8px}
|
||||
.result-card-short{font-size:13px;color:var(--c-sub);font-weight:500}
|
||||
|
||||
/* Bio Expander */
|
||||
.bio-expander{width:100%;max-width:400px;background:var(--c-card);border-radius:var(--radius);box-shadow:var(--shadow);margin-bottom:16px;overflow:hidden}
|
||||
.bio-expander-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;cursor:pointer;transition:background .15s}
|
||||
.bio-expander-header:hover{background:#FAFBFC}
|
||||
.bio-expander-left{display:flex;align-items:center;gap:8px}
|
||||
.bio-expander-icon{font-size:16px}
|
||||
.bio-expander-title{font-size:14px;font-weight:600;color:var(--c-text)}
|
||||
.bio-arrow{font-size:12px;color:var(--c-muted);transition:transform .2s}
|
||||
.bio-expander-body{max-height:0;overflow:hidden;transition:max-height .35s ease}
|
||||
.bio-inner{padding:0 18px 16px}
|
||||
.bio-text{font-size:13px;color:var(--c-sub);line-height:1.7;margin-bottom:12px}
|
||||
.bio-fact-box{display:flex;align-items:flex-start;gap:8px;background:#FEF9E7;border-radius:10px;padding:10px 14px}
|
||||
.bio-fact-emoji{font-size:14px;flex-shrink:0}
|
||||
.bio-fact-text{font-size:12px;color:#B45309;line-height:1.5}
|
||||
|
||||
/* Axes Bars */
|
||||
.result-axes{width:100%;max-width:400px;margin-bottom:20px;display:none}
|
||||
.axis-bar-row{display:flex;align-items:center;gap:8px;margin-bottom:10px}
|
||||
.axis-bar-label{font-size:12px;color:var(--c-sub);white-space:nowrap;min-width:56px;text-align:center}
|
||||
.axis-bar-track{flex:1;height:6px;background:var(--c-border);border-radius:3px;overflow:hidden}
|
||||
.axis-bar-fill{height:100%;background:linear-gradient(90deg,var(--c-primary),#818CF8);border-radius:3px;transition:width .5s ease}
|
||||
|
||||
/* Adopt */
|
||||
.result-adopt{width:100%;max-width:400px;text-align:center;margin-bottom:20px}
|
||||
.btn-adopt{width:100%;padding:14px 0;background:linear-gradient(135deg,#F97316,#FB923C);color:#fff;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;transition:all .2s;box-shadow:0 4px 16px rgba(249,115,22,.3)}
|
||||
.btn-adopt:hover{transform:translateY(-1px)}
|
||||
.btn-adopt:active{transform:scale(.98)}
|
||||
.btn-adopt.adopted{background:var(--c-border);color:var(--c-muted);box-shadow:none;cursor:default}
|
||||
.adopt-hint{font-size:12px;color:var(--c-muted);margin-top:8px;line-height:1.5}
|
||||
|
||||
/* Share */
|
||||
.result-share{width:100%;max-width:400px;display:flex;flex-direction:column;gap:8px;margin-bottom:16px}
|
||||
.btn-share{width:100%;padding:12px 0;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s}
|
||||
.btn-share.secondary{background:var(--c-bg);color:var(--c-sub);border:1px solid var(--c-border)}
|
||||
.btn-share.primary{background:var(--c-primary);color:#fff}
|
||||
.btn-share:active{transform:scale(.98)}
|
||||
|
||||
.share-card-preview{width:100%;max-width:400px;text-align:center;margin-bottom:12px}
|
||||
|
||||
.btn-retry{width:100%;max-width:400px;padding:12px 0;background:transparent;color:var(--c-muted);border:1px solid var(--c-border);border-radius:10px;font-size:14px;cursor:pointer;margin-top:8px;transition:all .2s}
|
||||
.btn-retry:hover{color:var(--c-sub);border-color:var(--c-sub)}
|
||||
|
||||
.result-stats5{width:100%;max-width:400px;background:var(--c-card);border-radius:var(--radius);padding:16px;margin-bottom:16px;box-shadow:var(--shadow)}
|
||||
.result-stats5-title{font-size:13px;font-weight:700;color:var(--c-sub);margin-bottom:10px;letter-spacing:.5px}
|
||||
|
||||
/* ---- Toast ---- */
|
||||
.toast{position:fixed;bottom:40px;left:50%;transform:translateX(-50%) translateY(20px);background:#1E293B;color:#fff;padding:10px 24px;border-radius:24px;font-size:14px;font-weight:600;z-index:99;opacity:0;transition:all .3s ease;pointer-events:none;white-space:nowrap;box-shadow:0 4px 16px rgba(0,0,0,.2)}
|
||||
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
|
||||
|
||||
/* ---- Desktop Tweaks ---- */
|
||||
@media(min-width:768px){
|
||||
.app{max-width:520px;padding:40px 0 60px}
|
||||
.intro-logo{width:88px;height:88px;font-size:42px}
|
||||
.intro-title{font-size:32px}
|
||||
.quiz-question{font-size:20px}
|
||||
}
|
||||
/* 进度条葫芦终点 */
|
||||
.quiz-progress-fill{position:relative}
|
||||
.quiz-progress-fill::after{content:"🏮";position:absolute;right:-10px;top:50%;transform:translateY(-50%);font-size:14px;display:var(--is-last, none)}
|
||||
|
||||
/* ========== 卡片领取通知 ========== */
|
||||
.result-card-notice{background:linear-gradient(135deg,#FDF4EC,#FDF8F4);border:1px solid #C47F3A;border-radius:12px;padding:16px;text-align:center;margin-bottom:16px;animation:gourd-bounce .5s ease-out}
|
||||
.rcn-icon{font-size:28px;margin-bottom:6px}
|
||||
.rcn-text{font-size:14px;font-weight:600;color:#3A3632;margin-bottom:4px}
|
||||
.rcn-hint{font-size:12px;color:#8A8580}
|
||||
@keyframes gourd-bounce{0%{transform:scale(1.2)}50%{transform:scale(.9)}70%{transform:scale(1.05)}100%{transform:scale(1)}}
|
||||
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 74 KiB |
@@ -0,0 +1,642 @@
|
||||
const API_BASE='',USER_ID=localStorage.getItem('mb_user_id')||('mb_'+Math.random().toString(36).slice(2,10));
|
||||
localStorage.setItem('mb_user_id',USER_ID);
|
||||
let CP='home',allV=[],allK=[],kT=[],vD=[],cD='',cT='',sD=null,sVN={currentNode:null,stats:{shuli:10,xiayi:10,xinzhi:10},fav:{}},simD=null,simS={currentNode:null,stats:{gpa:3,interest:0,skill:0}},favC={},chatCtx={},tt=null;
|
||||
let cardData=null,cardCache=null,_gachaBusy=false;
|
||||
let cardState={owned:[],draws:0,lastDaily:'',knowledgeRead:0,drawHistory:[]};
|
||||
let _lang=localStorage.getItem('mb_lang')||'zh';
|
||||
const I18N={zh:{home:'首页',discover:'发现',toolbox:'工具箱',knowledge:'知识卡片',story:'葫芦侠行',simulator:'专业模拟器',favorites:'我的收藏',profile:'个人中心',search:'搜索内容...',guest:'游客用户',
|
||||
heroTitle:'葫芦数学 · 用玩的方式打开数学',heroSub:'数学不是公式,是你理解世界的方式',heroCTA:'开始探索 →',
|
||||
exploreTitle:'探索数学的万千可能',tryTitle:'试试这些',
|
||||
mbtiName:'MathBTI 数学人格',mbtiDesc:'12题发现你的数学人格,领养专属数学小人 →',
|
||||
toolboxName:'数学工具箱',toolboxDesc:'口算竞赛 · 计算器 · 符号查询 · 函数绘图 →',
|
||||
storyName:'葫芦侠行',storyDesc:'北宋数学武侠 · 8章93节点 · 8种结局 →',
|
||||
simName:'数学人生',simDesc:'模拟大学数学系4年,选择方向走向不同结局 →',
|
||||
videoName:'发现视频',videoDesc:'12个学科分区',cardName:'知识卡片',cardDesc:'8个数学话题',
|
||||
aiName:'AI 助教',aiDesc:'随时解答数学疑问',
|
||||
petTip:'每天使用工具箱和模拟器获取经验升级',
|
||||
dailyFactPrefix:'💡 数学冷知识:',
|
||||
discoverGreet:'Hello there. 这里收集了来自各专业志愿者的数学科普视频。',
|
||||
filterAll:'全部',noVideo:'暂无视频',noCard:'暂无卡片',noResult:'无结果',
|
||||
loadFailVideo:'视频加载失败',loadFailCard:'卡片加载失败',loadFailProfile:'加载失败',
|
||||
loadMore:'下拉加载更多…',loadEnd:'— 已经到底啦 · 共 ',loadEndSuf:' 个视频 —',
|
||||
close:'关闭',save:'保存',saveOK:'保存成功',saveFail:'保存失败',
|
||||
toolboxHeader:'工具箱',toolboxSub:'动手做数学——口算、计算、查询、绘图',
|
||||
toolMathrace:'口算竞赛',toolMathraceDesc:'限时60秒,挑战你的心算速度',
|
||||
toolCalc:'计算器',toolCalcDesc:'支持加减乘除、幂运算、三角函数',
|
||||
toolSym:'数学符号查询',toolSymDesc:'点击符号查看含义和LaTeX写法',
|
||||
toolGraph:'函数图形绘图',toolGraphDesc:'输入 f(x),实时绘制函数曲线',
|
||||
knowledgeHeader:'知识卡片',knowledgeSub:'轻量数学发现,每天一个新概念',
|
||||
storyHeader:'葫芦侠行',storySub:'北宋数学武侠互动文字冒险 · 8章93节点 · 8+种结局',
|
||||
storyLoadFail:'故事加载失败',storyNodeNotFound:'节点未找到',
|
||||
simHeader:'专业模拟器',simSub:'模拟大学数学系4年 · 选择方向 · 走向不同结局',
|
||||
simLoadFail:'模拟器加载失败',simReset:'重新开始',
|
||||
favHeader:'我的收藏',favSub:'已收藏的视频和知识卡片',
|
||||
favEmpty:'暂无收藏',favEmptyHint:'在发现页点击 🤍 即可收藏',
|
||||
profileHeader:'个人中心',profileEdit:'编辑资料',
|
||||
profileAvatar:'头像:',profileNick:'昵称:',
|
||||
profileWatchCount:'观看次数',profileFavCount:'收藏数量',
|
||||
aiHeader:'AI 数学助教',aiWelcome:'你好!我是葫芦数学的AI助教 👋<br><br>试试问我:学数学对学医有什么用?',
|
||||
aiPlaceholder:'输入问题...',aiOffline:'抱歉,AI暂时无法回复',aiNetworkErr:'网络异常,请稍后重试',
|
||||
calcErr:'表达式有误',calcPlaceholder:'输入表达式 如 2+3*4',
|
||||
graphPlaceholder:'如 Math.sin(x) 或 x*x',graphBtn:'绘制',
|
||||
mrTitle:'准备好了吗?',mrPlaceholder:'输入答案',mrTimeout:'⏰ 时间到!得分:',mrCorrect:'正确',mrRight:'✅ 正确!+10分',
|
||||
symDetail:'👆 点击符号查看详情',
|
||||
mbtiFloat:'测测你的数学人格',
|
||||
aiAskBtn:'问问AI',aiAskSuffix:'是什么?',
|
||||
petLevelUp:'升级啦!Lv.{n}',
|
||||
staticHeroTitle:'葫芦数学 · 用玩的方式打开数学',staticHeroSub:'数学不是公式,是你理解世界的方式',staticHeroCTA:'开始探索 →',
|
||||
staticExploreTitle:'探索数学的万千可能',staticTryTitle:'试试这些',
|
||||
staticDiscoverGreet:'Hello there. 这里收集了来自各专业志愿者的数学科普视频。',
|
||||
staticToolboxTitle:'🧰 工具箱',staticToolboxSub:'动手做数学——口算、计算、查询、绘图',
|
||||
staticToolMathrace:'口算竞赛',staticToolMathraceDesc:'限时60秒,挑战你的心算速度',
|
||||
staticToolCalc:'计算器',staticToolCalcDesc:'支持加减乘除、幂运算、三角函数',
|
||||
staticToolSym:'数学符号查询',staticToolSymDesc:'点击符号查看含义和LaTeX写法',
|
||||
staticToolGraph:'函数图形绘图',staticToolGraphDesc:'输入 f(x),实时绘制函数曲线',
|
||||
staticKnowledgeTitle:'📚 知识卡片',staticKnowledgeSub:'轻量数学发现,每天一个新概念',
|
||||
staticStoryTitle:'⚔️ 葫芦侠行',staticStorySub:'北宋数学武侠互动文字冒险 · 8章93节点 · 8+种结局',
|
||||
staticSimTitle:'🎮 专业模拟器',staticSimSub:'模拟大学数学系4年 · 选择方向 · 走向不同结局',
|
||||
staticFavTitle:'❤️ 我的收藏',staticFavSub:'已收藏的视频和知识卡片',
|
||||
staticProfileTitle:'👤 个人中心',
|
||||
staticAIToggleTitle:'AI数学助教',staticAIHeader:'💬 AI 数学助教',
|
||||
staticAIWelcome:'你好!我是葫芦数学的AI助教 👋<br><br>试试问我:学数学对学医有什么用?',
|
||||
staticAIPlaceholder:'输入问题...',staticMbtiTooltip:'测测你的数学人格'},
|
||||
en:{home:'Home',discover:'Discover',toolbox:'Toolbox',knowledge:'Cards',story:'HuLu Quest',simulator:'Simulator',favorites:'Favorites',profile:'Profile',search:'Search...',guest:'Guest',
|
||||
heroTitle:'Hulu Math · Play your way into math',heroSub:'Math isn\'t formulas. It\'s how you make sense of things.',heroCTA:'Start Exploring →',
|
||||
exploreTitle:'Explore Math',tryTitle:'Try These',
|
||||
mbtiName:'MathBTI Personality Test',mbtiDesc:'12 questions. One math soul. Meet your sprite. →',
|
||||
toolboxName:'Math Toolbox',toolboxDesc:'Mental Math · Calculator · Symbol Lookup · Graph →',
|
||||
storyName:'HuLu Quest',storyDesc:'A Song Dynasty math wuxia · 8 chapters · 8+ endings →',
|
||||
simName:'Math Life',simDesc:'Live four years as a math major. Choose your path. →',
|
||||
videoName:'Discover Videos',videoDesc:'12 subject areas',cardName:'Knowledge Cards',cardDesc:'8 math topics',
|
||||
aiName:'AI Tutor',aiDesc:'Ask anything. No judgment.',
|
||||
petTip:'Come back daily to earn EXP and level up your sprite.',
|
||||
dailyFactPrefix:'💡 Math Trivia: ',
|
||||
discoverGreet:'A hand-picked collection of math videos, curated by volunteers from across the sciences.',
|
||||
filterAll:'All',noVideo:'No videos yet',noCard:'No cards yet',noResult:'No results',
|
||||
loadFailVideo:'Couldn\'t load videos',loadFailCard:'Couldn\'t load cards',loadFailProfile:'Couldn\'t load',
|
||||
loadMore:'Scroll for more…',loadEnd:'— That\'s all of them · ',loadEndSuf:' videos —',
|
||||
close:'Close',save:'Save',saveOK:'Saved',saveFail:'Couldn\'t save',
|
||||
toolboxHeader:'Toolbox',toolboxSub:'Get your hands dirty — race the clock, crunch numbers, explore symbols, draw curves',
|
||||
toolMathrace:'Mental Math',toolMathraceDesc:'60 seconds. How high can you score?',
|
||||
toolCalc:'Calculator',toolCalcDesc:'Quick arithmetic, powers, trig — whatever you need.',
|
||||
toolSym:'Symbol Lookup',toolSymDesc:'Tap a symbol to see what it means and how to write it in LaTeX.',
|
||||
toolGraph:'Function Grapher',toolGraphDesc:'Type any f(x) and watch its curve appear in real time.',
|
||||
knowledgeHeader:'Knowledge Cards',knowledgeSub:'One idea at a time. Bite-sized math, built for curiosity.',
|
||||
storyHeader:'HuLu Quest',storySub:'A math-fueled wuxia set in the Song Dynasty. 8 chapters. 8+ endings.',
|
||||
storyLoadFail:'Couldn\'t load the story',storyNodeNotFound:'Node not found',
|
||||
simHeader:'Math Major Simulator',simSub:'Four years. Real choices. Multiple endings. What kind of mathematician will you become?',
|
||||
simLoadFail:'Couldn\'t load the simulator',simReset:'Start fresh',
|
||||
favHeader:'My Favorites',favSub:'Your saved videos and cards',
|
||||
favEmpty:'Nothing saved yet',favEmptyHint:'Tap 🤍 on any video or card to save it here.',
|
||||
profileHeader:'Profile',profileEdit:'Edit Profile',
|
||||
profileAvatar:'Avatar:',profileNick:'Nickname:',
|
||||
profileWatchCount:'Videos watched',profileFavCount:'Favorites',
|
||||
aiHeader:'AI Math Tutor',aiWelcome:'Hey! I\'m your Hulu Math AI tutor 👋<br><br>Try asking: "Why do we even need calculus?"',
|
||||
aiPlaceholder:'Ask anything...',aiOffline:'The AI is taking a quick break. Try again in a moment.',aiNetworkErr:'Looks like you\'re offline. Check your connection and try again.',
|
||||
calcErr:'That doesn\'t look right',calcPlaceholder:'e.g. 2+3*4',
|
||||
graphPlaceholder:'e.g. Math.sin(x) or x*x',graphBtn:'Plot',
|
||||
mrTitle:'Ready?',mrPlaceholder:'Type your answer',mrTimeout:'⏰ Time\'s up! You scored ',mrCorrect:'right',mrRight:'✅ Correct! +10',
|
||||
symDetail:'👆 Tap any symbol to learn more',
|
||||
mbtiFloat:'Discover your Math Personality',
|
||||
aiAskBtn:'Ask AI',aiAskSuffix:'?',
|
||||
petLevelUp:'Level up! Now Lv.{n}',
|
||||
staticHeroTitle:'Hulu Math · Play your way into math',staticHeroSub:'Math isn\'t formulas. It\'s how you make sense of things.',staticHeroCTA:'Start Exploring →',
|
||||
staticExploreTitle:'Explore Math',staticTryTitle:'Try These',
|
||||
staticDiscoverGreet:'A hand-picked collection of math videos, curated by volunteers from across the sciences.',
|
||||
staticToolboxTitle:'🧰 Toolbox',staticToolboxSub:'Get your hands dirty — race the clock, crunch numbers, explore symbols, draw curves',
|
||||
staticToolMathrace:'Mental Math',staticToolMathraceDesc:'60 seconds. How high can you score?',
|
||||
staticToolCalc:'Calculator',staticToolCalcDesc:'Quick arithmetic, powers, trig — whatever you need.',
|
||||
staticToolSym:'Symbol Lookup',staticToolSymDesc:'Tap a symbol to see what it means and how to write it in LaTeX.',
|
||||
staticToolGraph:'Function Grapher',staticToolGraphDesc:'Type any f(x) and watch its curve appear in real time.',
|
||||
staticKnowledgeTitle:'📚 Knowledge Cards',staticKnowledgeSub:'One idea at a time. Bite-sized math, built for curiosity.',
|
||||
staticStoryTitle:'⚔️ HuLu Quest',staticStorySub:'A math-fueled wuxia set in the Song Dynasty. 8 chapters. 8+ endings.',
|
||||
staticSimTitle:'🎮 Math Major Simulator',staticSimSub:'Four years. Real choices. Multiple endings. What kind of mathematician will you become?',
|
||||
staticFavTitle:'❤️ My Favorites',staticFavSub:'Your saved videos and cards',
|
||||
staticProfileTitle:'👤 Profile',
|
||||
staticAIToggleTitle:'AI Math Tutor',staticAIHeader:'💬 AI Math Tutor',
|
||||
staticAIWelcome:'Hey! I\'m your Hulu Math AI tutor 👋<br><br>Try asking: "Why do we even need calculus?"',
|
||||
staticAIPlaceholder:'Ask anything...',
|
||||
staticMbtiTooltip:'Discover your Math Personality'}};
|
||||
function t(k){const d=I18N[_lang];return (d&&d[k])||(I18N.zh[k]||k);}
|
||||
function rI18n(){
|
||||
document.querySelectorAll('[data-i18n]').forEach(el=>{
|
||||
const k=el.getAttribute('data-i18n');
|
||||
const v=t(k);
|
||||
if(el.tagName==='INPUT'){el.placeholder=v}
|
||||
else if(el.hasAttribute('title')&&el.getAttribute('data-i18n-type')==='title'){el.title=v}
|
||||
else{el.innerHTML=v}
|
||||
});
|
||||
}
|
||||
function switchLang(l){
|
||||
_lang=l;localStorage.setItem('mb_lang',l);
|
||||
document.querySelectorAll('.lang-btn').forEach(b=>b.classList.toggle('active',b.dataset.lang===l));
|
||||
document.getElementById('globalSearch').placeholder=t('search');
|
||||
rI18n();
|
||||
navigateTo(CP);
|
||||
api('/api/user/profile?user_id='+USER_ID).then(r=>{if(r.code===0&&r.data){document.getElementById('sidebarName').textContent=r.data.nickname||t('guest')}}).catch(()=>{});
|
||||
}
|
||||
function toggleSidebar(){document.getElementById('sidebar').classList.toggle('open');document.getElementById('sidebarOverlay').classList.toggle('active')}
|
||||
function closeSidebar(){document.getElementById('sidebar').classList.remove('open');document.getElementById('sidebarOverlay').classList.remove('active')}
|
||||
let searchTimeout;
|
||||
function handleGlobalSearch(){
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout=setTimeout(()=>{
|
||||
const q=document.getElementById('globalSearch').value.trim().toLowerCase();
|
||||
const el=document.getElementById('searchResults');
|
||||
if(!q||q.length<1){el.classList.remove('show');return}
|
||||
const results=[];
|
||||
allV.forEach(v=>{if((v.title||'').toLowerCase().includes(q)||(v.author||'').toLowerCase().includes(q)||(v.discipline||'').toLowerCase().includes(q))results.push({t:t('videoTitle')||'Video',title:v.title,id:v.id,type:'video'})});
|
||||
allK.forEach(k=>{if((k.title||'').toLowerCase().includes(q)||(k.description||'').toLowerCase().includes(q))results.push({t:t('cardLabel')||'Card',title:k.title,id:k.id,type:'knowledge'})});
|
||||
if(results.length===0){el.innerHTML='<div style="padding:12px;color:#94A3B8;font-size:12px">'+t('noResult')+'</div>';el.classList.add('show');return}
|
||||
el.innerHTML=results.slice(0,8).map(r=>'<div class="search-result-item" onclick="searchNavigate(\''+r.type+'\','+r.id+')"><span class="search-result-type">'+(r.t==='视频'?'Video':r.t==='知识'?'Card':r.t)+'</span>'+esc(r.title)+'</div>').join('');
|
||||
el.classList.add('show');
|
||||
},200);
|
||||
}
|
||||
function searchNavigate(type,id){
|
||||
document.getElementById('globalSearch').value='';document.getElementById('searchResults').classList.remove('show');
|
||||
if(type==='video'){navigateTo('discover');setTimeout(()=>openVideo(id,'','','','',''),500)}
|
||||
else if(type==='knowledge'){navigateTo('discover');setTimeout(()=>openKnowledgeDetail(id),500)}
|
||||
}
|
||||
document.addEventListener('click',e=>{if(!e.target.closest('.sidebar-search'))document.getElementById('searchResults').classList.remove('show')});
|
||||
function toggleAIPanel(){var p=document.getElementById('aiPanel');if(!p)return;var m=document.getElementById('toolModal');if(m)m.classList.remove('show');if(p.classList.contains('open')){p.classList.remove('open')}else{p.classList.add('open')}}
|
||||
const _apiCache={},_CACHEABLE=['/api/videos','/api/knowledge','/api/story','/api/simulator','/api/math_personality'];
|
||||
async function api(p,o={}){const u=API_BASE+p,c={headers:{'Content-Type':'application/json'},...o};if(c.body&&typeof c.body==='object')c.body=JSON.stringify(c.body);const isGet=!c.method;if(isGet&&_CACHEABLE.some(x=>p.indexOf(x)===0)&&_apiCache[u])return _apiCache[u];const r=await fetch(u,c);const j=await r.json();if(isGet&&_CACHEABLE.some(x=>p.indexOf(x)===0))_apiCache[u]=j;return j}
|
||||
function navigateTo(p){
|
||||
CP=p;closeSidebar();closeToolModal();document.getElementById('searchResults').classList.remove('show');
|
||||
if(p!=='home'&&heroAnimId){cancelAnimationFrame(heroAnimId);heroAnimId=null}
|
||||
document.querySelectorAll('.nav-item').forEach(b=>b.classList.toggle('active',b.dataset.page===p));
|
||||
document.querySelectorAll('.page').forEach(pg=>pg.classList.remove('active'));
|
||||
const t=document.getElementById('page-'+p);if(t)t.classList.add('active');
|
||||
document.getElementById('mainContent').scrollTop=0;
|
||||
if(p==='home')loadHome();else if(p==='discover')loadDiscover();else if(p==='toolbox')loadToolbox();
|
||||
else if(p==='knowledge')loadKnowledgePage();else if(p==='story')loadStory();else if(p==='simulator')loadSimulator();else if(p==='favorites')loadFavorites();else if(p==='profile')loadProfile();else if(p==='cards')loadCardsPage();
|
||||
}
|
||||
let heroAnimId=null;
|
||||
async function loadHome(){
|
||||
chatCtx={};
|
||||
setTimeout(drawHeroCanvas,100);
|
||||
const pet=JSON.parse(localStorage.getItem('mathbrain_pet')||'null');
|
||||
const pa=document.getElementById('homePetArea');
|
||||
if(pet){pa.innerHTML='<div class="pet-card"><div class="pet-avatar">'+pet.icon+'</div><div class="pet-info"><div class="pet-name">'+esc(pet.name)+'</div><div class="pet-type">🎓 '+esc(pet.type)+' · Lv.'+pet.level+' · '+pet.exp+'/100 exp</div><div class="pet-exp-bar"><div class="pet-exp-fill" style="width:'+(pet.exp%100)+'%"></div></div><div class="pet-level">'+t('petTip')+'</div></div></div>';}
|
||||
else{pa.innerHTML='';}
|
||||
document.getElementById('homeFeatureCards').innerHTML=[
|
||||
{i:'🧬',n:t('mbtiName'),d:t('mbtiDesc'),click:"window.open('/mathbti','_blank')"},
|
||||
{i:'🧰',n:t('toolboxName'),d:t('toolboxDesc'),click:"navigateTo('toolbox')"},
|
||||
{i:'⚔️',n:t('storyName'),d:t('storyDesc'),click:"navigateTo('story')"},
|
||||
{i:'🎮',n:t('simName'),d:t('simDesc'),click:"navigateTo('simulator')"}
|
||||
].map(c=>'<div class="ds-feature-card" onclick="'+c.click+'"><div class="ds-feature-card-icon">'+c.i+'</div><div class="ds-feature-card-name">'+c.n+'</div><div class="ds-feature-card-desc">'+c.d+'</div></div>').join('');
|
||||
document.getElementById('homeExtraCards').innerHTML=[
|
||||
{i:'📺',n:t('videoName'),d:t('videoDesc'),click:"navigateTo('discover')"},
|
||||
{i:'📚',n:t('cardName'),d:t('cardDesc'),click:"navigateTo('knowledge')"},
|
||||
{i:'💬',n:t('aiName'),d:t('aiDesc'),click:"toggleAIPanel()"}
|
||||
].map(c=>'<div class="ds-feature-card" onclick="'+c.click+'"><div class="ds-feature-card-icon">'+c.i+'</div><div class="ds-feature-card-name">'+c.n+'</div><div class="ds-feature-card-desc">'+c.d+'</div></div>').join('');
|
||||
const facts=['π的小数点后第762位开始连续出现六个9,被称为「费曼点」。','世界上最大的已知质数有超过4100万位数字。','哥德巴赫猜想:每个大于2的偶数都可以写成两个质数之和——至今未被证明。','四色定理:任何地图只需4种颜色就能让相邻区域颜色不同。1976年用计算机证明。','莫比乌斯带只有一面——拿一条纸带,扭转180°后粘起来试试。'];
|
||||
document.getElementById('homeDailyFact').innerHTML=t('dailyFactPrefix')+facts[Math.floor(Math.random()*facts.length)];
|
||||
rCG();
|
||||
}
|
||||
function drawHeroCanvas(){
|
||||
const c=document.getElementById('heroCanvas');if(!c)return;
|
||||
if(heroAnimId){cancelAnimationFrame(heroAnimId);heroAnimId=null}
|
||||
const ctx=c.getContext('2d'),w=c.width,h=c.height;
|
||||
let t=0;
|
||||
function frame(){
|
||||
ctx.clearRect(0,0,w,h);
|
||||
ctx.strokeStyle='#D6D1CA';ctx.lineWidth=1;
|
||||
for(let i=0;i<w;i+=50){ctx.beginPath();ctx.moveTo(i,0);ctx.lineTo(i,h);ctx.stroke()}
|
||||
for(let i=0;i<h;i+=50){ctx.beginPath();ctx.moveTo(0,i);ctx.lineTo(w,i);ctx.stroke()}
|
||||
ctx.strokeStyle='#808C95';ctx.lineWidth=2.5;ctx.beginPath();
|
||||
for(let px=0;px<w;px++){const x=(px-w/2)/60;const y=Math.sin(x+t)*Math.cos(x*0.7+t*0.5)*50+h/2;if(px===0)ctx.moveTo(px,y);else ctx.lineTo(px,y)}ctx.stroke();
|
||||
ctx.strokeStyle='#9AA5AE';ctx.lineWidth=1.5;ctx.beginPath();
|
||||
for(let px=0;px<w;px++){const x=(px-w/2)/60;const y=Math.cos(x*1.3-t*0.7)*45+h/2;if(px===0)ctx.moveTo(px,y);else ctx.lineTo(px,y)}ctx.stroke();
|
||||
if(Math.sin(t*0.3)>0.7){
|
||||
ctx.save();ctx.globalAlpha=Math.min(1,(Math.sin(t*0.3)-0.7)/0.3*0.5);
|
||||
ctx.strokeStyle='#A8907E';ctx.lineWidth=1;
|
||||
const gx=w-60,gy=h-30;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(gx,gy-28,12,9,0,0,Math.PI*2);
|
||||
ctx.ellipse(gx,gy,16,13,0,0,Math.PI*2);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
t+=0.02;heroAnimId=requestAnimationFrame(frame);
|
||||
}
|
||||
frame();
|
||||
}
|
||||
async function loadDiscover(){
|
||||
chatCtx={};
|
||||
if(favP){await favP;favP=null}
|
||||
if(allV.length){rDF();rVCNP('discoverVideos',allV);return}
|
||||
renderSkeletons('discoverVideos',6);
|
||||
try{const r=await api('/api/videos?module=数人');if(r.code===0){allV=r.data||[];vD=r.disciplines||[];rDF();rVCNP('discoverVideos',allV)}}catch(e){document.getElementById('discoverVideos').innerHTML='<p style="color:#8A8580;text-align:center;padding:40px;grid-column:1/-1">'+t('loadFailVideo')+'</p>'}
|
||||
}
|
||||
const VC_PAGE=12;
|
||||
let vcList=[],vcCount=0,vcObserver=null;
|
||||
const UP_COLORS=['#5B6AF0','#818CF8','#F59E0B','#10B981','#EF4444','#8B5CF6','#EC4899','#14B8A6'];
|
||||
function hashStr(s){let h=0;for(let i=0;i<s.length;i++){h=((h<<5)-h)+s.charCodeAt(i);h|=0}return Math.abs(h)}
|
||||
function fmtNum(n){return n>=10000?(n/10000).toFixed(1)+'万':''+n}
|
||||
function fmtDur(m){m=parseInt(m)||0;return(m<10?'0'+m:''+m)+':00'}
|
||||
function rVCNP(cid,vids){
|
||||
const c=document.getElementById(cid);
|
||||
vcList=vids||[];vcCount=0;c.innerHTML='';
|
||||
if(!vcList.length){c.innerHTML='<p style="color:#94A3B8;text-align:center;padding:40px;grid-column:1/-1">'+t('noVideo')+'</p>';return}
|
||||
appendVC(cid);
|
||||
}
|
||||
function appendVC(cid){
|
||||
const c=document.getElementById(cid);
|
||||
const batch=vcList.slice(vcCount,vcCount+VC_PAGE);
|
||||
if(!batch.length)return;
|
||||
vcCount+=batch.length;
|
||||
const old=document.getElementById('npLoadHint');if(old)old.remove();
|
||||
c.insertAdjacentHTML('beforeend',batch.map(buildVC).join(''));
|
||||
if(vcCount<vcList.length){
|
||||
c.insertAdjacentHTML('beforeend','<div class="np-load-hint" id="npLoadHint">'+t('loadMore')+'</div>');
|
||||
if(!vcObserver){vcObserver=new IntersectionObserver(es=>{es.forEach(en=>{if(en.isIntersecting)appendVC('discoverVideos')})},{root:document.getElementById('mainContent'),rootMargin:'240px'})}
|
||||
vcObserver.observe(document.getElementById('npLoadHint'));
|
||||
}else if(vcList.length>VC_PAGE){
|
||||
c.insertAdjacentHTML('beforeend','<div class="np-load-hint">'+t('loadEnd')+vcList.length+t('loadEndSuf')+'</div>');
|
||||
}
|
||||
}
|
||||
function buildVC(v){
|
||||
const dur=v.duration_min||5,disc=v.discipline||'',author=v.author||'';
|
||||
const icon=v.discipline_icon&&v.discipline_icon!=='·'?v.discipline_icon:'📺';
|
||||
const h=hashStr(disc||v.title);
|
||||
const coverBg='linear-gradient(135deg,hsl('+(h%360)+',45%,90%),hsl('+((h+30)%360)+',45%,84%))';
|
||||
const views=hashStr(v.title)%50000+300,dms=hashStr(v.title+'#dm')%900;
|
||||
const upColor=UP_COLORS[hashStr(author)%UP_COLORS.length];
|
||||
const initial=(author||'?').replace(/学院|·|同学|系/g,'').slice(0,1)||'?';
|
||||
const faved=favC['video_'+v.id];
|
||||
const click='onclick="openVideo('+v.id+',\''+esc(v.title)+'\',\''+esc(v.description||'')+'\',\''+esc(v.video_url||'')+'\',\''+esc(author)+'\',\''+esc(disc)+'\')"';
|
||||
const cover=v.cover_url
|
||||
?'<div class="np-cover-inner"><img class="np-cover-img" src="'+esc(v.cover_url)+'" alt="" loading="lazy" onerror="this.parentNode.style.background=\''+coverBg+'\'"></div>'
|
||||
:'<div class="np-cover-inner" style="background:'+coverBg+'"><span class="np-cover-icon">'+icon+'</span></div>';
|
||||
return '<div class="np-card" '+click+'>'+
|
||||
'<div class="np-cover">'+cover+
|
||||
'<span class="np-cover-duration">'+fmtDur(dur)+'</span>'+
|
||||
'<span class="np-cover-play">▶</span>'+
|
||||
'<button class="np-cover-fav'+(faved?' faved':'')+'" onclick="event.stopPropagation();tF(\'video\','+v.id+',\''+esc(v.title)+'\',\''+esc(icon)+'\')">'+(faved?'❤️':'🤍')+'</button></div>'+
|
||||
'<div class="np-body"><div class="np-card-title">'+esc(v.title)+'</div>'+
|
||||
'<div class="np-stats"><span class="np-stat">👁 '+fmtNum(views)+'</span><span class="np-stat">💬 '+fmtNum(dms)+'</span></div>'+
|
||||
'<div class="np-uploader"><span class="np-up-avatar" style="background:'+upColor+'">'+esc(initial)+'</span><span class="np-up-name">'+esc(author)+'</span></div>'+
|
||||
'</div></div>';
|
||||
}
|
||||
async function loadKnowledgePage(){
|
||||
chatCtx={type:'knowledge'};
|
||||
if(allK.length){rKCFilters();rKCCards();rKP();return}
|
||||
try{const r=await api('/api/knowledge');if(r.code===0){allK=r.data||[];kT=r.topics||[];rKCFilters();rKCCards()}}catch(e){document.getElementById('knowledgeGrid').innerHTML='<p style="color:#94A3B8;text-align:center;padding:40px;grid-column:1/-1">卡片加载失败</p>'}}
|
||||
function rKCFilters(){let h='<button class="filter-btn'+(cT===''?' active':'')+'" onclick="fKC(\'\')">'+t('filterAll')+'</button>';kT.forEach(t=>{h+='<button class="filter-btn'+(cT===t?' active':'')+'" onclick="fKC(\''+esc(t)+'\')">'+esc(t)+'</button>'});document.getElementById('knowledgeFilters').innerHTML=h}
|
||||
function fKC(t){cT=t;rKCFilters();rKCCards()}
|
||||
function rKP(){
|
||||
const bar=document.getElementById('knowledgeDrawProgress');
|
||||
if(!bar)return;
|
||||
const readThisSession=(cardState.knowledgeRead||0)%3;
|
||||
const needMore=3-readThisSession;
|
||||
bar.style.display='flex';
|
||||
if(needMore<=0){
|
||||
document.getElementById('drawProgressText').textContent='🎉 可以抽卡了!点击去抽卡';
|
||||
document.getElementById('drawProgressFill').style.width='100%';
|
||||
bar.classList.add('draw-progress-ready');
|
||||
}else{
|
||||
document.getElementById('drawProgressText').textContent='再读 '+needMore+' 张知识卡片即可抽卡';
|
||||
document.getElementById('drawProgressFill').style.width=(readThisSession/3*100)+'%';
|
||||
bar.classList.remove('draw-progress-ready');
|
||||
}
|
||||
}
|
||||
function rKCCards(){const cards=cT?allK.filter(c=>c.topic===cT):allK;const g=document.getElementById('knowledgeGrid');if(!cards||cards.length===0){g.innerHTML='<p style="color:#94A3B8;text-align:center;padding:40px;grid-column:1/-1">'+t('noCard')+'</p>';return}g.innerHTML=cards.map(c=>'<div class="knowledge-card" onclick="openKnowledgeDetail('+c.id+')"><div class="knowledge-card-visual" style="background:linear-gradient(135deg,'+c.color+','+adj(c.color,30)+')"><span style="font-size:42px;z-index:1">'+c.icon+'</span><div class="knowledge-card-question">'+esc(c.question)+'</div></div><div class="knowledge-card-body"><div class="knowledge-card-title">'+esc(c.title)+'</div><div class="knowledge-card-desc">'+esc(c.description)+'</div><span class="knowledge-card-time">'+esc(c.read_time)+'</span></div></div>').join('');setTimeout(rKP,100)}
|
||||
function rDF(){let h='<button class="filter-btn'+(cD===''?' active':'')+'" onclick="fV(\'\')">'+t('filterAll')+'</button>';vD.forEach(d=>{h+='<button class="filter-btn'+(cD===d.discipline?' active':'')+'" onclick="fV(\''+esc(d.discipline)+'\')">'+(d.discipline_icon||'')+' '+esc(d.discipline)+'</button>'});document.getElementById('discoverFilters').innerHTML=h}
|
||||
function fV(d){cD=d;rDF();rVCNP('discoverVideos',d?allV.filter(v=>v.discipline===d):allV)}
|
||||
function openVideo(id,title,desc,url,author,disc){chatCtx={type:'video',title:title};rH('video',id,title);let h='<h2 style="font-size:18px;font-weight:700;margin-bottom:12px">'+esc(title)+'</h2>';h+=url?'<div style="aspect-ratio:16/9;background:#111;border-radius:12px;margin-bottom:12px;display:flex;align-items:center;justify-content:center"><iframe src="'+esc(url)+'" style="width:100%;height:100%;border-radius:12px" frameborder="0" allowfullscreen></iframe></div>':'<div style="aspect-ratio:16/9;background:#F1F5F9;border-radius:12px;margin-bottom:12px;display:flex;align-items:center;justify-content:center;font-size:48px;color:#CBD5E1">▶</div>';h+='<p style="font-size:14px;color:#64748B;line-height:1.7;margin-bottom:8px">'+esc(desc||'No description')+'</p><div style="display:flex;gap:8px;font-size:12px;color:#94A3B8"><span>'+esc(author)+'</span><span>'+esc(disc)+'</span></div><div style="margin-top:12px"><button onclick="closeToolModal()" style="padding:8px 20px;border-radius:10px;border:1.5px solid #E2E8F0;background:#fff;cursor:pointer;font-size:13px">'+t('close')+'</button></div>';document.getElementById('toolModalContent').innerHTML=h;document.getElementById('toolModal').classList.add('show')}
|
||||
async function openKnowledgeDetail(id){chatCtx={type:'knowledge',title:''};
|
||||
cardState.knowledgeRead=(cardState.knowledgeRead||0)+1;
|
||||
if(cardState.knowledgeRead>=3&&cardState.knowledgeRead%3===0){
|
||||
cardState.draws+=1;
|
||||
saveCardState();
|
||||
}else{
|
||||
saveCardState();
|
||||
}
|
||||
rKP();
|
||||
try{const r=await api('/api/knowledge/'+id);if(r.code===0){const c=r.data;chatCtx.title=c.title;document.getElementById('toolModalContent').innerHTML='<div style="width:100%;height:80px;border-radius:12px;background:linear-gradient(135deg,'+c.color+','+adj(c.color,30)+');display:flex;align-items:center;justify-content:center;font-size:40px;margin-bottom:16px">'+c.icon+'</div><h2 style="font-size:20px;font-weight:700;margin-bottom:4px">'+esc(c.title)+'</h2><p style="font-size:13px;color:#5B6AF0;font-weight:600;margin-bottom:12px">'+esc(c.question)+'</p><p style="font-size:14px;color:#475569;line-height:1.8">'+esc(c.detail||c.description)+'</p><div style="margin-top:16px;display:flex;justify-content:space-between;align-items:center"><span style="font-size:12px;color:#94A3B8">'+esc(c.read_time)+' · '+esc(c.topic||'')+'</span><button onclick="qA(\''+esc(c.title)+t('aiAskSuffix')+'\')" style="padding:6px 14px;border-radius:20px;border:none;background:#5B6AF0;color:#fff;font-size:12px;cursor:pointer">🤖 '+t('aiAskBtn')+'</button></div>'}}catch(e){}document.getElementById('toolModal').classList.add('show')}
|
||||
function closeToolModal(){var m=document.getElementById('toolModal');if(m)m.classList.remove('show')}
|
||||
function loadToolbox(){chatCtx={}}
|
||||
let toolState={};
|
||||
function openTool(name){
|
||||
let h='';
|
||||
if(name==='mathrace'){toolState={score:0,total:0,time:60,timer:null,correct:0};h=rMR()}
|
||||
else if(name==='calculator')h=rCalc();
|
||||
else if(name==='symbols')h=rSym();
|
||||
else if(name==='grapher')h=rGraph();
|
||||
document.getElementById('toolModalContent').innerHTML=h;
|
||||
document.getElementById('toolModal').classList.add('show');
|
||||
if(name==='mathrace')startMRLoop();
|
||||
if(name==='grapher')setTimeout(plotGraph,200);
|
||||
}
|
||||
function rMR(){return'<div class="math-race"><div class="timer-bar"><div class="timer-fill" id="mrTimer" style="width:100%"></div></div><div class="stats"><span>✅ <b id="mrScore">0</b></span><span>⏱ <b id="mrTime">60s</b></span><span>📝 <b id="mrTotal">0</b></span></div><div class="question" id="mrQuestion">准备好了吗?</div><div class="answer-area"><input type="number" id="mrAnswer" onkeydown="if(event.key===\'Enter\')checkMR()" placeholder="输入答案" autofocus></div><div class="result" id="mrResult"></div></div>'}
|
||||
function startMRLoop(){toolState={score:0,total:0,time:60,timer:null,correct:0};document.getElementById('mrScore').textContent='0';document.getElementById('mrTime').textContent='60s';document.getElementById('mrTotal').textContent='0';document.getElementById('mrResult').textContent='';document.getElementById('mrAnswer').value='';document.getElementById('mrAnswer').disabled=false;document.getElementById('mrAnswer').focus();genMR();toolState.timer=setInterval(()=>{toolState.time--;document.getElementById('mrTime').textContent=toolState.time+'s';document.getElementById('mrTimer').style.width=(toolState.time/60*100)+'%';if(toolState.time<=0){clearInterval(toolState.timer);document.getElementById('mrAnswer').disabled=true;document.getElementById('mrResult').innerHTML='⏰ 时间到!得分:<b>'+toolState.score+'</b> 正确:'+toolState.correct+'/'+toolState.total;addPetExp(toolState.score)}},1000);genMR()}
|
||||
function genMR(){const ops=['+','-','×'];const op=ops[Math.floor(Math.random()*3)];let a,b;if(op==='+'){a=Math.floor(Math.random()*90)+10;b=Math.floor(Math.random()*90)+10}else if(op==='-'){a=Math.floor(Math.random()*90)+10;b=Math.floor(Math.random()*a)}else{a=Math.floor(Math.random()*12)+2;b=Math.floor(Math.random()*12)+2}toolState.answer=op==='+'?a+b:op==='-'?a-b:a*b;toolState.total++;document.getElementById('mrTotal').textContent=toolState.total;document.getElementById('mrQuestion').textContent=a+' '+op+' '+b+' = ?';document.getElementById('mrAnswer').value='';document.getElementById('mrAnswer').focus()}
|
||||
function checkMR(){const ans=parseInt(document.getElementById('mrAnswer').value);if(isNaN(ans))return;if(ans===toolState.answer){toolState.score+=10;toolState.correct++;document.getElementById('mrResult').innerHTML='<span style="color:#8AA68A">✅ '+t('mrRight')+'</span>'}else{document.getElementById('mrResult').innerHTML='<span style="color:#C49595">❌ '+toolState.answer+'</span>'}document.getElementById('mrScore').textContent=toolState.score;if(toolState.time>0)setTimeout(genMR,600)}
|
||||
function rCalc(){return'<div style="padding:20px"><div style="display:flex;gap:8px;margin-bottom:16px"><input id="calcInput" style="flex:1;padding:12px;border:1.5px solid #E2E8F0;border-radius:12px;font-size:18px;text-align:right;outline:none" placeholder="输入表达式 如 2+3*4" onkeydown="if(event.key===\'Enter\')doCalc()"><button onclick="doCalc()" style="padding:12px 24px;border-radius:12px;border:none;background:#5B6AF0;color:#fff;font-size:16px;cursor:pointer">=</button></div><div style="text-align:right;font-size:24px;font-weight:800;min-height:36px" id="calcResult"></div><div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:16px">'+['(',')','C','÷','7','8','9','×','4','5','6','-','1','2','3','+','0','.','sin(','√('].map(k=>'<button onclick="calcKey(\''+k+'\')" style="padding:12px;border:1.5px solid #E2E8F0;border-radius:10px;background:#FFF;cursor:pointer;font-size:14px;font-weight:600;transition:all .15s">'+k+'</button>').join('')+'</div></div>'}
|
||||
function calcKey(k){const i=document.getElementById('calcInput');if(k==='C')i.value='';else if(k==='÷')i.value+='/';else if(k==='×')i.value+='*';else if(k==='sin(')i.value+='Math.sin(';else if(k==='√(')i.value+='Math.sqrt(';else i.value+=k;i.focus()}
|
||||
function doCalc(){try{const v=eval(document.getElementById('calcInput').value);document.getElementById('calcResult').textContent=Number.isInteger(v)?v:v.toFixed(6)}catch(e){document.getElementById('calcResult').textContent='表达式有误'}}
|
||||
function rSym(){
|
||||
const syms=[['π','Pi · 圆周率','3.14159... · \\pi'],['∞','无穷大','没有边界 · \\infty'],['Σ','求和符号','累加 · \\sum'],['∫','积分符号','面积/累积 · \\int'],['√','平方根','x²=a → x=√a · \\sqrt{}'],['Δ','变化量/判别式','差值△y/△x · \\Delta'],['∂','偏导数','多元函数求导 · \\partial'],['lim','极限','趋近于 · \\lim_{x \\to 0}'],['e','自然常数','≈2.71828 · e'],['i','虚数单位','i² = -1 · i'],['∏','连乘符号','累乘 · \\prod'],['∈','属于','元素∈集合 · \\in']];
|
||||
return'<div class="symbol-grid">'+syms.map(s=>'<button class="symbol-btn" onclick="sS(\''+s[0]+'\',\''+s[1]+'\',\''+s[2]+'\')">'+s[0]+'<div style="font-size:10px;color:#94A3B8">'+s[1]+'</div></button>').join('')+'</div><div class="symbol-detail" id="symDetail">👆 点击符号查看详情</div>'
|
||||
}
|
||||
function sS(sym,name,latex){document.getElementById('symDetail').innerHTML='<b style="font-size:28px">'+sym+'</b><div>'+name+'</div><div style="color:#5B6AF0;font-family:monospace">LaTeX: '+latex+'</div>'}
|
||||
function rGraph(){return'<div class="graph-container"><canvas id="graphCanvas" width="500" height="300"></canvas></div><div class="graph-inputs"><input id="graphFunc" value="Math.sin(x)" placeholder="如 Math.sin(x) 或 x*x"><button onclick="plotGraph()">绘制</button></div>'}
|
||||
function plotGraph(){const c=document.getElementById('graphCanvas');if(!c)return;const ctx=c.getContext('2d');ctx.clearRect(0,0,c.width,c.height);ctx.strokeStyle='#D6D1CA';ctx.lineWidth=1;for(let i=0;i<=c.width;i+=50){ctx.beginPath();ctx.moveTo(i,0);ctx.lineTo(i,c.height);ctx.stroke()}for(let i=0;i<=c.height;i+=50){ctx.beginPath();ctx.moveTo(0,i);ctx.lineTo(c.width,i);ctx.stroke()}ctx.strokeStyle='#808C95';ctx.lineWidth=2;ctx.beginPath();const fn=new Function('x','return '+document.getElementById('graphFunc').value);const sx=c.width/2,sy=c.height/2,scale=30;for(let px=0;px<c.width;px++){const x=(px-sx)/scale;try{const y=fn(x);const py=sy-y*scale;if(py>-100&&py<c.height+100){if(px===0)ctx.moveTo(px,py);else ctx.lineTo(px,py)}}catch(e){}}ctx.stroke()}
|
||||
async function loadStory(){chatCtx={};if(tt)clearTimeout(tt);try{const r=await api('/api/story');if(r.code===0){sD=r.data;const saved=localStorage.getItem('mb_story_save');if(saved){try{sVN=JSON.parse(saved)}catch(e){}}const sn=sD.start_node||'ch1_start';const ni=(sVN.currentNode&&sD.nodes&&sD.nodes[sVN.currentNode])?sVN.currentNode:sn;sVN.currentNode=ni;rSN(ni)}}catch(e){document.getElementById('storyScene').innerHTML='<p style="color:#EF4444">故事加载失败</p>'}}
|
||||
function rSN(ni){if(!sD||!sD.nodes||!sD.nodes[ni]){document.getElementById('storyScene').innerHTML='<p style="color:#EF4444">节点未找到</p>';return}const n=sD.nodes[ni];sVN.currentNode=ni;localStorage.setItem('mb_story_save',JSON.stringify(sVN));const ch=dC(ni),cN=['','初入江湖','西部教习','数理论道','脑学初创','报国之路','暗流涌动','陋室修炼','真相之门'];document.getElementById('storyStats').innerHTML='<span class="story-stat" style="background:#808C95">📐 '+(sVN.stats.shuli||10)+'</span><span class="story-stat" style="background:#C4A467">⚔️ '+(sVN.stats.xiayi||10)+'</span><span class="story-stat" style="background:#8AA68A">🧘 '+(sVN.stats.xinzhi||10)+'</span><span class="story-stat" style="background:#657078">'+ch+' · '+(cN[ch]||'')+'</span><button onclick="resetStory()" style="margin-left:auto;padding:6px 14px;border-radius:8px;border:1px solid #D6D1CA;background:#FDFCFA;cursor:pointer;font-size:12px;color:#8A8580">🔄 '+t('simReset')+'</button>';document.getElementById('storyScene').innerHTML=(n.character?'<div class="story-character">'+esc(n.character)+'</div>':'')+'<div class="story-text" id="storyText"></div>';document.getElementById('storyChoices').innerHTML='';if(tt)clearTimeout(tt);const te=document.getElementById('storyText'),tx=n.scene||'';let i=0;te.textContent='';function ty(){if(i<tx.length){te.textContent+=tx.charAt(i);i++;tt=setTimeout(ty,18)}else{rSCs(n.choices||[])}}ty()}
|
||||
function rSCs(choices){document.getElementById('storyChoices').innerHTML=choices.map((c,i)=>{let h='';if(c.effects){const p=[];if(c.effects.shuli)p.push('数理根基'+(c.effects.shuli>0?'+':'')+c.effects.shuli);if(c.effects.xiayi)p.push('侠义声望'+(c.effects.xiayi>0?'+':'')+c.effects.xiayi);if(c.effects.xinzhi)p.push('心志韧性'+(c.effects.xinzhi>0?'+':'')+c.effects.xinzhi);if(p.length)h='<span style="font-size:11px;color:#8B5CF6;font-weight:500">['+p.join(' | ')+']</span>'}return'<button class="story-choice" onclick="sC(\''+c.next+'\','+JSON.stringify(c.effects||{})+')">'+esc(c.text)+h+'</button>'}).join('')}
|
||||
function sC(next,ef){aSE(ef);rSN(next)}
|
||||
function aSE(ef){if(!ef)return;if(ef.shuli)sVN.stats.shuli=(sVN.stats.shuli||10)+ef.shuli;if(ef.xiayi)sVN.stats.xiayi=(sVN.stats.xiayi||10)+ef.xiayi;if(ef.xinzhi)sVN.stats.xinzhi=(sVN.stats.xinzhi||10)+ef.xinzhi;if(ef.favorability){if(!sVN.fav)sVN.fav={};for(const k in ef.favorability)sVN.fav[k]=(sVN.fav[k]||0)+ef.favorability[k]}}
|
||||
function resetStory(){if(tt)clearTimeout(tt);sVN={currentNode:null,stats:{shuli:10,xiayi:10,xinzhi:10},fav:{}};localStorage.removeItem('mb_story_save');loadStory()}
|
||||
function dC(ni){if(!ni)return 1;const m=ni.match(/^ch(\d+)/);return m?parseInt(m[1]):1}
|
||||
async function loadSimulator(){chatCtx={type:'simulator'};try{const r=await api('/api/simulator');if(r.code===0){simD=r.data;try{const sr=await api('/api/simulator/save?user_id='+USER_ID);if(sr.code===0&&sr.data)simS=sr.data}catch(e){}const sn=simD.start_node||'y1_welcome';if(!simS.currentNode||!simD.nodes||!simD.nodes[simS.currentNode])simS.currentNode=sn;rSiN(simS.currentNode)}}catch(e){document.getElementById('simScene').innerHTML='<p style="color:#EF4444">模拟器加载失败</p>'}}
|
||||
function rSiN(ni){if(!simD||!simD.nodes||!simD.nodes[ni]){document.getElementById('simScene').innerHTML='<p style="color:#EF4444">节点未找到</p>';return}const n=simD.nodes[ni];simS.currentNode=ni;api('/api/simulator/save',{method:'POST',body:{user_id:USER_ID,state:simS}}).catch(()=>{});document.getElementById('simStats').innerHTML='<div class="sim-stat"><div class="sim-stat-val" style="color:#808C95">'+(simS.stats.gpa||3).toFixed(1)+'</div><div class="sim-stat-label">GPA</div></div><div class="sim-stat"><div class="sim-stat-val" style="color:#C4A467">'+(simS.stats.interest||0)+'</div><div class="sim-stat-label">'+t('interestLabel')+'</div></div><div class="sim-stat"><div class="sim-stat-val" style="color:#8AA68A">'+(simS.stats.skill||0)+'</div><div class="sim-stat-label">'+t('skillLabel')+'</div></div><button onclick="resetSim()" style="margin-left:auto;padding:6px 14px;border-radius:8px;border:1px solid #D6D1CA;background:#FDFCFA;cursor:pointer;font-size:12px;color:#8A8580">🔄 '+t('simReset')+'</button>';document.getElementById('simScene').innerHTML=(n.stage?'<div style="font-size:12px;color:#5B6AF0;font-weight:600;margin-bottom:8px">📅 '+esc(n.stage)+'</div>':'')+(n.character?'<div class="story-character">'+esc(n.character)+'</div>':'')+'<div class="story-text">'+esc(n.scene||'')+'</div>';document.getElementById('simChoices').innerHTML=(n.choices||[]).map(c=>'<button class="story-choice" onclick="siC(\''+c.next+'\','+JSON.stringify(c.effects||{})+')">'+esc(c.text)+(c.hint?'<span style="font-size:11px;color:#94A3B8;font-weight:400;display:block;margin-top:4px">'+esc(c.hint)+'</span>':'')+'</button>').join('')}
|
||||
function siC(next,ef){if(ef){if(ef.gpa)simS.stats.gpa=Math.max(0,Math.min(4,(simS.stats.gpa||3)+ef.gpa*.1));if(ef.interest)simS.stats.interest=(simS.stats.interest||0)+ef.interest;if(ef.skill)simS.stats.skill=(simS.stats.skill||0)+ef.skill}rSiN(next);addPetExp(5)}
|
||||
function resetSim(){simS={currentNode:null,stats:{gpa:3,interest:0,skill:0}};loadSimulator()}
|
||||
async function loadFavorites(){chatCtx={};try{await rFC()}catch(e){}try{const r=await api('/api/favorites?user_id='+USER_ID);const g=document.getElementById('favGrid');if(r.code===0){const f=r.data||[];if(f.length===0){g.innerHTML='<p style="color:#94A3B8;text-align:center;padding:60px;grid-column:1/-1">暂无收藏<br><span style="font-size:12px;margin-top:8px;display:block">在发现页点击 🤍 即可收藏</span></p>';return}g.innerHTML=f.map(fv=>'<div class="fav-card"><button class="fav-card-remove" onclick="rF(\''+fv.target_type+'\','+fv.target_id+')">✕</button><div style="font-size:11px;color:#94A3B8;margin-bottom:4px">'+(fv.target_type==='video'?'📺 视频':'📚 知识卡片')+'</div><div style="font-size:14px;font-weight:600">'+esc(fv.title)+'</div></div>').join('')}}catch(e){document.getElementById('favGrid').innerHTML='<p style="color:#94A3B8;text-align:center;padding:40px">加载失败</p>'}}
|
||||
async function rFC(){try{const r=await api('/api/favorites?user_id='+USER_ID);if(r.code===0){favC={};(r.data||[]).forEach(f=>{favC[f.target_type+'_'+f.target_id]=true})}}catch(e){}}
|
||||
async function tF(type,id,title,cover){if(favC[type+'_'+id]){await api('/api/favorites',{method:'DELETE',body:{user_id:USER_ID,target_type:type,target_id:id}});favC[type+'_'+id]=false;showToast('🗑 '+t('close')+' ✔')}else{await api('/api/favorites',{method:'POST',body:{user_id:USER_ID,target_type:type,target_id:id,title:title,cover:cover}});favC[type+'_'+id]=true;showToast('❤ '+t('saveOK'))}if(CP==='discover')fV(cD);else if(CP==='favorites')loadFavorites()}
|
||||
async function rF(type,id){await api('/api/favorites',{method:'DELETE',body:{user_id:USER_ID,target_type:type,target_id:id}});loadFavorites()}
|
||||
async function rH(type,id,title){try{await api('/api/history',{method:'POST',body:{user_id:USER_ID,target_type:type,target_id:id,title:title}})}catch(e){}}
|
||||
async function loadProfile(){chatCtx={};try{const[pr,sr]=await Promise.all([api('/api/user/profile?user_id='+USER_ID),api('/api/user/stats?user_id='+USER_ID)]);const p=pr.code===0?pr.data:{nickname:'游客用户',avatar_emoji:'葫',avatar_color:'#5B6AF0'};const s=sr.code===0?sr.data:{watch_count:0,favorite_count:0};document.getElementById('profileContent').innerHTML='<div class="profile-card"><div class="profile-avatar-lg" style="background:rgba(255,255,255,.2)">'+esc(p.avatar_emoji||'葫')+'</div><div><div class="profile-name-lg">'+esc(p.nickname||'游客用户')+'</div><div class="profile-id-lg">ID: '+USER_ID+'</div></div></div><div class="profile-stats-grid"><div class="profile-stat-card"><div class="num">'+s.watch_count+'</div><div class="label">观看次数</div></div><div class="profile-stat-card"><div class="num">'+s.favorite_count+'</div><div class="label">收藏数量</div></div></div><div style="background:#fff;border-radius:16px;padding:16px 20px;box-shadow:0 1px 3px rgba(0,0,0,.04)"><div style="font-weight:600;margin-bottom:12px">编辑资料</div><div style="display:flex;gap:12px;align-items:center;margin-bottom:12px"><span style="font-size:13px;color:#64748B">头像:</span><input id="editAvatar" style="padding:8px 12px;border:1.5px solid #E2E8F0;border-radius:10px;font-size:14px;width:80px" value="'+esc(p.avatar_emoji||'葫')+'"></div><div style="display:flex;gap:12px;align-items:center;margin-bottom:16px"><span style="font-size:13px;color:#64748B">昵称:</span><input id="editNickname" style="flex:1;padding:8px 12px;border:1.5px solid #E2E8F0;border-radius:10px;font-size:14px" value="'+esc(p.nickname||'')+'"></div><button onclick="sP()" style="padding:10px 28px;border-radius:12px;border:none;background:#5B6AF0;color:#fff;font-size:14px;font-weight:600;cursor:pointer">保存</button></div>'}catch(e){document.getElementById('profileContent').innerHTML='<p style="color:#94A3B8">加载失败</p>'}}
|
||||
async function sP(){const n=document.getElementById('editNickname').value.trim();const a=document.getElementById('editAvatar').value.trim();try{await api('/api/user/profile',{method:'POST',body:{user_id:USER_ID,nickname:n,avatar_emoji:a}});document.getElementById('sidebarName').textContent=n||'游客用户';document.getElementById('sidebarAvatar').textContent=a||'葫';alert('保存成功');loadProfile()}catch(e){alert('保存失败')}}
|
||||
async function loadCardsPage(){
|
||||
chatCtx={};
|
||||
document.getElementById('cardsProgress').textContent=cardState.owned.length+'/16';
|
||||
const drawBtn=document.getElementById('cardsDrawBtn');
|
||||
const drawCount=document.getElementById('cardsDrawCount');
|
||||
drawCount.textContent=cardState.draws;
|
||||
drawBtn.style.display=cardState.draws>0?'inline-flex':'none';
|
||||
const grid=document.getElementById('cardsCollectionGrid');
|
||||
await loadCardData();
|
||||
const allCodes=getAllCardCodes();
|
||||
if(!allCodes.length){grid.innerHTML='<p style="color:#8A8580;text-align:center;padding:40px;grid-column:1/-1">卡片数据加载中…</p>';return}
|
||||
let h='';
|
||||
allCodes.forEach(code=>{
|
||||
const info=getCardInfo(code);
|
||||
const clan=info?getClanInfo(info.clan):null;
|
||||
const clanColor=clan?clan.color:'#A8907E';
|
||||
const owned=cardState.owned.indexOf(code)!==-1;
|
||||
h+='<div class="cc-card'+(owned?' owned':' locked')+'" style="--clan-color:'+clanColor+'"'+(owned?' onclick="openCardDetail(\''+code+'\')"':'')+'>'+
|
||||
(owned
|
||||
?'<div class="cc-card-icon" style="background:'+(info?info.icon_bg:'#F0F0F0')+'">'+(info?info.icon:'?')+'</div><div class="cc-card-name">'+(info?info.name:'???')+'</div><div class="cc-card-type">'+code+'</div><div class="cc-card-clan" style="background:'+clanColor+'15;color:'+clanColor+'">'+(clan?clan.emoji+' '+clan.name:'')+'</div>'
|
||||
:'<div class="cc-card-icon locked-icon">?</div><div class="cc-card-name">???</div><div class="cc-card-type">未获得</div><div class="cc-card-clan" style="color:#ABA6A1">'+(clan?clan.emoji+' '+clan.name:'未知')+'</div>')+
|
||||
'</div>';
|
||||
});
|
||||
grid.innerHTML=h;
|
||||
}
|
||||
function addPetExp(amt){
|
||||
let pet=JSON.parse(localStorage.getItem('mathbrain_pet')||'null');
|
||||
if(!pet)return;
|
||||
pet.exp=(pet.exp||0)+amt;
|
||||
let lvlU=false;
|
||||
while(pet.exp>=100){pet.exp-=100;pet.level=(pet.level||1)+1;lvlU=true}
|
||||
localStorage.setItem('mathbrain_pet',JSON.stringify(pet));
|
||||
const bar=document.querySelector('.pet-exp-fill');
|
||||
if(bar){const pct=pet.exp%100;bar.style.transition='none';bar.style.width='0%';requestAnimationFrame(()=>{requestAnimationFrame(()=>{bar.style.transition='width .4s cubic-bezier(.34,1.56,.64,1)';bar.style.width=pct+'%'})})}
|
||||
if(lvlU){showToast('🎉 '+t('petLevelUp').replace('{n}',pet.level));burstConfetti(document.querySelector('.pet-card'))}
|
||||
}
|
||||
async function sendChat(){const i=document.getElementById('chatInput');const m=i.value.trim();if(!m)return;aCM('user',m);i.value='';const tid=aCT();try{const r=await api('/api/chat',{method:'POST',body:{message:m,context:chatCtx}});rCT(tid);aCM('assistant',r.code===0?r.reply:'抱歉,AI暂时无法回复')}catch(e){rCT(tid);aCM('assistant','网络异常,请稍后重试')}}
|
||||
function qA(q){document.getElementById('chatInput').value=q;closeToolModal();toggleAIPanel();setTimeout(()=>sendChat(),300)}
|
||||
function aCM(role,text){const c=document.getElementById('aiMessages');const d=document.createElement('div');d.className='chat-msg '+role;d.innerHTML='<div class="chat-bubble">'+esc(text)+'</div>';c.appendChild(d);c.scrollTop=c.scrollHeight}
|
||||
function aCT(){const c=document.getElementById('aiMessages');const d=document.createElement('div');d.className='chat-msg assistant';d.innerHTML='<div class="chat-bubble"><span style="display:flex;gap:4px"><span style="width:6px;height:6px;border-radius:50%;background:#CBD5E1;animation:bounce 1s infinite"></span><span style="width:6px;height:6px;border-radius:50%;background:#CBD5E1;animation:bounce 1s infinite .2s"></span><span style="width:6px;height:6px;border-radius:50%;background:#CBD5E1;animation:bounce 1s infinite .4s"></span></span></div>';c.appendChild(d);c.scrollTop=c.scrollHeight;return d}
|
||||
function rCT(el){if(el&&el.parentNode)el.remove()}
|
||||
function esc(t){if(!t)return'';esc._d=esc._d||document.createElement('div');esc._d.textContent=t;return esc._d.innerHTML}
|
||||
function adj(hex,amt){const n=parseInt(hex.replace('#',''),16);const r=Math.min(255,(n>>16)+amt);const g=Math.min(255,((n>>8)&0xFF)+amt);const b=Math.min(255,(n&0xFF)+amt);return '#'+((r<<16)|(g<<8)|b).toString(16).padStart(6,'0')}
|
||||
function toggleHub(){document.getElementById('floatHub').classList.toggle('open')}
|
||||
function closeHub(){document.getElementById('floatHub').classList.remove('open')}
|
||||
document.addEventListener('click',e=>{if(!e.target.closest('.float-hub'))document.getElementById('floatHub').classList.remove('open')});
|
||||
function initCardState(){
|
||||
const raw=localStorage.getItem('hulu_card_state');
|
||||
if(raw){try{cardState=JSON.parse(raw)}catch(e){}}
|
||||
if(!cardState.owned)cardState.owned=[];
|
||||
if(!cardState.draws&&cardState.draws!==0)cardState.draws=0;
|
||||
if(!cardState.lastDaily)cardState.lastDaily='';
|
||||
if(!cardState.knowledgeRead&&cardState.knowledgeRead!==0)cardState.knowledgeRead=0;
|
||||
if(!cardState.drawHistory)cardState.drawHistory=[];
|
||||
const today=new Date().toISOString().slice(0,10);
|
||||
if(cardState.lastDaily!==today){
|
||||
cardState.draws+=1;
|
||||
cardState.lastDaily=today;
|
||||
saveCardState();
|
||||
}
|
||||
}
|
||||
function saveCardState(){localStorage.setItem('hulu_card_state',JSON.stringify(cardState));rCG();}
|
||||
function ownedCount(){return cardState.owned.length}
|
||||
function grantCard(code,method){
|
||||
if(!code)return;
|
||||
if(cardState.owned.indexOf(code)===-1){
|
||||
cardState.owned.push(code);
|
||||
cardState.drawHistory.push({code:code,date:new Date().toISOString().slice(0,10),method:method});
|
||||
saveCardState();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
async function loadCardData(){
|
||||
if(cardData)return cardData;
|
||||
try{
|
||||
const r=await api('/api/math_personality');
|
||||
if(r.code===0&&r.data&&r.data.results){cardData=r.data;cardCache=r.data;return cardData}
|
||||
}catch(e){}
|
||||
return null;
|
||||
}
|
||||
function getCardInfo(code){
|
||||
if(!cardCache||!cardCache.results)return null;
|
||||
return cardCache.results[code]||null;
|
||||
}
|
||||
function getAllCardCodes(){
|
||||
if(!cardCache||!cardCache.results)return[];
|
||||
return Object.keys(cardCache.results);
|
||||
}
|
||||
function getClanInfo(clanId){
|
||||
if(!cardCache||!cardCache.clans)return null;
|
||||
return cardCache.clans[clanId]||null;
|
||||
}
|
||||
function rCG(){
|
||||
const wrap=document.getElementById('homeCardGallery');
|
||||
const btn=document.getElementById('homeGachaBtn');
|
||||
const cnt=document.getElementById('cardCountText');
|
||||
const scroll=document.getElementById('cardGalleryScroll');
|
||||
if(!wrap||!scroll)return;
|
||||
const owned=cardState.owned;
|
||||
if(cnt)cnt.textContent=owned.length+'/16';
|
||||
if(owned.length===0){
|
||||
if(!localStorage.getItem('mathbrain_pet')){
|
||||
scroll.innerHTML='<div class="card-gallery-empty"><span class="cg-empty-icon">🏮</span>去做MathBTI<br>领取你的第一张数学人物小卡</div>';
|
||||
}else{
|
||||
scroll.innerHTML='<div class="card-gallery-empty"><span class="cg-empty-icon">🏮</span>还没获得卡片<br>去知识卡片页阅读攒抽卡机会吧</div>';
|
||||
}
|
||||
wrap.style.display='block';
|
||||
if(btn){
|
||||
if(cardState.draws>0){
|
||||
btn.style.display='inline-flex';
|
||||
document.getElementById('homeDrawCount').textContent=cardState.draws;
|
||||
}else{
|
||||
btn.style.display='none';
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
wrap.style.display='block';
|
||||
loadCardData().then(()=>{
|
||||
let h='';
|
||||
owned.forEach(code=>{
|
||||
const info=getCardInfo(code);
|
||||
const clan=info?getClanInfo(info.clan):null;
|
||||
const clanColor=clan?clan.color:'#A8907E';
|
||||
h+='<div class="card-gallery-item" onclick="openCardDetail(\''+code+'\')" style="--clan-color:'+clanColor+'">'+
|
||||
'<div class="cg-icon" style="background:'+(info?info.icon_bg:'#F0F0F0')+'">'+(info?info.icon:'?')+'</div>'+
|
||||
'<div class="cg-name" title="'+(info?info.name:'')+'">'+(info?info.name:'')+'</div></div>';
|
||||
});
|
||||
scroll.innerHTML=h;
|
||||
});
|
||||
if(btn){
|
||||
if(cardState.draws>0){
|
||||
btn.style.display='inline-flex';
|
||||
document.getElementById('homeDrawCount').textContent=cardState.draws;
|
||||
}else{
|
||||
btn.style.display='none';
|
||||
}
|
||||
}
|
||||
}
|
||||
function openGacha(){
|
||||
document.getElementById('gachaOverlay').style.display='flex';
|
||||
document.getElementById('gachaIdle').style.display='block';
|
||||
document.getElementById('gachaResult').style.display='none';
|
||||
document.getElementById('gachaDrawCount').textContent=cardState.draws;
|
||||
const all=cardData?Object.keys(cardData.results).length:16;
|
||||
const missing=all-cardState.owned.length;
|
||||
let pityHint='';
|
||||
if(cardState.drawHistory.length>0){
|
||||
const sinceNew=cardState.drawHistory.slice().reverse().findIndex(d=>cardState.owned.indexOf(d.code)===-1);
|
||||
if(sinceNew>=4)pityHint='⭐ 下次抽卡必出新卡!';
|
||||
else if(sinceNew>=0)pityHint='再抽'+(4-sinceNew)+'次保底出新卡';
|
||||
}
|
||||
document.getElementById('gachaPityHint').textContent=pityHint||'剩余未收集:'+missing+'/16';
|
||||
}
|
||||
function closeGacha(){document.getElementById('gachaOverlay').style.display='none'}
|
||||
function doDraw(){
|
||||
if(_gachaBusy)return;
|
||||
if(cardState.draws<=0){showToast('没有抽卡次数了!去读知识卡片获取吧');return}
|
||||
_gachaBusy=true;
|
||||
const gourd=document.getElementById('gachaGourd');
|
||||
gourd.classList.add('gourd-shaking');
|
||||
document.getElementById('gachaIdle').style.display='block';
|
||||
document.getElementById('gachaResult').style.display='none';
|
||||
setTimeout(()=>{
|
||||
gourd.classList.remove('gourd-shaking');
|
||||
cardState.draws--;
|
||||
saveCardState();
|
||||
let code;
|
||||
const allCodes=getAllCardCodes();
|
||||
const sinceNewIndex=cardState.drawHistory.slice().reverse().findIndex(d=>!cardState.owned.includes(d.code));
|
||||
if(sinceNewIndex>=4||(cardState.drawHistory.length>0&&sinceNewIndex===-1)){
|
||||
const missing2=allCodes.filter(c=>!cardState.owned.includes(c));
|
||||
code=missing2[Math.floor(Math.random()*missing2.length)];
|
||||
}else{
|
||||
code=allCodes[Math.floor(Math.random()*allCodes.length)];
|
||||
}
|
||||
const isNew=cardState.owned.indexOf(code)===-1;
|
||||
if(isNew){
|
||||
cardState.owned.push(code);
|
||||
}
|
||||
cardState.drawHistory.push({code:code,date:new Date().toISOString().slice(0,10),method:'gacha'});
|
||||
saveCardState();
|
||||
showDrawResult(code,isNew);
|
||||
const cr=document.querySelector('.gacha-card-reveal');
|
||||
if(cr)setTimeout(()=>burstGachaConfetti(cr),300);
|
||||
_gachaBusy=false;
|
||||
rCG();
|
||||
},900);
|
||||
}
|
||||
function showDrawResult(code,isNew){
|
||||
loadCardData().then(()=>{
|
||||
const info=getCardInfo(code);
|
||||
const clan=info?getClanInfo(info.clan):null;
|
||||
const clanColor=clan?clan.color:'#A8907E';
|
||||
document.getElementById('gachaIdle').style.display='none';
|
||||
const res=document.getElementById('gachaResult');
|
||||
res.style.display='block';
|
||||
res.innerHTML=
|
||||
'<div class="gacha-card-reveal card-flip-in" style="--clan-color:'+clanColor+'">'+
|
||||
(info&&info.portrait
|
||||
?'<div class="gacha-card-portrait"><img src="'+esc(info.portrait)+'" alt="" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'" style="width:100%;height:100%;object-fit:cover"><span style="display:none;font-size:48px">'+(info.icon||'?')+'</span></div>'
|
||||
:'<div class="gacha-card-portrait"><span style="font-size:48px">'+(info?info.icon:'?')+'</span></div>')+
|
||||
'<div class="gacha-card-name">'+(info?info.name:'???')+'</div>'+
|
||||
'<div class="gacha-card-type">'+code+'</div>'+
|
||||
(clan?'<div class="gacha-card-clan" style="background:'+clanColor+'15;color:'+clanColor+';border:1px solid '+clanColor+'30">'+clan.emoji+' '+clan.name+' · '+(info?info.mathematician:'')+'</div>':'')+
|
||||
'<div class="gacha-card-tagline">'+(info?info.tagline:'')+'</div>'+
|
||||
(isNew?'':'<div class="gacha-card-dupe">(重复获得 · 已自动分解)</div>')+
|
||||
'<button class="gacha-card-detail-btn" onclick="openCardDetail(\''+code+'\')">📋 查看卡片详情</button>'+
|
||||
'<div style="margin-top:12px"><button class="draw-btn" onclick="openGacha();doDraw()" style="font-size:12px;padding:8px 20px"'+(cardState.draws<=0?' disabled':'')+'>再抽一次 (剩余'+cardState.draws+'次)</button></div>'+
|
||||
'</div>';
|
||||
document.getElementById('gachaDrawCount').textContent=cardState.draws;
|
||||
});
|
||||
}
|
||||
function openCardDetail(code){
|
||||
loadCardData().then(()=>{
|
||||
const info=getCardInfo(code);
|
||||
if(!info)return;
|
||||
const clan=getClanInfo(info.clan);
|
||||
const clanColor=clan?clan.color:'#A8907E';
|
||||
const hist=cardState.drawHistory.filter(d=>d.code===code);
|
||||
const obtainInfo=hist.length>0
|
||||
?(hist[0].method==='mathbti'?'📋 MathBTI测试获得':'🏮 葫芦抽卡获得')+' · '+hist[0].date
|
||||
:'未知途径';
|
||||
const modal=document.getElementById('cardDetailModal');
|
||||
modal.innerHTML=
|
||||
'<div class="card-detail-header">'+
|
||||
'<div class="card-detail-bg" style="background:'+clanColor+'"></div>'+
|
||||
'<button class="card-detail-close" onclick="closeCardDetail()">✕</button>'+
|
||||
(info.portrait
|
||||
?'<div class="card-detail-portrait"><img src="'+esc(info.portrait)+'" alt="" onerror="this.style.display=\'none\'"><span style="font-size:40px;display:none">'+(info.icon||'?')+'</span></div>'
|
||||
:'<div class="card-detail-portrait"><span style="font-size:40px">'+(info.icon||'?')+'</span></div>')+
|
||||
'<div class="card-detail-name">'+esc(info.name)+'</div>'+
|
||||
'<div class="card-detail-binary">'+code+'</div>'+
|
||||
(clan?'<div class="card-detail-clan" style="background:'+clanColor+'15;color:'+clanColor+';border:1px solid '+clanColor+'30">'+clan.emoji+' '+clan.name+' · '+esc(info.mathematician)+'</div>':'')+
|
||||
'<div class="card-detail-tagline">'+esc(info.tagline||'')+'</div>'+
|
||||
'</div>'+
|
||||
'<div class="card-detail-body">'+
|
||||
'<div class="card-detail-section"><div class="card-detail-section-title">人物简介</div><p>'+esc(info.description||'')+'</p></div>'+
|
||||
'<div class="card-detail-section"><div class="card-detail-section-title">代表数学家 · '+esc(info.mathematician)+'</div><p>'+esc(info.bio||'')+'</p></div>'+
|
||||
'<div class="card-detail-fact"><span class="fact-emoji">💡</span><span class="fact-text">'+esc(info.bio_fact||'')+'</span></div>'+
|
||||
'<div class="card-detail-obtain"><span class="obtain-icon">🎴</span><span>'+obtainInfo+'</span></div>'+
|
||||
'</div>';
|
||||
document.getElementById('cardDetailOverlay').style.display='flex';
|
||||
});
|
||||
}
|
||||
function closeCardDetail(){document.getElementById('cardDetailOverlay').style.display='none'}
|
||||
function burstGachaConfetti(el){
|
||||
if(!el)return;const r=el.getBoundingClientRect();const cx=r.left+r.width/2,cy=r.top+r.height/2;
|
||||
const colors=['#A8907E','#C47F3A','#D4A574','#8A9BA8','#C49595','#8AA68A'];
|
||||
for(let i=0;i<18;i++){const p=document.createElement('div');p.className='gacha-particle';const a=Math.random()*Math.PI*2,d=40+Math.random()*60;p.style.cssText='left:'+cx+'px;top:'+cy+'px;--gpx:'+(Math.cos(a)*d)+'px;--gpy:'+(Math.sin(a)*d)+'px;background:'+colors[Math.floor(Math.random()*colors.length)];document.body.appendChild(p);setTimeout(()=>p.remove(),1100)}
|
||||
}
|
||||
function showToast(msg){
|
||||
let c=document.getElementById('toastBox');if(!c){c=document.createElement('div');c.id='toastBox';c.className='toast-container';document.body.appendChild(c)}
|
||||
const t=document.createElement('div');t.className='toast';t.innerHTML=msg;c.appendChild(t);setTimeout(()=>t.remove(),2400);
|
||||
}
|
||||
function burstConfetti(el){
|
||||
if(!el)return;const r=el.getBoundingClientRect();const cx=r.left+r.width/2,cy=r.top+r.height/2;
|
||||
const colors=['#808C95','#A8907E','#8AA68A','#C4A467','#C49595','#9AA5AE'];
|
||||
for(let i=0;i<14;i++){const p=document.createElement('div');p.className='particle';const a=Math.random()*Math.PI*2,d=30+Math.random()*50;p.style.cssText='left:'+cx+'px;top:'+cy+'px;width:6px;height:6px;border-radius:2px;background:'+colors[Math.floor(Math.random()*colors.length)]+';--px:'+(Math.cos(a)*d)+'px;--py:'+(Math.sin(a)*d)+'px';document.body.appendChild(p);setTimeout(()=>p.remove(),750)}
|
||||
}
|
||||
function renderSkeletons(cid,count){
|
||||
const c=document.getElementById(cid);c.innerHTML='';
|
||||
for(let i=0;i<count;i++){c.insertAdjacentHTML('beforeend','<div class="skeleton-card"><div class="skeleton skeleton-line w80"></div><div class="skeleton skeleton-line w60"></div><div class="skeleton skeleton-line w40"></div><div style="display:flex;align-items:center;gap:8px;margin-top:auto"><div class="skeleton skeleton-avatar"></div><div class="skeleton skeleton-line" style="width:50%;height:10px"></div></div></div>')}
|
||||
}
|
||||
let favP=null;
|
||||
document.addEventListener('DOMContentLoaded',()=>{
|
||||
document.querySelectorAll('.lang-btn').forEach(b=>b.classList.toggle('active',b.dataset.lang===_lang));
|
||||
document.getElementById('globalSearch').placeholder=t('search');
|
||||
rI18n();
|
||||
loadHome();
|
||||
initCardState();
|
||||
loadCardData();
|
||||
setTimeout(rCG,300);
|
||||
favP=rFC();
|
||||
api('/api/videos?module=数人').then(r=>{if(r.code===0){allV=r.data||[];vD=r.disciplines||[]}}).catch(()=>{});
|
||||
api('/api/knowledge').then(r=>{if(r.code===0){allK=r.data||[];kT=r.topics||[]}}).catch(()=>{});
|
||||
api('/api/user/profile?user_id='+USER_ID).then(r=>{if(r.code===0&&r.data){document.getElementById('sidebarName').textContent=r.data.nickname||'游客用户';document.getElementById('sidebarAvatar').textContent=r.data.avatar_emoji||'葫';document.getElementById('sidebarAvatar').style.background=r.data.avatar_color||'#808C95'}}).catch(()=>{});
|
||||
});
|
||||
@@ -0,0 +1,447 @@
|
||||
// MathBTI 前端逻辑 — 12题 → 16结果 → 4族
|
||||
let testData = null, answers = [], currentQ = 0, myResult = null;
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const r = await fetch('/data/seed_mathbti.json');
|
||||
testData = await r.json();
|
||||
renderIntro();
|
||||
} catch (e) {
|
||||
document.getElementById('introScreen').innerHTML =
|
||||
'<div style="text-align:center;padding:60px 20px"><p style="color:#94A3B8;margin-bottom:12px">加载失败,请检查网络</p><button class="btn-start" onclick="init()">重试</button></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderIntro() {
|
||||
const axesEl = document.getElementById('introAxes');
|
||||
if (axesEl && testData.axes) {
|
||||
axesEl.innerHTML = testData.axes.map(a =>
|
||||
'<div class="intro-axis-item">' +
|
||||
'<span class="axis-emoji">' + (a.emoji_left || '') + '</span>' +
|
||||
'<span class="axis-label">' + a.name + '</span>' +
|
||||
'<span class="axis-emoji">' + (a.emoji_right || '') + '</span>' +
|
||||
'</div>'
|
||||
).join('');
|
||||
}
|
||||
document.querySelector('.intro-meta').textContent =
|
||||
'⏱ 约2分钟 · 4选项 · 16种人格';
|
||||
}
|
||||
|
||||
function startQuiz() {
|
||||
answers = new Array(testData.questions.length).fill(-1);
|
||||
currentQ = 0;
|
||||
switchScreen('quizScreen');
|
||||
renderQuestion();
|
||||
}
|
||||
|
||||
function renderQuestion() {
|
||||
const q = testData.questions[currentQ];
|
||||
document.getElementById('quizQuestion').textContent = q.question;
|
||||
const optEl = document.getElementById('quizOptions');
|
||||
const sel = answers[currentQ];
|
||||
optEl.innerHTML = q.options.map((o, i) =>
|
||||
'<label class="quiz-option' + (sel === i ? ' selected' : '') + '" onclick="selectOption(' + i + ')">' +
|
||||
'<input type="radio" name="q' + currentQ + '" value="' + i + '"' + (sel === i ? ' checked' : '') + '>' +
|
||||
'<span class="opt-text">' + o.text + '</span></label>'
|
||||
).join('');
|
||||
|
||||
// Progress
|
||||
const pct = Math.round((currentQ + 1) / testData.questions.length * 100);
|
||||
document.getElementById('progressFill').style.width = pct + '%';
|
||||
// 最后一题时进度条末尾显示葫芦图标
|
||||
const isLast=currentQ===testData.questions.length-1;
|
||||
document.getElementById('progressFill').style.setProperty('--is-last',isLast?'1':'0');
|
||||
document.getElementById('progressText').textContent = (currentQ + 1) + '/' + testData.questions.length;
|
||||
|
||||
// Next button
|
||||
const btn = document.getElementById('btnNext');
|
||||
btn.disabled = answers[currentQ] === -1;
|
||||
btn.textContent = currentQ === testData.questions.length - 1 ? '查看结果' : '下一题';
|
||||
}
|
||||
|
||||
function selectOption(idx) {
|
||||
answers[currentQ] = idx;
|
||||
document.getElementById('btnNext').disabled = false;
|
||||
// Update visual
|
||||
document.querySelectorAll('.quiz-option').forEach((el, i) => {
|
||||
el.classList.toggle('selected', i === idx);
|
||||
});
|
||||
}
|
||||
|
||||
async function nextQuestion() {
|
||||
if (answers[currentQ] === -1) return;
|
||||
|
||||
if (currentQ < testData.questions.length - 1) {
|
||||
currentQ++;
|
||||
renderQuestion();
|
||||
return;
|
||||
}
|
||||
|
||||
// Last question → calculate locally
|
||||
const btn = document.getElementById('btnNext');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '计算中…';
|
||||
|
||||
try {
|
||||
myResult = calculateResult(answers);
|
||||
// 自动获得对应的数学人物卡
|
||||
const mbtiCode=myResult.binary_code;
|
||||
let gotNewCard=false;
|
||||
if(mbtiCode){
|
||||
let cardState2=JSON.parse(localStorage.getItem('hulu_card_state')||'{}');
|
||||
if(!cardState2.owned)cardState2.owned=[];
|
||||
if(!cardState2.drawHistory)cardState2.drawHistory=[];
|
||||
if(cardState2.owned.indexOf(mbtiCode)===-1){
|
||||
cardState2.owned.push(mbtiCode);
|
||||
cardState2.drawHistory.push({code:mbtiCode,date:new Date().toISOString().slice(0,10),method:'mathbti'});
|
||||
localStorage.setItem('hulu_card_state',JSON.stringify(cardState2));
|
||||
gotNewCard=true;
|
||||
}
|
||||
}
|
||||
renderResult();
|
||||
// 卡片领取提示
|
||||
if(gotNewCard){
|
||||
const res=myResult.result;
|
||||
const notice=document.getElementById('cardNotice');
|
||||
if(notice){notice.style.display='block';document.getElementById('cardNoticeName').textContent=res?res.name:'—'}
|
||||
showToast('🎴 获得数学人物小卡!前往葫芦数学查看卡册');
|
||||
}
|
||||
switchScreen('resultScreen');
|
||||
} catch (e) {
|
||||
showToast('提交失败,请重试');
|
||||
btn.disabled = false;
|
||||
btn.textContent = currentQ === testData.questions.length - 1 ? '查看结果' : '下一题';
|
||||
}
|
||||
}
|
||||
|
||||
function calculateResult(answers) {
|
||||
const axes = testData.scoring.axes; // ["style","purpose","era","social"]
|
||||
const maxPerAxis = testData.scoring.max_per_axis;
|
||||
const cutoff = testData.scoring.cutoff;
|
||||
|
||||
// 计算每个轴的得分
|
||||
const axisScores = {};
|
||||
axes.forEach(axis => {
|
||||
axisScores[axis] = 0;
|
||||
});
|
||||
testData.questions.forEach((q, i) => {
|
||||
const score = q.options[answers[i]].score;
|
||||
axisScores[q.axis] += score;
|
||||
});
|
||||
|
||||
// 生成二进制代码 (≤cutoff=0左极, ≥cutoff+1=1右极)
|
||||
const binaryCode = axes.map(axis => axisScores[axis] <= cutoff ? '0' : '1').join('');
|
||||
|
||||
// 获取结果
|
||||
const result = testData.results[binaryCode];
|
||||
|
||||
return {
|
||||
binary_code: binaryCode,
|
||||
axis_scores: axisScores,
|
||||
result: result
|
||||
};
|
||||
}
|
||||
|
||||
function renderResult() {
|
||||
const res = myResult.result;
|
||||
if (!res) return;
|
||||
|
||||
// Portrait
|
||||
const portraitEl = document.getElementById('resultPortrait');
|
||||
if (res.portrait) {
|
||||
portraitEl.innerHTML = '<div class="result-portrait" style="background:' + (res.icon_bg || '#F0F0F0') + '"><img src="' + res.portrait + '" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><span class="result-icon-fallback" style="display:none;font-size:48px">' + (res.icon || 'Σ') + '</span></div>';
|
||||
} else {
|
||||
portraitEl.innerHTML = '<div class="result-portrait" style="background:' + (res.icon_bg || '#F0F0F0') + '"><span class="result-icon-fallback" style="font-size:48px">' + (res.icon || 'Σ') + '</span></div>';
|
||||
}
|
||||
|
||||
// Name + Clan
|
||||
document.getElementById('resultName').innerHTML = res.name + ' <span class="type-code" style="font-size:13px;color:#94A3B8;font-weight:400">' + myResult.binary_code + '</span>';
|
||||
document.getElementById('resultTagline').textContent = res.tagline || '';
|
||||
|
||||
// Clan badge
|
||||
const clanData = testData.clans[res.clan];
|
||||
document.getElementById('resultMathematician').innerHTML =
|
||||
(clanData ? '<span class="clan-badge" style="background:' + (clanData.color || '#888') + '15;color:' + (clanData.color || '#888') + ';border:1px solid ' + (clanData.color || '#888') + '30">' + clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician + '</span>' : '') +
|
||||
'<span class="mathematician-name">代表数学家:' + (res.mathematician || '—') + '</span>';
|
||||
|
||||
// Keywords
|
||||
const kwEl = document.getElementById('resultKeywords');
|
||||
if (res.keywords) {
|
||||
kwEl.innerHTML = res.keywords.map(k => '<span class="kw-tag">' + k + '</span>').join('');
|
||||
kwEl.style.display = 'flex';
|
||||
} else {
|
||||
kwEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// Description
|
||||
document.getElementById('resultDesc').textContent = res.description || '';
|
||||
document.getElementById('resultShort').textContent = res.short_desc || '';
|
||||
|
||||
// Bio
|
||||
document.getElementById('bioTitle').textContent = '关于 ' + (res.mathematician || '这位数学家');
|
||||
document.getElementById('bioText').textContent = res.bio || '';
|
||||
document.getElementById('bioFact').textContent = res.bio_fact || '';
|
||||
|
||||
// Axes bars
|
||||
const axesEl = document.getElementById('resultAxes');
|
||||
if (testData.axes) {
|
||||
axesEl.innerHTML = testData.axes.map(a => {
|
||||
const score = myResult.axis_scores[a.id] || 0;
|
||||
const maxScore = testData.scoring.max_per_axis || 9;
|
||||
const pct = Math.round(score / maxScore * 100);
|
||||
return '<div class="axis-bar-row">' +
|
||||
'<span class="axis-bar-label">' + (a.emoji_left || '') + ' ' + a.left + '</span>' +
|
||||
'<div class="axis-bar-track"><div class="axis-bar-fill" style="width:' + pct + '%"></div></div>' +
|
||||
'<span class="axis-bar-label">' + a.right + ' ' + (a.emoji_right || '') + '</span>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
axesEl.style.display = 'block';
|
||||
}
|
||||
|
||||
// Five-dimension ability card from 16位数学家五维能力表
|
||||
const s5El = document.getElementById('resultStats5');
|
||||
if (s5El && res.stats5) {
|
||||
const labels = ['眼光', '人文', '侦探', '建模', '联结'];
|
||||
s5El.innerHTML = '<div class="result-stats5-title">五维能力</div>' + labels.map(k => {
|
||||
const v = res.stats5[k] || 0;
|
||||
return '<div class="axis-bar-row">' +
|
||||
'<span class="axis-bar-label">' + k + '</span>' +
|
||||
'<div class="axis-bar-track"><div class="axis-bar-fill" style="width:' + v + '%"></div></div>' +
|
||||
'<span class="axis-bar-label">' + v + '</span>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
s5El.style.display = 'block';
|
||||
}
|
||||
|
||||
// Adopt pet
|
||||
const petData = JSON.parse(localStorage.getItem('mb_pet') || '{}');
|
||||
const adoptBtn = document.querySelector('.btn-adopt');
|
||||
if (petData.type) {
|
||||
adoptBtn.textContent = '✅ 已领养 · 我的数学精灵';
|
||||
adoptBtn.classList.add('adopted');
|
||||
}
|
||||
|
||||
// Apply clan color
|
||||
if (clanData) {
|
||||
document.getElementById('resultScreen').style.setProperty('--clan-color', clanData.color);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Bio Expander ----
|
||||
function toggleBio() {
|
||||
const body = document.getElementById('bioBody');
|
||||
const arrow = document.getElementById('bioArrow');
|
||||
const expanded = body.style.maxHeight && body.style.maxHeight !== '0px';
|
||||
if (expanded) {
|
||||
body.style.maxHeight = '0px';
|
||||
arrow.textContent = '▾';
|
||||
} else {
|
||||
body.style.maxHeight = body.scrollHeight + 'px';
|
||||
arrow.textContent = '▴';
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Adopt Pet ----
|
||||
function adoptPet() {
|
||||
if (!myResult) return;
|
||||
const res = myResult.result;
|
||||
const dims = {};
|
||||
['眼光', '人文', '侦探', '建模', '联结'].forEach(k => {
|
||||
dims[k] = Math.max(10, Math.min(100, Number((res.stats5 && res.stats5[k]) || 0)));
|
||||
});
|
||||
const pet = {
|
||||
name: res.name || '—',
|
||||
type: res.name || '—',
|
||||
icon: res.icon || 'Σ',
|
||||
color: res.color || '#5B6AF0',
|
||||
mathematician: res.mathematician || '',
|
||||
clan: res.clan || '',
|
||||
binary_code: myResult.binary_code,
|
||||
level: 1,
|
||||
exp: 0,
|
||||
dims: dims
|
||||
};
|
||||
localStorage.setItem('mb_pet', JSON.stringify(pet));
|
||||
localStorage.setItem('mathbrain_pet', JSON.stringify(pet));
|
||||
const btn = document.querySelector('.btn-adopt');
|
||||
btn.textContent = '✅ 已领养 · 我的数学精灵';
|
||||
btn.classList.add('adopted');
|
||||
showToast('🧬 数学精灵已领养!每天来葫芦数学 App 喂它成长');
|
||||
}
|
||||
|
||||
// ---- Share ----
|
||||
function copyShare() {
|
||||
if (!myResult) return;
|
||||
const res = myResult.result;
|
||||
const clanData = testData.clans[res.clan];
|
||||
const text = '我的数学人格是「' + res.name + '」' + (res.icon || '') + '\n' +
|
||||
(clanData ? clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician : '') + '\n' +
|
||||
(res.short_desc || '') + '\n\n' +
|
||||
'👉 你也来测测:' + window.location.href + '\n\n' +
|
||||
'#数学人格测试 #MathBTI';
|
||||
navigator.clipboard.writeText(text).then(() => showToast('已复制分享文案!'));
|
||||
}
|
||||
|
||||
function shareResult() {
|
||||
if (!myResult) return;
|
||||
const res = myResult.result;
|
||||
const text = '我的数学人格是「' + res.name + '」' + (res.icon || '') + '!' + (res.short_desc || '');
|
||||
if (navigator.share) {
|
||||
navigator.share({ title: 'MathBTI · 数学人格测试', text: text, url: window.location.href }).catch(() => {});
|
||||
} else {
|
||||
copyShare();
|
||||
}
|
||||
}
|
||||
|
||||
function generateShareCard() {
|
||||
if (!myResult) return;
|
||||
const res = myResult.result;
|
||||
const clanData = testData.clans[res.clan];
|
||||
const canvas = document.getElementById('shareCardCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = 600;
|
||||
canvas.height = 800;
|
||||
|
||||
// Background
|
||||
const gradient = ctx.createLinearGradient(0, 0, 600, 800);
|
||||
gradient.addColorStop(0, (res.color || '#5B6AF0') + '22');
|
||||
gradient.addColorStop(1, (res.color || '#5B6AF0') + '08');
|
||||
ctx.fillStyle = '#FAFBFC';
|
||||
ctx.fillRect(0, 0, 600, 800);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, 600, 800);
|
||||
|
||||
// Top accent bar
|
||||
ctx.fillStyle = res.color || '#5B6AF0';
|
||||
ctx.fillRect(0, 0, 600, 6);
|
||||
|
||||
// Portrait placeholder
|
||||
ctx.fillStyle = res.icon_bg || '#F0F0F0';
|
||||
ctx.beginPath();
|
||||
ctx.arc(300, 180, 70, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#334155';
|
||||
ctx.font = '60px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(res.icon || 'Σ', 300, 180);
|
||||
|
||||
// Name
|
||||
ctx.fillStyle = '#1E293B';
|
||||
ctx.font = 'bold 36px "PingFang SC", sans-serif';
|
||||
ctx.fillText(res.name, 300, 280);
|
||||
|
||||
// Clan + Mathematician
|
||||
ctx.fillStyle = '#64748B';
|
||||
ctx.font = '18px "PingFang SC", sans-serif';
|
||||
if (clanData) {
|
||||
ctx.fillText(clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician, 300, 315);
|
||||
}
|
||||
|
||||
// Tagline
|
||||
ctx.fillStyle = res.color || '#5B6AF0';
|
||||
ctx.font = 'italic 15px "PingFang SC", sans-serif';
|
||||
const tagline = res.tagline || '';
|
||||
wrapText(ctx, tagline, 300, 350, 480, 22);
|
||||
|
||||
// Separator
|
||||
ctx.fillStyle = '#E2E8F0';
|
||||
ctx.fillRect(60, 380, 480, 1);
|
||||
|
||||
// Short desc
|
||||
ctx.fillStyle = '#334155';
|
||||
ctx.font = '15px "PingFang SC", sans-serif';
|
||||
const desc = res.short_desc || '';
|
||||
wrapText(ctx, desc, 300, 410, 480, 24);
|
||||
|
||||
// Axes
|
||||
let ay = 450;
|
||||
if (testData.axes && myResult.axis_scores) {
|
||||
ctx.fillStyle = '#64748B';
|
||||
ctx.font = '14px "PingFang SC", sans-serif';
|
||||
testData.axes.forEach(a => {
|
||||
const s = myResult.axis_scores[a.id] || 0;
|
||||
const maxS = testData.scoring.max_per_axis || 9;
|
||||
const pct = Math.round(s / maxS * 100);
|
||||
ctx.fillText(a.emoji_left + ' ' + a.left + ' — ' + pct + '% — ' + a.right + ' ' + a.emoji_right, 300, ay);
|
||||
ay += 28;
|
||||
});
|
||||
}
|
||||
|
||||
// Bottom
|
||||
ctx.fillStyle = '#94A3B8';
|
||||
ctx.font = '13px "PingFang SC", sans-serif';
|
||||
ctx.fillText('葫芦数学 · MathBTI', 300, 730);
|
||||
// 葫芦水印
|
||||
ctx.save();ctx.globalAlpha=.06;ctx.font='80px sans-serif';ctx.fillStyle='#A8907E';
|
||||
ctx.fillText('🏮',480,680);
|
||||
ctx.restore();
|
||||
ctx.fillText('你也来测测你的数学人格类型 →', 300, 755);
|
||||
|
||||
// QR placeholder
|
||||
ctx.fillStyle = '#E2E8F0';
|
||||
ctx.fillRect(255, 640, 90, 90);
|
||||
ctx.fillStyle = '#94A3B8';
|
||||
ctx.font = '11px sans-serif';
|
||||
ctx.fillText('扫码测', 300, 685);
|
||||
|
||||
document.getElementById('shareCardPreview').style.display = 'block';
|
||||
document.getElementById('shareCardPreview').scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function downloadShareCard() {
|
||||
const canvas = document.getElementById('shareCardCanvas');
|
||||
const link = document.createElement('a');
|
||||
link.download = 'mathbti_' + (myResult ? myResult.binary_code : 'result') + '.png';
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
link.click();
|
||||
showToast('📸 图片已保存!');
|
||||
}
|
||||
|
||||
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
|
||||
const lines = [];
|
||||
let current = '';
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const test = current + text[i];
|
||||
if (ctx.measureText(test).width > maxWidth && current.length > 0) {
|
||||
lines.push(current);
|
||||
current = text[i];
|
||||
} else {
|
||||
current = test;
|
||||
}
|
||||
}
|
||||
if (current) lines.push(current);
|
||||
lines.forEach((line, idx) => {
|
||||
ctx.fillText(line, x, y + idx * lineHeight);
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Retry ----
|
||||
function retryQuiz() {
|
||||
answers = [];
|
||||
currentQ = 0;
|
||||
myResult = null;
|
||||
document.getElementById('bioBody').style.maxHeight = '0px';
|
||||
document.getElementById('bioArrow').textContent = '▾';
|
||||
document.getElementById('shareCardPreview').style.display = 'none';
|
||||
switchScreen('introScreen');
|
||||
}
|
||||
|
||||
// ---- Helpers ----
|
||||
function switchScreen(id) {
|
||||
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
|
||||
document.getElementById(id).classList.add('active');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
let toastTimer = null;
|
||||
function showToast(msg) {
|
||||
const el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => el.classList.remove('show'), 2200);
|
||||
}
|
||||
|
||||
// Bootstrap
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
@@ -0,0 +1,325 @@
|
||||
let testData = null, answers = [], currentQ = 0, myResult = null;
|
||||
async function init() {
|
||||
try {
|
||||
const r = await fetch('/api/math_personality');
|
||||
const j = await r.json();
|
||||
if (j.code !== 0) { showToast('测试数据加载失败'); return; }
|
||||
testData = j.data;
|
||||
renderIntro();
|
||||
} catch (e) {
|
||||
document.getElementById('introScreen').innerHTML =
|
||||
'<div style="text-align:center;padding:60px 20px"><p style="color:#94A3B8;margin-bottom:12px">加载失败,请检查网络</p><button class="btn-start" onclick="init()">重试</button></div>';
|
||||
}
|
||||
}
|
||||
function renderIntro() {
|
||||
const axesEl = document.getElementById('introAxes');
|
||||
if (axesEl && testData.axes) {
|
||||
axesEl.innerHTML = testData.axes.map(a =>
|
||||
'<div class="intro-axis-item">' +
|
||||
'<span class="axis-emoji">' + (a.emoji_left || '') + '</span>' +
|
||||
'<span class="axis-label">' + a.name + '</span>' +
|
||||
'<span class="axis-emoji">' + (a.emoji_right || '') + '</span>' +
|
||||
'</div>'
|
||||
).join('');
|
||||
}
|
||||
document.querySelector('.intro-meta').textContent =
|
||||
'⏱ 约2分钟 · 4选项 · 16种人格';
|
||||
}
|
||||
function startQuiz() {
|
||||
answers = new Array(testData.questions.length).fill(-1);
|
||||
currentQ = 0;
|
||||
switchScreen('quizScreen');
|
||||
renderQuestion();
|
||||
}
|
||||
function renderQuestion() {
|
||||
const q = testData.questions[currentQ];
|
||||
document.getElementById('quizQuestion').textContent = q.question;
|
||||
const optEl = document.getElementById('quizOptions');
|
||||
const sel = answers[currentQ];
|
||||
optEl.innerHTML = q.options.map((o, i) =>
|
||||
'<label class="quiz-option' + (sel === i ? ' selected' : '') + '" onclick="selectOption(' + i + ')">' +
|
||||
'<input type="radio" name="q' + currentQ + '" value="' + i + '"' + (sel === i ? ' checked' : '') + '>' +
|
||||
'<span class="opt-text">' + o.text + '</span></label>'
|
||||
).join('');
|
||||
const pct = Math.round((currentQ + 1) / testData.questions.length * 100);
|
||||
document.getElementById('progressFill').style.width = pct + '%';
|
||||
const isLast=currentQ===testData.questions.length-1;
|
||||
document.getElementById('progressFill').style.setProperty('--is-last',isLast?'1':'0');
|
||||
document.getElementById('progressText').textContent = (currentQ + 1) + '/' + testData.questions.length;
|
||||
const btn = document.getElementById('btnNext');
|
||||
btn.disabled = answers[currentQ] === -1;
|
||||
btn.textContent = currentQ === testData.questions.length - 1 ? '查看结果' : '下一题';
|
||||
}
|
||||
function selectOption(idx) {
|
||||
answers[currentQ] = idx;
|
||||
document.getElementById('btnNext').disabled = false;
|
||||
document.querySelectorAll('.quiz-option').forEach((el, i) => {
|
||||
el.classList.toggle('selected', i === idx);
|
||||
});
|
||||
}
|
||||
async function nextQuestion() {
|
||||
if (answers[currentQ] === -1) return;
|
||||
if (currentQ < testData.questions.length - 1) {
|
||||
currentQ++;
|
||||
renderQuestion();
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById('btnNext');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '计算中…';
|
||||
try {
|
||||
const r = await fetch('/api/math_personality/calculate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ answers })
|
||||
});
|
||||
const j = await r.json();
|
||||
if (j.code !== 0) { showToast('计算结果失败'); return; }
|
||||
myResult = j.data;
|
||||
const mbtiCode=j.data.binary_code;
|
||||
let gotNewCard=false;
|
||||
if(mbtiCode){
|
||||
let cardState2=JSON.parse(localStorage.getItem('hulu_card_state')||'{}');
|
||||
if(!cardState2.owned)cardState2.owned=[];
|
||||
if(!cardState2.drawHistory)cardState2.drawHistory=[];
|
||||
if(cardState2.owned.indexOf(mbtiCode)===-1){
|
||||
cardState2.owned.push(mbtiCode);
|
||||
cardState2.drawHistory.push({code:mbtiCode,date:new Date().toISOString().slice(0,10),method:'mathbti'});
|
||||
localStorage.setItem('hulu_card_state',JSON.stringify(cardState2));
|
||||
gotNewCard=true;
|
||||
}
|
||||
}
|
||||
renderResult();
|
||||
if(gotNewCard){
|
||||
const res=j.data.result;
|
||||
const notice=document.getElementById('cardNotice');
|
||||
if(notice){notice.style.display='block';document.getElementById('cardNoticeName').textContent=res?res.name:'—'}
|
||||
showToast('🎴 获得数学人物小卡!前往葫芦数学查看卡册');
|
||||
}
|
||||
switchScreen('resultScreen');
|
||||
} catch (e) {
|
||||
showToast('提交失败,请重试');
|
||||
btn.disabled = false;
|
||||
btn.textContent = currentQ === testData.questions.length - 1 ? '查看结果' : '下一题';
|
||||
}
|
||||
}
|
||||
function renderResult() {
|
||||
const res = myResult.result;
|
||||
if (!res) return;
|
||||
const portraitEl = document.getElementById('resultPortrait');
|
||||
if (res.portrait) {
|
||||
portraitEl.innerHTML = '<div class="result-portrait" style="background:' + (res.icon_bg || '#F0F0F0') + '"><img src="' + res.portrait + '" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><span class="result-icon-fallback" style="display:none;font-size:48px">' + (res.icon || 'Σ') + '</span></div>';
|
||||
} else {
|
||||
portraitEl.innerHTML = '<div class="result-portrait" style="background:' + (res.icon_bg || '#F0F0F0') + '"><span class="result-icon-fallback" style="font-size:48px">' + (res.icon || 'Σ') + '</span></div>';
|
||||
}
|
||||
document.getElementById('resultName').innerHTML = res.name + ' <span class="type-code" style="font-size:13px;color:#94A3B8;font-weight:400">' + myResult.binary_code + '</span>';
|
||||
document.getElementById('resultTagline').textContent = res.tagline || '';
|
||||
const clanData = testData.clans[res.clan];
|
||||
document.getElementById('resultMathematician').innerHTML =
|
||||
(clanData ? '<span class="clan-badge" style="background:' + (clanData.color || '#888') + '15;color:' + (clanData.color || '#888') + ';border:1px solid ' + (clanData.color || '#888') + '30">' + clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician + '</span>' : '') +
|
||||
'<span class="mathematician-name">代表数学家:' + (res.mathematician || '—') + '</span>';
|
||||
const kwEl = document.getElementById('resultKeywords');
|
||||
if (res.keywords) {
|
||||
kwEl.innerHTML = res.keywords.map(k => '<span class="kw-tag">' + k + '</span>').join('');
|
||||
kwEl.style.display = 'flex';
|
||||
} else {
|
||||
kwEl.style.display = 'none';
|
||||
}
|
||||
document.getElementById('resultDesc').textContent = res.description || '';
|
||||
document.getElementById('resultShort').textContent = res.short_desc || '';
|
||||
document.getElementById('bioTitle').textContent = '关于 ' + (res.mathematician || '这位数学家');
|
||||
document.getElementById('bioText').textContent = res.bio || '';
|
||||
document.getElementById('bioFact').textContent = res.bio_fact || '';
|
||||
const axesEl = document.getElementById('resultAxes');
|
||||
if (testData.axes) {
|
||||
axesEl.innerHTML = testData.axes.map(a => {
|
||||
const score = myResult.axis_scores[a.id] || 0;
|
||||
const maxScore = testData.scoring.max_per_axis || 9;
|
||||
const pct = Math.round(score / maxScore * 100);
|
||||
return '<div class="axis-bar-row">' +
|
||||
'<span class="axis-bar-label">' + (a.emoji_left || '') + ' ' + a.left + '</span>' +
|
||||
'<div class="axis-bar-track"><div class="axis-bar-fill" style="width:' + pct + '%"></div></div>' +
|
||||
'<span class="axis-bar-label">' + a.right + ' ' + (a.emoji_right || '') + '</span>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
axesEl.style.display = 'block';
|
||||
}
|
||||
const petData = JSON.parse(localStorage.getItem('mb_pet') || '{}');
|
||||
const adoptBtn = document.querySelector('.btn-adopt');
|
||||
if (petData.type) {
|
||||
adoptBtn.textContent = '✅ 已领养 · 我的数学精灵';
|
||||
adoptBtn.classList.add('adopted');
|
||||
}
|
||||
if (clanData) {
|
||||
document.getElementById('resultScreen').style.setProperty('--clan-color', clanData.color);
|
||||
}
|
||||
}
|
||||
function toggleBio() {
|
||||
const body = document.getElementById('bioBody');
|
||||
const arrow = document.getElementById('bioArrow');
|
||||
const expanded = body.style.maxHeight && body.style.maxHeight !== '0px';
|
||||
if (expanded) {
|
||||
body.style.maxHeight = '0px';
|
||||
arrow.textContent = '▾';
|
||||
} else {
|
||||
body.style.maxHeight = body.scrollHeight + 'px';
|
||||
arrow.textContent = '▴';
|
||||
}
|
||||
}
|
||||
function adoptPet() {
|
||||
if (!myResult) return;
|
||||
const res = myResult.result;
|
||||
const pet = {
|
||||
type: res.name || '—',
|
||||
icon: res.icon || 'Σ',
|
||||
color: res.color || '#5B6AF0',
|
||||
mathematician: res.mathematician || '',
|
||||
clan: res.clan || '',
|
||||
binary_code: myResult.binary_code
|
||||
};
|
||||
localStorage.setItem('mb_pet', JSON.stringify(pet));
|
||||
const btn = document.querySelector('.btn-adopt');
|
||||
btn.textContent = '✅ 已领养 · 我的数学精灵';
|
||||
btn.classList.add('adopted');
|
||||
showToast('🧬 数学精灵已领养!每天来葫芦数学 App 喂它成长');
|
||||
}
|
||||
function copyShare() {
|
||||
if (!myResult) return;
|
||||
const res = myResult.result;
|
||||
const clanData = testData.clans[res.clan];
|
||||
const text = '我的数学人格是「' + res.name + '」' + (res.icon || '') + '\n' +
|
||||
(clanData ? clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician : '') + '\n' +
|
||||
(res.short_desc || '') + '\n\n' +
|
||||
'👉 你也来测测:' + window.location.href + '\n\n' +
|
||||
'#数学人格测试 #MathBTI';
|
||||
navigator.clipboard.writeText(text).then(() => showToast('已复制分享文案!'));
|
||||
}
|
||||
function shareResult() {
|
||||
if (!myResult) return;
|
||||
const res = myResult.result;
|
||||
const text = '我的数学人格是「' + res.name + '」' + (res.icon || '') + '!' + (res.short_desc || '');
|
||||
if (navigator.share) {
|
||||
navigator.share({ title: 'MathBTI · 数学人格测试', text: text, url: window.location.href }).catch(() => {});
|
||||
} else {
|
||||
copyShare();
|
||||
}
|
||||
}
|
||||
function generateShareCard() {
|
||||
if (!myResult) return;
|
||||
const res = myResult.result;
|
||||
const clanData = testData.clans[res.clan];
|
||||
const canvas = document.getElementById('shareCardCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = 600;
|
||||
canvas.height = 800;
|
||||
const gradient = ctx.createLinearGradient(0, 0, 600, 800);
|
||||
gradient.addColorStop(0, (res.color || '#5B6AF0') + '22');
|
||||
gradient.addColorStop(1, (res.color || '#5B6AF0') + '08');
|
||||
ctx.fillStyle = '#FAFBFC';
|
||||
ctx.fillRect(0, 0, 600, 800);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, 600, 800);
|
||||
ctx.fillStyle = res.color || '#5B6AF0';
|
||||
ctx.fillRect(0, 0, 600, 6);
|
||||
ctx.fillStyle = res.icon_bg || '#F0F0F0';
|
||||
ctx.beginPath();
|
||||
ctx.arc(300, 180, 70, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#334155';
|
||||
ctx.font = '60px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(res.icon || 'Σ', 300, 180);
|
||||
ctx.fillStyle = '#1E293B';
|
||||
ctx.font = 'bold 36px "PingFang SC", sans-serif';
|
||||
ctx.fillText(res.name, 300, 280);
|
||||
ctx.fillStyle = '#64748B';
|
||||
ctx.font = '18px "PingFang SC", sans-serif';
|
||||
if (clanData) {
|
||||
ctx.fillText(clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician, 300, 315);
|
||||
}
|
||||
ctx.fillStyle = res.color || '#5B6AF0';
|
||||
ctx.font = 'italic 15px "PingFang SC", sans-serif';
|
||||
const tagline = res.tagline || '';
|
||||
wrapText(ctx, tagline, 300, 350, 480, 22);
|
||||
ctx.fillStyle = '#E2E8F0';
|
||||
ctx.fillRect(60, 380, 480, 1);
|
||||
ctx.fillStyle = '#334155';
|
||||
ctx.font = '15px "PingFang SC", sans-serif';
|
||||
const desc = res.short_desc || '';
|
||||
wrapText(ctx, desc, 300, 410, 480, 24);
|
||||
let ay = 450;
|
||||
if (testData.axes && myResult.axis_scores) {
|
||||
ctx.fillStyle = '#64748B';
|
||||
ctx.font = '14px "PingFang SC", sans-serif';
|
||||
testData.axes.forEach(a => {
|
||||
const s = myResult.axis_scores[a.id] || 0;
|
||||
const maxS = testData.scoring.max_per_axis || 9;
|
||||
const pct = Math.round(s / maxS * 100);
|
||||
ctx.fillText(a.emoji_left + ' ' + a.left + ' — ' + pct + '% — ' + a.right + ' ' + a.emoji_right, 300, ay);
|
||||
ay += 28;
|
||||
});
|
||||
}
|
||||
ctx.fillStyle = '#94A3B8';
|
||||
ctx.font = '13px "PingFang SC", sans-serif';
|
||||
ctx.fillText('葫芦数学 · MathBTI', 300, 730);
|
||||
ctx.save();ctx.globalAlpha=.06;ctx.font='80px sans-serif';ctx.fillStyle='#A8907E';
|
||||
ctx.fillText('🏮',480,680);
|
||||
ctx.restore();
|
||||
ctx.fillText('你也来测测你的数学人格类型 →', 300, 755);
|
||||
ctx.fillStyle = '#E2E8F0';
|
||||
ctx.fillRect(255, 640, 90, 90);
|
||||
ctx.fillStyle = '#94A3B8';
|
||||
ctx.font = '11px sans-serif';
|
||||
ctx.fillText('扫码测', 300, 685);
|
||||
document.getElementById('shareCardPreview').style.display = 'block';
|
||||
document.getElementById('shareCardPreview').scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
function downloadShareCard() {
|
||||
const canvas = document.getElementById('shareCardCanvas');
|
||||
const link = document.createElement('a');
|
||||
link.download = 'mathbti_' + (myResult ? myResult.binary_code : 'result') + '.png';
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
link.click();
|
||||
showToast('📸 图片已保存!');
|
||||
}
|
||||
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
|
||||
const lines = [];
|
||||
let current = '';
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const test = current + text[i];
|
||||
if (ctx.measureText(test).width > maxWidth && current.length > 0) {
|
||||
lines.push(current);
|
||||
current = text[i];
|
||||
} else {
|
||||
current = test;
|
||||
}
|
||||
}
|
||||
if (current) lines.push(current);
|
||||
lines.forEach((line, idx) => {
|
||||
ctx.fillText(line, x, y + idx * lineHeight);
|
||||
});
|
||||
}
|
||||
function retryQuiz() {
|
||||
answers = [];
|
||||
currentQ = 0;
|
||||
myResult = null;
|
||||
document.getElementById('bioBody').style.maxHeight = '0px';
|
||||
document.getElementById('bioArrow').textContent = '▾';
|
||||
document.getElementById('shareCardPreview').style.display = 'none';
|
||||
switchScreen('introScreen');
|
||||
}
|
||||
function switchScreen(id) {
|
||||
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
|
||||
document.getElementById(id).classList.add('active');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
let toastTimer = null;
|
||||
function showToast(msg) {
|
||||
const el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => el.classList.remove('show'), 2200);
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||