(function () { function init() { const cv = document.getElementById("skc"); if (!cv) return; const ctx = cv.getContext("2d"); /* ── CANVAS SIZING ── Width = sky-login-card width Height = sky-login-card height Read after DOM paint via double requestAnimationFrame to get final dimensions. ResizeObserver keeps it in sync if card height changes later. */ function resizeCanvas() { const card = document.getElementById("sky-login-card"); if (!card) return; const w = Math.round(card.offsetWidth) || 440; const h = Math.round(card.offsetHeight) || 600; if (cv.width !== w) cv.width = w; if (cv.height !== h) cv.height = h; } /* Wait two frames so card.offsetHeight reflects final layout */ requestAnimationFrame(function () { requestAnimationFrame(function () { resizeCanvas(); }); }); window.addEventListener("resize", resizeCanvas); /* Track card height changes (font load, zoom, etc.) */ if (typeof ResizeObserver !== "undefined") { const ro = new ResizeObserver(resizeCanvas); const card = document.getElementById("sky-login-card"); if (card) ro.observe(card); } /* Dynamic accessors — always reflect current canvas size */ const dim = { get W() { return cv.width; }, get H() { return cv.height; }, get CX() { return cv.width / 2; }, get CY() { return cv.height / 2; }, }; /* ── 3D GEOMETRY ── Raw coordinates in mm, centred on AX / OZ. Scale (SC) applied dynamically in proj() every frame. X range : 64.491..151.341 = 86.85 mm centre AX = 107.916 Z range : 50.926..94.147 = 43.22 mm centre OZ = 72.536 D = half-depth of extrusion (mm) */ const AX = 107.916, OZ = 72.536, D = 9; /* Logo is rendered in the TOP THIRD of the canvas. CY_LOGO = vertical centre of the logo rendering area. Scale fits the logo into the top portion with margin. */ const LOGO_ZONE = 0.42; /* logo uses top 42% of canvas height */ function getSC() { const availH = dim.H * LOGO_ZONE; const scX = dim.W / (86.85 + D * 2); const scZ = availH / (43.22 + D * 2); return Math.min(scX, scZ) * 0.86; } /* Vertical centre of the logo zone */ function getLogoY() { return (dim.H * LOGO_ZONE) / 2; } function sv(x, z) { return [x - AX, z - OZ]; } /* Diamond (rotated square) */ const LO = [ sv(106.916, 51.0), sv(119.2, 61.8), sv(107.8, 74.5), sv(95.812, 62.842), ]; /* Right quadrilateral */ const FD = [ sv(121.188, 63.842), sv(151.341, 94.147), sv(109.186, 94.147), sv(109.186, 77.044), ]; /* Left quadrilateral (mirror of FD) */ const FG = [ sv(93.748, 64.742), sv(106.646, 77.044), sv(106.646, 94.147), sv(64.491, 94.147), ]; /* Extrude a 2D polygon into a 3D prism. Returns {verts, edges}. Front faces: indices 0..n-1 (y = -D) Back faces: indices n..2n-1 (y = +D) */ function extrude(pts2d) { const n = pts2d.length; const verts = [ ...pts2d.map(([x, z]) => [x, -D, z]), ...pts2d.map(([x, z]) => [x, D, z]), ]; const edges = []; for (let i = 0; i < n; i++) { const j = (i + 1) % n; edges.push([i, j, "f"]); /* front edge */ edges.push([i + n, j + n, "b"]); /* back edge */ edges.push([i, i + n, "s"]); /* side edge */ } return { verts, edges }; } const shapes = [extrude(LO), extrude(FD), extrude(FG)]; /* ── PERSPECTIVE PROJECTION ── SC applied here every frame so resizing is instant. Logo is centred on getLogoY() (top zone), not dim.CY. */ function proj([x, y, z]) { const sc = getSC(); const s = 1.0 / (1.0 + y / 500.0); return [dim.CX + x * sc * s, getLogoY() + z * sc * s, s, y]; } /* ── ROTATION HELPERS ── */ function rx(p, a) { const c = Math.cos(a), s = Math.sin(a); return p.map(([x, y, z]) => [x, y * c - z * s, y * s + z * c]); } function ry(p, a) { const c = Math.cos(a), s = Math.sin(a); return p.map(([x, y, z]) => [x * c + z * s, y, -x * s + z * c]); } function rz(p, a) { const c = Math.cos(a), s = Math.sin(a); return p.map(([x, y, z]) => [x * c - y * s, x * s + y * c, z]); } /* ── FLUORESCENT TUBE EFFECT ── 4-pass rendering: wide glow → mid glow → tight glow → core wire */ function tube(x1, y1, x2, y2, bright, al) { ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.strokeStyle = "rgba(100,180,255,0.05)"; ctx.lineWidth = 20; ctx.globalAlpha = al * 0.55; ctx.stroke(); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.strokeStyle = bright ? "rgba(60,190,255,0.20)" : "rgba(15,80,200,0.08)"; ctx.lineWidth = 8; ctx.globalAlpha = al * 0.88; ctx.stroke(); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.strokeStyle = bright ? "rgba(150,225,255,0.65)" : "rgba(45,120,255,0.22)"; ctx.lineWidth = 2.4; ctx.globalAlpha = al; ctx.stroke(); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.strokeStyle = bright ? "rgba(225,246,255,0.98)" : "rgba(85,165,255,0.52)"; ctx.lineWidth = 0.65; ctx.globalAlpha = al; ctx.stroke(); } /* ── CRT ARTEFACT STATE ── */ let t = 0, glT = 0, glL = [], scanY = 0, scanOn = false, scanTmr = 0; let flickAlpha = 1.0, flickTarget = 1.0, flickTimer = 0; function spawnGlitch() { glL = []; const n = (2 + Math.random() * 5) | 0; for (let i = 0; i < n; i++) glL.push({ y: (Math.random() * dim.H) | 0, h: (1 + Math.random() * 5) | 0, dx: (Math.random() - 0.5) * 26, a: 0.28 + Math.random() * 0.5, }); glT = (3 + Math.random() * 8) | 0; } /* ── MAIN RENDER LOOP ── */ function draw() { const W = dim.W, H = dim.H, CX = dim.CX; ctx.clearRect(0, 0, W, H); /* Global flicker (all shapes at once) */ if (--flickTimer <= 0) { flickTarget = Math.random() < 0.06 ? 0.55 + Math.random() * 0.35 : 1.0; flickTimer = (2 + Math.random() * 12) | 0; } flickAlpha += (flickTarget - flickAlpha) * 0.18; /* Slow tilted rotation */ const ax = t * 0.0042 + 0.2, ay = t * 0.0078; //const ax = 0; //ay = 0; const allEdges = []; shapes.forEach(function ({ verts, edges }) { let pts = verts; pts = rx(pts, ax); pts = ry(pts, ay); pts = rz(pts, 0.07); const pr = pts.map(proj); edges.forEach(function ([a, b, tp]) { allEdges.push({ pr, a, b, tp, d: (pr[a][3] + pr[b][3]) * 0.5 }); }); }); /* Ambient glow centred on logo zone */ const logoY = getLogoY(); const g = ctx.createRadialGradient(CX, logoY, 5, CX, logoY, 100); g.addColorStop(0, "rgba(0,65,195," + 0.14 * flickAlpha + ")"); g.addColorStop(1, "rgba(0,0,0,0)"); ctx.fillStyle = g; ctx.globalAlpha = 1; ctx.fillRect(0, 0, W, H); /* Painter's algorithm — back to front */ allEdges.sort(function (u, v) { return v.d - u.d; }); allEdges.forEach(function ({ pr, a, b, tp, d }) { const [ax2, ay2] = pr[a], [bx, by] = pr[b]; const d01 = (d + D) / (2 * D); const bright = tp === "f"; const base = tp === "f" ? 0.52 + d01 * 0.46 : tp === "b" ? 0.07 + d01 * 0.06 : 0.17 + d01 * 0.22; tube(ax2, ay2, bx, by, bright, base * flickAlpha); }); ctx.globalAlpha = 1; /* Glitch: horizontal band shifts + compression block artefacts */ if (glT > 0) { glT--; glL.forEach(function (gl) { try { const sl = ctx.getImageData(0, gl.y, W, gl.h); ctx.globalAlpha = gl.a; ctx.putImageData(sl, gl.dx, gl.y); } catch (e) {} ctx.fillStyle = "rgba(0,150,255,0.04)"; ctx.globalAlpha = 1; ctx.fillRect(0, gl.y, W, gl.h); }); if (Math.random() < 0.4) { ctx.globalAlpha = 0.08; ctx.fillStyle = "#00eeff"; ctx.fillRect(0, (Math.random() * H) | 0, W, 1); } if (Math.random() < 0.25) { ctx.globalAlpha = 0.12; ctx.fillStyle = "rgba(0,80,220,0.5)"; ctx.fillRect( (Math.random() * (W - 40)) | 0, (Math.random() * (H - 8)) | 0, (18 + Math.random() * 38) | 0, (2 + Math.random() * 7) | 0 ); } } /* Rolling scan line */ if (scanOn) { ctx.globalAlpha = 0.06; ctx.fillStyle = "rgba(80,200,255,1)"; ctx.fillRect(0, scanY, W, 2); scanY = (scanY + 3) % H; if (--scanTmr <= 0) scanOn = false; } /* Phosphor burn (persistence) */ if (Math.random() < 0.016) { ctx.globalAlpha = 0.025; ctx.fillStyle = "rgba(0,120,255,1)"; ctx.fillRect( (CX - 50 + Math.random() * 100) | 0, (logoY - 30 + Math.random() * 60) | 0, (Math.random() * 25) | 0, 1 ); } ctx.globalAlpha = 1; /* Random artefact triggers */ if (Math.random() < 0.012) spawnGlitch(); if (Math.random() < 0.006) { scanOn = true; scanY = 0; scanTmr = (20 + Math.random() * 30) | 0; } t++; requestAnimationFrame(draw); } draw(); } /* end init() */ if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })(); /* ── BRIDGE: Skynet UI → hidden Roundcube form ── */ document.addEventListener("DOMContentLoaded", function () { /* Detect Roundcube auth error and show Skynet error box */ const rcError = document.querySelector( "#messagestack .alert-warning, #messagestack .alert-danger, #messagestack .alert" ); if (rcError && rcError.textContent.trim() !== "") { const skyError = document.getElementById("sky-error"); skyError.textContent = "⚠ AUTHENTIFICATION REFUSÉE — ACCÈS INTERDIT"; skyError.classList.add("visible"); const card = document.getElementById("sky-login-card"); card.style.borderColor = "rgba(255,68,68,0.5)"; card.style.boxShadow = "0 0 30px rgba(255,68,68,0.1)"; const ms = document.getElementById("messagestack"); if (ms) ms.style.display = "none"; } /* Submit handler: copy values then submit real RC form */ document .getElementById("sky-submit-btn") .addEventListener("click", function () { const user = document.getElementById("sky-user").value.trim(); const pass = document.getElementById("sky-pass").value; if (!user || !pass) { document.getElementById("sky-error").textContent = "⚠ IDENTIFIANTS MANQUANTS"; document.getElementById("sky-error").classList.add("visible"); return; } const rcUser = document.getElementById("rcmloginuser") || document.querySelector('#login-form input[name="_user"]'); const rcPass = document.getElementById("rcmloginpwd") || document.querySelector('#login-form input[name="_pass"]'); if (rcUser) rcUser.value = user; if (rcPass) rcPass.value = pass; const btn = document.getElementById("sky-submit-btn"); btn.textContent = "AUTHENTIFICATION..."; btn.style.borderColor = "var(--cyan)"; btn.style.color = "var(--cyan)"; const rcForm = document.getElementById("login-form"); if (rcForm) { rcForm.style.display = "block"; rcForm.submit(); } }); /* Submit on Enter key */ ["sky-user", "sky-pass"].forEach(function (id) { document.getElementById(id).addEventListener("keydown", function (e) { if (e.key === "Enter") document.getElementById("sky-submit-btn").click(); }); }); /* Auto-focus username field */ document.getElementById("sky-user").focus(); }); /* ── FULLSCREEN PARTICLE BACKGROUND ── */ const canvas = document.getElementById("bg-canvas"); const ctx = canvas.getContext("2d"); function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resize(); window.addEventListener("resize", resize); const particles = []; for (let i = 0; i < 80; i++) { particles.push({ x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, vx: (Math.random() - 0.5) * 0.4, vy: -Math.random() * 0.6 - 0.1, r: Math.random() * 1.5 + 0.5, alpha: Math.random() * 0.5 + 0.1, color: Math.random() > 0.5 ? "#1a6fff" : "#00aaff", }); } const streams = []; for (let i = 0; i < 14; i++) { streams.push({ x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, speed: Math.random() * 1.5 + 0.5, length: Math.random() * 80 + 40, alpha: Math.random() * 0.15 + 0.05, }); } function drawBg() { ctx.clearRect(0, 0, canvas.width, canvas.height); /* Data streams */ streams.forEach(function (s) { const grad = ctx.createLinearGradient(s.x, s.y - s.length, s.x, s.y); grad.addColorStop(0, "rgba(26,111,255,0)"); grad.addColorStop(1, "rgba(0,170,255," + s.alpha + ")"); ctx.strokeStyle = grad; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(s.x, s.y - s.length); ctx.lineTo(s.x, s.y); ctx.stroke(); s.y += s.speed; if (s.y > canvas.height + s.length) { s.y = -s.length; s.x = Math.random() * canvas.width; } }); /* Floating particles */ particles.forEach(function (p) { ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fillStyle = p.color; ctx.globalAlpha = p.alpha; ctx.fill(); ctx.globalAlpha = 1; p.x += p.vx; p.y += p.vy; if (p.y < -5) { p.y = canvas.height + 5; p.x = Math.random() * canvas.width; } if (p.x < -5) p.x = canvas.width + 5; if (p.x > canvas.width + 5) p.x = -5; }); /* Connection lines between nearby particles */ for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 100) { ctx.globalAlpha = (1 - dist / 100) * 0.3; ctx.lineWidth = 0.5; ctx.strokeStyle = "#00cfff"; ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.stroke(); ctx.globalAlpha = 1; } } } requestAnimationFrame(drawBg); } drawBg();