This commit is contained in:
2026-08-08 15:49:06 +08:00
parent e7dce1680a
commit 0a7207fa0e
76 changed files with 22779 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>葫芦数学 · Hulu Math</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0%' stop-color='%23808C95'/><stop offset='100%' stop-color='%239AA5AE'/></linearGradient></defs><ellipse cx='32' cy='22' rx='14' ry='10' fill='url(%23g)'/><rect x='18' y='28' width='28' height='28' rx='14' fill='url(%23g)'/><text x='32' y='47' text-anchor='middle' font-size='20' font-weight='bold' fill='white' font-family='sans-serif'>葫</text></svg>">
<link rel="stylesheet" href="/static/css/desktop.css?v=19">
</head>
<body>
<div class="app-wrapper">
<div class="mobile-topbar"><button class="hamburger" onclick="toggleSidebar()"></button><div class="topbar-logo"><div class="topbar-logo-icon"></div><span class="topbar-logo-text">葫芦数学</span></div><div class="lang-switch mobile-lang"><button class="lang-btn active" data-lang="zh" onclick="switchLang('zh')"></button><button class="lang-btn" data-lang="en" onclick="switchLang('en')">EN</button></div></div>
<div class="app-layout">
<div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-logo"><div class="sidebar-logo-icon"></div><span class="sidebar-logo-text">葫芦数学</span><div class="lang-switch"><button class="lang-btn active" data-lang="zh" onclick="switchLang('zh')"></button><button class="lang-btn" data-lang="en" onclick="switchLang('en')">EN</button></div></div>
<div class="sidebar-search" style="position:relative">
<input type="text" id="globalSearch" placeholder="搜索内容..." oninput="handleGlobalSearch()" onfocus="handleGlobalSearch()">
<div class="search-results" id="searchResults"></div>
</div>
<nav class="sidebar-nav">
<button class="nav-item active" data-page="home" onclick="navigateTo('home')"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/><path d="M9 21v-6h6v6"/></svg></span> 首页</button>
<button class="nav-item" data-page="discover" onclick="navigateTo('discover')"><span class="nav-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="m15.5 8.5-2 5-5 2 2-5z"/></svg></span> 发现</button>
<button class="nav-item" data-page="toolbox" onclick="navigateTo('toolbox')"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M14.7 6.3a4.5 4.5 0 0 0-6 6L3 18l3 3 5.7-5.7a4.5 4.5 0 0 0 6-6L14 13l-3-3z"/></svg></span> 工具箱</button>
<button class="nav-item" data-page="knowledge" onclick="navigateTo('knowledge')"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M2 5h7a3 3 0 0 1 3 3v13a2.5 2.5 0 0 0-2.5-2.5H2z"/><path d="M22 5h-7a3 3 0 0 0-3 3v13a2.5 2.5 0 0 1 2.5-2.5H22z"/></svg></span> 知识卡片</button>
<button class="nav-item" data-page="story" onclick="navigateTo('story')"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M4 4l11 11"/><path d="M4 4v4l3 3"/><path d="M20 4l-11 11"/><path d="M20 4v4l-3 3"/><path d="m5 15 4 4"/><path d="m19 15-4 4"/><path d="m3 21 3-3"/><path d="m21 21-3-3"/></svg></span> 葫芦侠行</button>
<button class="nav-item" data-page="simulator" onclick="navigateTo('simulator')"><span class="nav-icon"><svg viewBox="0 0 24 24"><rect x="2" y="7" width="20" height="10" rx="5"/><path d="M7 11v2"/><path d="M6 12h2"/><circle cx="16.5" cy="11" r=".6"/><circle cx="18.5" cy="13" r=".6"/></svg></span> 数学人生</button>
<button class="nav-item" data-page="favorites" onclick="navigateTo('favorites')"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M12 20.5C7 16.5 3 13.3 3 9.3 3 6.4 5.2 4.5 7.7 4.5c1.7 0 3.3.9 4.3 2.4 1-1.5 2.6-2.4 4.3-2.4 2.5 0 4.7 1.9 4.7 4.8 0 4-4 7.2-9 11.2z"/></svg></span> 我的收藏</button>
<button class="nav-item" data-page="cards" onclick="navigateTo('cards')"><span class="nav-icon"><svg viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="3"/><path d="M8 10h8M8 14h5"/><circle cx="18" cy="9" r="2"/></svg></span> 我的卡册</button>
<button class="nav-item" data-page="profile" onclick="navigateTo('profile')"><span class="nav-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21c1.5-3.5 4.5-5 8-5s6.5 1.5 8 5"/></svg></span> 个人中心</button>
</nav>
<div class="sidebar-footer"><div class="sidebar-avatar" id="sidebarAvatar" style="background:#5B6AF0"></div><div><div class="sidebar-user-name" id="sidebarName">游客用户</div><div class="sidebar-user-id">Hulu Math</div></div></div>
</aside>
<!-- Main -->
<main class="main-content" id="mainContent"><div class="main-content-inner">
<!-- Home (Desmos-style) -->
<div class="page active" id="page-home">
<!-- Hero with canvas animation -->
<div class="ds-hero">
<h1 class="ds-hero-title" data-i18n="staticHeroTitle">葫芦数学 · 用玩的方式打开数学</h1>
<p class="ds-hero-sub" data-i18n="staticHeroSub">数学不是公式,是你理解世界的方式</p>
<canvas id="heroCanvas" width="900" height="200" class="ds-hero-canvas"></canvas>
<button class="ds-hero-cta" data-i18n="staticHeroCTA" onclick="navigateTo('discover')">开始探索 →</button>
</div>
<!-- Pet area -->
<div id="homePetArea"></div>
<div class="card-gallery-wrap" id="homeCardGallery" style="display:none">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
<span style="font-size:13px;font-weight:600;color:#8A8580">🎴 数学人物小卡 · <span id="cardCountText">0/16</span></span>
</div>
<div class="card-gallery-scroll" id="cardGalleryScroll"></div>
</div>
<!-- Feature cards grid (Desmos tool-grid style) -->
<h2 class="ds-section-title" data-i18n="staticExploreTitle">探索数学的万千可能</h2>
<div class="ds-card-grid" id="homeFeatureCards"></div>
<h2 class="ds-section-title" data-i18n="staticTryTitle">试试这些</h2>
<div class="ds-card-grid ds-card-grid-3" id="homeExtraCards"></div>
<!-- Daily math fact -->
<div class="ds-footer-note" id="homeDailyFact"></div>
<div style="text-align:center;margin-top:12px">
<button class="draw-btn" id="homeGachaBtn" onclick="openGacha()" style="display:none">🏮 抽卡 · <span id="homeDrawCount">0</span>次机会</button>
</div>
</div>
<!-- Discover (Numberphile-style, dual-column) -->
<div class="page" id="page-discover">
<div class="np-greeting" data-i18n="staticDiscoverGreet">Hello there. 这里收集了来自各专业志愿者的数学科普视频。</div>
<div id="dimMap"></div>
<div class="dim-tabs" id="dimTabs"></div>
<div class="filter-bar" id="discoverFilters"></div>
<div class="np-video-list" id="discoverVideos"><div class="spinner"></div></div>
</div>
<!-- Toolbox -->
<div class="page" id="page-toolbox">
<div class="page-header"><h1 class="page-title" data-i18n="staticToolboxTitle">🧰 工具箱</h1><p class="page-subtitle" data-i18n="staticToolboxSub">动手做数学——口算、计算、查询、绘图</p></div>
<div class="tool-grid">
<div class="tool-card" onclick="openTool('mathrace')"><div class="tool-card-icon"></div><div class="tool-card-name" data-i18n="staticToolMathrace">口算竞赛</div><div class="tool-card-desc" data-i18n="staticToolMathraceDesc">限时60秒,挑战你的心算速度</div></div>
<div class="tool-card" onclick="openTool('calculator')"><div class="tool-card-icon">🔢</div><div class="tool-card-name" data-i18n="staticToolCalc">计算器</div><div class="tool-card-desc" data-i18n="staticToolCalcDesc">支持加减乘除、幂运算、三角函数</div></div>
<div class="tool-card" onclick="openTool('symbols')"><div class="tool-card-icon">Σ</div><div class="tool-card-name" data-i18n="staticToolSym">数学符号查询</div><div class="tool-card-desc" data-i18n="staticToolSymDesc">点击符号查看含义和LaTeX写法</div></div>
<div class="tool-card" onclick="openTool('grapher')"><div class="tool-card-icon">📈</div><div class="tool-card-name" data-i18n="staticToolGraph">函数图形绘图</div><div class="tool-card-desc" data-i18n="staticToolGraphDesc">输入 f(x),实时绘制函数曲线</div></div>
</div>
</div>
<!-- Knowledge Cards -->
<div class="page" id="page-knowledge">
<div class="page-header"><h1 class="page-title" data-i18n="staticKnowledgeTitle">📚 知识卡片</h1><p class="page-subtitle" data-i18n="staticKnowledgeSub">轻量数学发现,每天一个新概念</p></div>
<div class="filter-bar" id="knowledgeFilters"></div>
<div class="knowledge-grid" id="knowledgeGrid"><div class="spinner"></div></div>
<div class="draw-progress-bar" id="knowledgeDrawProgress" onclick="openGacha()" style="display:none">
<span class="draw-progress-icon">🏮</span>
<span id="drawProgressText">再读 3 张知识卡片即可抽卡</span>
<div class="draw-progress-track"><div class="draw-progress-fill" id="drawProgressFill" style="width:0%"></div></div>
</div>
</div>
<!-- Story -->
<div class="page" id="page-story">
<div class="page-header"><h1 class="page-title" data-i18n="staticStoryTitle">⚔️ 葫芦侠行</h1><p class="page-subtitle" data-i18n="staticStorySub">北宋数学武侠互动文字冒险 · 8章93节点 · 8+种结局</p></div>
<div class="story-container" id="storyContainer"><div id="storyStats" class="story-stats"></div><div id="storyScene"><div class="spinner"></div></div><div id="storyChoices" class="story-choices"></div></div>
</div>
<!-- Simulator -->
<div class="page" id="page-simulator">
<div class="page-header"><h1 class="page-title" data-i18n="staticSimTitle">🧬 数学人生</h1><p class="page-subtitle" data-i18n="staticSimSub">四大主题 · 更多数学人生敬请期待</p></div>
<div id="mathLifeHub"></div>
<div class="sim-container" id="legacySimPlayer" style="display:none"><div id="simStats" class="sim-stats"></div><div id="simScene"></div><div id="simChoices" style="display:flex;flex-direction:column;gap:10px;margin-top:20px"></div></div>
<div id="mathTeenPlayer" style="display:none"></div>
<div id="mathAlumniPlayer" style="display:none"></div>
</div>
<!-- Favorites -->
<div class="page" id="page-favorites">
<div class="page-header"><h1 class="page-title">❤️ 我的收藏</h1><p class="page-subtitle">已收藏的视频和知识卡片</p></div>
<div class="fav-grid" id="favGrid"><div class="spinner"></div></div>
</div>
<!-- Profile -->
<div class="page" id="page-profile">
<div class="page-header"><h1 class="page-title" data-i18n="staticProfileTitle">👤 个人中心</h1></div>
<div id="profileContent"><div class="spinner"></div></div>
</div>
<!-- Card Collection -->
<div class="page" id="page-cards">
<div class="page-header"><h1 class="page-title">🎴 我的卡册</h1><p class="page-subtitle">收集全部16位数学人物 · <span id="cardsProgress">0/16</span></p></div>
<div style="text-align:center;margin-bottom:20px">
<button class="draw-btn" id="cardsDrawBtn" onclick="openGacha()">🏮 抽卡 · <span id="cardsDrawCount">0</span>次机会</button>
</div>
<div class="cards-collection-grid" id="cardsCollectionGrid"><div class="spinner"></div></div>
</div>
</div></main>
</div>
</div>
<!-- Float hub: merged AI + MathBTI expandable button -->
<div class="float-hub" id="floatHub">
<div class="float-hub-expand">
<button class="float-hub-child ai" onclick="toggleAIPanel();closeHub()" data-i18n-type="title" data-i18n="staticAIToggleTitle" title="AI数学助教">💬<span class="float-hub-label" data-i18n="staticAIToggleTitle">AI数学助教</span></button>
<a href="/mathbti" target="_blank" class="float-hub-child mbti" data-i18n-type="title" data-i18n="staticMbtiTooltip" title="测测你的数学人格">🧬<span class="float-hub-label" data-i18n="staticMbtiTooltip">测测你的数学人格</span></a>
<button class="float-hub-child cards" onclick="openGacha();closeHub()" data-i18n-type="title" title="抽卡"><span style="font-size:18px">🏮</span><span class="float-hub-label">抽卡</span></button>
</div>
<button class="float-hub-btn" onclick="toggleHub()" title="更多"></button>
</div>
<!-- AI Panel -->
<div class="ai-panel" id="aiPanel">
<div class="ai-panel-header"><span data-i18n="staticAIHeader">💬 AI 数学助教</span><button class="ai-panel-close" onclick="toggleAIPanel()"></button></div>
<div class="ai-panel-messages" id="aiMessages"><div class="chat-msg assistant"><div class="chat-bubble" data-i18n="staticAIWelcome">你好!我是葫芦数学的AI助教 👋<br><br>试试问我:学数学对学医有什么用?</div></div></div>
<div class="ai-panel-input-area"><input type="text" id="chatInput" placeholder="输入问题..." data-i18n="staticAIPlaceholder" onkeydown="if(event.key==='Enter')sendChat()"><button class="ai-send-btn" onclick="sendChat()"></button></div>
</div>
<!-- Modal -->
<div class="modal-overlay" id="toolModal" onclick="if(event.target===this)closeToolModal()">
<div class="modal-content" style="position:relative">
<button class="modal-close-btn" style="position:absolute" onclick="closeToolModal()"></button>
<div id="toolModalContent" class="tool-modal-body"></div>
</div>
</div>
<script src="/static/js/desktop.js?v=21"></script>
<!-- 抽卡弹窗 -->
<div class="gacha-overlay" id="gachaOverlay" style="display:none" onclick="if(event.target===this)closeGacha()">
<div class="gacha-modal" id="gachaModal">
<button class="gacha-close" onclick="closeGacha()"></button>
<div id="gachaIdle">
<div class="gacha-gourd" id="gachaGourd" onclick="doDraw()" title="点击葫芦抽卡!">🏮</div>
<div class="gacha-title">葫芦抽卡</div>
<div class="gacha-sub">摇晃葫芦,抽取数学人物小卡</div>
<div class="gacha-draws">剩余抽卡次数:<b id="gachaDrawCount">0</b></div>
<div id="gachaPityHint" style="font-size:11px;color:#ABA6A1;margin-top:4px"></div>
</div>
<div id="gachaResult" style="display:none"></div>
</div>
</div>
<!-- 卡片详情弹窗 -->
<div class="card-detail-overlay" id="cardDetailOverlay" style="display:none" onclick="if(event.target===this)closeCardDetail()">
<div class="card-detail-modal" id="cardDetailModal"></div>
</div>
</body>
</html>
+386
View File
@@ -0,0 +1,386 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>葫芦数学 · Hulu Math</title>
<link rel="stylesheet" href="/static/css/style.min.css?v=10.0">
</head>
<body>
<!-- =========================================================
葫芦数学 · MVP v6.0 — 底部TabBar布局
仿微信小程序风格:底部导航 + 顶部内容区
========================================================= -->
<div class="app-container">
<!-- ====== 顶部状态栏 ====== -->
<header class="top-bar" id="topBar">
<div class="top-bar-brand">
<span class="brand-icon-sm"></span>
<span class="brand-text-sm">葫芦数学</span>
</div>
<div class="top-bar-actions">
<button class="icon-btn" onclick="openChat()" title="AI问答">?</button>
</div>
</header>
<!-- ====== 内容区 ====== -->
<main class="content-area">
<!-- 搜索栏 -->
<div class="search-header" id="searchHeader">
<div class="search-bar-wrap">
<span class="search-icon">🔍</span>
<input type="text" id="videoSearch" placeholder="搜索你想看的数学内容..." oninput="handleSearch()">
</div>
</div>
<!-- 子导航 (热门/推荐/专业分区) -->
<div class="sub-nav" id="subNav">
<button class="sub-nav-btn active" data-tab="recommend" onclick="switchSubTab('recommend')">推荐</button>
<button class="sub-nav-btn" data-tab="hot" onclick="switchSubTab('hot')">热门</button>
<button class="sub-nav-btn" data-tab="discipline" onclick="switchSubTab('discipline')">专业分区</button>
</div>
<!-- 知识 / 我的 标题 -->
<div class="section-title-bar" id="sectionTitleBar" style="display:none;">
<h1 class="section-title" id="contentTitle">知识 · 数学发现</h1>
</div>
<!-- 剧本广场(数学人生入口) -->
<div class="script-square" id="scriptSquare" style="display:none;">
<!-- 快捷继续 -->
<div class="ss-continue" id="ssContinue" style="display:none;">
<div class="ss-continue-card" id="ssContinueCard" onclick="">
<div class="ss-continue-badge">▶ 继续冒险</div>
<div class="ss-continue-info">
<div class="ss-continue-title" id="ssContinueTitle"></div>
<div class="ss-continue-sub" id="ssContinueSub"></div>
</div>
<div class="ss-continue-arrow"></div>
</div>
</div>
<!-- 精选剧本大卡 -->
<div class="ss-section-title">精选剧本</div>
<div class="ss-hero-cards" id="ssHeroCards">
<!-- JS 动态渲染 -->
</div>
<!-- 即将上线小卡 -->
<div class="ss-section-title">即将上线</div>
<div class="ss-coming-cards" id="ssComingCards">
<!-- JS 动态渲染 -->
</div>
</div>
<!-- 剧本内子导航(退出剧本用) -->
<div class="life-sub-nav" id="lifeSubNav" style="display:none;">
<button class="life-sub-btn" onclick="backToSquare()">← 剧本广场</button>
<button class="life-sub-btn active" data-life="story" onclick="switchLifeTab('story')">📖 葫芦侠行</button>
<button class="life-sub-btn" data-life="simulator" onclick="switchLifeTab('simulator')">🎓 专业模拟器</button>
</div>
<!-- 模块简介 -->
<div class="module-banner" id="moduleBanner">
<span class="banner-icon"></span>
<div class="banner-text">
<div class="banner-title">发现值得一看的数学内容</div>
<div class="banner-sub">从大学专业到现实问题,快速进入你感兴趣的主题</div>
</div>
</div>
<!-- 学科分类筛选 (仅在点击具体分区后显示) -->
<div class="category-filter" id="categoryFilter" style="display: none;">
<button class="cat-btn active" data-cat="all" onclick="filterCategory('all')">全部</button>
</div>
<!-- 专业分区网格 (类似B站分区页) -->
<div class="discipline-grid-container" id="disciplineGridContainer" style="display: none;">
<div class="grid-section-title">全部分区</div>
<div class="discipline-grid" id="disciplineGrid">
<!-- JS 动态渲染图标 -->
</div>
</div>
<!-- 选中分区后的顶部导航 -->
<div class="discipline-active-header" id="disciplineActiveHeader" style="display: none;">
<button class="back-to-grid" onclick="backToDisciplineGrid()">
<span class="back-icon"></span>
<span id="activeDisciplineName">专业名</span>
</button>
</div>
<!-- 加载状态 -->
<div class="loading" id="loadingArea">
<div class="spinner"></div>
<p>加载中...</p>
</div>
<!-- 视频列表(数人/数思用) -->
<div class="video-list" id="videoList"></div>
<!-- ====== 数说专属:发现Feed ====== -->
<!-- Hero大卡 -->
<div class="shushuo-hero" id="shushuoHero" style="display:none;">
<div class="hero-card">
<div class="hero-badge">✦ 今日数学发现</div>
<div class="hero-title" id="heroTitle">数学不是公式,是你理解世界的方式</div>
<div class="hero-desc" id="heroDesc">每天一个数学概念,让思维变得更清晰</div>
<div class="hero-stats">
<span>📖 3个概念</span><span>⏱ 阅读约15分钟</span>
</div>
</div>
</div>
<!-- 横向滚动话题 -->
<div class="shushuo-topics" id="shushuoTopics" style="display:none;">
<div class="topics-scroll" id="topicsScroll">
<!-- JS 动态渲染 -->
</div>
</div>
<!-- 信息流卡片 -->
<div class="shushuo-feed" id="shushuoFeed" style="display:none;">
<!-- JS 动态渲染 -->
</div>
<!-- 数学人生故事容器 -->
<div id="storyContainer" style="display:none;"></div>
<!-- 模拟器容器 -->
<div id="simulatorContainer" style="display:none;"></div>
<!-- 我的页面 -->
<div class="profile-page" id="profilePage" style="display:none;">
<div class="profile-card" onclick="openAvatarPicker()">
<div class="profile-main">
<div class="profile-avatar" id="profileAvatar"></div>
<div class="profile-meta">
<div class="profile-name" id="profileName">游客用户</div>
<div class="profile-id" id="profileId">ID: ---</div>
<div class="profile-tags">
<span>数学爱好者</span>
<span>开放体验版</span>
</div>
</div>
</div>
<div class="profile-edit">编辑资料</div>
</div>
<div class="profile-stats">
<div class="profile-stat" onclick="showHistory()">
<div class="profile-stat-num">0</div>
<div class="profile-stat-label">已看内容</div>
</div>
<div class="profile-stat" onclick="showFavorites()">
<div class="profile-stat-num">0</div>
<div class="profile-stat-label">收藏专题</div>
</div>
<div class="profile-stat">
<div class="profile-stat-num">2</div>
<div class="profile-stat-label">剧情存档</div>
</div>
</div>
<div class="profile-section">
<div class="profile-section-title">个人信息</div>
<div class="profile-menu-card">
<div class="profile-menu-item" onclick="editNickname()">
<div class="profile-menu-left"><span class="menu-icon"></span><span>昵称与头像</span></div>
<span class="menu-arrow"></span>
</div>
<div class="profile-menu-item">
<div class="profile-menu-left"><span class="menu-icon"></span><span>兴趣方向</span></div>
<span class="menu-arrow"></span>
</div>
<div class="profile-menu-item">
<div class="profile-menu-left"><span class="menu-icon"></span><span>学习偏好</span></div>
<span class="menu-arrow"></span>
</div>
</div>
</div>
<div class="profile-section">
<div class="profile-section-title">我的内容</div>
<div class="profile-menu-card">
<div class="profile-menu-item" onclick="showFavorites()">
<div class="profile-menu-left"><span class="menu-icon"></span><span>我的收藏</span></div>
<span class="menu-arrow"></span>
</div>
<div class="profile-menu-item" onclick="showHistory()">
<div class="profile-menu-left"><span class="menu-icon"></span><span>观看记录</span></div>
<span class="menu-arrow"></span>
</div>
<div class="profile-menu-item">
<div class="profile-menu-left"><span class="menu-icon"></span><span>数学人生存档</span></div>
<span class="menu-arrow"></span>
</div>
</div>
</div>
<div class="profile-section">
<div class="profile-section-title">更多</div>
<div class="profile-menu-card">
<div class="profile-menu-item">
<div class="profile-menu-left"><span class="menu-icon"></span><span>设置与帮助</span></div>
<span class="menu-arrow"></span>
</div>
<div class="profile-menu-item">
<div class="profile-menu-left"><span class="menu-icon"></span><span>意见反馈</span></div>
<span class="menu-arrow"></span>
</div>
</div>
</div>
</div>
</main>
<!-- ====== 底部导航栏 TabBar ====== -->
<nav class="bottom-tab-bar">
<button class="tab-btn active" data-module="发现" onclick="switchModule('发现')">
<span class="tab-icon tab-icon-discover"></span>
<span class="tab-label">发现</span>
</button>
<button class="tab-btn" data-module="知识" onclick="switchModule('知识')">
<span class="tab-icon tab-icon-knowledge"></span>
<span class="tab-label">知识</span>
</button>
<button class="tab-btn" data-module="数学人生" onclick="switchModule('数学人生')">
<span class="tab-icon tab-icon-life"></span>
<span class="tab-label">数学人生</span>
</button>
<button class="tab-btn" data-module="我的" onclick="switchModule('我的')">
<span class="tab-icon tab-icon-mine"></span>
<span class="tab-label">我的</span>
</button>
</nav>
</div>
<!-- ====== AI 聊天弹出层 ====== -->
<div class="chat-overlay" id="chatOverlay">
<div class="chat-panel">
<div class="chat-header">
<div class="chat-header-left">
<div class="ai-avatar">?</div>
<span>AI 数学助教</span>
</div>
<button class="chat-close" onclick="closeChat()"></button>
</div>
<div class="chat-messages" id="chatMessages">
<div class="chat-msg assistant">
<div class="msg-bubble">
你好!我是数学桥梁计划的 AI 助教<br><br>
<b>试试问我:</b><br>
· 学数学对学医有什么用?<br>
· 数学在建筑里怎么用?<br>
· 为什么学数学很重要?
</div>
</div>
</div>
<div class="quick-asks">
<button class="quick-ask" onclick="quickAsk('学数学对学医有什么用?')">数学与医学</button>
<button class="quick-ask" onclick="quickAsk('数学在建筑里怎么用?')">数学与建筑</button>
<button class="quick-ask" onclick="quickAsk('AI和数学有什么关系?')">数学与AI</button>
<button class="quick-ask" onclick="quickAsk('为什么学数学很重要?')">为什么要学</button>
</div>
<div class="chat-input-area">
<input type="text" id="chatInput" placeholder="输入你的问题..." onkeydown="if(event.key==='Enter')sendMessage()">
<button class="send-btn" onclick="sendMessage()"></button>
</div>
</div>
</div>
<!-- ====== 视频弹窗 ====== -->
<div class="video-modal" id="videoModal">
<div class="video-modal-content">
<div class="video-modal-header">
<h3 id="videoModalTitle">视频标题</h3>
<button onclick="closeVideo()"></button>
</div>
<div class="video-player-area">
<div class="video-placeholder">
<div class="play-icon"></div>
<p id="videoPlaceholderText">点击下方按钮在浏览器中查看完整课程</p>
</div>
</div>
<div class="video-info">
<p class="video-desc" id="videoModalDesc"></p>
<div class="video-meta">
<span id="videoModalAuthor">--</span>
<span id="videoModalCat">--</span>
</div>
</div>
<div class="video-actions">
<button class="btn-secondary" onclick="toggleVideoFav()" id="videoModalFav">☆ 收藏</button>
<button class="btn-secondary" onclick="closeVideo();openChat();quickAsk(document.getElementById('videoModalTitle').textContent+' 这个视频讲了什么?')">? 问AI</button>
<button class="btn-primary" id="videoLinkBtn" onclick="openVideoLink()">查看原视频</button>
</div>
</div>
</div>
<!-- ====== 个人中心列表面板 ====== -->
<div class="profile-list-panel" id="profileListPanel" style="display:none;">
<div class="plp-header">
<button class="plp-back" onclick="hideProfileListPanel()">← 返回</button>
<span class="plp-title" id="profileListTitle">列表</span>
</div>
<div class="plp-content" id="profileListContent"></div>
</div>
<!-- ====== 头像选择器 ====== -->
<div class="avatar-picker-overlay" id="avatarPickerOverlay" onclick="closeAvatarPicker(event)">
<div class="avatar-picker-card" onclick="event.stopPropagation()">
<div class="picker-title">选择头像</div>
<div class="picker-emoji-grid" id="pickerEmojiGrid"></div>
<div class="picker-color-row" id="pickerColorRow"></div>
<div class="picker-actions">
<button class="picker-btn" onclick="editNickname()">✏️ 改昵称</button>
<button class="picker-btn primary" onclick="closeAvatarPicker()">完成</button>
</div>
</div>
</div>
<!-- ====== 模拟器结局面板 ====== -->
<div class="ending-overlay" id="endingOverlay" onclick="closeEnding()">
<div class="ending-card" onclick="event.stopPropagation()">
<div class="ending-badge" id="endingBadge">🎓</div>
<h3 class="ending-type" id="endingType">结局</h3>
<p class="ending-title" id="endingTitle"></p>
<div class="ending-stats" id="endingStats"></div>
<p class="ending-scene" id="endingScene"></p>
<button class="picker-btn primary" onclick="closeEnding();resetSimulator()">重新开始</button>
<button class="picker-btn" onclick="closeEnding()">关闭</button>
</div>
</div>
<!-- ====== 存档面板 ====== -->
<div class="save-panel-overlay" id="savePanelOverlay" onclick="closeSavePanel()">
<div class="save-panel" onclick="event.stopPropagation()">
<div class="save-panel-header">
<button class="sp-back" onclick="closeSavePanel()">← 返回</button>
<span class="sp-title">存档管理</span>
</div>
<div class="save-slots" id="saveSlots">
<!-- JS 动态渲染 -->
</div>
</div>
</div>
<!-- ====== 结局画廊 ====== -->
<div class="gallery-overlay" id="galleryOverlay" onclick="closeGallery()">
<div class="gallery-panel" onclick="event.stopPropagation()">
<div class="gallery-header">
<button class="sp-back" onclick="closeGallery()">← 返回</button>
<span class="sp-title">结局画廊</span>
<span class="gallery-progress" id="galleryProgress"></span>
</div>
<div class="gallery-grid" id="galleryGrid">
<!-- JS 动态渲染 -->
</div>
</div>
</div>
<script src="/static/js/app.min.js?v=10.1"></script>
</body>
</html>
+411
View File
@@ -0,0 +1,411 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数学少年线 · 互动剧本</title>
<style>
:root{
--ink:#182430;
--ink-2:#263947;
--paper:#F7F3EA;
--paper-2:#FFFDF8;
--teal:#2E7D72;
--gold:#B8893A;
--blue:#3F6C8F;
--red:#A64A44;
--muted:#7C8790;
--line:#D8D2C4;
--shadow:0 18px 50px rgba(24,36,48,.16);
}
*{box-sizing:border-box;margin:0;padding:0}
body{
min-height:100vh;
background:
radial-gradient(circle at 15% 10%, rgba(62,108,143,.16), transparent 32%),
radial-gradient(circle at 85% 20%, rgba(46,125,114,.14), transparent 30%),
linear-gradient(160deg,#E9E4D8 0%,#F4F0E7 45%,#DDE5E4 100%);
color:var(--ink);
font-family:"PingFang SC","Microsoft YaHei","Segoe UI",sans-serif;
display:flex;
align-items:center;
justify-content:center;
padding:22px;
}
.shell{
width:100%;
max-width:860px;
background:var(--paper-2);
border:1px solid var(--line);
border-radius:18px;
box-shadow:var(--shadow);
overflow:hidden;
}
.topbar{
display:flex;
align-items:center;
gap:14px;
padding:18px 22px;
background:linear-gradient(120deg,var(--ink) 0%,var(--ink-2) 100%);
color:#F7F3EA;
}
.mark{
width:46px;
height:46px;
border-radius:50% 50% 50% 50% / 60% 60% 40% 40%;
display:flex;
align-items:center;
justify-content:center;
font-size:24px;
font-weight:700;
background:linear-gradient(140deg,var(--teal),var(--blue));
color:#fff;
box-shadow:0 8px 20px rgba(0,0,0,.22);
flex:none;
}
.topbar h1{font-size:21px;font-weight:700;letter-spacing:.5px}
.topbar p{font-size:12px;color:#C9D4DA;margin-top:4px;line-height:1.5}
.top-actions{margin-left:auto;display:flex;gap:8px}
.top-actions button{
border:1px solid rgba(255,255,255,.24);
background:rgba(255,255,255,.08);
color:#F7F3EA;
padding:8px 13px;
border-radius:10px;
font-size:12px;
cursor:pointer;
transition:.2s;
}
.top-actions button:hover{background:rgba(255,255,255,.18)}
.stats{
display:flex;
gap:10px;
padding:14px 22px;
border-bottom:1px solid var(--line);
background:var(--paper);
flex-wrap:wrap;
}
.stat{
flex:1 1 130px;
min-width:130px;
border:1px solid var(--line);
border-radius:12px;
padding:10px 12px;
background:#fff;
display:flex;
align-items:center;
gap:9px;
}
.stat-ico{
width:30px;
height:30px;
border-radius:9px;
display:flex;
align-items:center;
justify-content:center;
font-size:14px;
font-weight:700;
color:#fff;
flex:none;
}
.stat b{display:block;font-size:15px;line-height:1.2}
.stat span{font-size:11px;color:var(--muted)}
.stage{
padding:22px 22px 0;
display:flex;
align-items:center;
gap:12px;
font-size:12px;
color:var(--muted);
}
.stage-line{height:1px;flex:1;background:var(--line)}
.stage-tag{
color:var(--teal);
font-weight:700;
border:1px solid rgba(46,125,114,.35);
background:rgba(46,125,114,.08);
padding:4px 10px;
border-radius:999px;
letter-spacing:1px;
}
.reader{
padding:18px 24px 26px;
min-height:300px;
}
.speaker{
display:inline-flex;
align-items:center;
gap:8px;
margin-bottom:14px;
color:var(--ink-2);
font-size:13px;
font-weight:700;
}
.speaker::before{
content:"";
width:8px;
height:8px;
border-radius:50%;
background:var(--gold);
}
.scene-text{
font-size:17px;
line-height:1.95;
white-space:pre-wrap;
color:var(--ink);
min-height:180px;
}
.choices{
display:flex;
flex-direction:column;
gap:10px;
padding:0 24px 26px;
}
.choice{
display:block;
width:100%;
text-align:left;
border:1px solid var(--line);
border-left:4px solid var(--teal);
background:#fff;
color:var(--ink);
border-radius:12px;
padding:14px 16px;
font-size:15px;
line-height:1.5;
cursor:pointer;
transition:.18s;
}
.choice:hover{
transform:translateY(-2px);
border-color:var(--teal);
box-shadow:0 8px 20px rgba(46,125,114,.14);
}
.choice small{
display:block;
margin-top:5px;
color:var(--muted);
font-size:11px;
}
.end-note{
text-align:center;
padding:10px 0 4px;
color:var(--muted);
font-size:13px;
}
.footer{
padding:12px 22px;
border-top:1px solid var(--line);
background:var(--paper);
color:var(--muted);
font-size:11px;
text-align:center;
}
@media (max-width:640px){
body{padding:12px}
.shell{border-radius:14px}
.topbar{padding:14px 16px}
.stats{padding:12px 14px}
.stat{flex-basis:calc(50% - 6px);min-width:0}
.reader{padding:16px 16px 20px}
.choices{padding:0 16px 20px}
.scene-text{font-size:15px}
}
</style>
</head>
<body>
<div class="shell">
<div class="topbar">
<div class="mark"></div>
<div>
<h1>数学少年线</h1>
<p>八幕完整版 · 大学数学专业四年 · 从录取通知书到直博</p>
</div>
<div class="top-actions">
<button onclick="resetGame()">重新开始</button>
</div>
</div>
<div class="stats" id="statsBar"></div>
<div class="stage" id="stageBar"></div>
<div class="reader">
<div class="speaker" id="speaker">旁白</div>
<div class="scene-text" id="sceneText">正在加载剧本……</div>
</div>
<div class="choices" id="choicesArea"></div>
<div class="footer">数学少年线 · 完整互动剧本 v2 · 85 节点 / 107 选项</div>
</div>
<script>
const SAVE_KEY = 'math_teen_story_save';
let storyData = null;
let state = {
currentNode: null,
stats: { shuli: 10, xiayi: 10, xinzhi: 10 },
fav: {},
history: []
};
let typeTimer = null;
function esc(s){
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}
function stageOf(id){
const map = {
c1: '第一幕 · 大一上:小镇少年走进数学系',
c2: '第二幕 · 大一下:第一次完整证明',
c3: '第三幕 · 大二上:讨论班与诺特',
c4: '第四幕 · 大二下:数学与生活',
c5: '第五幕 · 大三上:方向与高斯',
c6: '第六幕 · 大三下:科研低谷与诺特',
c7: '第七幕 · 大四上:直博申请',
c8: '第八幕 · 毕业前夜与直博第一年'
};
const key = (id || '').slice(0,2);
return map[key] || '数学少年线';
}
function statColor(k){
if(k==='shuli') return '#3F6C8F';
if(k==='xinzhi') return '#2E7D72';
return '#B8893A';
}
function statLabel(k){
if(k==='shuli') return '数理根基';
if(k==='xinzhi') return '心志韧性';
return '同行温度';
}
function statIcon(k){
if(k==='shuli') return '∑';
if(k==='xinzhi') return 'θ';
return 'π';
}
function renderStats(){
const keys = ['shuli','xiayi','xinzhi'];
document.getElementById('statsBar').innerHTML = keys.map(k => {
const v = state.stats[k] || 0;
return '<div class="stat"><div class="stat-ico" style="background:'+statColor(k)+'">'+statIcon(k)+'</div><div><b>'+v+'</b><span>'+statLabel(k)+'</span></div></div>';
}).join('') + '<div class="stat" style="flex-basis:120px"><div class="stat-ico" style="background:#A64A44">4</div><div><b>4 位数学家</b><span>高斯 · 祖冲之 · 欧拉 · 诺特</span></div></div>';
}
function renderStage(){
document.getElementById('stageBar').innerHTML = '<span class="stage-tag">'+esc(stageOf(state.currentNode))+'</span><span class="stage-line"></span><span>'+esc(storyData.title)+'</span>';
}
function renderNode(){
if(!storyData || !storyData.nodes) return;
const node = storyData.nodes[state.currentNode];
if(!node){
document.getElementById('sceneText').textContent = '节点未找到,请重新开始。';
return;
}
if(typeTimer) clearTimeout(typeTimer);
renderStats();
renderStage();
document.getElementById('speaker').textContent = node.character || '旁白';
const textEl = document.getElementById('sceneText');
const full = node.scene || '';
textEl.textContent = '';
let i = 0;
function type(){
if(i < full.length){
textEl.textContent += full.charAt(i);
i++;
typeTimer = setTimeout(type, 16);
} else {
renderChoices(node.choices || []);
}
}
type();
saveState();
}
function renderChoices(choices){
const area = document.getElementById('choicesArea');
if(!choices.length){
area.innerHTML = '<div class="end-note">故事结束 · 你的数学,刚刚开始</div><button class="choice" onclick="resetGame()">再走一次这段路</button>';
return;
}
area.innerHTML = choices.map((c, idx) => {
const effect = c.effects || {};
const parts = [];
if(effect.shuli) parts.push('数理根基 '+(effect.shuli>0?'+':'')+effect.shuli);
if(effect.xinzhi) parts.push('心志韧性 '+(effect.xinzhi>0?'+':'')+effect.xinzhi);
if(effect.xiayi) parts.push('同行温度 '+(effect.xiayi>0?'+':'')+effect.xiayi);
const hint = parts.length ? '<small>'+parts.join(' · ')+'</small>' : '';
return '<button class="choice" onclick="choose(\''+esc(c.next)+'\', '+idx+')">'+esc(c.text)+hint+'</button>';
}).join('');
}
function choose(next, idx){
if(typeTimer) clearTimeout(typeTimer);
const node = storyData.nodes[state.currentNode];
const choice = (node.choices || [])[idx];
if(!choice) return;
const effects = choice.effects || {};
Object.keys(effects).forEach(k => {
if(k === 'favorability'){
Object.keys(effects[k] || {}).forEach(name => {
state.fav[name] = (state.fav[name] || 0) + effects[k][name];
});
} else {
state.stats[k] = (state.stats[k] || 0) + effects[k];
}
});
state.history.push(state.currentNode);
state.currentNode = next;
renderNode();
}
function saveState(){
try { localStorage.setItem(SAVE_KEY, JSON.stringify(state)); } catch(e){}
}
function loadState(){
try {
const raw = localStorage.getItem(SAVE_KEY);
if(raw){
const saved = JSON.parse(raw);
if(saved && saved.currentNode && storyData && storyData.nodes[saved.currentNode]){
state = Object.assign(state, saved);
return true;
}
}
} catch(e){}
return false;
}
function resetGame(){
if(typeTimer) clearTimeout(typeTimer);
state = {
currentNode: storyData ? storyData.start_node : null,
stats: { shuli: 10, xiayi: 10, xinzhi: 10 },
fav: {},
history: []
};
try { localStorage.removeItem(SAVE_KEY); } catch(e){}
if(storyData) renderNode();
}
async function boot(){
try {
const resp = await fetch('/api/story/math_teen');
const json = await resp.json();
if(json.code !== 0) throw new Error(json.msg || '加载失败');
storyData = json.data;
if(!loadState()){
state.currentNode = storyData.start_node;
}
renderNode();
} catch(e){
document.getElementById('sceneText').textContent = '剧本加载失败,请先启动 Flask 服务后访问本页。';
}
}
boot();
</script>
</body>
</html>
+102
View File
@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>数学人格测试 · MathBTI</title>
<meta name="description" content="12题发现你的数学人格类型——16种人格,4大族群">
<link rel="stylesheet" href="/static/css/mathbti.css?v=4">
</head>
<body>
<div class="app" id="app">
<!-- Intro Screen -->
<div class="screen active" id="introScreen">
<div class="intro-header">
<div class="intro-logo"></div>
<h1 class="intro-title">数学人格测试</h1>
<p class="intro-sub">12题发现你的数学人格类型</p>
<div class="intro-meta">⏱ 约2分钟 · 4选项 · 12人格</div>
</div>
<div class="intro-desc">
<span>你习惯靠直觉还是靠推演?</span>
<span>你更喜欢独自钻研还是和同伴讨论?</span>
<span>你偏爱古典数学还是现代科技?</span>
</div>
<div class="intro-axes" id="introAxes"></div>
<button class="btn-start" onclick="startQuiz()">开始测试</button>
</div>
<!-- Quiz Screen -->
<div class="screen" id="quizScreen">
<div class="quiz-progress-wrap">
<div class="quiz-progress-bar"><div class="quiz-progress-fill" id="progressFill" style="width:0%"></div></div>
<div class="quiz-progress-text" id="progressText">1/12</div>
</div>
<div class="quiz-question" id="quizQuestion"></div>
<div class="quiz-options" id="quizOptions"></div>
<button class="btn-next" id="btnNext" disabled onclick="nextQuestion()">下一题</button>
</div>
<!-- Result Screen -->
<div class="screen" id="resultScreen">
<div class="result-header">
<div class="result-portrait-wrap" id="resultPortrait"></div>
<h2 class="result-type-name" id="resultName"></h2>
<p class="result-tagline" id="resultTagline"></p>
<div class="result-mathematician" id="resultMathematician"></div>
</div>
<div class="result-keywords" id="resultKeywords"></div>
<div class="result-card">
<div class="result-card-title">你的数学人格画像</div>
<p class="result-card-desc" id="resultDesc"></p>
<p class="result-card-short" id="resultShort"></p>
</div>
<!-- Bio Expander -->
<div class="bio-expander" id="bioExpander">
<div class="bio-expander-header" id="bioHeader" onclick="toggleBio()">
<div class="bio-expander-left">
<span class="bio-expander-icon">📖</span>
<span class="bio-expander-title" id="bioTitle">关于 拉马努金</span>
</div>
<span class="bio-arrow" id="bioArrow"></span>
</div>
<div class="bio-expander-body" id="bioBody">
<div class="bio-inner">
<p class="bio-text" id="bioText"></p>
<div class="bio-fact-box">
<span class="bio-fact-emoji">💡</span>
<span class="bio-fact-text" id="bioFact"></span>
</div>
</div>
</div>
</div>
<div class="result-axes" id="resultAxes"></div>
<div class="result-stats5" id="resultStats5"></div>
<div class="result-adopt">
<button class="btn-adopt" onclick="adoptPet()">🧬 领养你的数学精灵</button>
<p class="adopt-hint">它将每天等你回来,喂经验、升级、解锁数学家的人生剧本</p>
</div>
<div class="result-card-notice" id="cardNotice">
<div class="rcn-icon">🎴</div>
<div class="rcn-text">恭喜获得「<span id="cardNoticeName"></span>」数学人物小卡!</div>
<div class="rcn-hint">去葫芦数学 <a href="/web" style="color:#A8907E;text-decoration:underline">查看我的卡册</a>,还有15张等你收集</div>
</div>
<div class="result-share">
<button class="btn-share secondary" onclick="copyShare()">📋 复制结果</button>
<button class="btn-share primary" onclick="generateShareCard()">📸 生成分享卡片</button>
<button class="btn-share" onclick="shareResult()">📤 分享给朋友</button>
</div>
<div class="share-card-preview" id="shareCardPreview" style="display:none">
<canvas id="shareCardCanvas" width="600" height="800" style="max-width:100%;border-radius:12px;margin-top:16px"></canvas>
<button class="btn-share primary" onclick="downloadShareCard()" style="margin-top:8px">💾 保存图片</button>
</div>
<button class="btn-retry" onclick="retryQuiz()">↻ 重新测试</button>
</div>
</div>
<div class="toast" id="toast"></div>
<script src="/static/js/mathbti.js?v=4"></script>
</body>
</html>