Skip to content

Commit 032ed8a

Browse files
Political Economics
1 parent 6d124a1 commit 032ed8a

6 files changed

Lines changed: 1556 additions & 203 deletions
27.5 MB
Binary file not shown.

dynamics/The_physics_of_the_austerity_trap.txt

Lines changed: 903 additions & 0 deletions
Large diffs are not rendered by default.

dynamics/index.html

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>Earth Monitor</title>
7+
<style>
8+
*{box-sizing:border-box;margin:0;padding:0}
9+
body{background:#000;color:#7cff7c;font-family:monospace;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:16px}
10+
canvas{display:block;border-radius:50%;background:#000}
11+
.controls{margin-top:14px;display:flex;gap:10px;align-items:center}
12+
button{background:#001400;border:1px solid #00ff00;color:#7cff7c;padding:8px 14px;cursor:pointer;font-family:monospace;font-size:13px}
13+
button:hover{background:#002200}
14+
.seek-bar{width:220px;height:8px;background:#001a00;border:1px solid #00aa44;border-radius:3px;cursor:pointer;overflow:hidden}
15+
.seek-fill{height:100%;width:0%;background:linear-gradient(90deg,#00ff44,#7cff7c)}
16+
.subs{margin-top:12px;max-width:600px;min-height:2.5em;opacity:.85;text-align:center;font-size:13px;line-height:1.5}
17+
.footer{margin-top:10px;font-size:.75em;opacity:.6}
18+
.footer a{color:#7cff7c;text-decoration:none}
19+
</style>
20+
</head>
21+
<body>
22+
23+
<canvas id="c"></canvas>
24+
25+
<div class="controls">
26+
<button id="play">▶ Play</button>
27+
<div class="seek-bar" id="seek"><div class="seek-fill" id="seekFill"></div></div>
28+
</div>
29+
30+
<div id="subs" class="subs"></div>
31+
32+
<div class="footer">
33+
<a href="https://standardgalactic.github.io/spherepop/entropy_of_austerity.pdf" target="_blank">entropy_of_austerity.pdf</a>
34+
</div>
35+
36+
<audio id="audio" crossorigin="anonymous">
37+
<source src="The_physics_of_the_austerity_trap.mp3">
38+
<track kind="subtitles" src="The_physics_of_the_austerity_trap.vtt" default>
39+
</audio>
40+
41+
<script>
42+
const canvas = document.getElementById('c')
43+
const g = canvas.getContext('2d')
44+
const audio = document.getElementById('audio')
45+
const playBtn = document.getElementById('play')
46+
const subsEl = document.getElementById('subs')
47+
const seek = document.getElementById('seek')
48+
const seekFill = document.getElementById('seekFill')
49+
50+
const SIZE = Math.min(460, window.innerWidth - 32)
51+
canvas.width = SIZE
52+
canvas.height = SIZE
53+
const R = SIZE * 0.37
54+
const cx = SIZE / 2
55+
const cy = SIZE / 2
56+
57+
/* ── offscreen cloud buffer ── */
58+
const cloudCanvas = document.createElement('canvas')
59+
cloudCanvas.width = SIZE
60+
cloudCanvas.height = SIZE
61+
const cg = cloudCanvas.getContext('2d')
62+
63+
/* ── seek bar ── */
64+
seek.onclick = e => {
65+
if(!audio.duration) return
66+
const r = seek.getBoundingClientRect()
67+
audio.currentTime = ((e.clientX - r.left) / r.width) * audio.duration
68+
}
69+
70+
/* ── audio init ── */
71+
let actx, analyser, timeBuf
72+
let smoothEnergy = 0
73+
let bandEnergy = [0,0,0,0]
74+
75+
function initAudio(){
76+
if(actx) return
77+
actx = new (window.AudioContext||window.webkitAudioContext)()
78+
analyser = actx.createAnalyser()
79+
analyser.fftSize = 1024
80+
analyser.smoothingTimeConstant = 0.85
81+
const src = actx.createMediaElementSource(audio)
82+
src.connect(analyser)
83+
analyser.connect(actx.destination)
84+
timeBuf = new Uint8Array(analyser.fftSize)
85+
}
86+
87+
playBtn.onclick = () => {
88+
initAudio()
89+
if(audio.paused){ audio.play(); playBtn.textContent='⏸ Pause' }
90+
else { audio.pause(); playBtn.textContent='▶ Play' }
91+
}
92+
93+
/* ── mobile-safe subtitles ── */
94+
function setupSubtitles(){
95+
const track = audio.textTracks && audio.textTracks[0]
96+
if(!track){ subsEl.textContent = ''; return }
97+
98+
track.mode = 'hidden'
99+
100+
function update(){
101+
const cues = track.activeCues
102+
subsEl.textContent = (cues && cues.length) ? cues[0].text : ''
103+
}
104+
105+
audio.addEventListener('timeupdate', update)
106+
audio.addEventListener('seeked', update)
107+
audio.addEventListener('play', update)
108+
109+
// iOS needs a moment before track.mode sticks
110+
setTimeout(() => { try{ track.mode = 'hidden' }catch(e){} }, 500)
111+
112+
// polling fallback — critical for mobile where cues load late
113+
let tries = 0
114+
const interval = setInterval(() => {
115+
tries++
116+
if(track.cues && track.cues.length){ update(); clearInterval(interval) }
117+
if(tries > 100) clearInterval(interval)
118+
}, 100)
119+
}
120+
121+
audio.addEventListener('loadedmetadata', setupSubtitles)
122+
audio.addEventListener('loadeddata', setupSubtitles)
123+
124+
/* ── energy ── */
125+
function updateEnergy(){
126+
if(audio.duration) seekFill.style.width = (audio.currentTime / audio.duration * 100) + '%'
127+
if(!analyser) return
128+
129+
analyser.getByteTimeDomainData(timeBuf)
130+
let e = 0
131+
for(let i=0;i<timeBuf.length;i++){ const d=timeBuf[i]-128; e+=d*d }
132+
e /= timeBuf.length
133+
smoothEnergy += (Math.min(e/3000,1) - smoothEnergy) * 0.05
134+
135+
const freq = new Uint8Array(analyser.frequencyBinCount)
136+
analyser.getByteFrequencyData(freq)
137+
const band = Math.floor(freq.length/4)
138+
for(let b=0;b<4;b++){
139+
let s=0; for(let i=b*band;i<(b+1)*band;i++) s+=freq[i]
140+
bandEnergy[b] += (s/band/255 - bandEnergy[b]) * 0.07
141+
}
142+
}
143+
144+
/* ── projection ── */
145+
const DEG = Math.PI/180
146+
let yaw = 0
147+
148+
function project(lat, lon){
149+
const la=lat*DEG, lo=lon*DEG
150+
let x=Math.cos(la)*Math.cos(lo)
151+
let y=Math.sin(la)
152+
let z=Math.cos(la)*Math.sin(lo)
153+
const c=Math.cos(yaw), s=Math.sin(yaw)
154+
const rx=x*c+z*s
155+
const rz=-x*s+z*c
156+
return { x:cx+rx*R, y:cy-y*R, visible:rz>-0.05, depth:(rz+1)/2 }
157+
}
158+
159+
/* ── continents ── */
160+
const continents = [
161+
[[60,-140],[65,-120],[60,-90],[55,-60],[50,-55],[45,-60],[35,-75],[25,-80],[20,-87],[15,-90],[10,-85],[8,-77],[15,-75],[22,-100],[30,-115],[45,-125],[55,-130],[60,-140]],
162+
[[10,-75],[8,-62],[5,-52],[0,-50],[-5,-35],[-10,-37],[-20,-40],[-33,-52],[-45,-65],[-55,-68],[-40,-62],[-25,-48],[-15,-38],[-5,-35],[0,-48],[5,-60],[10,-75]],
163+
[[70,30],[65,25],[60,20],[55,5],[50,0],[45,0],[44,10],[38,15],[37,22],[40,28],[45,35],[50,30],[55,20],[60,25],[65,28],[70,30]],
164+
[[37,10],[30,5],[15,-15],[5,-15],[-5,-5],[-15,12],[-25,15],[-35,20],[-34,26],[-28,32],[-15,36],[0,42],[10,44],[15,42],[22,37],[30,32],[35,25],[37,10]],
165+
[[70,30],[68,60],[65,90],[60,120],[55,130],[45,135],[35,120],[25,120],[20,110],[10,100],[5,100],[10,77],[22,70],[28,50],[35,36],[40,38],[45,40],[50,50],[55,60],[60,60],[65,60],[70,30]],
166+
[[-15,130],[-17,140],[-20,148],[-30,153],[-38,146],[-38,140],[-35,118],[-32,115],[-22,114],[-17,122],[-15,130]],
167+
[[60,-44],[65,-52],[70,-54],[76,-65],[83,-45],[82,-25],[78,-18],[72,-22],[66,-36],[60,-44]]
168+
]
169+
170+
function drawContinents(){
171+
g.save()
172+
g.strokeStyle='rgba(110,255,155,0.85)'
173+
g.lineWidth=1.2
174+
for(const poly of continents){
175+
g.beginPath()
176+
let started=false
177+
for(const [la,lo] of poly){
178+
const p=project(la,lo)
179+
if(!p.visible){ started=false; continue }
180+
started ? g.lineTo(p.x,p.y) : g.moveTo(p.x,p.y)
181+
started=true
182+
}
183+
if(started) g.closePath()
184+
g.stroke()
185+
g.fillStyle='rgba(0,255,100,0.05)'
186+
g.fill()
187+
}
188+
g.restore()
189+
}
190+
191+
/* ── grid ── */
192+
function drawGrid(){
193+
g.save()
194+
g.strokeStyle='rgba(0,255,136,0.09)'
195+
g.lineWidth=0.8
196+
for(let lat=-60;lat<=60;lat+=30){
197+
g.beginPath(); let first=true
198+
for(let lon=-180;lon<=180;lon+=4){
199+
const p=project(lat,lon)
200+
if(!p.visible){ first=true; continue }
201+
first ? g.moveTo(p.x,p.y) : g.lineTo(p.x,p.y)
202+
first=false
203+
}
204+
g.stroke()
205+
}
206+
for(let lon=-150;lon<=180;lon+=30){
207+
g.beginPath(); let first=true
208+
for(let lat=-88;lat<=88;lat+=3){
209+
const p=project(lat,lon)
210+
if(!p.visible){ first=true; continue }
211+
first ? g.moveTo(p.x,p.y) : g.lineTo(p.x,p.y)
212+
first=false
213+
}
214+
g.stroke()
215+
}
216+
g.restore()
217+
}
218+
219+
/* ── clouds ── */
220+
const clouds = []
221+
for(let i=0;i<5;i++) clouds.push({ lat:-15+Math.random()*30, baseLon:Math.random()*360-180, lon:0, baseSize:22+Math.random()*20, speed:0.008+Math.random()*0.008, phase:Math.random()*Math.PI*2, band:0 })
222+
for(let i=0;i<4;i++) clouds.push({ lat:40+Math.random()*20, baseLon:Math.random()*360-180, lon:0, baseSize:18+Math.random()*16, speed:0.010+Math.random()*0.010, phase:Math.random()*Math.PI*2, band:1 })
223+
for(let i=0;i<4;i++) clouds.push({ lat:-40-Math.random()*20, baseLon:Math.random()*360-180, lon:0, baseSize:18+Math.random()*16, speed:0.010+Math.random()*0.010, phase:Math.random()*Math.PI*2, band:2 })
224+
for(let i=0;i<3;i++){
225+
clouds.push({ lat:65+Math.random()*15, baseLon:Math.random()*360-180, lon:0, baseSize:14+Math.random()*12, speed:0.006+Math.random()*0.006, phase:Math.random()*Math.PI*2, band:3 })
226+
clouds.push({ lat:-65-Math.random()*15, baseLon:Math.random()*360-180, lon:0, baseSize:14+Math.random()*12, speed:0.006+Math.random()*0.006, phase:Math.random()*Math.PI*2, band:3 })
227+
}
228+
229+
function drawClouds(t){
230+
cg.clearRect(0,0,SIZE,SIZE)
231+
cg.globalCompositeOperation='lighter'
232+
233+
for(const c of clouds){
234+
c.lon = c.baseLon + t*c.speed
235+
if(c.lon>180) c.lon-=360
236+
237+
const p=project(c.lat, c.lon)
238+
if(!p.visible) continue
239+
240+
const depthScale=0.45+p.depth*0.55
241+
const pulse=1+bandEnergy[c.band]*1.6+smoothEnergy*0.5
242+
const r=c.baseSize*depthScale*pulse
243+
const alpha=0.04+smoothEnergy*0.18
244+
245+
const grad=cg.createRadialGradient(p.x,p.y,0,p.x,p.y,r)
246+
grad.addColorStop(0, `rgba(140,255,160,${alpha})`)
247+
grad.addColorStop(0.5,`rgba(120,220,140,${(alpha*0.6).toFixed(3)})`)
248+
grad.addColorStop(1, 'rgba(0,0,0,0)')
249+
cg.fillStyle=grad
250+
cg.beginPath()
251+
cg.arc(p.x,p.y,r,0,Math.PI*2)
252+
cg.fill()
253+
}
254+
255+
cg.globalCompositeOperation='source-over'
256+
cg.filter='blur(16px)'
257+
cg.drawImage(cloudCanvas,0,0)
258+
cg.filter='none'
259+
260+
g.save()
261+
g.beginPath()
262+
g.arc(cx,cy,R,0,Math.PI*2)
263+
g.clip()
264+
g.globalAlpha=0.9
265+
g.drawImage(cloudCanvas,0,0)
266+
g.restore()
267+
}
268+
269+
/* ── sphere ── */
270+
function drawSphere(){
271+
const bg=g.createRadialGradient(cx-R*0.2,cy-R*0.2,R*0.1,cx,cy,R)
272+
bg.addColorStop(0,'rgba(10,40,20,0.5)')
273+
bg.addColorStop(0.7,'rgba(2,12,5,0.95)')
274+
bg.addColorStop(1,'rgba(0,0,0,0.98)')
275+
g.beginPath(); g.arc(cx,cy,R,0,Math.PI*2)
276+
g.fillStyle=bg; g.fill()
277+
g.strokeStyle='rgba(80,255,130,0.32)'
278+
g.lineWidth=1.2; g.stroke()
279+
280+
const glow=g.createRadialGradient(cx,cy,0,cx,cy,R)
281+
glow.addColorStop(0,`rgba(0,255,120,${0.02+smoothEnergy*0.07})`)
282+
glow.addColorStop(1,'rgba(0,0,0,0)')
283+
g.beginPath(); g.arc(cx,cy,R,0,Math.PI*2)
284+
g.fillStyle=glow; g.fill()
285+
286+
for(let i=0;i<3;i++){
287+
g.beginPath()
288+
g.arc(cx,cy,R*(1.012+i*0.012),0,Math.PI*2)
289+
g.strokeStyle=`rgba(100,255,160,${0.04+smoothEnergy*0.05})`
290+
g.lineWidth=1; g.stroke()
291+
}
292+
}
293+
294+
/* ── stars ── */
295+
function drawStars(t){
296+
const n=Math.floor(SIZE*SIZE/4200)
297+
g.save()
298+
for(let i=0;i<n;i++){
299+
const hx=Math.sin(i*91.7+1.1)*43758.54
300+
const hy=Math.sin(i*57.3+2.9+t*0.00003)*21781.13
301+
const x=(hx-Math.floor(hx))*SIZE
302+
const y=(hy-Math.floor(hy))*SIZE
303+
const z=0.4+((Math.sin(i*12.9+0.7)*9182.33%1+1)%1)*0.6
304+
g.fillStyle=`rgba(130,255,170,${(0.14+z*0.3).toFixed(2)})`
305+
g.fillRect(x,y,z>0.85?2:1,z>0.85?2:1)
306+
}
307+
g.restore()
308+
}
309+
310+
/* ── render loop ── */
311+
let t=0
312+
function draw(){
313+
updateEnergy()
314+
t++
315+
yaw+=0.0012
316+
317+
g.fillStyle='rgba(0,0,0,0.18)'
318+
g.fillRect(0,0,SIZE,SIZE)
319+
320+
drawStars(t)
321+
drawSphere()
322+
drawGrid()
323+
drawContinents()
324+
drawClouds(t)
325+
326+
requestAnimationFrame(draw)
327+
}
328+
draw()
329+
</script>
330+
</body>
331+
</html>

entropy_of_austerity.pdf

117 KB
Binary file not shown.

0 commit comments

Comments
 (0)