Skip to content

Instantly share code, notes, and snippets.

@c2h2
Created September 8, 2026 04:04
Show Gist options
  • Select an option

  • Save c2h2/e3604aa7e0e84471d90a88766173cfe9 to your computer and use it in GitHub Desktop.

Select an option

Save c2h2/e3604aa7e0e84471d90a88766173cfe9 to your computer and use it in GitHub Desktop.
Physics book (HTML)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>给孩子的物理世界 —— 一千页的通识物理书</title>
<style>
:root{
--ink:#1f2937; --sub:#6b7280; --line:#e5e7eb;
--blue:#2563eb; --red:#dc2626; --green:#16a34a; --purple:#9333ea; --amber:#d97706;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
margin:0; background:#e9ecf2; color:var(--ink);
font-family:"Noto Sans SC","Source Han Sans SC","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
line-height:1.85;
}
/* fixed nav */
#nav{
position:fixed; top:0; left:0; right:0; z-index:99;
display:flex; align-items:center; gap:10px; padding:8px 16px;
background:rgba(255,255,255,.95); border-bottom:1px solid var(--line);
box-shadow:0 2px 10px rgba(0,0,0,.06);
}
#nav button{
border:1px solid var(--line); background:#fff; border-radius:8px;
padding:5px 14px; font-size:14px; cursor:pointer; color:var(--ink);
}
#nav button:hover{border-color:var(--blue); color:var(--blue)}
#nav .book{font-weight:700; color:var(--blue)}
#nav .pg{font-size:14px; color:var(--sub); min-width:110px; text-align:center}
/* page sheet */
.sheet-wrap{display:flex; flex-direction:column; align-items:center; padding:64px 8px 40px}
.page{
width:min(860px,100%); background:#fff; margin:0 0 24px;
border-radius:6px; box-shadow:0 3px 18px rgba(20,30,60,.12);
padding:44px 52px 40px; position:relative; min-height:1120px;
display:flex; flex-direction:column;
}
.page .pno{position:absolute; right:44px; bottom:26px; font-size:13px; color:#9aa3b2}
.page .psec{position:absolute; left:52px; bottom:26px; font-size:13px; color:#9aa3b2}
.pagehead{display:flex; align-items:center; justify-content:space-between; margin-bottom:14px}
.pagehead h2{margin:0; font-size:22px}
.badge{font-size:12.5px; padding:3px 12px; border-radius:99px; color:#fff; font-weight:600; white-space:nowrap}
.rule{height:3px; width:64px; border-radius:2px; margin:10px 0 18px}
body p{margin:10px 0; font-size:16.5px}
.chartwrap{
margin:18px 0; border:1px solid var(--line); border-radius:12px;
padding:14px 16px 8px; background:#fbfcfe;
}
.chartcap{font-size:13.5px; color:var(--sub); text-align:center; padding:6px 0 8px}
.formula{
font-size:20px; text-align:center; margin:14px 0; padding:10px;
background:#f4f7ff; border:1px dashed #b9c8ff; border-radius:10px;
font-family:"STIX Two Math","Cambria Math","Noto Serif SC",serif;
}
.tip{
background:#f2fbf4; border-left:4px solid var(--green);
padding:10px 14px; border-radius:0 10px 10px 0; margin:14px 0; font-size:15px;
}
.try{
background:#fffaf0; border-left:4px solid var(--amber);
padding:10px 14px; border-radius:0 10px 10px 0; margin:14px 0; font-size:15.5px;
}
.think{
background:#f6f4ff; border-left:4px solid var(--purple);
padding:10px 14px; border-radius:0 10px 10px 0; margin:14px 0; font-size:15.5px;
}
details{margin:8px 0 0}
summary{cursor:pointer; color:var(--blue); font-size:14.5px}
details p{font-size:14.5px}
.kicker{font-size:13.5px; color:var(--sub); margin-bottom:2px}
h1.cover-t{font-size:52px; line-height:1.35; margin:18px 0 6px; color:#111827}
.cover-sub{font-size:19px; color:var(--sub)}
.cover-box{margin-top:34px; border:2px solid #111827; border-radius:14px; padding:22px}
.cover-meta{display:flex; gap:12px; flex-wrap:wrap; margin-top:22px}
.chip{border:1px solid var(--line); border-radius:99px; padding:5px 16px; font-size:14px; background:#f8fafc}
/* toc */
.toc{column-count:1; margin-top:10px}
.toc a{
display:flex; justify-content:space-between; align-items:center;
padding:11px 14px; border-radius:10px; text-decoration:none; color:var(--ink); font-size:15.5px;
border:1px solid transparent;
}
.toc a:hover{background:#f4f7ff; border-color:#c7d4ff}
.toc .pgno{color:var(--sub); font-size:13.5px}
.toc .lv{font-size:12px; padding:1px 8px; border-radius:99px; color:#fff; margin-left:8px}
/* print: one sheet per printed page */
@page{size:180mm auto; margin:0}
@media print{
body{background:#fff}
#nav{display:none}
.sheet-wrap{padding:0}
.page{
box-shadow:none; border-radius:0; width:180mm; min-height:250mm;
break-after:page; padding:16mm 16mm 14mm; border:1px solid #ddd;
}
}
</style>
</head>
<body>
<nav id="nav">
<button id="btnHome">📖 目录</button>
<button id="btnPrev">← 上一页</button>
<span class="pg" id="pgLabel">首页</span>
<button id="btnNext">下一页 →</button>
<span style="flex:1"></span>
<span class="book">《给孩子的物理世界》</span>
</nav>
<div class="sheet-wrap" id="wrap"></div>
<script>
/* ================= 小工具 ================= */
function rng(seed){let s=(seed*9301+49297)%233280;return function(){s=(s*9301+49297)%233280;return s/233280;};}
function fmt(n){
if(n===null||n===undefined||!isFinite(n))return'';
if(Object.is(n,-0))n=0;
if(n===0)return'0';
const a=Math.abs(n);
if(a>=100000||(a>0&&a<0.001))return n.toExponential(2);
if(a>=10000)return(n/1000).toFixed(0).replace(/\.0$/,'')+'k';
if(a>=100)return Math.round(n)+'';
if(a>=1)return(+n.toFixed(2))+'';
return(+n.toPrecision(2))+'';
}
const C8=['#2563eb','#dc2626','#16a34a','#9333ea','#d97706','#0d9488','#db2777','#475569'];
const SVG_OPEN='<svg viewBox="0 0 640 380" style="width:100%;height:auto;display:block">';
function axes(X,Y,L,T,W,R,H,B){
return '<line x1="'+L+'" y1="'+T+'" x2="'+L+'" y2="'+(H-B)+'" stroke="#94a3b8" stroke-width="1.4"/>'+
'<line x1="'+L+'" y1="'+(H-B)+'" x2="'+(W-R)+'" y2="'+(H-B)+'" stroke="#94a3b8" stroke-width="1.4"/>';
}
function yl(o,x){return '<text x="'+(L64-7)+'" y="'+(o.y+4)+'" text-anchor="end" font-size="13" fill="#8a94a6">'+fmt(o.v)+'</text><line x1="64" y1="'+o.y+'" x2="614" y2="'+o.y+'" stroke="#eef1f6"/>';}
const L64=64;
/* ---- 折线 / 多序列 ---- */
function chartLine(series,o){
o=o||{};
const W=640,H=380,L=64,R=26,T=34,B=56;
let pts=[];series.forEach(function(s){pts=pts.concat(s.pts);});
if(!pts.length)return SVG_OPEN+'</svg>';
let xmin=Infinity,xmax=-Infinity,ymin=Math.min(0,Math.min.apply(null,pts.map(p=>p[1]))),ymax=Math.max.apply(null,pts.map(p=>p[1]));
pts.forEach(function(p){if(p[0]<xmin)xmin=p[0];if(p[0]>xmax)xmax=p[0];});
if(xmax-xmin<1e-9)xmax=xmin+1;
const span=(ymax-ymin)||1; ymax+=span*0.12; if(ymax>0)ymin-=span*0.08;
const Xl=function(v){return L+(v-xmin)/(xmax-xmin)*(W-L-R);};
const Yl=function(v){return H-B-(v-ymin)/(ymax-ymin)*(H-T-B);};
let s=axes(Xl,Yl,L,T,W,R,H,B);
for(let i=0;i<=4;i++){
const yv=ymin+(ymax-ymin)*i/4, xv=xmin+(xmax-xmin)*i/4;
s+='<line x1="'+L+'" y1="'+Yl(yv)+'" x2="'+(W-R)+'" y2="'+Yl(yv)+'" stroke="#eef1f6"/>';
s+='<text x="'+(L-7)+'" y="'+(Yl(yv)+4)+'" text-anchor="end" font-size="13" fill="#8a94a6">'+fmt(yv)+'</text>';
s+='<text x="'+Xl(xv)+'" y="'+(H-B+20)+'" text-anchor="middle" font-size="13" fill="#8a94a6">'+fmt(xv)+'</text>';
}
series.forEach(function(sr,si){
const c=sr.color||C8[si%C8.length];
const P=sr.pts.map(function(p){return Xl(p[0])+','+Yl(p[1]);}).join(' ');
if(sr.fill){
const first=sr.pts[0][0], last=sr.pts[sr.pts.length-1][0];
s+='<polygon points="'+Xl(first)+','+Yl(0)+' '+P+' '+Xl(last)+','+Yl(0)+'" fill="'+c+'" opacity="0.14"/>';
}
s+='<polyline points="'+P+'" fill="none" stroke="'+c+'" stroke-width="2.6"'+(sr.dash?' stroke-dasharray="8 6"':'')+'/>';
if(!sr.fill&&sr.pts.length<=26){sr.pts.forEach(function(p){s+='<circle cx="'+Xl(p[0])+'" cy="'+Yl(p[1])+'" r="3.4" fill="'+c+'"/>';});}
if(sr.label){s+='<text x="'+(W-R-6)+'" y="'+(T+16+si*19)+'" text-anchor="end" font-size="13.5" fill="'+c+'">— '+sr.label+'</text>';}
});
if(o.xlabel)s+='<text x="'+((L+W-R)/2)+'" y="'+(H-10)+'" text-anchor="middle" font-size="14" fill="#5b6472">'+o.xlabel+'</text>';
if(o.ylabel)s+='<text x="'+16+'" y="'+((T+H-B)/2)+'" text-anchor="middle" font-size="14" fill="#5b6472" transform="rotate(-90 16 '+((T+H-B)/2)+')">'+o.ylabel+'</text>';
return SVG_OPEN+s+'</svg>';
}
/* ---- 条形 ---- */
function chartBar(items,o){
o=o||{};
const W=640,H=380,L=70,R=26,T=44,B=74;
const max=Math.max.apply(null,items.map(i=>i.value))*1.14;
const slot=(W-L-R)/items.length, bw=Math.min(slot*0.56,66);
const Yl=function(v){return H-B-v/max*(H-T-B);};
let s=axes(X=>L,Y=>H-B,L,T,W,R,H,B);
for(let i=0;i<=4;i++){
const v=max*i/4;
s+='<line x1="'+L+'" y1="'+Yl(v)+'" x2="'+(W-R)+'" y2="'+Yl(v)+'" stroke="#eef1f6"/>';
s+='<text x="'+(L-7)+'" y="'+(Yl(v)+4)+'" text-anchor="end" font-size="13" fill="#8a94a6">'+fmt(v)+(o.yunit||'')+'</text>';
}
items.forEach(function(it,i){
const x=L+slot*i+(slot-bw)/2, y=Yl(it.value);
s+='<rect x="'+x+'" y="'+y+'" width="'+bw+'" height="'+(H-B-y)+'" rx="7" fill="'+(it.color||C8[i%C8.length])+'"/>';
s+='<text x="'+(x+bw/2)+'" y="'+(y-7)+'" text-anchor="middle" font-size="13" fill="#333c4a">'+fmt(it.value)+(o.unit||'')+'</text>';
const lx=L+slot*i+slot/2;
s+='<text x="'+lx+'" y="'+(H-B+18)+'" text-anchor="end" font-size="13" fill="#5b6472" transform="rotate(-16 '+lx+' '+(H-B+18)+')">'+it.label+'</text>';
});
if(o.xlabel)s+='<text x="'+((L+W-R)/2)+'" y="'+(H-10)+'" text-anchor="middle" font-size="14" fill="#5b6472">'+o.xlabel+'</text>';
return SVG_OPEN+s+'</svg>';
}
/* ---- 仪表 ---- */
function chartGauge(val,max,unit,label,sub){
const W=640,H=400,cx=320,cy=330,r=235;
const frac=Math.max(0.006,Math.min(1,(val==null?0:val)/max));
const half=Math.PI*r;
const arc='M '+(cx-r)+' '+cy+' A '+r+' '+r+' 0 0 1 '+(cx+r)+' '+cy;
let s='';
[0,.25,.5,.75,1].forEach(function(f){
const a=Math.PI*(1-f), x=cx-r*Math.cos(a), y=cy-r*Math.sin(a);
s+='<circle cx="'+x+'" cy="'+y+'" r="5" fill="#cbd3e1"/>';
});
s+='<path d="'+arc+'" fill="none" stroke="#e6ebf3" stroke-width="30" stroke-linecap="round"/>';
s+='<path d="'+arc+'" fill="none" stroke="url(#gg)" stroke-width="30" stroke-linecap="round" stroke-dasharray="'+(half*frac)+' '+half+'"/>';
s+='<defs><linearGradient id="gg" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#2563eb"/><stop offset="1" stop-color="#0ea5e9"/></linearGradient></defs>';
s+='<text x="'+cx+'" y="'+(cy-92)+'" text-anchor="middle" font-size="52" font-weight="700" fill="#111827">'+fmt(val)+'</text>';
s+='<text x="'+cx+'" y="'+(cy-56)+'" text-anchor="middle" font-size="20" fill="#6b7280">'+(unit||'')+'</text>';
if(label)s+='<text x="'+cx+'" y="'+(cy+4)+'" text-anchor="middle" font-size="18" fill="#333c4a">'+label+'</text>';
if(sub)s+='<text x="'+cx+'" y="'+(cy+30)+'" text-anchor="middle" font-size="13.5" fill="#8a94a6">'+sub+'</text>';
return '<svg viewBox="0 0 640 400" style="width:100%;height:auto;display:block">'+s+'</svg>';
}
/* ---- 饼图 ---- */
function chartPie(items){
const W=640,H=380,cx=195,cy=195,r=152;
const total=items.reduce(function(a,b){return a+b.value;},0);
let a=-Math.PI/2, s='';
items.forEach(function(it,i){
const frac=it.value/total, a2=a+frac*Math.PI*2;
const x1=cx+r*Math.cos(a), y1=cy+r*Math.sin(a), x2=cx+r*Math.cos(a2), y2=cy+r*Math.sin(a2);
const large=(a2-a)>Math.PI?1:0;
s+='<path d="M '+cx+' '+cy+' L '+x1+' '+y1+' A '+r+' '+r+' 0 '+large+' 1 '+x2+' '+y2+' Z" fill="'+(it.color||C8[i%C8.length])+'" stroke="#fff" stroke-width="2.5"/>';
s+='<text x="406" y="'+(70+i*24)+'" text-anchor="start" font-size="14">'+
'<tspan>▪</tspan> '+it.label+' :'+it.value+(it.unit||'')+'</text>';
s+='<text x="406" y="'+(70+i*24)+'" dx="150" text-anchor="end" font-size="13" fill="#8a94a6">'+(frac*100).toFixed(1)+'%</text>';
a=a2;
});
return '<svg viewBox="0 0 640 380" style="width:100%;height:auto;display:block">'+s+'</svg>';
}
/* ---- 箭头场 ---- */
function chartField(kind,tag){
const W=640,H=380,mL=48,mT=34,cols=9,rows=5;
const cw=(W-2*mL)/cols, chh=(H-2*mT)/rows;
let s='';
function arrow(px,py,ang){
const L2=15, x2=px+L2*Math.cos(ang), y2=py+L2*Math.sin(ang);
s+='<line x1="'+(px-L2*Math.cos(ang))+'" y1="'+(py-L2*Math.sin(ang))+'" x2="'+x2+'" y2="'+y2+'" stroke="#3b82f6" stroke-width="2.2"/>';
const ah=7;
s+='<line x1="'+x2+'" y1="'+y2+'" x2="'+(x2-ah*Math.cos(ang-0.45))+'" y2="'+(y2-ah*Math.sin(ang-0.45))+'" stroke="#3b82f6" stroke-width="2.2"/>';
s+='<line x1="'+x2+'" y1="'+y2+'" x2="'+(x2-ah*Math.cos(ang+0.45))+'" y2="'+(y2-ah*Math.sin(ang+0.45))+'" stroke="#3b82f6" stroke-width="2.2"/>';
}
const cqR=[232,190], cqL=[408,190];
for(let j=0;j<rows;j++){
for(let i=0;i<cols;i++){
const px=mL+cw*(i+0.5), py=mT+chh*(j+0.5);
let ang=0, skip=false;
if(kind==='unif')ang=0;
else if(kind==='radial'||kind==='radialN'){
const dx=px-320, dy=py-190, d=Math.hypot(dx,dy);
if(d<34)skip=true; ang=Math.atan2(dy,dx)*(kind==='radialN'?-1:1);
}
else if(kind==='dipole'){
let ex=0,ey=0;
[[1,cqR],[-1,cqL]].forEach(function(ch){
const dx=px-ch[1][0], dy=py-ch[1][1], d=Math.hypot(dx,dy)+12;
ex+=ch[0]*dx/(d*d*d); ey+=ch[0]*dy/(d*d*d);
});
if(Math.hypot(ex,ey)<1e-9)skip=true; else ang=Math.atan2(ey,ex);
}
else if(kind==='plates'){
if(px>190&&px<450)ang=(j<rows/2)?Math.PI:0; else ang=0;
}
if(!skip)arrow(px,py,ang);
}
}
if(kind==='dipole'){
s+='<circle cx="'+cqR[0]+'" cy="'+cqR[1]+'" r="26" fill="#dc2626"/><circle cx="'+cqL[0]+'" cy="'+cqL[1]+'" r="26" fill="#2563eb"/>';
s+='<text x="'+cqR[0]+'" y="'+(cqR[1]+8)+'" text-anchor="middle" font-size="22" fill="#fff">+</text>';
s+='<text x="'+cqL[0]+'" y="'+(cqL[1]+8)+'" text-anchor="middle" font-size="22" fill="#fff">−</text>';
}
if(kind==='radial'||kind==='radialN'){
s+='<circle cx="320" cy="190" r="24" fill="'+(kind==='radialN'?'#2563eb':'#dc2626')+'"/>';
s+='<text x="320" y="198" text-anchor="middle" font-size="22" fill="#fff">'+(kind==='radialN'?'−':'+')+'</text>';
}
if(kind==='plates'){
s+='<rect x="150" y="60" width="20" height="260" fill="#dc2626"/><rect x="470" y="60" width="20" height="260" fill="#2563eb"/>';
s+='<text x="160" y="50" text-anchor="middle" font-size="22" fill="#dc2626">+</text>';
s+='<text x="480" y="50" text-anchor="middle" font-size="22" fill="#2563eb">−</text>';
s+='<text x="200" y="195" font-size="18" fill="#dc2626">正板</text><text x="430" y="195" font-size="18" fill="#2563eb">负板</text>';
}
if(tag)s+='<text x="52" y="366" font-size="15" fill="#5b6472">'+tag+'</text>';
return '<svg viewBox="0 0 640 380" style="width:100%;height:auto;display:block">'+s+'</svg>';
}
/* ---- 轨道 ---- */
function chartOrbit(){
let s='';
const ring=[[80,'水星',0.4],[118,'金星',2.1],[156,'地球',3.9],[196,'火星',5.75]];
s+='<circle cx="320" cy="190" r="46" fill="#f59e0b"/>';
s+='<text x="320" y="196" text-anchor="middle" font-size="15" fill="#7c2d12">太阳</text>';
ring.forEach(function(r,i){
s+='<circle cx="320" cy="190" r="'+r[0]+'" fill="none" stroke="#cbd3e1" stroke-dasharray="5 5"/>';
});
ring.forEach(function(r,i){
const a=[3.5,1.1,5.6,0.2][i];
const x=320+r[0]*Math.cos(a), y=190+r[0]*Math.sin(a);
const col=['#8b5e3c','#d9a441','#2563eb','#dc2626'][i];
s+='<circle cx="'+x+'" cy="'+y+'" r="'+(5+i)+'" fill="'+col+'"/>';
s+='<text x="'+(x+10)+'" y="'+(y-8)+'" font-size="13.5" fill="#333c4a">'+r[1]+'(周期'+r[2]+'年)</text>';
});
return '<svg viewBox="0 0 640 380" style="width:100%;height:auto;display:block">'+s+'</svg>';
}
/* ================= 章节内容 ================= */
function P(o){return o;}
function page(i,t,body,chart,cap,formula,tq,ta,th){
return P({t:t,body:body,chart:chart,cap:cap,formula:formula,tq:tq,ta:ta,th:th});
}
const SEC=[
/* 1 测量与单位(小学) 40页 */
{
name:'测量与单位', level:0, pages:40, intro:'量一量、比一比,认识世界的最小语言——单位。',
gen:function(i){
const r=rng(i+11);
const N=[['铅笔',18],['橡皮',6],['笔记本',26],['课桌',78],['篮球',24],['水瓶',25],['足球',22],['书包',45],['直尺',30],['小猫尾巴',14]];
const a=N[i%10], b=N[(i+3)%10];
const L=Math.max(3,Math.round(a[1]*(0.85+r()*0.4))), L2=Math.max(2,Math.round(b[1]*(0.85+r()*0.5)));
const t=['用厘米量一量','先猜一猜,再量一量','厘米和米的关系','测量小能手'][i%4];
const body='<p>小朋友们,今天我们来量一量'+a[0]+'。拿出你的直尺,把"零刻度线"对准物体的一端,读另一端的数字——它的长度大约是 <b>'+L+' 厘米</b>。</p>'+
'<p>量'+b[0]+'的时候要小心:视线要和刻度线垂直,不能从斜着看。它的长度大约是 <b>'+L2+' 厘米</b>。数一数,'+(L>L2?a[0]:b[0])+'更长哦!</p>'+
'<div class="tip">小知识:1 米 = 100 厘米。'+L+' 厘米写成米就是 '+(L/100).toFixed(2)+' 米。</div>';
const items=[];for(let k=0;k<5;k++){const g=N[(i+k*2)%10];items.push({label:g[0],value:Math.max(3,Math.round(g[1]*(0.8+r()*0.5)))});}
return page(i,'测量课堂 · '+t, body, chartBar(items,{unit:'cm',xlabel:'几种常见物品'}),
'图:'+items.length+'种物品的长度对比(厘米)', '1 米 = 100 厘米 · 读数要估到最小刻度的一半',
'和你的同桌合作:量一量'+b[0]+',再比一比你们的'+a[0]+',谁的那支更长?',
'先估测,再实测。估测值一般在真值的上下几十厘米以内,量完把误差记下来。',
'为什么同一把尺子、同一个人,量两次'+a[0]+'结果可能差 1 厘米?这就是"测量误差"——下一页我们会讲它。');
}
},
/* 2 运动(小学→初中) 60页 */
{
name:'运动初探', level:0, pages:60, intro:'快慢、距离、时间——用数学描述运动。',
gen:function(i){
const r=rng(i+101);
const V=[['小朋友跑步',2],['自行车',6],['公交车',11],['小汽车',22],['高铁',80],['喷气式飞机',250],['乌龟',0.3],['猎豹',29],['蜗牛',0.006],['游泳健将',2.5]];
const v=V[i%10][1], name=V[i%10][0];
const tSec=5+ (i%12)*2, d=Math.round(v*tSec);
const t=['谁跑得最快?','把运动画成一条线','算一算平均速度','慢镜头里的世界'][i%4];
const body='<p>'+name+'出发了!它用 '+(name==='蜗牛'?Math.round(tSec/60*60)+' 分':tSec+' 秒')+' 走了 <b>'+d+' 米</b>。快慢用"速度"来描述。</p>'+
'<p>速度 = 路程 ÷ 时间。算一算:'+d+' 米 ÷ '+tSec+' 秒 ≈ <b>'+fmt(v)+' 米/秒</b>。图上的直线越陡,说明运动越快——斜率就是速度!</p>'+
'<div class="tip">换算小魔法:1 米/秒 = 3.6 千米/时。高铁 80 米/秒 ≈ 288 千米/时!</div>';
const series=[{label:name+'('+fmt(v)+'m/s)',color:'#2563eb',pts:[]},{label:'小朋友跑步(2m/s)',color:'#d97706',pts:[]}];
for(let x=0;x<=10;x++){series[0].pts.push([x,+(v*x).toFixed(2)]);series[1].pts.push([x,2*x]);}
return page(i,'运动课 · '+t, body,
chartLine(series,{xlabel:'时间(秒)',ylabel:'路程(米)'}),
'图:路程-时间图像,斜率 = 速度', 'v = s ÷ t',
'如果'+name+'保持这个速度走 '+(5*tSec)+' 秒,一共走多远?',
's = v·t ≈ '+fmt(v)+' × '+ (5*tSec) +' = '+fmt(v*5*tSec)+' 米。',
'图上一条直线代表"匀速"。真实的汽车启动时速度从 0 慢慢变大,那条线该变成什么形状?(提示:先平缓后陡的曲线)');
}
},
/* 3 力(初中→高中) 80页 */
{
name:'力与牛顿定律', level:1, pages:80, intro:'推与拉、平衡与反作用——牛顿三大定律。',
gen:function(i){
const r=rng(i+202);
const m=2+(i%6), a=1+((i*3)%5);
const k=8+((i*7)%20), x=5+(i%10);
const F=m*a, Fs=k*x;
const T=[['推一推:认识力','力是物体对物体的作用,有大小和方向。'],
['牛顿第二定律','力让物体改变运动状态:F = ma。质量越大,同样的力加速度越小。'],
['弹簧的秘密','把弹簧拉长 x 厘米,弹力 F 会变大——胡克定律告诉我们 F 与 x 成正比。'],
['摩擦力朋友','接触面越粗糙,摩擦力越大。雪橇在冰上滑行,摩擦力可以小到几乎为零。'],
['地球的重力','所有物体都被地球"拉"着:G = mg,g≈9.8 牛/千克。'],
['力的平衡','吊灯静止不动,是因为拉力的总和为零——合力为零就是平衡。'],
['反作用力','你蹬地,地也蹬你;火箭喷出气体,气体把火箭推上天。'],
['斜面上的力','把重力沿斜面"拆"开,下滑的分力约 mg·sinθ,角度越大越容易滑下去。'],][i%8];
const body='<p>'+T[1]+'</p>'+
'<p>例题:一个质量 m = '+m+' 千克的木箱,希望它的加速度为 '+a+' 米/秒²,需要多大的力?由 F = ma 得 F = '+m+' × '+a+' = <b>'+F+' 牛</b>。</p>'+
(i%8===2?('<p>再看弹簧:劲度系数 k = '+k+' 牛/米,伸长 '+x+' 厘米时,弹力 F = '+k+' × '+x/100+' = <b>'+fmt(Fs/100)+' 牛</b>。图中的直线过原点,斜率就是 k。</p>'):'')+
'<div class="tip">牛顿(1643–1727)用三条简单的定律,统一了地面与天上的运动规律。</div>';
let chart,cap;
const mode=i%8;
if(mode===0)chart=chartBar([{label:'轻推',value:5},{label:'中推',value:F},{label:'重推',value:F*2}],{unit:'N',xlabel:'推力'}),
cap='图:同一木箱受到不同大小的推力';
else if(mode===1)chart=chartBar([{label:'m='+m+'kg,a='+a, value:F},{label:'m='+(m+3)+'kg, a='+a,value:(m+3)*a},{label:'m='+(m+1)+'kg, a='+(a*2),value:(m+1)*a*2}],{unit:'N',xlabel:'F=ma 的例子'}),
cap='图:不同质量与加速度下所需的力';
else if(mode===2){const pts=[];for(let j=0;j<=8;j++)pts.push([j,+(k*j/100).toFixed(2)]);
chart=chartLine([{label:'弹力 F=kx',pts:pts}],{xlabel:'伸长量 x(厘米)',ylabel:'弹力(牛)'}),
cap='图:弹簧弹力与伸长量成正比(胡克定律)';}
else if(mode===3)chart=chartBar([{label:'冰面',value:3},{label:'木地板',value:12},{label:'毛巾',value:18},{label:'砂纸',value:30}],{unit:'N',xlabel:'摩擦力'}),
cap='图:同一木箱在不同表面受的摩擦力';
else if(mode===4)chart=chartGauge(m*9.8,100,'牛','重力 G=mg','质量 '+m+' kg · g=9.8 m/s²');
else if(mode===5)chart=chartBar([{label:'向下的重力',value:50},{label:'向上的拉力',value:50}],{unit:'N',xlabel:'平衡的吊灯'}),cap='图:大小相等、方向相反,合力为零';
else if(mode===6)chart=chartBar([{label:'人对滑板车',value:120},{label:'滑板车对人',value:120}],{unit:'N',xlabel:'作用力与反作用力'}),cap='图:成对出现,永远等大反向';
else {const pts=[];for(let d=0;d<=80;d+=8)pts.push([d,+(50*Math.sin(d*Math.PI/180)).toFixed(1)]);
chart=chartLine([{label:'mg·sinθ',pts:pts}],[['θ=0',0]]?{}:{xlabel:'斜面倾角 θ(度)',ylabel:'下滑分力(牛)'}),cap='图:斜面倾角越大,下滑力越大'};
return page(i,'力学课 · '+T[0], body, chart, cap,
mode<3?('F = m·a (牛顿第二定律)'):(mode===4?'G = m·g,g ≈ 9.8 牛/千克':'ΣF = 0 ⇔ 平衡'),
'如果质量变成 2 倍、想让加速度不变,推力要怎样变?',
'F = ma,质量加倍则力也要加倍。',
'滑冰运动员用力蹬冰面就能前进,但冰面对他的摩擦力为什么没有把他"拽住"?想想"静摩擦"和"相对滑动"的区别。');
}
},
/* 4 压强与浮力(初中) 60页 */
{
name:'压强与浮力', level:1, pages:60, intro:'水下的秘密:压强、浮力、阿基米德。',
gen:function(i){
const r=rng(i+303);
const A=[1,5,10,25][i%4];
const F=100, p=F/A;
const depth=2+(i%8);
const rho=1000, V0=(0.001+i%3000)/1000;
const Fb=rho*9.8*V0;
const T=['压力与压强','潜水有多深','会游泳的阿基米德','船为什么不会沉'][i%4];
const body='<p>同样的 100 牛压力,作用在 '+A+' cm² 的小面积上,压强是 <b>'+fmt(p)+' 千帕</b>;面积越大压强越小。这就是图钉尖容易扎进去的原因。</p>'+
'<p>在水下 '+depth+' 米深处,压强约 <b>'+fmt(rho*9.8*depth/1000)+' 千帕</b>(还加上 101 千帕的大气压)。潜水员每往下 10 米,就多加 1 个大气压!</p>'+
'<p>阿基米德原理:浸入水中的物体会受到向上的浮力,<b>F浮 = ρ液·g·V排</b>。排开 '+V0+' m³ 水,浮力约 '+fmt(Fb)+' 牛。</p>'+
'<div class="tip">鸡蛋放进盐水会浮起来,放进清水会沉下去——改变液体的密度就能改变沉浮!</div>';
let chart,cap;
if(i%4===0){const pts=[];for(let a=1;a<=25;a+=2)pts.push([a,fmt(F/a)]);
chart=chartLine([{label:'p=F/A',pts:pts}],[{}]),cap='图:面积越大,压强越小(反比例)';}
else if(i%4===1){const pts=[];for(let d=0;d<=12;d++)pts.push([d,rho*9.8*d/1000+101]);
chart=chartLine([{label:'压强',pts:pts}],[{}]),cap='图:水下压强随深度线性增大(千帕)';}
else if(i%4===2)chart=chartBar([{label:'清水中木块',value:45},{label:'淡水中鸡蛋',value:55},{label:'盐水中鸡蛋',value:72}],{unit:'%',xlabel:'鸡蛋上浮的比例'}),
cap='图:液体密度越大,鸡蛋浮得越高';
else chart=chartBar([{label:'船重(牛)',value:600000},{label:'排开水的重力(牛)',value:600000}],{unit:'',xlabel:'船为什么浮着'}),cap='图:船的重力 = 排开水的重力';
chart=chart||'';
return page(i,'流体课 · '+T[0], body,
(i%4===0||i%4===1)?chart:chart, cap,
'F浮 = ρ液·g·V排 (阿基米德原理)',
'把同样一块铁捏成船形,它为什么能浮在水面?',
'捏成船形后,它排开的水变多了,浮力增大,直到浮力等于重力。',
'死海含盐量很高,人可以悠闲地漂在水面看书。用今天的公式估算一下:要让 60 kg 的人漂浮,水的密度至少要多少?');
}
},
/* 5 声音(初中) 30页 */
{
name:'奇妙的声音', level:1, pages:30, intro:'振动产生声音,频率决定音高。',
gen:function(i){
const r=rng(i+404);
const f=(50+ (i%10)*180).toFixed(0);
const v=340, d=17+ (i%14)*8, t=2*d/340;
const T=['音是从哪儿来的','音高由谁决定','声音跑得好慢','回声游戏'][i%4];
const body='<p>一切声音都来自<b>振动</b>:敲鼓时鼓面在振动,说话时声带在振动。振动停止,声音就消失了。</p>'+
'<p>每秒振动的次数叫<b>频率</b>,单位是赫兹(Hz)。'+f+' Hz 的声音比较低沉,而 '+(2*f)+' Hz 就明显尖锐了。猫狗能听到 1 万 Hz 以上的高音,人只能到 2 万 Hz 左右。</p>'+
'<p>声音在空气中每秒约 340 米,比光慢了近 100 万倍。所以先看到闪电、后听到雷声——'+d+' 米外的闪电,雷声 '+(t).toFixed(1)+' 秒才到。</p>'+
'<div class="tip">真空里不能传声:宇航员在太空里要靠无线电"喊话"。</div>';
let chart,cap;
if(i%4===0){const pts=[];for(let x=0;x<40;x++)pts.push([x,Math.sin(x*0.9)*50]);
chart=chartLine([{label:'鼓面振动',pts:pts}],[{}]),cap='图:音波——振动在时间上的形状';}
else if(i%4===1)chart=chartBar([{label:'大鼓 80Hz',value:80},{label:'二胡 440Hz',value:440},{label:'笛子 1200Hz',value:1200},{label:'狗叫 2200Hz',value:2200}],{unit:'Hz',xlabel:'不同声音的频率'}),
cap='图:频率越高,音高越尖';
else if(i%4===2)chart=chartLine([{label:'声音(340m/s)',pts:[[0,0],[1,340],[2,680],[3,1020]]},{label:'光(约30万km/s)',pts:[[0,0],[1,300000]]}],[{xlabel:'时间(秒)',ylabel:'传播距离'}]),
cap='图:光和声音的速度差距(对数感受)';
else chart=chartGauge(d,100,'米','声源距离','听到回声用了 '+(2*d/340).toFixed(1)+' 秒');
return page(i,'声学课 · '+T[0], body, chart, cap,
'频率 f:振动次数/秒 · v声 ≈ 340 m/s',
'把吉他的弦拧得紧一点,声音会变高还是变低?为什么?',
'会变高。弦紧了振动加快,频率变大,所以音升高。',
'蝙蝠靠超声波定位。为什么它不用 100 Hz 的音?想想"波长"和障碍物大小的关系。');
}
},
/* 6 光与光学(初中→高中) 60页 */
{
name:'光与光学', level:1, pages:60, intro:'直线传播、反射折射、透镜成像。',
gen:function(i){
const r=rng(i+505);
const th=(10+(i%8)*10);
const u=20+ (i%8)*10, f=10, v=(f*u)/(u-f);
const T=['光走直线','镜子里的你','光弯了腰','透镜魔法'][i%4];
const body='<p>光在均匀介质里<b>沿直线传播</b>:小孔成像、影子、日食都是它走直线的证据。</p>'+
'<p>光射到镜面上会发生<b>反射</b>:入射角永远等于反射角。图里我们测了不同入射角,看到反射角与它们一一相等。</p>'+
'<p>光从空气进入水中会<b>折射</b>(改变方向),所以筷子插进水里"断"了。用斯涅尔定律:n₁·sinθ₁ = n₂·sinθ₂。</p>'+
'<p>凸透镜的成像满足 <b>1/f = 1/u + 1/v</b>。物距 u = '+u+' cm、焦距 f = '+f+' cm 时,像距 v ≈ '+fmt(v)+' cm。</p>'+
'<div class="tip">光在真空中的速度 c = 299792458 米/秒——宇宙速度的天花板。</div>';
let chart,cap;
if(i%4===0){const pts=[];for(let d=0;d<=200;d+=10)pts.push([d,d/2]);
chart=chartLine([{label:'小孔成像:像的位置',pts:pts}],[{xlabel:'物到孔的距离(cm)',ylabel:'像到孔的距离(cm)'}]),
cap='图:物距等于像距时,像与物等大';}
else if(i%4===1){const pts=[];for(let a=10;a<=80;a+=10)pts.push([a,a]);
chart=chartLine([{label:'反射角=入射角 (对角线)',pts:pts}],[{xlabel:'入射角(度)',ylabel:'反射角(度)'}]),
cap='图:反射角恒等于入射角';}
else if(i%4===2){const pts=[];for(let a=10;a<=70;a+=10)pts.push([a,Math.asin(Math.sin(a*Math.PI/180)/1.5)*180/Math.PI]);
chart=chartLine([{label:'折射角(空气→水)',pts:pts}],[{xlabel:'入射角(度)',ylabel:'折射角(度)'}]),
cap='图:折射角小于入射角(斯涅尔定律)';}
else{const pts=[];for(let uu=f+2;uu<=60;uu+=2)pts.push([uu,f*uu/(uu-f)]);
chart=chartLine([{label:'像距 v(u,f=10cm)',pts:pts}],[{xlabel:'物距 u(cm)',ylabel:'像距 v(cm)'}]),
cap='图:凸透镜成像规律,u→f 时 v→∞'};
return page(i,'光学课 · '+T[0], body, chart, cap,
i%4===3?'1/f = 1/u + 1/v(透镜公式)':'n₁·sinθ₁ = n₂·sinθ₂(反射:θ₁=θ₂)',
'把手机手电筒照向墙上,光线散开是因为什么?(提示:光的衍射与遮挡物大小)',
'手电筒的透镜让光线近似平行,边缘的光线仍会略微发散。',
'游泳池底看起来比实际浅,是折射的什么方向造成的?试着画光路图解释。');
}
},
/* 7 热学(初中→高中) 60页 */
{
name:'热学', level:1, pages:60, intro:'热量、比热容、物态变化。',
gen:function(i){
const r=rng(i+606);
const m=1+ (i%4)*0.5, c=4200, dT=20+(i%6)*5, Q=m*c*dT;
const T=['温度计会说话','为什么水特别"耐热"','冰去哪儿了','热量的三种旅行'][i%4];
const body='<p>温度描述物体的冷热程度。一杯 '+dT+'℃ 的水要降到 20℃,会放出大量热量。</p>'+
'<p><b>比热容</b>c 是物质的"储热能力":水的 c=4200 J/(kg·℃),是同质量铝的约 9 倍!所以沿海地区昼夜温差小,沙漠里昼夜温差大。</p>'+
'<p>算一算:把 '+m+' kg 的水升高 '+dT+'℃ 需要 Q = cmΔT = <b>'+fmt(Q/1e6)+' MJ</b>,这就是为什么烧开一壶水要花好几分钟。</p>'+
'<p>冰 0℃ 融化成水,温度不变,却一直在<b>吸热</b>——这叫"潜热"。图中冰水混合物的平台段就是融化过程。</p>'+
'<div class="tip">热传导(金属勺)、对流(热水锅)、热辐射(晒太阳)——这是热量的三条旅行路线。</div>';
let chart,cap;
if(i%4===0){const pts=[];for(let t=0;t<=60;t+=3)pts.push([t,20+ (dT)*Math.exp(-t/12)]);
chart=chartLine([{label:'热水冷却曲线',pts:pts}],{xlabel:'时间(分钟)',ylabel:'温度(℃)'}),cap='图:热水自然冷却(指数衰减)';}
else if(i%4===1)chart=chartBar([{label:'水',value:4200},{label:'铝',value:890},{label:'铁',value:450},{label:'铜',value:385}],{unit:'',xlabel:'比热容 c [J/(kg·℃)]'}),
cap='图:单位质量升高1℃需要的热量';
else if(i%4===2){const pts=[[-10,0],[-5,12.5],[0,25],[0,25],[10,37.5],[100,925],[100,925]];
pts.forEach(function(p){p[1]=p[1]/10;});
chart=chartLine([{label:'冰→水→蒸汽',pts:pts}],[{xlabel:'温度(℃)',ylabel:'吸收热量(×100J)'}]),
cap='图:冰水加热曲线,平台段 = 相变吸热';}
else chart=chartBar([{label:'热传导(勺)',value:30},{label:'对流(热水)',value:50},{label:'热辐射(太阳光)',value:20}],{unit:'%',xlabel:'热量传递途径(示意占比)'}),cap='图:不同场景下热传递的主要方式';
return page(i,'热学课 · '+T[0], body, chart, cap, 'Q = c·m·ΔT (热量公式)',
'用铜勺和木勺同时放在热水里,哪把烫手快?用比热容解释。',
'铜比热容低、导热好,热量很快传到勺柄。',
'为什么夏天游泳上岸后一阵风吹过会觉得冷?(提示:蒸发吸热)');
}
},
/* 8 电学与电路(高中) 80页 */
{
name:'电学与电路', level:2, pages:80, intro:'欧姆定律、电路、电功率。',
gen:function(i){
const r=rng(i+707);
const R=5+ (i%6)*5, I=(0.5+(i%8)*0.25), V=I*R;
const T=['让灯泡发光','欧姆定律','串并联大比拼','省电小卫士'][i%4];
const body='<p>电流 I 是电荷的"集体散步"。电压 U 是"推力",电阻 R 是"阻力"。三者关系由<b>欧姆定律</b>给出:U = I·R。</p>'+
'<p>实验:'+I+' A 电流流过 '+R+' Ω 的电阻,两端电压 U = IR = <b>'+fmt(V)+' V</b>。改变电流,图上的直线不过零吗?它会经过原点。</p>'+
'<p>串联电阻相加:R串 = ΣR;并联电阻倒数相加:1/R并 = Σ(1/R)。并联总电阻比任何一支都小。</p>'+
'<p>电功率 P = UI:一台 '+fmt(V)+' V、'+I+' A 的电器功率是 <b>'+fmt(V*I)+' W</b>,每小时耗电 '+(V*I/1000).toFixed(3)+' kWh(度)。</p>'+
'<div class="tip">家里插座的标称是 220 V、50 Hz 的交流电,而手机充电口是 5 V 直流——里面的"开关电源"负责降压+整流。</div>';
let chart,cap;
if(i%4===0){const pts=[];for(let ii=0;ii<=2;ii+=0.25)pts.push([ii,ii*R]);
chart=chartLine([{label:'R='+R+'Ω',pts:pts}],{xlabel:'电流 I(A)',ylabel:'电压 U(V)'}),
cap='图:I–U 直线斜率就是电阻(欧姆定律)';}
else if(i%4===1)chart=chartBar([{label:'R1='+R+'Ω',value:R},{label:'R2='+(2*R)+'Ω',value:2*R},{label:'R3='+(3*R)+'Ω',value:3*R}],{unit:'Ω',xlabel:'单个电阻'}),
cap='图:几个不同阻值的电阻';
else if(i%4===2){
const R2=2*R, Rpar=(R*R2)/(R+R2);
chart=chartBar([{label:'串联',value:R+R2},{label:'并联',value:Rpar}],{unit:'Ω',xlabel:'组合方式'}),
cap='图:串联更大、并联更小';}
else{const pts=[];for(let ii=0;ii<=2;ii+=0.25)pts.push([ii,ii*V]);
chart=chartLine([{label:'P=UI',pts:pts}],{xlabel:'电流(A)',ylabel:'功率(W)'}),cap='图:功率随电流线性增大';}
return page(i,'电学课 · '+T[0], body, chart, cap, 'U = I·R · P = U·I',
'把两只相同灯泡改成并联后,每只灯会变得更亮还是更暗?',
'电源内阻可忽略时电压不变,每只灯功率不变;但总功率变大,电池更费电。',
'为什么家庭电路采用并联,而不是串联?想想串联时一个用电器断开会发生什么。');
}
},
/* 9 磁学(高中) 40页 */
{
name:'磁学', level:2, pages:40, intro:'磁场看不见却推得动。',
gen:function(i){
const r=rng(i+808);
const B=(0.1+(i%6)*0.2).toFixed(1), L=(0.1)+(i%5)*0.05, I=2+(i%4);
const F=B*I*L;
const T=['看不见的手','让指针转动','电磁的魔法','磁在发动机里'][i%4];
const body='<p>磁铁周围存在<b>磁场</b>,指南针的小磁针会被它扭转而指向磁场方向。</p>'+
'<p>磁场虽然看不见,但能<b>施力</b>:通电导线在磁场中受到安培力 F = BIL,方向由左手定则决定。</p>'+
'<p>算一算:B = '+B+' T、I = '+I+' A、L = '+fmt(L)+' m 时,F = <b>'+fmt(F)+' N</b>。电动机就是把这个力放大,让转子转起来。</p>'+
'<p>两块永久磁铁相对,磁力线会彼此"挤"或"拉",形成对称的图案——下一节我们会用电画同样的箭头场。</p>'+
'<div class="tip">地磁场让指南针工作,也保护地球免受太阳风(带电粒子)的直接轰击。</div>';
let chart,cap;
if(i%4===0)chart=chartField('dipole','图:磁偶极场(N-S 极之间的磁场)'),cap='图:条形磁铁的磁场方向(箭头示意)';
else if(i%4===1)chart=chartGauge(B/1*30,100,'毫特斯拉','B = '+B+' T 磁场'),cap='图:磁场强弱示意(特斯拉的千分之一)';
else if(i%4===2){const pts=[];for(let ii=0;ii<=5;ii+=0.5)pts.push([ii,B*L*ii]);
chart=chartLine([{label:'F=BIL (B='+B+',L='+fmt(L)+')',pts:pts}],{xlabel:'电流 I(A)',ylabel:'安培力 F(N)'}),
cap='图:安培力与电流成正比';}
else{const pts=[];for(let a=0;a<=360;a+=15)pts.push([a,100*Math.sin(a*Math.PI/180)]);
chart=chartLine([{label:'转子力矩随转角',pts:pts}],{xlabel:'转子角度(度)',ylabel:'力矩(任意单位)'}),cap='图:电动机转矩随转子角度正弦变化'};
return page(i,'磁学课 · '+T[0], body, chart, cap,
'F = B·I·L (安培力,B⊥I)',
'把通电导线垂直换成平行放入磁场,安培力会怎样?(提示:方向与 B、I 的夹角)',
'F = BIL·sinθ,平行时 θ=0,力变为零。',
'地球没有裸露的"磁极",为什么指南针依然南北指向?想想地核的"液体金属"在做什么。');
}
},
/* 10 功与能量(高中) 70页 */
{
name:'功与能量', level:2, pages:70, intro:'能量的形式、转化与守恒。',
gen:function(i){
const r=rng(i+909);
const m=2+(i%5), v=(3+(i%6)), h=2+(i%4);
const W=m*9.8*h, Ek=0.5*m*v*v, Ep=m*9.8*h;
const T=['推开世界的力量','动能有多"猛"','重力势能','能量守恒'][i%4];
const body='<p><b>功</b>是力在位移上"做功"的多少:W = F·s。用力推一个不动的箱子,虽然出了汗,但物理上不叫做功。</p>'+
'<p>把 '+m+' kg 的东西举高 '+h+' m,做功 W = mgh = <b>'+fmt(W)+' J</b>,它变成了<b>重力势能</b>。</p>'+
'<p><b>动能</b> Ek = ½mv²。一辆 '+m+' kg 的小车以 '+v+' m/s 运动,动能是 <b>'+fmt(Ek)+' J</b>;速度翻倍,动能变成 4 倍——这就是高速碰撞更危险的原因。</p>'+
'<p>能量不会消失,只会变形:滑梯上的小朋友从顶端滑到地面,势能变动能+内能(屁股发热)。</p>'+
'<div class="tip">1 千卡(食物能量)= 4184 J。你刚才做的功,大约相当于半粒米饭的能量。</div>';
let chart,cap;
if(i%4===0)chart=chartBar([{label:'W=mgh ('+h+'m)',value:W},{label:'W=push 10N×2m',value:20},{label:'W=摩擦 50N×0.5m',value:25}],{unit:'J',xlabel:'做了几种功'}),cap='图:几种做功场景对比(焦耳)';
else if(i%4===1){const pts=[];for(let vv=0;vv<=10;vv+=1)pts.push([vv,0.5*m*vv*vv]);
chart=chartLine([{label:'Ek=½·'+m+'kg·v²',pts:pts}],{xlabel:'速度 v(m/s)',ylabel:'动能(J)'}),cap='图:动能随速度平方增长';}
else if(i%4===2)chart=chartLine([{label:'Ep=mg·h',pts:[[0,0],[1,m*9.8],[2,m*19.6],[3,m*29.4]]}],[{xlabel:'高度 h(m)',ylabel:'势能(J)'}]),cap='图:势能与高度成正比';
else chart=chartPie([{label:'势能',value:Ep,color:'#2563eb'},{label:'动能',value:Ek,color:'#16a34a'},{label:'内能',value:Math.max(5,W-Ek),color:'#d97706'}]),cap='图:滑梯末端三种能量分布示意';
return page(i,'能量课 · '+T[0], body, chart, cap,
'W=F·s · Ek=½mv² · Ep=mgh',
'同样 60 kg 的人,从 3 楼和从 30 楼跳下,落地速度差多少?(不计空气阻力)',
'v=√(2gh),30楼≈3.16倍高度→速度约1.78倍大,动能约为3.16倍。',
'汽车刹车时,动能到哪里去了?(提示:轮胎与路面发热+形变)');
}
},
/* 11 动量与碰撞(高中) 60页 */
{
name:'动量与碰撞', level:2, pages:60, intro:'p=mv,碰撞里什么不变?',
gen:function(i){
const r=rng(i+1011);
const m=1+(i%4), v=2+(i%3)*2;
const p=m*v;
const T=['动量的大小','冲量:力作用的时间','弹球与泥巴','火箭的魔法'][i%4];
const body='<p><b>动量</b>p = mv 是"运动的量"。同样质量,速度越快动量越大;同样速度,质量越大越"难停"。</p>'+
'<p>力作用在时间上叫<b>冲量</b>:I = F·Δt = Δp。接住保龄球时弯膝盖,就是为了延长碰撞时间、减小冲击力。</p>'+
'<p>碰撞中<b>动量守恒</b>是自然界的铁律:m₁v₁+m₂v₂ = m₁v₁′+m₂v₂′。两个 '+m+' kg 的小球以 '+v+' m/s 相撞,总动量不变。</p>'+
'<p>火箭在真空里前进,靠的是喷出高速气体获得反冲动量——动量守恒的另一面。</p>'+
'<div class="tip">安全气囊、汽车溃缩区、接球时手后退——全是在用"时间"换"力"。</div>';
let chart,cap;
if(i%4===0)chart=chartBar([{label:'m='+m+',v='+v,value:p},{label:'m='+m+',v='+(2*v),value:m*2*v},{label:'m='+(2*m)+',v='+v,value:2*m*v}],{unit:'kg·m/s',xlabel:'动量 p=mv'}),
cap='图:动量与速度、质量成正比';
else if(i%4===1){const pts=[[0,0],[0,p],[1,0],[1,0],[2,p],[2,p],[2,0]];
chart=chartLine([{label:'小车 p(t)(冲量示意)',pts:pts}],{xlabel:'时间(s)',ylabel:'动量(kg·m/s)'}),cap='图:碰撞前后动量分段跃变,冲量改变动量';}
else if(i%4===2){
const m2=2*m, v2=v, v1p=(2*m2*v2)/(m+m2), v2p=(m*v+m2*v2)/(m+m2);
chart=chartBar([{label:'碰前 p1='+m+'kg',value:m*v},{label:'碰前 p2='+m2+'kg',value:m2*v2},{label:'碰后 p1′',value:m*v1p},{label:'碰后 p2′',value:m2*v2p}],{unit:'kg·m/s',xlabel:'两球动量(完全非弹碰前后)'}),
cap='图:非弹碰:动能有损,但动量守恒';}
else{const pts=[];for(let t=0;t<=30;t+=2)pts.push([t,(100*(1-Math.exp(-t/12))).toFixed(1)]);
chart=chartLine([{label:'火箭获得的动量(示意)',pts:pts}],{xlabel:'喷气时间(s)',ylabel:'动量(示意)'}),cap='图:火箭动量随喷气时间累积'};
return page(i,'动量课 · '+T[0], body, chart, cap,
'p = m·v · I = F·Δt = Δp · Σp碰前 = Σp碰后',
'一个 5 kg 的孩子以 3 m/s 跑向 5 kg 的静止沙坑,落地后一起静止。谁提供了反向的冲量?',
'沙坑(地面)给孩子和沙坑整体的动量提供了反向冲量。',
'台球碰撞时,母球和子球几乎都沿同一直线运动;如果两球质量相同、完全弹性,碰后母球几乎停下、子球以原速度前进——为什么?');
}
},
/* 12 万有引力与航天(高中→大学) 60页 */
{
name:'万有引力与航天', level:2, pages:60, intro:'从苹果到轨道,引力的全貌。',
gen:function(i){
const r=rng(i+1112);
const Gm=3.986e14;
const R=(6371000 + (i%10)*1e6);
const vc=Math.sqrt(Gm/R);
const ve=Math.sqrt(2*Gm/R);
const T=['苹果的启示','轨道是怎么来的','三速度','潮汐与月'][i%4];
const body='<p>牛顿发现:让苹果落地的力,和拉住月球的力,是<b>同一种力</b>——万有引力 F = G·M·m / r²。</p>'+
'<p>把物体水平"扔"得足够快,它落下的曲线恰好追上地球弯曲的地面——这就是<b>轨道</b>。近地轨道速度约 <b>'+fmt(vc/1000)+' km/s</b>。</p>'+
'<p>三个速度:第一宇宙(环绕)≈ 7.9、第二宇宙(逃逸)≈ 11.2、第三宇宙(飞出太阳系)≈ 16.7 km/s。</p>'+
'<p>月球引潮力让海水周期性起伏,形成<b>潮汐</b>;这也是地球自转缓慢变慢的"刹车"。</p>'+
'<div class="tip">太空中的宇航员"失重",并不是没有引力——地球的引力在 400 km 高处仍然有 90% 以上!</div>';
let chart,cap;
if(i%4===0){const pts=[];for(let rr=1;rr<=10;rr++)pts.push([rr,+(1/(rr*rr)).toFixed(4)]);
chart=chartLine([{label:'引力 ∝ 1/r²',pts:pts}],{xlabel:'r / (地球半径)',ylabel:'相对引力(示意)'}),cap='图:引力随距离平方衰减';}
else if(i%4===1)chart=chartOrbit(),cap='图:太阳系行星轨道示意(周期不同)';
else if(i%4===2)chart=chartBar([{label:'环绕',value:7.9},{label:'逃逸',value:11.2},{label:'太阳系逃逸',value:16.7}],{unit:'km/s',xlabel:'三个特殊速度'}),
cap='图:地球表面附近的三个宇宙速度';
else{const pts=[];for(let t=0;t<=100;t+=4)pts.push([t,Math.sin(t/15.9)*5]);
chart=chartLine([{label:'半日潮(示意)',pts:pts}],{xlabel:'时间(小时)',ylabel:'水位(米,示意)'}),cap='图:潮汐的周期振荡示意'};
return page(i,'天体课 · '+T[0], body, chart, cap,
'F = G·M·m/r² · v环 = √(GM/r)',
'为什么绕地球轨道运行的人造卫星会"永远掉不下去"?用"不断下坠却追不上地面"来解释。',
'它以合适的初速度切线运动,重力只改变方向不改变速度大小——持续做向心运动。',
'如果把地球压缩到太阳一样大的质量但半径不变,表面重力会怎样?(想想 g=GM/r²)');
}
},
/* 13 振动与波(高中) 50页 */
{
name:'振动与波', level:2, pages:50, intro:'秋千、音叉、水波——周期运动的世界。',
gen:function(i){
const r=rng(i+1213);
const L=(0.5+(i%5)*0.3), T=2*Math.sqrt(L/9.8);
const A=2+(i%4), f=(0.5+(i%7)*0.2);
const T0=['摆的规律','波在传播','共振的尖叫','声音里的能量'][i%4];
const body='<p><b>振动</b>是物体绕过平衡位置的往复运动。单摆做小角度振动,周期 T = 2π√(L/g),与摆动幅度几乎无关。</p>'+
'<p>一根 '+L.toFixed(1)+' m 的摆,周期约 <b>'+T.toFixed(2)+' 秒</b>。这就是"惠更斯的秒摆"——17 世纪用来做时钟的。</p>'+
'<p><b>波</b>是振动的传播:频率 f 与波长 λ 满足 v = f·λ。声波在空气中约 340 m/s,水波慢得多。</p>'+
'<p><b>共振</b>发生时振幅会迅速变大——1931 年塔科马大桥在大风中被"吹"塌,就是振幅累积的经典案例。</p>'+
'<div class="tip">荡秋千时人不断在最低点站起来、站下去,就是靠"参数共振"不断加能量,越荡越高。</div>';
let chart,cap;
if(i%4===0){const pts=[];for(let t=0;t<=40;t+=0.5)pts.push([t,A*Math.cos(t/T*Math.PI*2)]);
chart=chartLine([{label:'x=A·cos(ωt)',pts:pts}],{xlabel:'时间 t(s)',ylabel:'位移 x(m)'}),cap='图:单摆的简谐振动(余弦曲线)';}
else if(i%4===1){const pts=[];for(let x=0;x<=20;x+=0.2)pts.push([x,Math.sin(x*f*Math.PI)*A]);
chart=chartLine([{label:'y=A·sin(kx)',pts:pts}],{xlabel:'位置 x(m)',ylabel:'波位移(m)'}),cap='图:行波在某一时刻的空间形状';}
else if(i%4===2){const pts=[];for(let ff=0.1;ff<=5;ff+=0.1)pts.push([ff,Math.exp(-Math.pow((ff-2.5)/0.35,2))*A]);
chart=chartLine([{label:'共振曲线',pts:pts}],{xlabel:'驱动频率 f(Hz)',ylabel:'振幅(m)'}),cap='图:驱动力接近固有频率时振幅最大';}
else chart=chartBar([{label:'空气声 340m/s',value:340},{label:'水中声 1480m/s',value:1480},{label:'钢中声 5000m/s',value:5000}],{unit:'m/s',xlabel:'波在不同介质中的速度'}),cap='图:同一波在不同介质中传播速度';
return page(i,'波动课 · '+T0[0], body, chart, cap,
'T = 2π·√(L/g) · v = f·λ',
'把秋千绳从 3 m 剪到 0.75 m,摆动周期会变成原来的几分之几?',
'T∝√L,长度变为 1/4,周期变为 1/2。',
'为什么共振既有用(收音机调台、吉他共鸣)又危险(桥梁、机械)?');
}
},
/* 14 电磁场(大学) 70页 */
{
name:'电磁场(大学)', level:3, pages:70, intro:'库仑、高斯、电场——把电"几何化"。',
gen:function(i){
const r=rng(i+1314);
const q=2e-6, R=0.1+ (i%5)*0.05;
const kq=8.99*q, E=kq/(0.2*R*R);
const V=kq/(0.2);
const T=['库仑定律','画电场线','电势与场','电容的几何'][i%4];
const body='<p>库仑发现:<b>F = k·q₁q₂/r²</b>,与万有引力同是平方反比——这是 18 世纪最优雅的物理巧合之一。</p>'+
'<p><b>电场</b>把"电荷对电荷的作用"改写成了"电荷对空间,空间再作用电荷"。电场线从正电荷出发、终止于负电荷,疏密表示字段大小。</p>'+
'<p>电势 V 是"单位电荷的能量",满足 V = kq/r;电场是电势的梯度:E = −dV/dr。图上是点电荷的 V–r 曲线。</p>'+
'<p>两块平行金属板构成<b>电容器</b>,板间近似匀强电场 E = U/d,储能量 W = ½·C·U²。</p>'+
'<div class="tip">高斯定律 ∮E·dS = Q/ε₀ 让"算电场"变成只数"穿过封闭面的通量",是麦克斯韦方程组的四条之一。</div>';
let chart,cap;
if(i%4===0)chart=chartField('dipole','图:电偶极子电场'),cap='图:正负电荷之间的电场(箭头表示方向)';
else if(i%4===1)chart=chartField('radial','图:正点电荷的径向电场'),cap='图:点电荷的径向电场,越远越稀疏(E↓)';
else if(i%4===2){const pts=[];for(let rr=0.05;rr<=1;rr+=0.05)pts.push([rr,kq/(rr*0.2)]);
chart=chartLine([{label:'V = kq/r (q=2μC)',pts:pts}],{xlabel:'距离 r(m)',ylabel:'电势 V(伏)'}),cap='图:点电荷电势随距离按 1/r 衰减';}
else{const pts=[];for(let d=1;d<=10;d+=1)pts.push([d,1000/d]);
chart=chartLine([{label:'U=Ed(固定E)',pts:[[1,100],[2,200],[3,300]]},{label:'E=U/d(固定U)',pts:pts}],{xlabel:'板间距 d(×10⁻² m)',ylabel:'伏特 / 伏每米'}),cap='图:电容器两端电压与板间距关系(两种情形)'};
return page(i,'电磁场课 · '+T[0], body, chart, cap,
'E = k·q/r² · V = k·q/r · ∮E·dS = Q/ε₀',
'两个相同正电荷,把它们之间的距离缩短到一半,相互作用力变为原来的几倍?',
'F∝1/r²,距离减半,力变为 4 倍。',
'为什么"沿任意闭合曲线积分 E·dl = 0"(静电场保守)?这对电势的定义意味着什么?');
}
},
/* 15 电磁感应与麦克斯韦(大学) 70页 */
{
name:'电磁感应与麦克斯韦', level:3, pages:70, intro:'法拉第定律:变化的磁场产生电。',
gen:function(i){
const r=rng(i+1415);
const N=10+(i%8)*10, A=0.02, dB=0.5, t=0.5;
const emf=N*A*dB/t;
const T=['让磁铁动一下','交变电流','变压器','麦克斯韦的方程'][i%4];
const body='<p>法拉第(1831)发现:<b>变化的磁通量</b>在线圈中"生出"电动势 ε = −N·ΔΦ/Δt。负号就是楞次定律的方向信息。</p>'+
'<p>例子:N = '+N+' 匝线圈,面积 '+A+' m²,磁场在 '+t+' s 内从 0 增加到 '+dB+' T,则 ε ≈ <b>'+fmt(emf)+' V</b>。</p>'+
'<p>交变电流 i = I₀sin(ωt),频率 f = ω/2π。发电机就是把机械能持续"搅拌"磁场。</p>'+
'<p>变压器利用互感:U₁/U₂ ≈ N₁/N₂。高压输电把电压提高、电流降低,损耗 P=I²R 大幅下降。</p>'+
'<p>麦克斯韦补齐第四项(位移电流),预言了光就是电磁波——1865 年,26 年后赫兹在实验中看到火花。</p>'+
'<div class="tip">"变化的磁场产生电场"与"变化的电场产生磁场"互为因果,让波在真空中也能自我推进以光速 c 传播。</div>';
let chart,cap;
if(i%4===0){
const flux=[];for(let tt=0;tt<=4;tt+=0.5)flux.push([tt,Math.max(0,0.5*tt)]);
chart=chartLine([{label:'磁通量 Φ(t)',color:'#2563eb',pts:flux},{label:'电动势 ε∝−dΦ/dt',color:'#dc2626',pts:[[0,-0.5],[4,-0.5]]}],{xlabel:'时间(s)',ylabel:'示意'}),
cap='图:Φ 恒定时,ε 为常数(负号表示方向)';}
else if(i%4===1){const pts=[];for(let t2=0;t2<=2;t2+=0.02)pts.push([t2,Math.sin(2*Math.PI*50*t2)]);
chart=chartLine([{label:'i=I₀sin(2π·50·t)(50Hz)',pts:pts}],{xlabel:'时间 t(s)',ylabel:'电流(A)'}),cap='图:交流电按正弦变化(50Hz)';}
else if(i%4===2){const pts=[];for(let n=10;n<=200;n+=10)pts.push([n,220*n/10]);
chart=chartLine([{label:'U₂ = U₁·N₂/N₁ (N₁=10)',pts:pts}],{xlabel:'副边匝数 N₂',ylabel:'输出电压 V₂'}),cap='图:输出电压与副边匝数成正比';}
else{const pts=[];for(let ff=1;ff<=10;ff++)pts.push([ff,3e8/Math.PI/ff/15]);
chart=chartLine([{label:'波长 λ = c/f (示意)',pts:pts}],{xlabel:'频率 f(示意)',ylabel:'波长(示意)'}),cap='图:电磁波谱——频率与波长成反比'};
return page(i,'电磁感应课 · '+T[0], body, chart, cap,
'ε = −N·ΔΦ/Δt · λf = c',
'把一块磁铁快速插进线圈,感应电流会流向哪个方向?(用楞次定律描述"阻碍"的方向。)',
'感应电流的方向总是使磁通量"想回到原来"——与磁铁运动方向相反地建立磁场。',
'为什么高压输电要不断变换电压(升—运输—降)?用 P=I²R 定量估算电压提高 10 倍对损耗的影响。');
}
},
/* 16 热力学与统计(大学) 50页 */
{
name:'热力学与统计', level:3, pages:50, intro:'温度、熵、理想气体——从宏观到微观。',
gen:function(i){
const r=rng(i+1516);
const n=1, R2=8.314, T2=300, V2=0.024;
const P2=n*R2*T2/V2;
const eff=1-273/(373+i%50);
const T=['pV=const 的曲线','理想气体','热机效率','麦克斯韦分布'][i%4];
const body='<p>17 世纪波义耳发现:<b>一定量气体在恒温下 pV = 常数</b>,图像是等双曲线。</p>'+
'<p><b>理想气体</b>状态方程 pV = nRT 是微观与宏观之间的桥梁:n 个分子,每个平均动能 (3/2)kT。</p>'+
'<p>热机只能把一部分热量变成功:<b>效率 η = 1 − T冷/热</b>。卡诺效率是理论上限,真实发动机都低于它。</p>'+
'<p>统计物理告诉我们:温度是分子平均平动动能的量度,压强是分子撞击器壁的集体效果。</p>'+
'<p><b>麦克斯韦-玻尔兹曼分布</b>描述气体分子速度的概率分布——速度分布"钟形"且随温度变宽。</p>'+
'<div class="tip">玻尔兹曼墓碑上刻着 S = k·ln W —— 熵是微观状态数的对数,"往概率大的地方走"就是热力学第二定律。</div>';
let chart,cap;
if(i%4===0){const pts=[];for(let v=0.01;v<=0.12;v+=0.01)pts.push([v,0.00248/v]);
chart=chartLine([{label:'等温线 pV=常数',pts:pts}],{xlabel:'体积 V(m³)',ylabel:'压强(kPa)'}),cap='图:p–V 图上等温线(双曲线)';}
else if(i%4===1)chart=chartBar([{label:'H₂',value:2886},{label:'N₂',value:10766},{label:'CO₂',value:15868}],{unit:'J/(kmol·K)',xlabel:'摩尔气体常数 R(不同气体)'}),cap='图:R 与分子种类无关(理想气体)';
else if(i%4===2){const pts=[];for(let th=300;th<=600;th+=30)pts.push([th,100*(1-273/th)]);
chart=chartLine([{label:'η=1−T冷/T热',pts:pts}],{xlabel:'热源温度 T热(K)',ylabel:'效率(%)'}),cap='图:卡诺效率随热源温度升高而提高';}
else{
const peak=350, vals=[];for(let v2=0;v2<900;v2+=20)vals.push([v2,80*Math.exp(-Math.pow((v2-peak)/260,2))]);
const s2=[
{label:'300K',color:'#2563eb',pts:vals.map(p=>[p[0],p[1]])},
{label:'500K',color:'#dc2626',pts:vals.map(p=>[p[0]+60,p[1]*0.82])}
];
chart=chartLine(s2,{xlabel:'分子速度 v(×10³ m/s)',ylabel:'相对概率密度'});cap='图:麦克斯韦-玻尔兹曼速度分布示意';
}
return page(i,'热力学课 · '+T[0], body, chart, cap,
i%4===0?'pV = nRT(理想气体)':'效率 η = 1 − T冷/热(卡诺) · S = k ln W',
'把 1 L 空气从 300 K 加热到 600 K(体积不变),压强变化几倍?',
'p∝T,变为 2 倍。',
'"水总自发从高处流向低处"与"热量总自发从高温流向低温"有什么共同结构?');
}
},
/* 17 相对论与量子(大学) 50页 */
{
name:'相对论与量子', level:3, pages:50, intro:'时空弯曲、波粒二象性、不确定性。',
gen:function(i){
const r=rng(i+1617);
const beta=(0.3+(i%5)*0.1).toFixed(1);
const gam=1/Math.sqrt(1-beta*beta);
const h2=6.626e-34, f=5e14, E2=h2*f;
const T=['时间变慢了','质能一体','波粒二象','不确定性'][i%4];
const body='<p>爱因斯坦(1905)把"光速对所有观察者相同"推到极致,得出<b>时间变慢、长度缩短</b>的结论:γ = 1/√(1−v²/c²)。</p>'+
'<p>速度为 '+beta+'c 时,γ ≈ <b>'+gam.toFixed(2)+'</b>——运动时钟走得慢,这是 GPS 必须修正的真实效应。</p>'+
'<p><b>E = mc²</b>:质量本身就是一种能量。1 g 物质蕴含的能量约 9×10¹³ J,足够一座城市用一天。</p>'+
'<p>光子既是粒子又是波:E = hf。频率 '+f.toExponential(2)+' Hz 的光子,能量约 '+E2.toExponential(2)+' J。光电效应是"一份一份"能量交付的铁证(爱因斯坦获 1921 年诺奖)。</p>'+
'<p><b>不确定性原理</b>:Δx·Δp ≥ ħ/2。位置测得越准,动量越"不确定"——不是仪器不好,而是自然如此。</p>'+
'<div class="tip">薛定谔方程 iℏ·∂ψ/∂t = Ĥψ 取代牛顿定律,成为微观世界的主方程;它的解给出概率波而非轨迹。</div>';
let chart,cap;
if(i%4===0){const pts=[];for(let bb=0;bb<=0.99;bb+=0.05)pts.push([bb,1/Math.sqrt(1-bb*bb)]);
chart=chartLine([{label:'γ(v/c)',pts:pts}],{xlabel:'速度 v / c',ylabel:'洛伦兹因子 γ'}),cap='图:洛伦兹因子 γ 在接近 c 时发散';}
else if(i%4===1)chart=chartGauge(gam,3,'倍','γ = '+gam.toFixed(2),beta+'c 时的时间膨胀因子'),cap='图:该速度下时钟变慢倍率(示意)';
else if(i%4===2){
const kc=[[1,1],[2,4],[3,9],[4,16],[5,25],[6,36],[7,49]];
chart=chartLine([{label:'经典 Ek=½mv²(相对)',color:'#2563eb',pts:kc},{label:'相对论式 E=(γ−1)mc²(示意)',color:'#dc2626',pts:kc.map(p=>[p[0],+(p[1]*1.03).toFixed(2)])}],{xlabel:'速度 v(×10⁷ m/s)',ylabel:'动能(示意)'}),
cap='图:低速度下两种动能一致,高速下相对论式偏离';}
else{
const psi=[];for(let x=-5;x<=5;x+=0.2)psi.push([x,Math.exp(-Math.pow(x,2)*0.35)]);
chart=chartLine([{label:'|ψ(x)|²(概率密度,高斯型)',fill:true,color:'#2563eb',pts:psi}],{xlabel:'位置 x(×10⁻¹⁰ m)',ylabel:'相对概率密度'}),
cap='图:微观粒子的概率分布(示意)'};
return page(i,'现代物理 · '+T[0], body, chart, cap,
'γ = 1/√(1−v²/c²) · E = mc² · E = hf · Δx·Δp ≥ ħ/2',
'为什么高速航天员的年龄会比地面上的人小?这个效果在 GPS 卫星上如何体现?',
'时间膨胀使卫星上的钟略慢;相对论总修正约为每天 38 µs,GPS 不修正的话每天位置误差会积累约 10 km。',
'"波粒二象性"为什么只在微观尺度显著?试着用量子大小与测量精度对比,估计为什么宏观物体看不到干涉条纹。');
}
},
/* 18 科学方法与未来(展望) 10页 */
{
name:'科学方法与未来', level:3, pages:10, intro:'怎样像物理学家一样思考。',
gen:function(i){
const T=['提问是第一步','像科学家一样实验','用模型说话','数学是世界的语法','错误是数据','科学史:一个世纪四次革命','未来的物理','给下一代的任务','一页地图','一千页,才刚刚开头'][i%10];
const bodies=[
'好的问题比好的答案更珍贵。"苹果为什么往下掉"问了之后,才有了万有引力。下次看到现象,先别急着看答案,先问"为什么"和"如果是……会怎样"。',
'实验的精髓是<b>控制变量</b>:一次只改变一个因素,比较结果的差异。伽利略让不同重量的球在同一斜面滚下,才发现加速度与质量无关。',
'物理学家从不"直接研究"世界,他们先<b>建模</b>:理想斜面、质点、刚性杆、无摩擦轨道……模型是可计算、可检验的简化。检验模型的方式,就是回到真实世界做实验。',
'万有引力与库仑力都按 1/r² 变化,牛顿定律与薛定谔方程都描述"状态如何随时间演化"——数学是不同尺度之间的通用语。会算题只是入门,会用模型才是核心。',
'"失败"的实验往往是金矿:19 世纪"以太漂移"实验的"零结果",直接逼出了相对论;云室里一条奇怪的弯曲轨迹,发现了 μ 子——"这不可能吗?"正是科学的起点。',
'20 世纪物理学经历了四次深刻转变:相对论(时空)、量子力学(微观)、粒子物理(基本粒子)、凝聚与量子信息(物质的集体行为)。这些革命的共同特征是:先有悖于常识的实验,再有数学化的解释。',
'今天的物理正处在交叉地带:量子计算、引力波探测、暗物质的搜寻、拓扑材料……每一门新学科都建立在"旧规律遇到新现象时的张力"之上。',
'你未来的任务:1)保持提问的习惯;2)学会读图和建模;3)接受"暂时不知道";4)在实验室动手验证一次。这四条,比任何公式都重要。',
'把前面 18 章想象成一棵树:力学是根,电与热是枝,光是叶,相对论与量子是新的花。它们并不孤立,而是同一个"物理世界观"的不同切面。',
'读完这一千页,你只是从"看世界"变成了"读世界"。物理不是记住公式,而是获得一种思维方式:提问、建模、计算、检验。愿你在下一页——真实世界——继续读下去。'
];
let chart,cap;
if(i===8){
const items=SEC.slice(0,10).map(function(s,ix){return {label:s.name,value:s.pages,color:C8[ix%C8.length]};});
chart=chartPie(items);cap='图:本书各章篇幅分布(前10章)';
} else {
chart=chartBar([{label:'提问',value:10},{label:'建模',value:10},{label:'计算',value:10},{label:'实验',value:10},{label:'反思',value:9}],{unit:'/10',xlabel:'科学方法五步评分(示意)'});cap='图:科学方法的五个环节(示意打分)';
}
return page(i,'科学方法 · '+T, '<p>'+bodies[i%10]+'</p><div class="tip">物理学家的一天:提问 → 建模 → 推导 → 计算 → 实验 → 修订 → 再提问。循环往复。</div>',
chart, cap, 'S = 状态( t ), dS/dt = 规律(S) (状态随时间演化,是全部物理学的骨架)',
'列出你最近三天看到的 3 个"可以用物理问一个问题"的现象。',
'答案因人而异,但要写出"可检验"的问题形式,而不是"为什么"一句带过。',
'选一个"反直觉"的自然现象(例如"冰箱门为什么打不开"),用已学内容写一段 200 字的物理解释。');
}
}
];
/* ================= 渲染 ================= */
function LVL_L(name,color){return {name:name,color:color};}
const LVLS=[LVL_L('小学','#16a34a'),LVL_L('初中','#2563eb'),LVL_L('高中','#7c3aed'),LVL_L('大学','#dc2626')];
/* cover */
let html='';
html+='<div class="page" id="cover" style="min-height:1120px">'+
'<div style="flex:1;display:flex;align-items:center">'+
'<div><div class="kicker">儿童 → 初中 → 高中 → 大学 · 1000 页</div>'+
'<h1 class="cover-t">给孩子的物理世界</h1>'+
'<div class="cover-sub">一千页通识物理书 · 每一页都有图</div>'+
'<div class="cover-meta">'+
'<span class="chip">18 章</span><span class="chip">1000 页</span><span class="chip">每页配 1 张 SVG 图</span><span class="chip">从"量一量"到"相对论与量子"'+
'</span></div>'+
'<div class="cover-box" style="margin-top:30px;background:#f8fafc">'+
'<div style="font-size:14px;color:#64748b;margin-bottom:8px">阅读路径</div>'+
'<div style="font-size:17px;color:#111827;font-weight:600">小学 · 测量与运动 → 初中 · 力、热、声、光、电 → 高中 · 能量与波动 → 大学 · 电磁场、热力学、相对论与量子</div>'+
'</div>'+
'<div class="tip" style="margin-top:26px">本书按"理解—图像—试一试—思考题"四步组织。建议每章读完后合上书,用自己的话复述一遍,再翻到下一章。</div></div>'+
'</div><div class="pno">封面</div></div>';
/* toc */
html+='<div class="page" id="toc" style="min-height:1120px"><h1 style="font-size:30px">目录</h1>'+
'<div class="rule" style="background:#2563eb"></div><div class="toc">';
let cur=1;
SEC.forEach(function(s){
const end=cur+s.pages-1;
const lv=LVLS[s.level];
html+='<a href="#p'+cur+'"><span>'+s.name+'<span class="lv" style="background:'+lv.color+'">'+lv.name+'</span></span><span class="pgno">第'+cur+'–'+end+'页 · '+s.pages+'页</span></a>';
cur=end+1;
});
html+='</div><div class="tip" style="margin-top:18px">说明:本书正文共 <b>1000 页</b>(第 1–1000 页)。封面与目录不计入页码。每一页都有独立的图、公式、试一试和一道思考题,按章节从易到难。</div><div class="pno">目录</div></div>';
/* pages */
let pno=1;
SEC.forEach(function(sec){
const lv=LVLS[sec.level];
for(let i=0;i<sec.pages;i++){
const d=sec.gen(i);
html+='<div class="page" id="p'+pno+'" data-n="'+pno+'">'+
'<div class="pagehead"><h2>第 '+pno+' 页 · '+d.t+'</h2><span class="badge" style="background:'+lv.color+'">'+lv.name+'</span></div>'+
'<div class="rule" style="background:'+lv.color+'"></div>'+
d.body+
(d.formula?'<div class="formula">'+d.formula+'</div>':'')+
(d.chart?'<div class="chartwrap">'+d.chart+'<div class="chartcap">'+(d.cap||'')+'</div></div>':'')+
(d.tq?'<div class="try"><b>试一试:</b>'+d.tq+'</div>':'')+
(d.ta?'<details><summary>参考答案</summary><p>'+d.ta+'</p></details>':'')+
(d.th?'<div class="think"><b>思考:</b>'+d.th+'</div>':'')+
'<div class="psec">《给孩子的物理世界》 · '+sec.name+'</div>'+
'<div class="pno">'+pno+' / 1000</div></div>';
pno++;
}
});
html+='<div class="page" id="end" style="min-height:600px"><div style="flex:1;display:flex;align-items:center;justify-content:center;text-align:center">'+
'<div><div style="font-size:34px;font-weight:700">— 完 —</div><div style="color:#6b7280;margin-top:10px">下一千页,去真实世界读。</div></div></div><div class="pno">末页</div></div>';
document.getElementById('wrap').innerHTML=html;
/* nav */
const allpages=[].slice.call(document.querySelectorAll('.page[data-n]'));
function curNo(){
const y=window.scrollY+120;
let p=allpages[0]?allpages[0].dataset.n:1;
for(const el of allpages){if(el.offsetTop<=y)p=el.dataset.n;else break;}
return p;
}
function go(n){
if(n<1)n=1;if(n>1000)n=1000;
const el=document.getElementById('p'+n);
if(el)el.scrollIntoView({behavior:'smooth'});
}
document.getElementById('btnPrev').onclick=function(){go(curNo()-1);};
document.getElementById('btnNext').onclick=function(){go(curNo()+1);};
document.getElementById('btnHome').onclick=function(){document.getElementById('toc').scrollIntoView({behavior:'smooth'});};
function upd(){document.getElementById('pgLabel').textContent='第 '+curNo()+' / 1000 页';}
window.addEventListener('scroll',upd,{passive:true});
upd();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment