mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
wip(mobile): checkpoint the vision mockup in progress
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VVYRAqiQj9Y1V9ZfG1RcDH
This commit is contained in:
12 files changed
+70
-66
No files matched your search
+35
-33
@@ -307,7 +307,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.compose .in input { flex: 1; background: none; border: 0; outline: none; color: var(--text); font-size: 15px; min-width: 0; }
|
||||
|
||||
/* ---------- capture ---------- */
|
||||
#v-capture { background: #000; z-index: 25; }
|
||||
#v-capture { background: transparent; z-index: 25; }
|
||||
#v-capture.on { display: block; }
|
||||
.cap-ui { position: absolute; inset: 0; pointer-events: none; }
|
||||
.cap-ui > * { pointer-events: auto; }
|
||||
@@ -320,7 +320,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.callout span { color: var(--text2); font-size: 11px; }
|
||||
.callout .ci { width: 28px; height: 28px; border-radius: 9px; display: grid; place-items: center; background: color-mix(in oklab, var(--c) 22%, transparent); color: var(--c); }
|
||||
.reticle { position: absolute; width: 34px; height: 34px; margin: -17px 0 0 -17px; border: 1.5px solid var(--c, #fff); border-radius: 10px; pointer-events: none; box-shadow: 0 0 12px var(--c); }
|
||||
.cap-bottom { position: absolute; left: 0; right: 0; bottom: 0; padding: 10px 16px calc(var(--sab) + 18px); display: flex; flex-direction: column; align-items: center; gap: 14px; background: linear-gradient(to top, rgba(0,0,0,.55), transparent); }
|
||||
.cap-bottom { position: absolute; left: 0; right: 0; bottom: 0; padding: 10px 16px calc(var(--sab) + var(--tabH) + 26px); display: flex; flex-direction: column; align-items: center; gap: 14px; background: linear-gradient(to top, rgba(0,0,0,.55), transparent); }
|
||||
.zooms { display: flex; gap: 6px; padding: 4px; border-radius: 22px; }
|
||||
.zooms button { width: 36px; height: 36px; border-radius: 50%; font-size: 12px; font-weight: 700; color: var(--text2); }
|
||||
.zooms button.on { background: rgba(255,255,255,.14); color: var(--warn); }
|
||||
@@ -334,7 +334,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.shutter:active i { transform: scale(.88); }
|
||||
.lastcap { width: 48px; height: 48px; border-radius: 12px; background: #111 center/cover; box-shadow: inset 0 0 0 1px rgba(255,255,255,.2); position: relative; }
|
||||
.lastcap .seal { position: absolute; right: -6px; top: -6px; width: 20px; height: 20px; }
|
||||
.sensors { position: absolute; left: 12px; bottom: calc(var(--sab) + 210px); border-radius: 16px; padding: 8px 10px; font-size: 11px; display: none; gap: 3px; flex-direction: column; min-width: 158px; color: var(--text2); }
|
||||
.sensors { position: absolute; left: 12px; bottom: calc(var(--sab) + var(--tabH) + 236px); border-radius: 16px; padding: 8px 10px; font-size: 11px; display: none; gap: 3px; flex-direction: column; min-width: 158px; color: var(--text2); }
|
||||
.sensors.on { display: flex; }
|
||||
.sensors b { color: var(--text); font-variant-numeric: tabular-nums; font-weight: 600; }
|
||||
.sensors svg { width: 140px; height: 26px; }
|
||||
@@ -351,7 +351,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.step .ck { width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; box-shadow: inset 0 0 0 1.5px var(--text3); color: transparent; transition: all .3s; }
|
||||
.step.done .ck { background: var(--gold); box-shadow: none; color: #1A1408; }
|
||||
.step b { font-weight: 600; } .step .h { font-family: var(--mono); font-size: 11px; color: var(--text2); word-break: break-all; line-height: 1.35; margin-top: 2px; }
|
||||
.step svg { width: 100%; height: 30px; margin-top: 4px; }
|
||||
.step svg.trace { width: 100%; height: 30px; margin-top: 4px; display: block; }
|
||||
.btnrow { display: flex; gap: 8px; }
|
||||
.btn { flex: 1; height: 50px; border-radius: 25px; font-weight: 700; font-size: 15px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; background: rgba(255,255,255,.1); box-shadow: inset 0 0 0 .5px rgba(255,255,255,.12); }
|
||||
.btn.gold { background: linear-gradient(180deg, #F3D68A, #D9AE4F); color: #1C1405; box-shadow: inset 0 .5px 0 rgba(255,255,255,.6); }
|
||||
@@ -377,7 +377,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.ws .wsh > * { pointer-events: auto; }
|
||||
.ws .wsh .tt { flex: 1; min-width: 0; height: 44px; border-radius: 22px; display: flex; align-items: center; gap: 10px; padding: 0 8px 0 14px; }
|
||||
.ws .wsh .tt b { font-size: 14.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; }
|
||||
.ws .wsh .tt span { font-size: 11.5px; color: var(--text2); }
|
||||
.ws .wsh .tt span { font-size: 11.5px; color: var(--text2); display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.ws .body { position: absolute; left: 0; right: 0; bottom: 0; top: 40%; border-radius: 28px 28px 0 0; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.ws .seg { display: flex; margin: 12px 14px 8px; padding: 3px; border-radius: 12px; background: rgba(255,255,255,.07); flex: none; }
|
||||
.ws .seg button { flex: 1; height: 34px; border-radius: 9px; font-size: 13px; font-weight: 600; color: var(--text2); transition: all .25s; }
|
||||
@@ -391,7 +391,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.obs .ot { font-size: 14px; font-weight: 600; line-height: 1.3; }
|
||||
.obs .om { font-size: 11.5px; color: var(--text3); margin-top: 3px; display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; }
|
||||
.obs .om .mono { color: var(--text2); }
|
||||
.obs .tagid { font-family: var(--mono); font-size: 11px; color: var(--gold2); background: rgba(233,196,106,.12); padding: 1px 6px; border-radius: 6px; }
|
||||
.tagid { display: inline-block; font-family: var(--font); font-weight: 700; font-size: 11px; letter-spacing: .02em; color: var(--gold2); background: rgba(233,196,106,.13); box-shadow: inset 0 0 0 .5px rgba(233,196,106,.35); padding: 2px 7px; border-radius: 7px; line-height: 1.3; }
|
||||
.hyp { border-radius: 20px; padding: 13px 14px; margin: 0 0 10px; background: var(--card2); box-shadow: inset 0 0 0 .5px rgba(255,255,255,.08); }
|
||||
.hyp.ai { box-shadow: inset 0 0 0 1px rgba(183,156,255,.45); background: linear-gradient(180deg, rgba(183,156,255,.10), rgba(183,156,255,.02)), var(--card2); }
|
||||
.hyp .hh { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text2); }
|
||||
@@ -500,7 +500,9 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.cold .eye { width: 60px; height: 60px; margin-bottom: 18px; opacity: 0; transition: opacity 1.2s; color: #fff; }
|
||||
.cold.s2 .eye { opacity: .95; }
|
||||
.concept-tag { position: absolute; z-index: 90; left: 14px; top: calc(var(--sat) + 58px); pointer-events: none; font-size: 9.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: rgba(246,223,160,.72); padding: 3px 7px; border-radius: 6px; background: rgba(10,12,18,.35); box-shadow: inset 0 0 0 .5px rgba(233,196,106,.3); }
|
||||
.app[data-tab="now"] .concept-tag, .app[data-tab="threads"] .concept-tag, .app[data-tab="you"] .concept-tag { top: calc(var(--sat) + 14px); left: auto; right: 14px; }
|
||||
.app[data-tab="now"] .concept-tag { top: calc(var(--sat) + 14px); left: auto; right: 14px; }
|
||||
.app[data-tab="threads"]:not([data-mode="workspace"]) .concept-tag, .app[data-tab="you"] .concept-tag { display: none; }
|
||||
.app[data-mode="workspace"] .concept-tag { top: calc(var(--sat) + 62px); left: auto; right: 14px; }
|
||||
.app[data-tab="capture"] .concept-tag { top: calc(var(--sat) + 100px); left: 14px; }
|
||||
.app[data-mode="situation"] .concept-tag { left: auto; right: 14px; top: calc(var(--sat) + 62px); }
|
||||
|
||||
@@ -1407,7 +1409,7 @@ vec3 aurora(vec3 n, float nightF){
|
||||
float curt = 0.45 + 0.55*fbm(vec2(lonm*9. + uT*0.0006, uT*0.00025));
|
||||
float g = exp(-pow((ml-band)/(2.2+uAurora*0.6), 2.)) * curt;
|
||||
float r = exp(-pow((ml-band-3.5)/3.5, 2.)) * 0.25;
|
||||
return (vec3(0.12, 1.0, 0.5)*g + vec3(0.75, 0.15, 0.45)*r*0.45) * nightF * uAurora * 1.15;
|
||||
return (vec3(0.12, 1.0, 0.5)*g + vec3(0.75, 0.15, 0.45)*r*0.45) * nightF * uAurora * 0.75;
|
||||
}
|
||||
vec3 cityDetail(vec3 base, vec2 q, float urban, float night, float fw, out vec3 emis){
|
||||
emis = vec3(0.);
|
||||
@@ -1437,7 +1439,7 @@ vec3 cityDetail(vec3 base, vec2 q, float urban, float night, float fw, out vec3
|
||||
if (uTraffic > 0.5) {
|
||||
vec2 rp = p/B; float along = abs(f.x-0.5) < abs(f.y-0.5) ? rp.y : rp.x;
|
||||
float cars = smoothstep(0.75, 1., sin(along*38. - uT*2.5 + h*20.)) * road * urban * (1.-aa);
|
||||
emis += mix(vec3(1.,0.15,0.1), vec3(1.,0.95,0.8), step(0.5, fract(along*3.)))*cars*(0.25 + night*1.4);
|
||||
emis += mix(vec3(1.,0.15,0.1), vec3(1.,0.95,0.8), step(0.5, fract(along*3.)))*cars*(0.03 + night*1.2);
|
||||
}
|
||||
float lit = step(0.72, hash12(id*3.1)) * (1.-road) * urban * night;
|
||||
emis += vec3(0.9,0.8,0.6)*lit*0.035;
|
||||
@@ -1565,7 +1567,7 @@ void main(){
|
||||
float tc = -b; vec3 cp = ro + rd*max(tc,0.); float hr = length(cp);
|
||||
if (uAurora > 0. && hr > 1.004 && hr < 1.05) {
|
||||
vec3 cn = normalize(cp); float nf = 1.-smoothstep(-0.2,0.0,dot(cn,uSun));
|
||||
col += aurora(cn, nf) * smoothstep(1.05,1.012,hr) * smoothstep(1.004,1.012,hr) * 0.3;
|
||||
col += aurora(cn, nf) * smoothstep(1.05,1.012,hr) * smoothstep(1.004,1.012,hr) * 0.12 * smoothstep(1.5, 0.4, uRange);
|
||||
}
|
||||
heat = 0.;
|
||||
}
|
||||
@@ -1849,8 +1851,8 @@ void main(){
|
||||
vec3 base; vec3 emis = vec3(0.); float heat = 0.35; float spec = 0.;
|
||||
if (type == 0) { // building
|
||||
float h = vAttr.y;
|
||||
base = mix(vec3(0.34,0.35,0.37), vec3(0.62,0.57,0.5), h);
|
||||
base = mix(base, vec3(0.2,0.25,0.3), step(0.7, h)); // glass towers
|
||||
base = mix(vec3(0.15,0.155,0.165), vec3(0.32,0.29,0.25), fract(h*7.13));
|
||||
base = mix(base, vec3(0.07,0.09,0.11), step(0.68, h)); // glass towers
|
||||
if (abs(n.z) < 0.5) {
|
||||
vec2 f = vec2(vUV.x/3.2, vUV.y/3.6);
|
||||
vec2 id = floor(f); vec2 fr = fract(f);
|
||||
@@ -1860,7 +1862,7 @@ void main(){
|
||||
vec3 wc = mix(vec3(1.0,0.72,0.42), vec3(0.75,0.85,1.0), step(0.6, hash12(id*1.3+h)));
|
||||
emis += wc * lit * (0.9 + 0.6*r) * uNight * 2.2;
|
||||
base = mix(base, base*0.45 + uSkyCol*0.08, win*0.8);
|
||||
spec = win*(1.-uNight)*0.6;
|
||||
spec = win*(1.-uNight)*0.9 + step(0.68, h)*0.5;
|
||||
} else base *= 0.82;
|
||||
float ao = smoothstep(0., 14., vUV.y + (1.-abs(n.z))*0.) * 0.35 + 0.65;
|
||||
if (abs(n.z) < 0.5) base *= ao;
|
||||
@@ -2164,10 +2166,10 @@ W.layers = [
|
||||
L('holo', 'eyes', 'Holograms', true, 'cube'), L('streams', 'eyes', 'Live streams', false, 'live'),
|
||||
L('transit', 'ground', 'Transit vehicles', false, 'tram'), L('traffic', 'ground', 'Traffic flow', true, 'car'), L('bikes', 'ground', 'Bike share', false, 'bike'),
|
||||
L('rail', 'ground', 'Rail', false, 'rail'), L('venues', 'ground', 'Events & venues', true, 'ticket'), L('alpr', 'ground', 'ALPR cameras', false, 'plate'),
|
||||
L('radar', 'wx', 'Radar', true, 'radar'), L('clouds', 'wx', 'Clouds', true, 'cloud'), L('lightning', 'wx', 'Lightning', true, 'bolt'),
|
||||
L('radar', 'wx', 'Radar', false, 'radar'), L('clouds', 'wx', 'Clouds', true, 'cloud'), L('lightning', 'wx', 'Lightning', true, 'bolt'),
|
||||
L('wind', 'wx', 'Wind', false, 'wind'), L('models', 'wx', 'Forecast models', false, 'spaghetti'),
|
||||
L('dc', 'infra', 'Datacenters', false, 'server'), L('dams', 'infra', 'Dams', false, 'dam'), L('power', 'infra', 'Power plants', false, 'bolt'),
|
||||
L('milbase', 'infra', 'Military installations', false, 'shield'), L('gnss', 'infra', 'GNSS interference', true, 'jam'), L('imagery', 'infra', 'Recent satellite imagery', false, 'frame'),
|
||||
L('milbase', 'infra', 'Military installations', false, 'shield'), L('gnss', 'infra', 'GNSS interference', false, 'jam'), L('imagery', 'infra', 'Recent satellite imagery', false, 'frame'),
|
||||
];
|
||||
W.layerById = Object.fromEntries(W.layers.map((l) => [l.id, l]));
|
||||
W.on = (id) => !!W.layerById[id]?.on;
|
||||
@@ -2440,7 +2442,7 @@ W.updateDynamic = function () {
|
||||
GE.SHIPS.forEach((s, i) => {
|
||||
const p = W._shipCache[i]; const tank = s.kind === 'tanker' || s.kind === 'LNG';
|
||||
if (!showAis && !tank) return;
|
||||
const ak = clamp(lerp(0.32, 0.95, smooth(1.2, 0.08, W.lastRange ?? 1)), 0.3, 1);
|
||||
const ak = clamp(lerp(0.16, 0.95, smooth(1.4, 0.08, W.lastRange ?? 1)), 0.16, 1);
|
||||
const col = showTk && tank ? [1, 0.7, 0.35, ak] : s.queue ? [0.5, 1, 0.9, ak] : [0.22, 0.88, 0.78, ak * 0.95];
|
||||
pushSpr(a, geo.vec(p.lat, p.lon, 0.03), col, (showTk && tank ? 10 : 8.5), 4, p.hdg, -1e9, 1e9, 0, i, 0.35);
|
||||
});
|
||||
@@ -2536,6 +2538,7 @@ W.project = function (view, p) { // world unit coords → css px {x,y,z,vis}
|
||||
|
||||
/* ---------- render ---------- */
|
||||
W.quality = { scale: 1, steps: 12, bloom: true, grain: true };
|
||||
const LOD_MIN = { radar: 0.12 };
|
||||
const LOD = { cctv: 0.07, street: 0.03, alpr: 0.035, bikes: 0.04, streams: 0.4, signed: 0.6, venues: 0.9, power: 0.7, dc: 1.6, dams: 1.6, milbase: 1.6, airports: 1.3, ports: 1.4, imagery: 1.6, fishing: 2.2, radar: 1.6 };
|
||||
W.render = function (view, chain, outFb, opts = {}) {
|
||||
const gl = GL.gl, PR = GL.progs;
|
||||
@@ -2634,7 +2637,7 @@ W.render = function (view, chain, outFb, opts = {}) {
|
||||
// alpha pass (smoke, AQ, radar, GNSS, cone)
|
||||
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE);
|
||||
common(SP); gl.uniform1f(SP.u.uPass, 1);
|
||||
for (const id in W.static) if (W.on(id) && range < (LOD[id] || 99)) for (const s of W.static[id]) if (s.kind === 'sprite' && s.pass === 'alpha') { gl.bindVertexArray(s.vao); gl.drawArrays(gl.POINTS, 0, s.count); }
|
||||
for (const id in W.static) if (W.on(id) && range < (LOD[id] || 99) && range > (LOD_MIN[id] || 0)) for (const s of W.static[id]) if (s.kind === 'sprite' && s.pass === 'alpha') { gl.bindVertexArray(s.vao); gl.drawArrays(gl.POINTS, 0, s.count); }
|
||||
if (W.alphaVao && W.alphaCount) { gl.bindVertexArray(W.alphaVao); gl.drawArrays(gl.POINTS, 0, W.alphaCount); }
|
||||
gl.disable(gl.BLEND); gl.disable(gl.DEPTH_TEST); gl.depthMask(true);
|
||||
gl.bindVertexArray(null);
|
||||
@@ -2934,7 +2937,7 @@ SC.draw = function (sc, view, { sun, t, eye, opts }) {
|
||||
const clear = (opts.lens || GE.world.lens) === 'clear';
|
||||
gl.uniform3fv(P.u.uSunCol, clear ? [2.2, 2.2, 2.2] : tr.map((x) => x * k * (opts.nightForce ? 0.03 : 1)));
|
||||
const amb = smooth(-0.25, 0.5, sunL[2]);
|
||||
gl.uniform3fv(P.u.uSkyCol, clear ? [1.2, 1.25, 1.35] : [0.03 * 20 / Math.PI * 1.6 * amb + 0.014, 0.045 * 20 / Math.PI * 1.6 * amb + 0.02, 0.085 * 20 / Math.PI * 1.6 * amb + 0.04]);
|
||||
gl.uniform3fv(P.u.uSkyCol, clear ? [1.2, 1.25, 1.35] : [0.03 * 20 / Math.PI * 0.9 * amb + 0.014, 0.045 * 20 / Math.PI * 0.9 * amb + 0.02, 0.085 * 20 / Math.PI * 0.9 * amb + 0.04]);
|
||||
// fog colour: inscatter along the horizon in the camera's look direction
|
||||
const fwd = v3.scale(view.back, -1); const fh = v3.norm(v3.madd(fwd, e.up, -v3.dot(fwd, e.up))); const ro = v3.madd(o, e.up, 0.0003);
|
||||
let fog = atmoJS(ro, fh, 0.012, sd, 8); fog = fog.map((x) => x * 0.8 + 0.0015);
|
||||
@@ -3195,7 +3198,7 @@ add({
|
||||
id: 'cyclone', short: 'Typhoon Halcyon', fam: 'haz', kind: 'cyclone', section: 'now', live: true, aspect: 1.25, ageS: 120, watchers: 48200,
|
||||
title: 'Typhoon Halcyon tracks toward Luzon Strait', place: 'Philippine Sea', lat: 16.2, lon: 128.8,
|
||||
camera: { lat: 15.2, lon: 127.6, range: 0.42, heading: -0.25, tilt: 0.42 }, thumbCam: { lat: 16.6, lon: 128.4, range: 0.3, heading: -0.2, tilt: 0.36 },
|
||||
layers: ['cyclones', 'radar', 'lightning', 'clouds', 'models', 'ais', 'flights'], source: 'Advisory feed (illustrative)',
|
||||
layers: ['cyclones', 'lightning', 'clouds', 'models', 'ais', 'flights'], source: 'Advisory feed (illustrative)',
|
||||
metric: () => { const c = GE.CYCLONE.at(GE.world.offsetH); return [`${Math.round(c.wind)}<small>km/h</small>`, `${Math.round(c.p)} hPa · moving WNW 19 km/h`]; },
|
||||
metrics: () => { const c = GE.CYCLONE.at(GE.world.offsetH); return [{ v: Math.round(c.wind), u: 'km/h', l: 'Max sustained', s: 'Advisory · 2 min' }, { v: Math.round(c.p), u: 'hPa', l: 'Central pressure', s: 'Advisory · 2 min' }, { v: 610, u: 'km', l: 'To Luzon Strait', s: 'Computed · now' }]; },
|
||||
accessory: () => { const c = GE.CYCLONE.at(GE.world.offsetH); return `${Math.round(c.wind)} km/h · ${Math.round(c.p)} hPa · updated 2 min ago`; },
|
||||
@@ -3263,7 +3266,7 @@ add({
|
||||
add({
|
||||
id: 'novosel', short: 'Fort Novosel circuits', fam: 'sky', kind: 'heli', section: 'always', live: true, aspect: 1.5, ageS: 15, watchers: 3100, ride: true,
|
||||
title: 'Fort Novosel helicopter school — jump into the chopper', place: 'Cairns Army Airfield, Alabama', lat: 31.276, lon: -85.713,
|
||||
camera: { lat: 31.268, lon: -85.705, range: 0.0019, heading: -0.6, tilt: 1.12 }, layers: ['helis', 'flights', 'ga', 'traffic'], source: 'ADS-B / MLAT', time: dayReplay(31.276, -85.713, 15.2),
|
||||
camera: { lat: 31.2705, lon: -85.7075, range: 0.0011, heading: -0.65, tilt: 1.15 }, layers: ['helis', 'flights', 'ga', 'traffic'], source: 'ADS-B / MLAT', time: dayReplay(31.276, -85.713, 15.2),
|
||||
metric: () => [`${GE.HELI_CIRCUITS.filter((h) => h.field).length}<small>in circuits</small>`, 'Cairns + 3 stagefields · 500 ft AGL'],
|
||||
metrics: () => [{ v: GE.HELI_CIRCUITS.filter((h) => h.field).length, u: '', l: 'Helicopters in circuits', s: 'ADS-B · 15 s' }, { v: 500, u: 'ft', l: 'Pattern altitude', s: 'Observed' }, { v: 5.5, u: 'min', l: 'Circuit time', s: 'Derived' }],
|
||||
brief: { lede: 'Student pilots are flying rectangular traffic patterns at Cairns and three stagefields: upwind, crosswind, downwind, base, final — over and over. Tap Ride to sit in the left seat of one of them.', series: [{ label: 'Helicopters aloft', unit: '', data: [2, 4, 9, 14, 17, 19, 19, 18, 19, 17, 19, 19], src: 'ADS-B / MLAT · hourly · 15 s', fmt: (v) => v }, { label: 'Groundspeed, HAWK 21', unit: 'kt', data: Array.from({ length: 40 }, (_, i) => 70 + 12 * Math.sin(i / 3.2) + (i % 10 > 7 ? -30 : 0)), src: 'ADS-B · 1 s · 15 s', fmt: (v) => Math.round(v) }], sources: [['ADS-B / MLAT', '15 s', 'live']] },
|
||||
@@ -3431,8 +3434,8 @@ APP.minimize = function (on) {
|
||||
};
|
||||
APP.showAcc = () => !!APP.following && (APP.tab === 'now' || APP.tab === 'threads' || APP.tab === 'you' || (APP.isWide() && APP.tab === 'globe')) && !APP.mode;
|
||||
APP.updateChrome = function () {
|
||||
const ride = APP.mode === 'ride', sit = APP.mode === 'situation';
|
||||
$('#tabwrap').classList.toggle('hidden', ride || (sit && !APP.isWide()) || APP.intro);
|
||||
const ride = APP.mode === 'ride', sit = APP.mode === 'situation', wsm = APP.mode === 'workspace';
|
||||
$('#tabwrap').classList.toggle('hidden', ride || ((sit || wsm) && !APP.isWide()) || APP.intro);
|
||||
$('#accHost').classList.toggle('hidden', !APP.showAcc() || APP._mini || APP.intro);
|
||||
$('#back').classList.toggle('on', sit);
|
||||
$('#sitbtns').classList.toggle('on', sit);
|
||||
@@ -3734,12 +3737,12 @@ SH.open = function (id, o = {}) {
|
||||
if (o.ride && s.ride) setTimeout(() => GE.ride.start({ kind: 'heli', id: 0 }), 650);
|
||||
APP.updateChrome();
|
||||
};
|
||||
SH.close = function (silent) {
|
||||
SH.close = function (silent, keepTime) {
|
||||
if (!SH.id) return; const id = SH.id; const W = GE.world;
|
||||
const el = sheetEl(); el.classList.remove('drag', 'wopen'); el.style.transform = APP.isWide() ? '' : 'translateY(100%)';
|
||||
SH.id = null; W.focus = null; GE.setExtras && GE.setExtras(null);
|
||||
if (SH._savedLayers) { W.layers.forEach((l, i) => (l.on = SH._savedLayers[i])); SH._savedLayers = null; bus.emit('layers', W.layers); }
|
||||
W.setWindow(null); W.setTime(null); W.auroraK = 0.85;
|
||||
if (!keepTime) { W.setWindow(null); W.setTime(null); } W.auroraK = 0.85;
|
||||
APP.setMode('');
|
||||
if (silent) return;
|
||||
const fromNow = !APP.isWide() && GE.now && $(`#v-now .card[data-id="${id}"]`);
|
||||
@@ -4129,7 +4132,7 @@ const CHAT_JET = [
|
||||
];
|
||||
RIDE.start = function (p) {
|
||||
const W = GE.world;
|
||||
if (APP.mode === 'situation') { GE.sheet.close(true); }
|
||||
if (APP.mode === 'situation') { GE.sheet.close(true, true); }
|
||||
RIDE.p = p; RIDE.on = true; RIDE.yaw = 0; RIDE.pitch = 0; RIDE.zoom = 1;
|
||||
if (APP.tab !== 'globe') { APP.tab = 'globe'; $('#app').dataset.tab = 'globe'; $$('.pane .view').forEach((v) => v.classList.remove('on')); }
|
||||
APP.setMode('ride');
|
||||
@@ -4185,7 +4188,7 @@ RIDE.frame = function (view) {
|
||||
g += `</g>`;
|
||||
g += `<g stroke="rgba(190,255,200,.95)" fill="none" stroke-width="1.6"><circle cx="${cx}" cy="${cy}" r="5"/><line x1="${cx - 18}" y1="${cy}" x2="${cx - 5}" y2="${cy}"/><line x1="${cx + 5}" y1="${cy}" x2="${cx + 18}" y2="${cy}"/><line x1="${cx}" y1="${cy - 5}" x2="${cx}" y2="${cy - 12}"/></g>`;
|
||||
// heading tape
|
||||
const ty = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--sat')) + 70;
|
||||
const ty = (parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--sat')) || 0) + 100;
|
||||
g += `<g font-size="11" font-family="var(--mono)" fill="rgba(190,255,200,.85)" stroke="rgba(190,255,200,.6)" text-anchor="middle">`;
|
||||
for (let d = Math.floor(hdg / 5) * 5 - 40; d <= hdg + 40; d += 5) { const x = cx + (d - hdg) * 4; const dd = ((d % 360) + 360) % 360; g += `<line x1="${x}" y1="${ty}" x2="${x}" y2="${ty + (dd % 10 === 0 ? 9 : 5)}"/>`; if (dd % 30 === 0) g += `<text x="${x}" y="${ty + 22}" stroke="none">${{ 0: 'N', 90: 'E', 180: 'S', 270: 'W' }[dd] ?? String(dd / 10).padStart(2, '0')}</text>`; }
|
||||
g += `<rect x="${cx - 24}" y="${ty - 22}" width="48" height="18" rx="4" fill="rgba(0,20,8,.35)"/><text x="${cx}" y="${ty - 9}" stroke="none" font-weight="700">${String(Math.round(hdg)).padStart(3, '0')}</text></g>`;
|
||||
@@ -4198,7 +4201,7 @@ RIDE.exit = function (silent) {
|
||||
$('#ride').classList.remove('on'); $('#ride').innerHTML = '';
|
||||
const v = Cam.view(400, 800); const [la, lo] = geo.ll(v.eye);
|
||||
Cam.setRig(null); Cam.set({ lat: la, lon: lo, range: Math.max(0.002, v3.len(v.eye) - 1 + 0.002), tilt: 0.9, heading: RIDE.s ? RIDE.s.hdg : 0, shiftY: 0.06 });
|
||||
APP.setMode('');
|
||||
APP.setMode(''); GE.world.setWindow(null); GE.world.setTime(null);
|
||||
if (!silent) Cam.flyTo({ range: 0.012, tilt: 0.8 });
|
||||
};
|
||||
|
||||
@@ -4315,7 +4318,7 @@ CAP.frame = function (view) {
|
||||
if (!el) { el = h('div', { 'data-k': it.k }); el.innerHTML = `<div class="reticle" style="--c:${it.c}"></div><div class="callout glass" style="--c:${it.c}"><span class="ci">${ic(it.i, 'sm')}</span><span><b>${esc(it.t)}</b><span data-s></span></span></div>`; host.append(el); }
|
||||
el.querySelector('[data-s]').textContent = it.s;
|
||||
el.querySelector('.reticle').style.transform = `translate(${s.x}px, ${s.y}px)`;
|
||||
el.querySelector('.callout').style.transform = `translate(${s.x + 24}px, ${s.y}px) translate(0, -50%)`;
|
||||
const flip = s.x > view.w - 210; el.querySelector('.callout').style.transform = flip ? `translate(${s.x - 24}px, ${s.y}px) translate(-100%, -50%)` : `translate(${s.x + 24}px, ${s.y}px) translate(0, -50%)`;
|
||||
}
|
||||
host.querySelectorAll('[data-k]').forEach((e) => { if (!seen.has(e.dataset.k)) e.remove(); });
|
||||
// witness sensors
|
||||
@@ -4357,7 +4360,7 @@ CAP.ceremony = function (b, cv) {
|
||||
ov.innerHTML = `<div class="bundle glass strong"><div class="ph" id="bph"><svg class="bigseal" id="bigseal" viewBox="0 0 24 24"><use href="#i-seal"/></svg></div>
|
||||
<div class="steps">
|
||||
<div class="step" data-s="0"><span class="ck">${ic('check', 'xs')}</span><div><b>Frame hashed</b> · SHA-256 of ${fmt.n(b.bytes.length)} JPEG bytes<div class="h" id="sh0">…</div></div></div>
|
||||
<div class="step" data-s="1"><span class="ck">${ic('check', 'xs')}</span><div><b>Session sealed</b> · IMU 150 samples @ 50 Hz · GPS ±4 m · heading ${b.session.heading}°<svg viewBox="0 0 300 30" preserveAspectRatio="none">${GE.sparkPath(b.session.imu.map((x) => x.g + x.b * 0.02), 300, 30, '#5CF2A0', 1.2)}</svg></div></div>
|
||||
<div class="step" data-s="1"><span class="ck">${ic('check', 'xs')}</span><div><b>Session sealed</b> · IMU 150 samples @ 50 Hz · GPS ±4 m · heading ${b.session.heading}°<svg class="trace" viewBox="0 0 300 30" preserveAspectRatio="none">${GE.sparkPath(b.session.imu.map((x) => x.g + x.b * 0.02), 300, 30, '#5CF2A0', 1.2)}</svg></div></div>
|
||||
<div class="step" data-s="2"><span class="ck">${ic('check', 'xs')}</span><div><b>Chained</b> to this device’s previous bundle<div class="h">prev ${b.prev.slice(0, 24)}…</div></div></div>
|
||||
<div class="step" data-s="3"><span class="ck">${ic('check', 'xs')}</span><div><b>Bundle hash</b><div class="h" id="sh3">…</div></div></div>
|
||||
<div class="step" data-s="4"><span class="ck">${ic('check', 'xs')}</span><div><b>Signed</b> · ECDSA P-256, key generated on device, non-extractable<div class="h" id="sh4">…</div></div></div>
|
||||
@@ -4536,12 +4539,11 @@ THR.init = async function () {
|
||||
/* ---------- list ---------- */
|
||||
THR.render = function () {
|
||||
const v = $('#v-threads');
|
||||
v.innerHTML = `<div class="tlist" id="tlist" style="background:var(--bg);min-height:100%"><div class="navbar">Threads</div><div class="lt"><div class="kicker">${ic('seal', 'xs')} Signed evidence · open ledgers</div><div class="row"><h1>Threads</h1><button class="avatar md" style="--h:205" id="thMe">Y</button></div></div>
|
||||
v.innerHTML = `<div class="tlist" id="tlist" style="background:var(--bg);min-height:100%"><div class="navbar">Threads</div><div class="lt"><div class="kicker">${ic('seal', 'xs')} Signed evidence · open ledgers</div><div class="row"><h1>Threads</h1><span class="concept" style="margin-bottom:8px">${ic('sparkles', 'xs')} Concept · illustrative data</span></div></div>
|
||||
<div class="livebar" style="padding-bottom:14px"><span class="presence"><span class="stack">${['kora', 'hydro', 'tamang', 'geo', 'sky'].map((k) => `<span class="avatar sm on" style="--h:${P[k].hue}">${P[k].h[0].toUpperCase()}</span>`).join('')}</span></span><span><b class="num">37</b> people reconstructing now</span></div>
|
||||
${Object.values(THREADS).map((T) => { const s = GE.SIT.byId[T.sit]; return `<article class="tcard" data-t="${T.id}" data-id="${T.sit}"><div class="thumb"><canvas width="640" height="360"></canvas><div class="bl"><div><h3>${esc(T.title)}</h3><p>${esc(T.place)} · ${esc(T.date)}</p></div><span class="badge d4">4D</span></div></div><div class="stats"><div><b>${T.obs.length}</b>observations</div><div><b>${T.hyps.length}</b>hypotheses</div><div><b>${THR.ledgers[T.id]?.length || '–'}</b>ledger</div><span class="stack">${T.people.slice(0, 4).map((k) => `<span class="avatar sm" style="--h:${P[k].hue}">${P[k].h[0].toUpperCase()}</span>`).join('')}</span></div></article>`; }).join('')}
|
||||
<div style="padding:6px 14px 150px"><button class="btn" style="width:100%" id="newThread">${ic('plus', 'sm')}Start a reconstruction</button><p style="font-size:11.5px;color:var(--text3);text-align:center;margin-top:12px">Concept · illustrative data. Witness posts, handles and claims are fictional.</p></div></div>
|
||||
<div class="ws" id="ws"></div>`;
|
||||
$('#thMe').addEventListener('click', () => APP.setTab('you'));
|
||||
$('#newThread').addEventListener('click', () => APP.toast(`${ic('threads', 'sm')} New thread · add your first observation`));
|
||||
$$('.tcard').forEach((c) => {
|
||||
c.addEventListener('click', () => THR.openWs(c.dataset.t));
|
||||
@@ -4775,7 +4777,7 @@ function globeVisible() {
|
||||
let lastT = performance.now(), lastDyn = 0, lastSec = 0, running = true;
|
||||
function frame(now) {
|
||||
if (!running) return;
|
||||
const dt = Math.min(0.1, Math.max(0.001, (now - lastT) / 1000)); lastT = now;
|
||||
const dt = Math.min(0.25, Math.max(0.001, (now - lastT) / 1000)); lastT = now;
|
||||
W.simMs = Date.now() + W.offsetH * 3600000; W.t = (W.simMs - NOW) / 1000; W.sun = astro.sun(W.simMs);
|
||||
Cam.update(dt);
|
||||
if (APP.mode === 'situation' && !Cam.anim && GE.sheet.targetShift != null && !APP.isWide()) Cam.shiftY += (GE.sheet.targetShift - Cam.shiftY) * Math.min(1, dt * 7);
|
||||
@@ -4811,7 +4813,7 @@ function coldOpen() {
|
||||
APP.intro = true; app.classList.add('intro'); APP.updateChrome();
|
||||
if (skipAll) { Cam.set(finalCam); end(); return; }
|
||||
Cam.set({ lat: 8, lon: finalCam.lon + 75, range: 5.8, heading: 0, tilt: 0, shiftY: 0.0, shiftX: 0 });
|
||||
const T = red ? 1.2 : 3.4; const t0 = performance.now();
|
||||
const T = (red ? 1.2 : 3.4) * (HASH.slow ? +HASH.slow : 1); const t0 = performance.now();
|
||||
GE.introFade = 0;
|
||||
if (!red) Cam.flyTo(finalCam, { duration: T });
|
||||
else Cam.set(finalCam);
|
||||
|
||||
@@ -302,7 +302,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.compose .in input { flex: 1; background: none; border: 0; outline: none; color: var(--text); font-size: 15px; min-width: 0; }
|
||||
|
||||
/* ---------- capture ---------- */
|
||||
#v-capture { background: #000; z-index: 25; }
|
||||
#v-capture { background: transparent; z-index: 25; }
|
||||
#v-capture.on { display: block; }
|
||||
.cap-ui { position: absolute; inset: 0; pointer-events: none; }
|
||||
.cap-ui > * { pointer-events: auto; }
|
||||
@@ -315,7 +315,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.callout span { color: var(--text2); font-size: 11px; }
|
||||
.callout .ci { width: 28px; height: 28px; border-radius: 9px; display: grid; place-items: center; background: color-mix(in oklab, var(--c) 22%, transparent); color: var(--c); }
|
||||
.reticle { position: absolute; width: 34px; height: 34px; margin: -17px 0 0 -17px; border: 1.5px solid var(--c, #fff); border-radius: 10px; pointer-events: none; box-shadow: 0 0 12px var(--c); }
|
||||
.cap-bottom { position: absolute; left: 0; right: 0; bottom: 0; padding: 10px 16px calc(var(--sab) + 18px); display: flex; flex-direction: column; align-items: center; gap: 14px; background: linear-gradient(to top, rgba(0,0,0,.55), transparent); }
|
||||
.cap-bottom { position: absolute; left: 0; right: 0; bottom: 0; padding: 10px 16px calc(var(--sab) + var(--tabH) + 26px); display: flex; flex-direction: column; align-items: center; gap: 14px; background: linear-gradient(to top, rgba(0,0,0,.55), transparent); }
|
||||
.zooms { display: flex; gap: 6px; padding: 4px; border-radius: 22px; }
|
||||
.zooms button { width: 36px; height: 36px; border-radius: 50%; font-size: 12px; font-weight: 700; color: var(--text2); }
|
||||
.zooms button.on { background: rgba(255,255,255,.14); color: var(--warn); }
|
||||
@@ -329,7 +329,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.shutter:active i { transform: scale(.88); }
|
||||
.lastcap { width: 48px; height: 48px; border-radius: 12px; background: #111 center/cover; box-shadow: inset 0 0 0 1px rgba(255,255,255,.2); position: relative; }
|
||||
.lastcap .seal { position: absolute; right: -6px; top: -6px; width: 20px; height: 20px; }
|
||||
.sensors { position: absolute; left: 12px; bottom: calc(var(--sab) + 210px); border-radius: 16px; padding: 8px 10px; font-size: 11px; display: none; gap: 3px; flex-direction: column; min-width: 158px; color: var(--text2); }
|
||||
.sensors { position: absolute; left: 12px; bottom: calc(var(--sab) + var(--tabH) + 236px); border-radius: 16px; padding: 8px 10px; font-size: 11px; display: none; gap: 3px; flex-direction: column; min-width: 158px; color: var(--text2); }
|
||||
.sensors.on { display: flex; }
|
||||
.sensors b { color: var(--text); font-variant-numeric: tabular-nums; font-weight: 600; }
|
||||
.sensors svg { width: 140px; height: 26px; }
|
||||
@@ -346,7 +346,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.step .ck { width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; box-shadow: inset 0 0 0 1.5px var(--text3); color: transparent; transition: all .3s; }
|
||||
.step.done .ck { background: var(--gold); box-shadow: none; color: #1A1408; }
|
||||
.step b { font-weight: 600; } .step .h { font-family: var(--mono); font-size: 11px; color: var(--text2); word-break: break-all; line-height: 1.35; margin-top: 2px; }
|
||||
.step svg { width: 100%; height: 30px; margin-top: 4px; }
|
||||
.step svg.trace { width: 100%; height: 30px; margin-top: 4px; display: block; }
|
||||
.btnrow { display: flex; gap: 8px; }
|
||||
.btn { flex: 1; height: 50px; border-radius: 25px; font-weight: 700; font-size: 15px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; background: rgba(255,255,255,.1); box-shadow: inset 0 0 0 .5px rgba(255,255,255,.12); }
|
||||
.btn.gold { background: linear-gradient(180deg, #F3D68A, #D9AE4F); color: #1C1405; box-shadow: inset 0 .5px 0 rgba(255,255,255,.6); }
|
||||
@@ -372,7 +372,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.ws .wsh > * { pointer-events: auto; }
|
||||
.ws .wsh .tt { flex: 1; min-width: 0; height: 44px; border-radius: 22px; display: flex; align-items: center; gap: 10px; padding: 0 8px 0 14px; }
|
||||
.ws .wsh .tt b { font-size: 14.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; }
|
||||
.ws .wsh .tt span { font-size: 11.5px; color: var(--text2); }
|
||||
.ws .wsh .tt span { font-size: 11.5px; color: var(--text2); display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.ws .body { position: absolute; left: 0; right: 0; bottom: 0; top: 40%; border-radius: 28px 28px 0 0; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.ws .seg { display: flex; margin: 12px 14px 8px; padding: 3px; border-radius: 12px; background: rgba(255,255,255,.07); flex: none; }
|
||||
.ws .seg button { flex: 1; height: 34px; border-radius: 9px; font-size: 13px; font-weight: 600; color: var(--text2); transition: all .25s; }
|
||||
@@ -386,7 +386,7 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.obs .ot { font-size: 14px; font-weight: 600; line-height: 1.3; }
|
||||
.obs .om { font-size: 11.5px; color: var(--text3); margin-top: 3px; display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; }
|
||||
.obs .om .mono { color: var(--text2); }
|
||||
.obs .tagid { font-family: var(--mono); font-size: 11px; color: var(--gold2); background: rgba(233,196,106,.12); padding: 1px 6px; border-radius: 6px; }
|
||||
.tagid { display: inline-block; font-family: var(--font); font-weight: 700; font-size: 11px; letter-spacing: .02em; color: var(--gold2); background: rgba(233,196,106,.13); box-shadow: inset 0 0 0 .5px rgba(233,196,106,.35); padding: 2px 7px; border-radius: 7px; line-height: 1.3; }
|
||||
.hyp { border-radius: 20px; padding: 13px 14px; margin: 0 0 10px; background: var(--card2); box-shadow: inset 0 0 0 .5px rgba(255,255,255,.08); }
|
||||
.hyp.ai { box-shadow: inset 0 0 0 1px rgba(183,156,255,.45); background: linear-gradient(180deg, rgba(183,156,255,.10), rgba(183,156,255,.02)), var(--card2); }
|
||||
.hyp .hh { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text2); }
|
||||
@@ -495,7 +495,9 @@ input, textarea { font: inherit; color: inherit; }
|
||||
.cold .eye { width: 60px; height: 60px; margin-bottom: 18px; opacity: 0; transition: opacity 1.2s; color: #fff; }
|
||||
.cold.s2 .eye { opacity: .95; }
|
||||
.concept-tag { position: absolute; z-index: 90; left: 14px; top: calc(var(--sat) + 58px); pointer-events: none; font-size: 9.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: rgba(246,223,160,.72); padding: 3px 7px; border-radius: 6px; background: rgba(10,12,18,.35); box-shadow: inset 0 0 0 .5px rgba(233,196,106,.3); }
|
||||
.app[data-tab="now"] .concept-tag, .app[data-tab="threads"] .concept-tag, .app[data-tab="you"] .concept-tag { top: calc(var(--sat) + 14px); left: auto; right: 14px; }
|
||||
.app[data-tab="now"] .concept-tag { top: calc(var(--sat) + 14px); left: auto; right: 14px; }
|
||||
.app[data-tab="threads"]:not([data-mode="workspace"]) .concept-tag, .app[data-tab="you"] .concept-tag { display: none; }
|
||||
.app[data-mode="workspace"] .concept-tag { top: calc(var(--sat) + 62px); left: auto; right: 14px; }
|
||||
.app[data-tab="capture"] .concept-tag { top: calc(var(--sat) + 100px); left: 14px; }
|
||||
.app[data-mode="situation"] .concept-tag { left: auto; right: 14px; top: calc(var(--sat) + 62px); }
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ vec3 aurora(vec3 n, float nightF){
|
||||
float curt = 0.45 + 0.55*fbm(vec2(lonm*9. + uT*0.0006, uT*0.00025));
|
||||
float g = exp(-pow((ml-band)/(2.2+uAurora*0.6), 2.)) * curt;
|
||||
float r = exp(-pow((ml-band-3.5)/3.5, 2.)) * 0.25;
|
||||
return (vec3(0.12, 1.0, 0.5)*g + vec3(0.75, 0.15, 0.45)*r*0.45) * nightF * uAurora * 1.15;
|
||||
return (vec3(0.12, 1.0, 0.5)*g + vec3(0.75, 0.15, 0.45)*r*0.45) * nightF * uAurora * 0.75;
|
||||
}
|
||||
vec3 cityDetail(vec3 base, vec2 q, float urban, float night, float fw, out vec3 emis){
|
||||
emis = vec3(0.);
|
||||
@@ -175,7 +175,7 @@ vec3 cityDetail(vec3 base, vec2 q, float urban, float night, float fw, out vec3
|
||||
if (uTraffic > 0.5) {
|
||||
vec2 rp = p/B; float along = abs(f.x-0.5) < abs(f.y-0.5) ? rp.y : rp.x;
|
||||
float cars = smoothstep(0.75, 1., sin(along*38. - uT*2.5 + h*20.)) * road * urban * (1.-aa);
|
||||
emis += mix(vec3(1.,0.15,0.1), vec3(1.,0.95,0.8), step(0.5, fract(along*3.)))*cars*(0.25 + night*1.4);
|
||||
emis += mix(vec3(1.,0.15,0.1), vec3(1.,0.95,0.8), step(0.5, fract(along*3.)))*cars*(0.03 + night*1.2);
|
||||
}
|
||||
float lit = step(0.72, hash12(id*3.1)) * (1.-road) * urban * night;
|
||||
emis += vec3(0.9,0.8,0.6)*lit*0.035;
|
||||
@@ -303,7 +303,7 @@ void main(){
|
||||
float tc = -b; vec3 cp = ro + rd*max(tc,0.); float hr = length(cp);
|
||||
if (uAurora > 0. && hr > 1.004 && hr < 1.05) {
|
||||
vec3 cn = normalize(cp); float nf = 1.-smoothstep(-0.2,0.0,dot(cn,uSun));
|
||||
col += aurora(cn, nf) * smoothstep(1.05,1.012,hr) * smoothstep(1.004,1.012,hr) * 0.3;
|
||||
col += aurora(cn, nf) * smoothstep(1.05,1.012,hr) * smoothstep(1.004,1.012,hr) * 0.12 * smoothstep(1.5, 0.4, uRange);
|
||||
}
|
||||
heat = 0.;
|
||||
}
|
||||
@@ -587,8 +587,8 @@ void main(){
|
||||
vec3 base; vec3 emis = vec3(0.); float heat = 0.35; float spec = 0.;
|
||||
if (type == 0) { // building
|
||||
float h = vAttr.y;
|
||||
base = mix(vec3(0.34,0.35,0.37), vec3(0.62,0.57,0.5), h);
|
||||
base = mix(base, vec3(0.2,0.25,0.3), step(0.7, h)); // glass towers
|
||||
base = mix(vec3(0.15,0.155,0.165), vec3(0.32,0.29,0.25), fract(h*7.13));
|
||||
base = mix(base, vec3(0.07,0.09,0.11), step(0.68, h)); // glass towers
|
||||
if (abs(n.z) < 0.5) {
|
||||
vec2 f = vec2(vUV.x/3.2, vUV.y/3.6);
|
||||
vec2 id = floor(f); vec2 fr = fract(f);
|
||||
@@ -598,7 +598,7 @@ void main(){
|
||||
vec3 wc = mix(vec3(1.0,0.72,0.42), vec3(0.75,0.85,1.0), step(0.6, hash12(id*1.3+h)));
|
||||
emis += wc * lit * (0.9 + 0.6*r) * uNight * 2.2;
|
||||
base = mix(base, base*0.45 + uSkyCol*0.08, win*0.8);
|
||||
spec = win*(1.-uNight)*0.6;
|
||||
spec = win*(1.-uNight)*0.9 + step(0.68, h)*0.5;
|
||||
} else base *= 0.82;
|
||||
float ao = smoothstep(0., 14., vUV.y + (1.-abs(n.z))*0.) * 0.35 + 0.65;
|
||||
if (abs(n.z) < 0.5) base *= ao;
|
||||
|
||||
@@ -30,10 +30,10 @@ W.layers = [
|
||||
L('holo', 'eyes', 'Holograms', true, 'cube'), L('streams', 'eyes', 'Live streams', false, 'live'),
|
||||
L('transit', 'ground', 'Transit vehicles', false, 'tram'), L('traffic', 'ground', 'Traffic flow', true, 'car'), L('bikes', 'ground', 'Bike share', false, 'bike'),
|
||||
L('rail', 'ground', 'Rail', false, 'rail'), L('venues', 'ground', 'Events & venues', true, 'ticket'), L('alpr', 'ground', 'ALPR cameras', false, 'plate'),
|
||||
L('radar', 'wx', 'Radar', true, 'radar'), L('clouds', 'wx', 'Clouds', true, 'cloud'), L('lightning', 'wx', 'Lightning', true, 'bolt'),
|
||||
L('radar', 'wx', 'Radar', false, 'radar'), L('clouds', 'wx', 'Clouds', true, 'cloud'), L('lightning', 'wx', 'Lightning', true, 'bolt'),
|
||||
L('wind', 'wx', 'Wind', false, 'wind'), L('models', 'wx', 'Forecast models', false, 'spaghetti'),
|
||||
L('dc', 'infra', 'Datacenters', false, 'server'), L('dams', 'infra', 'Dams', false, 'dam'), L('power', 'infra', 'Power plants', false, 'bolt'),
|
||||
L('milbase', 'infra', 'Military installations', false, 'shield'), L('gnss', 'infra', 'GNSS interference', true, 'jam'), L('imagery', 'infra', 'Recent satellite imagery', false, 'frame'),
|
||||
L('milbase', 'infra', 'Military installations', false, 'shield'), L('gnss', 'infra', 'GNSS interference', false, 'jam'), L('imagery', 'infra', 'Recent satellite imagery', false, 'frame'),
|
||||
];
|
||||
W.layerById = Object.fromEntries(W.layers.map((l) => [l.id, l]));
|
||||
W.on = (id) => !!W.layerById[id]?.on;
|
||||
@@ -306,7 +306,7 @@ W.updateDynamic = function () {
|
||||
GE.SHIPS.forEach((s, i) => {
|
||||
const p = W._shipCache[i]; const tank = s.kind === 'tanker' || s.kind === 'LNG';
|
||||
if (!showAis && !tank) return;
|
||||
const ak = clamp(lerp(0.32, 0.95, smooth(1.2, 0.08, W.lastRange ?? 1)), 0.3, 1);
|
||||
const ak = clamp(lerp(0.16, 0.95, smooth(1.4, 0.08, W.lastRange ?? 1)), 0.16, 1);
|
||||
const col = showTk && tank ? [1, 0.7, 0.35, ak] : s.queue ? [0.5, 1, 0.9, ak] : [0.22, 0.88, 0.78, ak * 0.95];
|
||||
pushSpr(a, geo.vec(p.lat, p.lon, 0.03), col, (showTk && tank ? 10 : 8.5), 4, p.hdg, -1e9, 1e9, 0, i, 0.35);
|
||||
});
|
||||
@@ -402,6 +402,7 @@ W.project = function (view, p) { // world unit coords → css px {x,y,z,vis}
|
||||
|
||||
/* ---------- render ---------- */
|
||||
W.quality = { scale: 1, steps: 12, bloom: true, grain: true };
|
||||
const LOD_MIN = { radar: 0.12 };
|
||||
const LOD = { cctv: 0.07, street: 0.03, alpr: 0.035, bikes: 0.04, streams: 0.4, signed: 0.6, venues: 0.9, power: 0.7, dc: 1.6, dams: 1.6, milbase: 1.6, airports: 1.3, ports: 1.4, imagery: 1.6, fishing: 2.2, radar: 1.6 };
|
||||
W.render = function (view, chain, outFb, opts = {}) {
|
||||
const gl = GL.gl, PR = GL.progs;
|
||||
@@ -500,7 +501,7 @@ W.render = function (view, chain, outFb, opts = {}) {
|
||||
// alpha pass (smoke, AQ, radar, GNSS, cone)
|
||||
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE);
|
||||
common(SP); gl.uniform1f(SP.u.uPass, 1);
|
||||
for (const id in W.static) if (W.on(id) && range < (LOD[id] || 99)) for (const s of W.static[id]) if (s.kind === 'sprite' && s.pass === 'alpha') { gl.bindVertexArray(s.vao); gl.drawArrays(gl.POINTS, 0, s.count); }
|
||||
for (const id in W.static) if (W.on(id) && range < (LOD[id] || 99) && range > (LOD_MIN[id] || 0)) for (const s of W.static[id]) if (s.kind === 'sprite' && s.pass === 'alpha') { gl.bindVertexArray(s.vao); gl.drawArrays(gl.POINTS, 0, s.count); }
|
||||
if (W.alphaVao && W.alphaCount) { gl.bindVertexArray(W.alphaVao); gl.drawArrays(gl.POINTS, 0, W.alphaCount); }
|
||||
gl.disable(gl.BLEND); gl.disable(gl.DEPTH_TEST); gl.depthMask(true);
|
||||
gl.bindVertexArray(null);
|
||||
|
||||
@@ -220,7 +220,7 @@ SC.draw = function (sc, view, { sun, t, eye, opts }) {
|
||||
const clear = (opts.lens || GE.world.lens) === 'clear';
|
||||
gl.uniform3fv(P.u.uSunCol, clear ? [2.2, 2.2, 2.2] : tr.map((x) => x * k * (opts.nightForce ? 0.03 : 1)));
|
||||
const amb = smooth(-0.25, 0.5, sunL[2]);
|
||||
gl.uniform3fv(P.u.uSkyCol, clear ? [1.2, 1.25, 1.35] : [0.03 * 20 / Math.PI * 1.6 * amb + 0.014, 0.045 * 20 / Math.PI * 1.6 * amb + 0.02, 0.085 * 20 / Math.PI * 1.6 * amb + 0.04]);
|
||||
gl.uniform3fv(P.u.uSkyCol, clear ? [1.2, 1.25, 1.35] : [0.03 * 20 / Math.PI * 0.9 * amb + 0.014, 0.045 * 20 / Math.PI * 0.9 * amb + 0.02, 0.085 * 20 / Math.PI * 0.9 * amb + 0.04]);
|
||||
// fog colour: inscatter along the horizon in the camera's look direction
|
||||
const fwd = v3.scale(view.back, -1); const fh = v3.norm(v3.madd(fwd, e.up, -v3.dot(fwd, e.up))); const ro = v3.madd(o, e.up, 0.0003);
|
||||
let fog = atmoJS(ro, fh, 0.012, sd, 8); fog = fog.map((x) => x * 0.8 + 0.0015);
|
||||
|
||||
@@ -48,7 +48,7 @@ add({
|
||||
id: 'cyclone', short: 'Typhoon Halcyon', fam: 'haz', kind: 'cyclone', section: 'now', live: true, aspect: 1.25, ageS: 120, watchers: 48200,
|
||||
title: 'Typhoon Halcyon tracks toward Luzon Strait', place: 'Philippine Sea', lat: 16.2, lon: 128.8,
|
||||
camera: { lat: 15.2, lon: 127.6, range: 0.42, heading: -0.25, tilt: 0.42 }, thumbCam: { lat: 16.6, lon: 128.4, range: 0.3, heading: -0.2, tilt: 0.36 },
|
||||
layers: ['cyclones', 'radar', 'lightning', 'clouds', 'models', 'ais', 'flights'], source: 'Advisory feed (illustrative)',
|
||||
layers: ['cyclones', 'lightning', 'clouds', 'models', 'ais', 'flights'], source: 'Advisory feed (illustrative)',
|
||||
metric: () => { const c = GE.CYCLONE.at(GE.world.offsetH); return [`${Math.round(c.wind)}<small>km/h</small>`, `${Math.round(c.p)} hPa · moving WNW 19 km/h`]; },
|
||||
metrics: () => { const c = GE.CYCLONE.at(GE.world.offsetH); return [{ v: Math.round(c.wind), u: 'km/h', l: 'Max sustained', s: 'Advisory · 2 min' }, { v: Math.round(c.p), u: 'hPa', l: 'Central pressure', s: 'Advisory · 2 min' }, { v: 610, u: 'km', l: 'To Luzon Strait', s: 'Computed · now' }]; },
|
||||
accessory: () => { const c = GE.CYCLONE.at(GE.world.offsetH); return `${Math.round(c.wind)} km/h · ${Math.round(c.p)} hPa · updated 2 min ago`; },
|
||||
@@ -116,7 +116,7 @@ add({
|
||||
add({
|
||||
id: 'novosel', short: 'Fort Novosel circuits', fam: 'sky', kind: 'heli', section: 'always', live: true, aspect: 1.5, ageS: 15, watchers: 3100, ride: true,
|
||||
title: 'Fort Novosel helicopter school — jump into the chopper', place: 'Cairns Army Airfield, Alabama', lat: 31.276, lon: -85.713,
|
||||
camera: { lat: 31.268, lon: -85.705, range: 0.0019, heading: -0.6, tilt: 1.12 }, layers: ['helis', 'flights', 'ga', 'traffic'], source: 'ADS-B / MLAT', time: dayReplay(31.276, -85.713, 15.2),
|
||||
camera: { lat: 31.2705, lon: -85.7075, range: 0.0011, heading: -0.65, tilt: 1.15 }, layers: ['helis', 'flights', 'ga', 'traffic'], source: 'ADS-B / MLAT', time: dayReplay(31.276, -85.713, 15.2),
|
||||
metric: () => [`${GE.HELI_CIRCUITS.filter((h) => h.field).length}<small>in circuits</small>`, 'Cairns + 3 stagefields · 500 ft AGL'],
|
||||
metrics: () => [{ v: GE.HELI_CIRCUITS.filter((h) => h.field).length, u: '', l: 'Helicopters in circuits', s: 'ADS-B · 15 s' }, { v: 500, u: 'ft', l: 'Pattern altitude', s: 'Observed' }, { v: 5.5, u: 'min', l: 'Circuit time', s: 'Derived' }],
|
||||
brief: { lede: 'Student pilots are flying rectangular traffic patterns at Cairns and three stagefields: upwind, crosswind, downwind, base, final — over and over. Tap Ride to sit in the left seat of one of them.', series: [{ label: 'Helicopters aloft', unit: '', data: [2, 4, 9, 14, 17, 19, 19, 18, 19, 17, 19, 19], src: 'ADS-B / MLAT · hourly · 15 s', fmt: (v) => v }, { label: 'Groundspeed, HAWK 21', unit: 'kt', data: Array.from({ length: 40 }, (_, i) => 70 + 12 * Math.sin(i / 3.2) + (i % 10 > 7 ? -30 : 0)), src: 'ADS-B · 1 s · 15 s', fmt: (v) => Math.round(v) }], sources: [['ADS-B / MLAT', '15 s', 'live']] },
|
||||
|
||||
@@ -55,8 +55,8 @@ APP.minimize = function (on) {
|
||||
};
|
||||
APP.showAcc = () => !!APP.following && (APP.tab === 'now' || APP.tab === 'threads' || APP.tab === 'you' || (APP.isWide() && APP.tab === 'globe')) && !APP.mode;
|
||||
APP.updateChrome = function () {
|
||||
const ride = APP.mode === 'ride', sit = APP.mode === 'situation';
|
||||
$('#tabwrap').classList.toggle('hidden', ride || (sit && !APP.isWide()) || APP.intro);
|
||||
const ride = APP.mode === 'ride', sit = APP.mode === 'situation', wsm = APP.mode === 'workspace';
|
||||
$('#tabwrap').classList.toggle('hidden', ride || ((sit || wsm) && !APP.isWide()) || APP.intro);
|
||||
$('#accHost').classList.toggle('hidden', !APP.showAcc() || APP._mini || APP.intro);
|
||||
$('#back').classList.toggle('on', sit);
|
||||
$('#sitbtns').classList.toggle('on', sit);
|
||||
|
||||
@@ -45,12 +45,12 @@ SH.open = function (id, o = {}) {
|
||||
if (o.ride && s.ride) setTimeout(() => GE.ride.start({ kind: 'heli', id: 0 }), 650);
|
||||
APP.updateChrome();
|
||||
};
|
||||
SH.close = function (silent) {
|
||||
SH.close = function (silent, keepTime) {
|
||||
if (!SH.id) return; const id = SH.id; const W = GE.world;
|
||||
const el = sheetEl(); el.classList.remove('drag', 'wopen'); el.style.transform = APP.isWide() ? '' : 'translateY(100%)';
|
||||
SH.id = null; W.focus = null; GE.setExtras && GE.setExtras(null);
|
||||
if (SH._savedLayers) { W.layers.forEach((l, i) => (l.on = SH._savedLayers[i])); SH._savedLayers = null; bus.emit('layers', W.layers); }
|
||||
W.setWindow(null); W.setTime(null); W.auroraK = 0.85;
|
||||
if (!keepTime) { W.setWindow(null); W.setTime(null); } W.auroraK = 0.85;
|
||||
APP.setMode('');
|
||||
if (silent) return;
|
||||
const fromNow = !APP.isWide() && GE.now && $(`#v-now .card[data-id="${id}"]`);
|
||||
|
||||
@@ -19,7 +19,7 @@ const CHAT_JET = [
|
||||
];
|
||||
RIDE.start = function (p) {
|
||||
const W = GE.world;
|
||||
if (APP.mode === 'situation') { GE.sheet.close(true); }
|
||||
if (APP.mode === 'situation') { GE.sheet.close(true, true); }
|
||||
RIDE.p = p; RIDE.on = true; RIDE.yaw = 0; RIDE.pitch = 0; RIDE.zoom = 1;
|
||||
if (APP.tab !== 'globe') { APP.tab = 'globe'; $('#app').dataset.tab = 'globe'; $$('.pane .view').forEach((v) => v.classList.remove('on')); }
|
||||
APP.setMode('ride');
|
||||
@@ -75,7 +75,7 @@ RIDE.frame = function (view) {
|
||||
g += `</g>`;
|
||||
g += `<g stroke="rgba(190,255,200,.95)" fill="none" stroke-width="1.6"><circle cx="${cx}" cy="${cy}" r="5"/><line x1="${cx - 18}" y1="${cy}" x2="${cx - 5}" y2="${cy}"/><line x1="${cx + 5}" y1="${cy}" x2="${cx + 18}" y2="${cy}"/><line x1="${cx}" y1="${cy - 5}" x2="${cx}" y2="${cy - 12}"/></g>`;
|
||||
// heading tape
|
||||
const ty = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--sat')) + 70;
|
||||
const ty = (parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--sat')) || 0) + 100;
|
||||
g += `<g font-size="11" font-family="var(--mono)" fill="rgba(190,255,200,.85)" stroke="rgba(190,255,200,.6)" text-anchor="middle">`;
|
||||
for (let d = Math.floor(hdg / 5) * 5 - 40; d <= hdg + 40; d += 5) { const x = cx + (d - hdg) * 4; const dd = ((d % 360) + 360) % 360; g += `<line x1="${x}" y1="${ty}" x2="${x}" y2="${ty + (dd % 10 === 0 ? 9 : 5)}"/>`; if (dd % 30 === 0) g += `<text x="${x}" y="${ty + 22}" stroke="none">${{ 0: 'N', 90: 'E', 180: 'S', 270: 'W' }[dd] ?? String(dd / 10).padStart(2, '0')}</text>`; }
|
||||
g += `<rect x="${cx - 24}" y="${ty - 22}" width="48" height="18" rx="4" fill="rgba(0,20,8,.35)"/><text x="${cx}" y="${ty - 9}" stroke="none" font-weight="700">${String(Math.round(hdg)).padStart(3, '0')}</text></g>`;
|
||||
@@ -88,6 +88,6 @@ RIDE.exit = function (silent) {
|
||||
$('#ride').classList.remove('on'); $('#ride').innerHTML = '';
|
||||
const v = Cam.view(400, 800); const [la, lo] = geo.ll(v.eye);
|
||||
Cam.setRig(null); Cam.set({ lat: la, lon: lo, range: Math.max(0.002, v3.len(v.eye) - 1 + 0.002), tilt: 0.9, heading: RIDE.s ? RIDE.s.hdg : 0, shiftY: 0.06 });
|
||||
APP.setMode('');
|
||||
APP.setMode(''); GE.world.setWindow(null); GE.world.setTime(null);
|
||||
if (!silent) Cam.flyTo({ range: 0.012, tilt: 0.8 });
|
||||
};
|
||||
@@ -110,7 +110,7 @@ CAP.frame = function (view) {
|
||||
if (!el) { el = h('div', { 'data-k': it.k }); el.innerHTML = `<div class="reticle" style="--c:${it.c}"></div><div class="callout glass" style="--c:${it.c}"><span class="ci">${ic(it.i, 'sm')}</span><span><b>${esc(it.t)}</b><span data-s></span></span></div>`; host.append(el); }
|
||||
el.querySelector('[data-s]').textContent = it.s;
|
||||
el.querySelector('.reticle').style.transform = `translate(${s.x}px, ${s.y}px)`;
|
||||
el.querySelector('.callout').style.transform = `translate(${s.x + 24}px, ${s.y}px) translate(0, -50%)`;
|
||||
const flip = s.x > view.w - 210; el.querySelector('.callout').style.transform = flip ? `translate(${s.x - 24}px, ${s.y}px) translate(-100%, -50%)` : `translate(${s.x + 24}px, ${s.y}px) translate(0, -50%)`;
|
||||
}
|
||||
host.querySelectorAll('[data-k]').forEach((e) => { if (!seen.has(e.dataset.k)) e.remove(); });
|
||||
// witness sensors
|
||||
@@ -152,7 +152,7 @@ CAP.ceremony = function (b, cv) {
|
||||
ov.innerHTML = `<div class="bundle glass strong"><div class="ph" id="bph"><svg class="bigseal" id="bigseal" viewBox="0 0 24 24"><use href="#i-seal"/></svg></div>
|
||||
<div class="steps">
|
||||
<div class="step" data-s="0"><span class="ck">${ic('check', 'xs')}</span><div><b>Frame hashed</b> · SHA-256 of ${fmt.n(b.bytes.length)} JPEG bytes<div class="h" id="sh0">…</div></div></div>
|
||||
<div class="step" data-s="1"><span class="ck">${ic('check', 'xs')}</span><div><b>Session sealed</b> · IMU 150 samples @ 50 Hz · GPS ±4 m · heading ${b.session.heading}°<svg viewBox="0 0 300 30" preserveAspectRatio="none">${GE.sparkPath(b.session.imu.map((x) => x.g + x.b * 0.02), 300, 30, '#5CF2A0', 1.2)}</svg></div></div>
|
||||
<div class="step" data-s="1"><span class="ck">${ic('check', 'xs')}</span><div><b>Session sealed</b> · IMU 150 samples @ 50 Hz · GPS ±4 m · heading ${b.session.heading}°<svg class="trace" viewBox="0 0 300 30" preserveAspectRatio="none">${GE.sparkPath(b.session.imu.map((x) => x.g + x.b * 0.02), 300, 30, '#5CF2A0', 1.2)}</svg></div></div>
|
||||
<div class="step" data-s="2"><span class="ck">${ic('check', 'xs')}</span><div><b>Chained</b> to this device’s previous bundle<div class="h">prev ${b.prev.slice(0, 24)}…</div></div></div>
|
||||
<div class="step" data-s="3"><span class="ck">${ic('check', 'xs')}</span><div><b>Bundle hash</b><div class="h" id="sh3">…</div></div></div>
|
||||
<div class="step" data-s="4"><span class="ck">${ic('check', 'xs')}</span><div><b>Signed</b> · ECDSA P-256, key generated on device, non-extractable<div class="h" id="sh4">…</div></div></div>
|
||||
|
||||
@@ -151,12 +151,11 @@ THR.init = async function () {
|
||||
/* ---------- list ---------- */
|
||||
THR.render = function () {
|
||||
const v = $('#v-threads');
|
||||
v.innerHTML = `<div class="tlist" id="tlist" style="background:var(--bg);min-height:100%"><div class="navbar">Threads</div><div class="lt"><div class="kicker">${ic('seal', 'xs')} Signed evidence · open ledgers</div><div class="row"><h1>Threads</h1><button class="avatar md" style="--h:205" id="thMe">Y</button></div></div>
|
||||
v.innerHTML = `<div class="tlist" id="tlist" style="background:var(--bg);min-height:100%"><div class="navbar">Threads</div><div class="lt"><div class="kicker">${ic('seal', 'xs')} Signed evidence · open ledgers</div><div class="row"><h1>Threads</h1><span class="concept" style="margin-bottom:8px">${ic('sparkles', 'xs')} Concept · illustrative data</span></div></div>
|
||||
<div class="livebar" style="padding-bottom:14px"><span class="presence"><span class="stack">${['kora', 'hydro', 'tamang', 'geo', 'sky'].map((k) => `<span class="avatar sm on" style="--h:${P[k].hue}">${P[k].h[0].toUpperCase()}</span>`).join('')}</span></span><span><b class="num">37</b> people reconstructing now</span></div>
|
||||
${Object.values(THREADS).map((T) => { const s = GE.SIT.byId[T.sit]; return `<article class="tcard" data-t="${T.id}" data-id="${T.sit}"><div class="thumb"><canvas width="640" height="360"></canvas><div class="bl"><div><h3>${esc(T.title)}</h3><p>${esc(T.place)} · ${esc(T.date)}</p></div><span class="badge d4">4D</span></div></div><div class="stats"><div><b>${T.obs.length}</b>observations</div><div><b>${T.hyps.length}</b>hypotheses</div><div><b>${THR.ledgers[T.id]?.length || '–'}</b>ledger</div><span class="stack">${T.people.slice(0, 4).map((k) => `<span class="avatar sm" style="--h:${P[k].hue}">${P[k].h[0].toUpperCase()}</span>`).join('')}</span></div></article>`; }).join('')}
|
||||
<div style="padding:6px 14px 150px"><button class="btn" style="width:100%" id="newThread">${ic('plus', 'sm')}Start a reconstruction</button><p style="font-size:11.5px;color:var(--text3);text-align:center;margin-top:12px">Concept · illustrative data. Witness posts, handles and claims are fictional.</p></div></div>
|
||||
<div class="ws" id="ws"></div>`;
|
||||
$('#thMe').addEventListener('click', () => APP.setTab('you'));
|
||||
$('#newThread').addEventListener('click', () => APP.toast(`${ic('threads', 'sm')} New thread · add your first observation`));
|
||||
$$('.tcard').forEach((c) => {
|
||||
c.addEventListener('click', () => THR.openWs(c.dataset.t));
|
||||
|
||||
@@ -30,7 +30,7 @@ function globeVisible() {
|
||||
let lastT = performance.now(), lastDyn = 0, lastSec = 0, running = true;
|
||||
function frame(now) {
|
||||
if (!running) return;
|
||||
const dt = Math.min(0.1, Math.max(0.001, (now - lastT) / 1000)); lastT = now;
|
||||
const dt = Math.min(0.25, Math.max(0.001, (now - lastT) / 1000)); lastT = now;
|
||||
W.simMs = Date.now() + W.offsetH * 3600000; W.t = (W.simMs - NOW) / 1000; W.sun = astro.sun(W.simMs);
|
||||
Cam.update(dt);
|
||||
if (APP.mode === 'situation' && !Cam.anim && GE.sheet.targetShift != null && !APP.isWide()) Cam.shiftY += (GE.sheet.targetShift - Cam.shiftY) * Math.min(1, dt * 7);
|
||||
@@ -66,7 +66,7 @@ function coldOpen() {
|
||||
APP.intro = true; app.classList.add('intro'); APP.updateChrome();
|
||||
if (skipAll) { Cam.set(finalCam); end(); return; }
|
||||
Cam.set({ lat: 8, lon: finalCam.lon + 75, range: 5.8, heading: 0, tilt: 0, shiftY: 0.0, shiftX: 0 });
|
||||
const T = red ? 1.2 : 3.4; const t0 = performance.now();
|
||||
const T = (red ? 1.2 : 3.4) * (HASH.slow ? +HASH.slow : 1); const t0 = performance.now();
|
||||
GE.introFade = 0;
|
||||
if (!red) Cam.flyTo(finalCam, { duration: T });
|
||||
else Cam.set(finalCam);
|
||||
|
||||
Reference in new issue
Block a user