1
0
mirror of git://f0xx.org/ac/ac-be-hub synced 2026-08-09 20:59:23 +03:00

Hub globe v2: mercator wrap, GPU path, landing layout updates.

Refine 3D globe rendering, layer CSS, landing page wiring and TOC UX.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-08-07 22:15:46 +02:00
parent 78d5911e48
commit c54919189c
7 changed files with 1404 additions and 199 deletions

225
assets/hub-globe-gpu.js Normal file
View File

@@ -0,0 +1,225 @@
/**
* WebGL land disk (globe3d=11) — GPU orthographic mercator sample; ocean/sun stay Canvas2D.
*/
const VS = `
attribute vec2 aPos;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
}
`;
const FS = `
precision highp float;
uniform vec3 uDisk;
uniform float uRotY;
uniform sampler2D uMerc;
uniform float uLatf;
uniform float uLngf;
uniform float uZy;
uniform float uZx;
uniform float uPp;
uniform float uPe;
uniform float uLonCenter;
uniform float uLimbZ;
const float PI = 3.141592653589793;
const float TAU = 6.283185307179586;
float wrapLon(float lon) {
lon = mod(lon + PI, TAU);
if (lon < 0.0) lon += TAU;
return lon - PI;
}
float latToMercatorV(float lat) {
float clamped = clamp(lat, -1.52, 1.52);
float t = 0.5 + asinh(tan(clamped)) / TAU;
return clamp(1.0 - t, 0.0, 1.0);
}
vec2 landZoomLonLat(float lon, float lat, vec3 tex) {
float latT = min(1.0, abs(lat) / (PI * 0.5));
float lonT = min(1.0, abs(lon) / PI);
float rimT = min(1.0, length(tex.xz));
float latMul = uLatf * (uZy * (1.0 - latT * latT) + latT * latT);
float lonMul = uLngf * (uZx * (1.0 - lonT * lonT) + lonT * lonT);
float poleLens = 1.0 - uPp * latT * latT;
float edgeLens = 1.0 - uPe * rimT * rimT;
return vec2(
wrapLon(lon * lonMul * edgeLens + uLonCenter),
clamp(lat * latMul * poleLens, -1.52, 1.52)
);
}
void main() {
vec2 p = (gl_FragCoord.xy - uDisk.xy) / uDisk.z;
float rr = dot(p, p);
if (rr > 1.0) {
discard;
}
float nz = sqrt(max(0.0, 1.0 - rr));
if (nz <= uLimbZ) {
discard;
}
float cosR = cos(uRotY);
float sinR = sin(uRotY);
float bx = p.x * cosR - nz * sinR;
float by = p.y;
float bz = p.x * sinR + nz * cosR;
vec3 tex = vec3(bx, by, bz);
float lon = atan(bx, bz);
float lat = asin(clamp(by, -1.0, 1.0));
vec2 mapped = landZoomLonLat(lon, lat, tex);
float u = fract((mapped.x + PI) / TAU);
float v = latToMercatorV(mapped.y);
vec4 merc = texture2D(uMerc, vec2(u, v));
float alpha = merc.a / 255.0;
if (alpha <= 0.004) {
discard;
}
gl_FragColor = vec4(merc.rgb / 255.0, alpha);
}
`;
function compileShader(gl, type, src) {
const sh = gl.createShader(type);
gl.shaderSource(sh, src);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
console.warn('Globe GPU shader:', gl.getShaderInfoLog(sh));
gl.deleteShader(sh);
return null;
}
return sh;
}
function buildMercTexture(gl, cache, cs) {
const w = cache.w;
const h = cache.h;
const pixels = new Uint8Array(w * h * 4);
for (let i = 0; i < w * h; i++) {
const k = cache.kind[i];
if (!k) {
continue;
}
let r = cache.rgb[i * 3];
let g = cache.rgb[i * 3 + 1];
let b = cache.rgb[i * 3 + 2];
let a = cs.fillAlpha;
if (k === 2 || k === 3) {
r = Math.min(255, r + cs.borderBoostR);
g = Math.min(255, g + cs.borderBoostG);
b = Math.min(255, b + cs.borderBoostB);
a = cs.borderAlpha;
}
const o = i * 4;
pixels[o] = r;
pixels[o + 1] = g;
pixels[o + 2] = b;
pixels[o + 3] = a;
}
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
gl.bindTexture(gl.TEXTURE_2D, null);
return tex;
}
/**
* @param {HTMLCanvasElement} canvas
* @param {{ cache: object, continentStyle: object, landZoom: object, lonCenter: number }} opts
*/
export function createGpuGlobeRenderer(canvas, opts) {
const gl = canvas.getContext('webgl', {
alpha: true,
antialias: false,
depth: false,
stencil: false,
preserveDrawingBuffer: false,
powerPreference: 'high-performance',
});
if (!gl) {
return null;
}
const vs = compileShader(gl, gl.VERTEX_SHADER, VS);
const fs = compileShader(gl, gl.FRAGMENT_SHADER, FS);
if (!vs || !fs) {
return null;
}
const prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
console.warn('Globe GPU link:', gl.getProgramInfoLog(prog));
return null;
}
gl.deleteShader(vs);
gl.deleteShader(fs);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
const cs = opts.continentStyle;
const mercTex = buildMercTexture(gl, opts.cache, cs);
const lz = opts.landZoom;
const loc = {
aPos: gl.getAttribLocation(prog, 'aPos'),
uDisk: gl.getUniformLocation(prog, 'uDisk'),
uRotY: gl.getUniformLocation(prog, 'uRotY'),
uMerc: gl.getUniformLocation(prog, 'uMerc'),
uLatf: gl.getUniformLocation(prog, 'uLatf'),
uLngf: gl.getUniformLocation(prog, 'uLngf'),
uZy: gl.getUniformLocation(prog, 'uZy'),
uZx: gl.getUniformLocation(prog, 'uZx'),
uPp: gl.getUniformLocation(prog, 'uPp'),
uPe: gl.getUniformLocation(prog, 'uPe'),
uLonCenter: gl.getUniformLocation(prog, 'uLonCenter'),
uLimbZ: gl.getUniformLocation(prog, 'uLimbZ'),
};
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
return {
draw(cx, cy, r, rotY, width, height) {
gl.viewport(0, 0, width, height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(prog);
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.enableVertexAttribArray(loc.aPos);
gl.vertexAttribPointer(loc.aPos, 2, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, mercTex);
gl.uniform1i(loc.uMerc, 0);
gl.uniform3f(loc.uDisk, cx, cy, r);
gl.uniform1f(loc.uRotY, rotY);
gl.uniform1f(loc.uLatf, lz.latf);
gl.uniform1f(loc.uLngf, lz.lngf);
gl.uniform1f(loc.uZy, lz.zy);
gl.uniform1f(loc.uZx, lz.zx);
gl.uniform1f(loc.uPp, lz.pp);
gl.uniform1f(loc.uPe, lz.pe);
gl.uniform1f(loc.uLonCenter, opts.lonCenter);
gl.uniform1f(loc.uLimbZ, 0.002);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
},
destroy() {
gl.deleteTexture(mercTex);
gl.deleteBuffer(buf);
gl.deleteProgram(prog);
},
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -108,7 +108,7 @@
document.body.addEventListener('click', function (ev) { document.body.addEventListener('click', function (ev) {
var t = ev.target.closest('[data-goto-page]'); var t = ev.target.closest('[data-goto-page]');
if (!t) return; if (!t) return;
if (t.tagName === 'A' && t.getAttribute('href') === '#') { if (t.tagName === 'A') {
ev.preventDefault(); ev.preventDefault();
} }
setActivePage(Number(t.getAttribute('data-goto-page'))); setActivePage(Number(t.getAttribute('data-goto-page')));

View File

@@ -5,12 +5,32 @@
--landing-footer-h: 40px; --landing-footer-h: 40px;
--landing-left-w: 56px; --landing-left-w: 56px;
--landing-rail-w: 44px; --landing-rail-w: 44px;
/* Clock-widget derived brick chrome */
--landing-brick-bg: rgba(18, 24, 34, 0.88);
--landing-brick-border: rgba(255, 255, 255, 0.72);
--landing-brick-shadow: 0 10px 30px rgba(0, 0, 0, 0.48);
--landing-brick-radius: 18px;
--landing-brick-border-hover: rgba(165, 225, 255, 0.98);
--landing-brick-glow-ring: rgba(140, 210, 255, 0.55);
--landing-brick-glow-near: rgba(120, 200, 255, 0.52);
--landing-brick-glow-far: rgba(90, 175, 255, 0.32);
--landing-brick-glow-inset: rgba(120, 200, 255, 0.07);
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
height: 100vh; height: 100vh;
height: 100dvh; height: 100dvh;
overflow: hidden; overflow: hidden;
} }
[data-theme="light"] .hub-landing {
--landing-brick-bg: rgba(255, 255, 255, 0.92);
--landing-brick-border: rgba(180, 200, 224, 0.88);
--landing-brick-shadow: 0 10px 28px rgba(26, 35, 50, 0.14);
--landing-brick-border-hover: rgba(61, 139, 253, 0.82);
--landing-brick-glow-ring: rgba(61, 139, 253, 0.45);
--landing-brick-glow-near: rgba(61, 139, 253, 0.28);
--landing-brick-glow-far: rgba(61, 139, 253, 0.16);
--landing-brick-glow-inset: rgba(61, 139, 253, 0.05);
}
/* (1) Top header */ /* (1) Top header */
.landing-top { .landing-top {
@@ -227,15 +247,94 @@
padding: 8px 4px; padding: 8px 4px;
} }
.landing-page-inner.card.card--lift { /* Clock-widget glow + border — all landing bricks/panels */
padding: 28px 32px; .hub-landing .landing-page-inner.card.card--lift,
background: rgba(26, 35, 50, 0.78); .hub-landing .landing-feature-grid .card,
backdrop-filter: blur(8px); .hub-landing .landing-price-card,
border: 1px solid rgba(45, 58, 79, 0.85); .hub-landing .landing-gallery__tile,
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.35), 0 0 32px rgba(61, 139, 253, 0.08); .hub-landing .landing-download-card,
.hub-landing .landing-docs-grid .card,
.hub-landing .hub-docs-panel.card {
background: var(--landing-brick-bg);
backdrop-filter: blur(1.5px);
border: 1px solid var(--landing-brick-border);
border-radius: var(--landing-brick-radius);
box-shadow: var(--landing-brick-shadow);
transition:
transform 0.22s ease,
box-shadow 0.28s ease,
border-color 0.28s ease,
filter 0.22s ease;
} }
[data-theme="light"] .landing-page-inner.card.card--lift {
background: rgba(255, 255, 255, 0.88); .hub-landing .landing-page-inner.card.card--lift {
padding: 28px 32px;
}
.hub-landing .landing-page-inner.card.card--lift:hover,
.hub-landing .landing-feature-grid .card:hover,
.hub-landing .landing-price-card:hover,
.hub-landing .landing-gallery__tile:hover,
.hub-landing .landing-download-card:hover,
.hub-landing .landing-docs-grid .card:hover,
.hub-landing .hub-docs-panel.card:hover,
.hub-landing .landing-feature-grid .card:focus-within,
.hub-landing .landing-download-card:focus-visible,
.hub-landing .landing-docs-grid .card:focus-within {
cursor: pointer;
border-color: var(--landing-brick-border-hover);
box-shadow:
0 16px 42px rgba(0, 0, 0, 0.62),
0 0 0 1px var(--landing-brick-glow-ring),
0 0 22px var(--landing-brick-glow-near),
0 0 44px var(--landing-brick-glow-far),
inset 0 0 14px var(--landing-brick-glow-inset);
filter: saturate(1.06);
}
[data-theme="light"] .hub-landing .landing-page-inner.card.card--lift:hover,
[data-theme="light"] .hub-landing .landing-feature-grid .card:hover,
[data-theme="light"] .hub-landing .landing-price-card:hover,
[data-theme="light"] .hub-landing .landing-gallery__tile:hover,
[data-theme="light"] .hub-landing .landing-download-card:hover,
[data-theme="light"] .hub-landing .landing-docs-grid .card:hover,
[data-theme="light"] .hub-landing .hub-docs-panel.card:hover,
[data-theme="light"] .hub-landing .landing-feature-grid .card:focus-within,
[data-theme="light"] .hub-landing .landing-download-card:focus-visible,
[data-theme="light"] .hub-landing .landing-docs-grid .card:focus-within {
box-shadow:
0 14px 36px rgba(26, 35, 50, 0.16),
0 0 0 1px var(--landing-brick-glow-ring),
0 0 20px var(--landing-brick-glow-near),
0 0 36px var(--landing-brick-glow-far),
inset 0 0 12px var(--landing-brick-glow-inset);
}
.hub-landing .landing-page-inner.card.card--lift:hover {
transform: scale(1.012);
}
.hub-landing .landing-feature-grid .card:hover,
.hub-landing .landing-price-card:hover,
.hub-landing .landing-docs-grid .card:hover,
.hub-landing .landing-download-card:hover,
.hub-landing .landing-download-card:focus-visible {
transform: translateY(-3px) scale(1.02);
}
.hub-landing .landing-download-card.btn.btn-primary {
color: var(--text);
background: var(--landing-brick-bg);
}
.hub-landing .landing-download-card.btn.btn-primary:hover,
.hub-landing .landing-download-card.btn.btn-primary:focus-visible {
color: var(--text);
background: var(--landing-brick-bg);
}
.hub-landing .hub-docs-panel.card:hover {
transform: translate(-50%, -50%);
} }
.landing-page h2 { .landing-page h2 {
@@ -263,8 +362,6 @@
display: block; display: block;
width: 100%; width: 100%;
aspect-ratio: 4 / 3; aspect-ratio: 4 / 3;
border-radius: 10px;
border: 1px solid var(--border);
background: linear-gradient(145deg, rgba(61, 139, 253, 0.22), rgba(12, 24, 48, 0.85)); background: linear-gradient(145deg, rgba(61, 139, 253, 0.22), rgba(12, 24, 48, 0.85));
} }
.landing-gallery__tile--network { .landing-gallery__tile--network {
@@ -289,11 +386,27 @@
gap: 14px; gap: 14px;
margin-top: 20px; margin-top: 20px;
} }
.landing-feature-grid .card { .hub-landing .landing-feature-grid .card {
padding: 16px; padding: 16px;
margin: 0; margin: 0;
} }
.hub-landing .landing-feature-card {
display: block;
text-decoration: none;
color: inherit;
}
.hub-landing .landing-feature-card:hover,
.hub-landing .landing-feature-card:focus-visible {
color: inherit;
text-decoration: none;
}
.hub-landing .landing-feature-card h3 {
margin: 0 0 8px;
}
.landing-price-grid { .landing-price-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
@@ -302,9 +415,6 @@
} }
.landing-price-card { .landing-price-card {
padding: 20px; padding: 20px;
border-radius: 12px;
border: 1px solid var(--border);
background: var(--surface2);
} }
.landing-price-card h3 { margin: 0 0 8px; } .landing-price-card h3 { margin: 0 0 8px; }
.landing-price-card .price { .landing-price-card .price {
@@ -346,6 +456,14 @@
} }
.landing-page--hero h2 { .landing-page--hero h2 {
font-size: clamp(2rem, 5vw, 3rem); font-size: clamp(2rem, 5vw, 3rem);
color: #fff;
-webkit-text-stroke: 0.65px #000;
paint-order: stroke fill;
}
.landing-page--hero .landing-lead {
color: #fff;
-webkit-text-stroke: 0.45px #000;
paint-order: stroke fill;
} }
/* (4) Right page rail — hidden until handle hover/drag */ /* (4) Right page rail — hidden until handle hover/drag */
@@ -504,7 +622,6 @@
width: min(220px, 100%); width: min(220px, 100%);
min-height: 168px; min-height: 168px;
padding: 24px 20px 20px; padding: 24px 20px 20px;
border-radius: 18px;
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
text-align: center; text-align: center;
@@ -514,8 +631,6 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 16px; gap: 16px;
transition: box-shadow 0.2s ease, transform 0.15s ease;
border: 1px solid rgba(61, 139, 253, 0.35);
} }
.landing-download-card__icon { .landing-download-card__icon {
@@ -541,12 +656,6 @@
letter-spacing: 0.01em; letter-spacing: 0.01em;
} }
.landing-download-card:hover,
.landing-download-card:focus-visible {
transform: translateY(-2px);
box-shadow: 0 0 30px rgba(61, 139, 253, 0.45);
}
/* Legacy pill buttons (documentation page, resources) */ /* Legacy pill buttons (documentation page, resources) */
.landing-download-btn { .landing-download-btn {
min-width: 200px; min-width: 200px;

View File

@@ -36,7 +36,30 @@
border-radius: 50%; border-radius: 50%;
} }
.hub-globe-canvas.is-dragging { /* v11 fog halo extends outside the disk — square canvas, no circular CSS clip */
.hub-globe-canvas--halo {
border-radius: 0;
}
.hub-globe-canvas--body,
.hub-globe-canvas--land {
pointer-events: none;
}
.hub-globe-canvas--overlay {
z-index: 2;
}
.hub-globe-canvas--land {
z-index: 1;
}
.hub-globe-canvas--body {
z-index: 0;
}
.hub-globe-canvas.is-dragging,
.hub-globe-canvas--overlay.is-dragging {
pointer-events: auto; pointer-events: auto;
cursor: grabbing; cursor: grabbing;
} }

View File

@@ -40,8 +40,8 @@ function hub_h(string $s): string {
<link rel="apple-touch-icon" href="/app/androidcast_project/apple-touch-icon.ico"> <link rel="apple-touch-icon" href="/app/androidcast_project/apple-touch-icon.ico">
<link rel="stylesheet" href="/app/androidcast_project/issues/assets/css/app.css"> <link rel="stylesheet" href="/app/androidcast_project/issues/assets/css/app.css">
<link rel="stylesheet" href="hub.css?v=20260711overlay"> <link rel="stylesheet" href="hub.css?v=20260711overlay">
<link rel="stylesheet" href="hub-logo-layers.css?v=20260711overlay"> <link rel="stylesheet" href="hub-logo-layers.css?v=20260704globe39">
<link rel="stylesheet" href="hub-landing.css?v=20260711layout"> <link rel="stylesheet" href="hub-landing.css?v=20260704cards">
<script src="/app/androidcast_project/issues/assets/js/nav_shell.js" defer></script> <script src="/app/androidcast_project/issues/assets/js/nav_shell.js" defer></script>
<script src="assets/hub-i18n.js" defer></script> <script src="assets/hub-i18n.js" defer></script>
<script src="assets/hub-landing.js" defer></script> <script src="assets/hub-landing.js" defer></script>
@@ -187,7 +187,10 @@ function hub_h(string $s): string {
var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover')); var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover'));
var logoEnabled = !!(stage && supportsSvg && supportsLayout); var logoEnabled = !!(stage && supportsSvg && supportsLayout);
var logoBuild = '20260704globe31'; var logoBuild = '20260704globe56';
var GLOBE3D_DEFAULT = 11;
var GLOBE_SPIN_DEFAULT = 5;
function hubAsset(rel) { function hubAsset(rel) {
var link = document.querySelector('link[href*="hub.css"]'); var link = document.querySelector('link[href*="hub.css"]');
@@ -254,8 +257,8 @@ function hub_h(string $s): string {
} }
var globe3dRaw = params.get('globe3d'); var globe3dRaw = params.get('globe3d');
var globe3dVersion = globe3dRaw === null || globe3dRaw === '' ? 1 : parseInt(globe3dRaw, 10); var globe3dVersion = globe3dRaw === null || globe3dRaw === '' ? GLOBE3D_DEFAULT : parseInt(globe3dRaw, 10);
if (!isFinite(globe3dVersion)) globe3dVersion = 1; if (!isFinite(globe3dVersion)) globe3dVersion = GLOBE3D_DEFAULT;
var globeLandZoom = {}; var globeLandZoom = {};
['latf', 'lngf', 'zy', 'zx', 'pp', 'pe'].forEach(function (key) { ['latf', 'lngf', 'zy', 'zx', 'pp', 'pe'].forEach(function (key) {
var raw = params.get(key); var raw = params.get(key);
@@ -269,11 +272,68 @@ function hub_h(string $s): string {
if (isFinite(fLegacy)) globeLandZoom.latf = fLegacy; if (isFinite(fLegacy)) globeLandZoom.latf = fLegacy;
} }
var globeAxRaw = params.get('ax'); var globeAxRaw = params.get('ax');
var globeSpinFactor = 1; var globeSpinFactor = GLOBE_SPIN_DEFAULT;
if (globeAxRaw !== null && globeAxRaw !== '') { if (globeAxRaw !== null && globeAxRaw !== '') {
var axParsed = parseFloat(globeAxRaw); var axParsed = parseFloat(globeAxRaw);
if (isFinite(axParsed)) globeSpinFactor = axParsed; if (isFinite(axParsed)) globeSpinFactor = axParsed;
} }
var globeContinentStyle = {};
var globeSunlightStyle = {};
var globeFogStyle = {};
var globePerf = {};
if (globe3dVersion >= 11) {
var continentParamMap = {
cfa: 'fillAlpha',
cba: 'borderAlpha',
cbr: 'borderBoostR',
cbg: 'borderBoostG',
cbb: 'borderBoostB',
ceps: 'coastEps'
};
Object.keys(continentParamMap).forEach(function (key) {
var raw = params.get(key);
if (raw === null || raw === '') return;
var n = parseFloat(raw);
if (isFinite(n)) globeContinentStyle[continentParamMap[key]] = n;
});
var sunlightParamMap = {
slx: 'lx',
sly: 'ly',
slx2: 'lx2',
sly2: 'ly2',
sld: 'dir',
sll: 'span',
sli: 'intensity',
slhi: 'highlight',
slsh: 'shadow'
};
Object.keys(sunlightParamMap).forEach(function (key) {
var raw = params.get(key);
if (raw === null || raw === '') return;
var n = parseFloat(raw);
if (isFinite(n)) globeSunlightStyle[sunlightParamMap[key]] = n;
});
var fogParamMap = { fogi: 'intensity', fogr: 'outer', fogin: 'inner' };
Object.keys(fogParamMap).forEach(function (key) {
var raw = params.get(key);
if (raw === null || raw === '') return;
var n = parseFloat(raw);
if (isFinite(n)) globeFogStyle[fogParamMap[key]] = n;
});
var globeFpsRaw = params.get('globeFps');
if (globeFpsRaw !== null && globeFpsRaw !== '') {
var fpsParsed = parseFloat(globeFpsRaw);
if (isFinite(fpsParsed)) globePerf.spinFps = fpsParsed;
}
var globeDprRaw = params.get('globeDpr');
if (globeDprRaw !== null && globeDprRaw !== '') {
var dprParsed = parseFloat(globeDprRaw);
if (isFinite(dprParsed)) globePerf.dprMax = dprParsed;
}
var globeGpuRaw = params.get('globeGpu');
if (globeGpuRaw === '0') globePerf.useGpu = false;
if (params.get('globeProfile') === '1') globePerf.profile = true;
}
var useGlobe3d = logoEnabled && globe3dVersion !== 0; var useGlobe3d = logoEnabled && globe3dVersion !== 0;
var globe3dLayers = { globe: true, continents: true, gridOverlay: true }; var globe3dLayers = { globe: true, continents: true, gridOverlay: true };
@@ -321,9 +381,21 @@ function hub_h(string $s): string {
if (Object.keys(globeLandZoom).length > 0) { if (Object.keys(globeLandZoom).length > 0) {
globeOpts.landZoom = globeLandZoom; globeOpts.landZoom = globeLandZoom;
} }
if (globeSpinFactor !== 1) { if (globeSpinFactor !== GLOBE_SPIN_DEFAULT) {
globeOpts.spinFactor = globeSpinFactor; globeOpts.spinFactor = globeSpinFactor;
} }
if (Object.keys(globeContinentStyle).length > 0) {
globeOpts.continentStyle = globeContinentStyle;
}
if (Object.keys(globeSunlightStyle).length > 0) {
globeOpts.sunlightStyle = globeSunlightStyle;
}
if (Object.keys(globeFogStyle).length > 0) {
globeOpts.fogStyle = globeFogStyle;
}
if (Object.keys(globePerf).length > 0) {
globeOpts.globePerf = globePerf;
}
var globeModuleUrl = new URL('assets/hub-globe.js', window.location.href); var globeModuleUrl = new URL('assets/hub-globe.js', window.location.href);
globeModuleUrl.searchParams.set('v', logoBuild); globeModuleUrl.searchParams.set('v', logoBuild);
globeModuleUrl.searchParams.set('globe3d', String(globe3dVersion)); globeModuleUrl.searchParams.set('globe3d', String(globe3dVersion));

View File

@@ -12,18 +12,18 @@
<h2 data-i18n="page2.title">AndroidCast</h2> <h2 data-i18n="page2.title">AndroidCast</h2>
<p class="landing-lead" data-i18n="page2.lead">Mirror Android screens with low latency, native codecs, and operator-grade tooling.</p> <p class="landing-lead" data-i18n="page2.lead">Mirror Android screens with low latency, native codecs, and operator-grade tooling.</p>
<div class="landing-feature-grid"> <div class="landing-feature-grid">
<article class="card card--lift"> <a class="card card--lift landing-feature-card" href="?page=7" data-goto-page="7">
<h3>Cast</h3> <h3>Cast</h3>
<p class="muted">WiFi and USB transport with adaptive bitrate and session metrics.</p> <p class="muted">WiFi and USB transport with adaptive bitrate and session metrics.</p>
</article> </a>
<article class="card card--lift"> <a class="card card--lift landing-feature-card" href="issues/?view=reports">
<h3>Issues</h3> <h3>Issues</h3>
<p class="muted">Ingest, triage tickets, and correlate fingerprints across releases.</p> <p class="muted">Ingest, triage tickets, and correlate fingerprints across releases.</p>
</article> </a>
<article class="card card--lift"> <a class="card card--lift landing-feature-card" href="build/">
<h3>Builder</h3> <h3>Builder</h3>
<p class="muted">Docker CI for APK baking, OTA artifacts, and pipeline history.</p> <p class="muted">Docker CI for APK baking, OTA artifacts, and pipeline history.</p>
</article> </a>
</div> </div>
</div> </div>
</section> </section>
@@ -57,10 +57,14 @@
<h2 data-i18n="page5.title">Technologies</h2> <h2 data-i18n="page5.title">Technologies</h2>
<p class="landing-lead" data-i18n="page5.lead">WebRTC, Opus/Speex, WireGuard remote access, PHP consoles, and Docker CI pipelines.</p> <p class="landing-lead" data-i18n="page5.lead">WebRTC, Opus/Speex, WireGuard remote access, PHP consoles, and Docker CI pipelines.</p>
<div class="landing-feature-grid"> <div class="landing-feature-grid">
<article class="card"><strong>WebRTC / UDP</strong><p class="muted">Low-latency mirror paths</p></article> <article class="card card--lift">
<article class="card"><strong>Opus · Speex</strong><p class="muted">Native codec validation</p></article> <strong>WebRTC / UDP</strong><p class="muted">Low-latency mirror paths</p></article>
<article class="card"><strong>WireGuard</strong><p class="muted">Remote device sessions</p></article> <article class="card card--lift">
<article class="card"><strong>PHP · SQLite</strong><p class="muted">Crash + ticket console</p></article> <strong>Opus · Speex</strong><p class="muted">Native codec validation</p></article>
<article class="card card--lift">
<strong>WireGuard</strong><p class="muted">Remote device sessions</p></article>
<article class="card card--lift">
<strong>PHP · SQLite</strong><p class="muted">Crash + ticket console</p></article>
</div> </div>
</div> </div>
</section> </section>