Files
anwar-pi-website/assets/anwarpi.js
T
ahmed 8fe818e647
Deploy GitHub Pages / deploy (push) Canceled after 0s
Initial site release
2026-10-02 12:46:11 +03:00

216 lines
8.9 KiB
JavaScript

/* AnwarPi — WebGL light-beam background + scroll reveal.
Loads deferred on every page; no-ops gracefully if WebGL is unavailable. */
(function () {
"use strict";
var reduced = false;
try { reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches; } catch (e) {}
var staticMode = false;
try { staticMode = new URLSearchParams(location.search).has("static"); } catch (e) {}
/* ================= WebGL beam field ================= */
function initWebGL() {
var canvas = document.createElement("canvas");
canvas.id = "anwarpi-bg";
canvas.setAttribute("aria-hidden", "true");
var first = document.body.firstChild;
if (first) document.body.insertBefore(canvas, first);
else document.body.appendChild(canvas);
var gl = canvas.getContext("webgl", { antialias: false, alpha: false, depth: false, stencil: false })
|| canvas.getContext("experimental-webgl");
if (!gl) { canvas.parentNode.removeChild(canvas); return; }
var vertSrc = "attribute vec2 p;void main(){gl_Position=vec4(p,0.0,1.0);}";
var fragSrc = [
"precision highp float;",
"uniform float uTime;",
"uniform vec2 uRes;",
"uniform vec2 uMouse;",
"float hash(float n){return fract(sin(n*127.1)*43758.5453);}",
"void main(){",
" vec2 uv = gl_FragCoord.xy / uRes;", /* y: 0 bottom -> 1 top */
" float t = uTime * 0.06;",
" float v = 0.0;",
/* vertical light beams — the AnwarPi "A" ray motif */
" for(int i = 0; i < 7; i++){",
" float fi = float(i);",
" float x = 0.08 + 0.84 * hash(fi + 1.0) + 0.05 * sin(t * 0.7 + fi * 2.4);",
" x += (uMouse.x - x) * 0.16;",
" float w = 0.016 + 0.05 * hash(fi + 7.0);",
" float beam = exp(-pow((uv.x - x) / w, 2.0));",
" float flick = 0.55 + 0.45 * sin(t * 1.1 + fi * 1.7);",
" flick *= 1.0 + 0.55 * exp(-pow((uv.x - uMouse.x) / 0.24, 2.0));",
" float fade = smoothstep(1.0, 0.12, uv.y);", /* brighter toward the bottom, like the mark */
" v += beam * flick * fade * (0.10 + 0.17 * hash(fi + 13.0));",
" }",
/* cursor halo — the light follows the visitor */
" v += 0.11 * exp(-length((uv - uMouse) * vec2(1.0, 0.9)) * 4.5);",
/* broad floor glow + soft vignette */
" v += 0.15 * exp(-uv.y * 2.4);",
" v *= smoothstep(1.3, 0.55, uv.y);",
" v = clamp(v, 0.0, 1.0);",
/* deep blue-black base, aligned with the shared --bg token */
" vec3 base = vec3(0.0005, 0.0618, 0.0896);",
" gl_FragColor = vec4(base + vec3(v), 1.0);",
"}"
].join("\n");
function compile(type, src) {
var s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { gl.deleteShader(s); return null; }
return s;
}
var vs = compile(gl.VERTEX_SHADER, vertSrc);
var fs = compile(gl.FRAGMENT_SHADER, fragSrc);
if (!vs || !fs) { canvas.parentNode.removeChild(canvas); return; }
var prog = gl.createProgram();
gl.attachShader(prog, vs); gl.attachShader(prog, fs); gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { canvas.parentNode.removeChild(canvas); return; }
gl.useProgram(prog);
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
var loc = gl.getAttribLocation(prog, "p");
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
var uTime = gl.getUniformLocation(prog, "uTime");
var uRes = gl.getUniformLocation(prog, "uRes");
var uMouse = gl.getUniformLocation(prog, "uMouse");
/* ---- pointer reaction: beams lean toward the cursor, halo follows ---- */
var mouseTX = 0.5, mouseTY = 0.55, mouseX = 0.5, mouseY = 0.55;
if (!reduced && !staticMode) {
window.addEventListener("pointermove", function (e) {
if (e.pointerType === "touch") return;
mouseTX = e.clientX / window.innerWidth;
mouseTY = 1 - e.clientY / window.innerHeight;
}, { passive: true });
document.addEventListener("pointerleave", function () {
mouseTX = 0.5; mouseTY = 0.55;
});
}
gl.uniform2f(uMouse, mouseX, mouseY);
var DPR_CAP = 1.5;
function resize() {
var dpr = Math.min(window.devicePixelRatio || 1, DPR_CAP);
var w = Math.max(1, Math.floor(window.innerWidth * dpr));
var h = Math.max(1, Math.floor(window.innerHeight * dpr));
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w; canvas.height = h;
gl.viewport(0, 0, w, h);
}
gl.uniform2f(uRes, canvas.width, canvas.height);
}
var start = (window.performance && performance.now()) || 0;
function draw(now) {
mouseX += (mouseTX - mouseX) * 0.07;
mouseY += (mouseTY - mouseY) * 0.07;
gl.uniform1f(uTime, (now - start) / 1000);
gl.uniform2f(uMouse, mouseX, mouseY);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
resize();
draw(start); /* paint one frame synchronously so screenshots capture the field */
/* ---- article reading mode: fade the field down once past the header ---- */
if (document.body.hasAttribute("data-anw-dim")) {
var first = document.querySelector("main") && document.querySelector("main").firstElementChild;
var dimFrom = first ? first.offsetHeight : 420;
var onScroll = function () {
canvas.style.opacity = (window.scrollY || 0) > dimFrom ? "0.22" : "1";
};
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
}
if (reduced || staticMode) {
window.addEventListener("resize", function () { resize(); draw(start); });
return;
}
var running = true;
function loop(now) {
if (running) { draw(now); }
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop);
document.addEventListener("visibilitychange", function () {
running = !document.hidden;
if (running) start = (window.performance && performance.now()) - 0; /* keep phase simple */
});
}
/* ================= Scroll reveal ================= */
function initReveal() {
if (reduced || staticMode) return;
var vh = window.innerHeight || 800;
var candidates = document.querySelectorAll(
"main section > .container > *, main > .container > *, main .container > .grid > *, main .grid > *"
);
var seen = {};
var targets = [];
for (var i = 0; i < candidates.length; i++) {
var el = candidates[i];
if (seen[el.anwId]) continue;
if (el.closest(".menu-panel, .modal, dialog")) continue;
var r = el.getBoundingClientRect();
if (r.top < vh * 0.92) continue; /* above the fold: show instantly, never hide */
el.anwId = "anw" + i;
seen[el.anwId] = 1;
el.setAttribute("data-anw", "");
el.style.transitionDelay = ((i % 3) * 70) + "ms";
targets.push(el);
}
if (!targets.length) return;
document.documentElement.classList.add("anw-anim");
/* Force reveal after a safety window so nothing can stay hidden */
window.setTimeout(function () {
targets.forEach(function (el) { el.classList.add("anw-in"); });
}, 3000);
if (!("IntersectionObserver" in window)) return;
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("anw-in");
io.unobserve(entry.target);
}
});
}, { threshold: 0.1, rootMargin: "0px 0px -8% 0px" });
targets.forEach(function (el) { io.observe(el); });
}
/* ================= Current page in mobile navigation ================= */
function initActiveNav() {
var topnav = document.querySelector(".topnav");
if (!topnav) return;
var page = (location.pathname.split("/").pop() || "index.html").toLowerCase();
var selector = {
"index.html": ".logo",
"arabic-ai-learning-catalog.html": "nav a[href^='#catalog']",
"arabic-ai-learning-catalog-v2.html": "nav a[href^='#catalog']",
"article-agent-guide.html": "nav a[href='article-agent-guide.html']",
"post-locked.html": "nav a[href='article-agent-guide.html']",
"post-unlocked.html": "nav a[href='article-agent-guide.html']",
"subscription.html": "nav a[href='#exclusive-guides']",
"vip-onboarding.html": "nav a[href='subscription.html']",
"account-settings.html": ".nav-account",
"login.html": ".logo",
"forgot-password.html": ".logo",
"privacy.html": "nav a[href='privacy.html']"
}[page];
var active = selector && topnav.querySelector(selector);
if (active) active.setAttribute("aria-current", "page");
}
function boot() { initActiveNav(); initWebGL(); initReveal(); }
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
else boot();
})();