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:
225
assets/hub-globe-gpu.js
Normal file
225
assets/hub-globe-gpu.js
Normal 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);
|
||||
},
|
||||
};
|
||||
}
|
||||
1086
assets/hub-globe.js
1086
assets/hub-globe.js
File diff suppressed because it is too large
Load Diff
@@ -108,7 +108,7 @@
|
||||
document.body.addEventListener('click', function (ev) {
|
||||
var t = ev.target.closest('[data-goto-page]');
|
||||
if (!t) return;
|
||||
if (t.tagName === 'A' && t.getAttribute('href') === '#') {
|
||||
if (t.tagName === 'A') {
|
||||
ev.preventDefault();
|
||||
}
|
||||
setActivePage(Number(t.getAttribute('data-goto-page')));
|
||||
|
||||
155
hub-landing.css
155
hub-landing.css
@@ -5,12 +5,32 @@
|
||||
--landing-footer-h: 40px;
|
||||
--landing-left-w: 56px;
|
||||
--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%;
|
||||
max-width: 100%;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
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 */
|
||||
.landing-top {
|
||||
@@ -227,15 +247,94 @@
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
.landing-page-inner.card.card--lift {
|
||||
padding: 28px 32px;
|
||||
background: rgba(26, 35, 50, 0.78);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(45, 58, 79, 0.85);
|
||||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.35), 0 0 32px rgba(61, 139, 253, 0.08);
|
||||
/* Clock-widget glow + border — all landing bricks/panels */
|
||||
.hub-landing .landing-page-inner.card.card--lift,
|
||||
.hub-landing .landing-feature-grid .card,
|
||||
.hub-landing .landing-price-card,
|
||||
.hub-landing .landing-gallery__tile,
|
||||
.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 {
|
||||
@@ -263,8 +362,6 @@
|
||||
display: block;
|
||||
width: 100%;
|
||||
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));
|
||||
}
|
||||
.landing-gallery__tile--network {
|
||||
@@ -289,11 +386,27 @@
|
||||
gap: 14px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.landing-feature-grid .card {
|
||||
.hub-landing .landing-feature-grid .card {
|
||||
padding: 16px;
|
||||
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 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
@@ -302,9 +415,6 @@
|
||||
}
|
||||
.landing-price-card {
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface2);
|
||||
}
|
||||
.landing-price-card h3 { margin: 0 0 8px; }
|
||||
.landing-price-card .price {
|
||||
@@ -346,6 +456,14 @@
|
||||
}
|
||||
.landing-page--hero h2 {
|
||||
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 */
|
||||
@@ -504,7 +622,6 @@
|
||||
width: min(220px, 100%);
|
||||
min-height: 168px;
|
||||
padding: 24px 20px 20px;
|
||||
border-radius: 18px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
@@ -514,8 +631,6 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
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 {
|
||||
@@ -541,12 +656,6 @@
|
||||
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) */
|
||||
.landing-download-btn {
|
||||
min-width: 200px;
|
||||
|
||||
@@ -36,7 +36,30 @@
|
||||
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;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
86
index.php
86
index.php
@@ -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="stylesheet" href="/app/androidcast_project/issues/assets/css/app.css">
|
||||
<link rel="stylesheet" href="hub.css?v=20260711overlay">
|
||||
<link rel="stylesheet" href="hub-logo-layers.css?v=20260711overlay">
|
||||
<link rel="stylesheet" href="hub-landing.css?v=20260711layout">
|
||||
<link rel="stylesheet" href="hub-logo-layers.css?v=20260704globe39">
|
||||
<link rel="stylesheet" href="hub-landing.css?v=20260704cards">
|
||||
<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-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 logoEnabled = !!(stage && supportsSvg && supportsLayout);
|
||||
|
||||
var logoBuild = '20260704globe31';
|
||||
var logoBuild = '20260704globe56';
|
||||
|
||||
var GLOBE3D_DEFAULT = 11;
|
||||
var GLOBE_SPIN_DEFAULT = 5;
|
||||
|
||||
function hubAsset(rel) {
|
||||
var link = document.querySelector('link[href*="hub.css"]');
|
||||
@@ -254,8 +257,8 @@ function hub_h(string $s): string {
|
||||
}
|
||||
|
||||
var globe3dRaw = params.get('globe3d');
|
||||
var globe3dVersion = globe3dRaw === null || globe3dRaw === '' ? 1 : parseInt(globe3dRaw, 10);
|
||||
if (!isFinite(globe3dVersion)) globe3dVersion = 1;
|
||||
var globe3dVersion = globe3dRaw === null || globe3dRaw === '' ? GLOBE3D_DEFAULT : parseInt(globe3dRaw, 10);
|
||||
if (!isFinite(globe3dVersion)) globe3dVersion = GLOBE3D_DEFAULT;
|
||||
var globeLandZoom = {};
|
||||
['latf', 'lngf', 'zy', 'zx', 'pp', 'pe'].forEach(function (key) {
|
||||
var raw = params.get(key);
|
||||
@@ -269,11 +272,68 @@ function hub_h(string $s): string {
|
||||
if (isFinite(fLegacy)) globeLandZoom.latf = fLegacy;
|
||||
}
|
||||
var globeAxRaw = params.get('ax');
|
||||
var globeSpinFactor = 1;
|
||||
var globeSpinFactor = GLOBE_SPIN_DEFAULT;
|
||||
if (globeAxRaw !== null && globeAxRaw !== '') {
|
||||
var axParsed = parseFloat(globeAxRaw);
|
||||
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 globe3dLayers = { globe: true, continents: true, gridOverlay: true };
|
||||
|
||||
@@ -321,9 +381,21 @@ function hub_h(string $s): string {
|
||||
if (Object.keys(globeLandZoom).length > 0) {
|
||||
globeOpts.landZoom = globeLandZoom;
|
||||
}
|
||||
if (globeSpinFactor !== 1) {
|
||||
if (globeSpinFactor !== GLOBE_SPIN_DEFAULT) {
|
||||
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);
|
||||
globeModuleUrl.searchParams.set('v', logoBuild);
|
||||
globeModuleUrl.searchParams.set('globe3d', String(globe3dVersion));
|
||||
|
||||
@@ -12,18 +12,18 @@
|
||||
<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>
|
||||
<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>
|
||||
<p class="muted">Wi‑Fi and USB transport with adaptive bitrate and session metrics.</p>
|
||||
</article>
|
||||
<article class="card card--lift">
|
||||
</a>
|
||||
<a class="card card--lift landing-feature-card" href="issues/?view=reports">
|
||||
<h3>Issues</h3>
|
||||
<p class="muted">Ingest, triage tickets, and correlate fingerprints across releases.</p>
|
||||
</article>
|
||||
<article class="card card--lift">
|
||||
</a>
|
||||
<a class="card card--lift landing-feature-card" href="build/">
|
||||
<h3>Builder</h3>
|
||||
<p class="muted">Docker CI for APK baking, OTA artifacts, and pipeline history.</p>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -57,10 +57,14 @@
|
||||
<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>
|
||||
<div class="landing-feature-grid">
|
||||
<article class="card"><strong>WebRTC / UDP</strong><p class="muted">Low-latency mirror paths</p></article>
|
||||
<article class="card"><strong>Opus · Speex</strong><p class="muted">Native codec validation</p></article>
|
||||
<article class="card"><strong>WireGuard</strong><p class="muted">Remote device sessions</p></article>
|
||||
<article class="card"><strong>PHP · SQLite</strong><p class="muted">Crash + ticket console</p></article>
|
||||
<article class="card card--lift">
|
||||
<strong>WebRTC / UDP</strong><p class="muted">Low-latency mirror paths</p></article>
|
||||
<article class="card card--lift">
|
||||
<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>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user