Thème Keycloak : faire apparaître la constellation (attendre le DOM)

Keycloak injecte le script dans <head> → il tournait avant que <body>
existe (insertBefore plantait) → pas de canvas. Corrigé : init différé à
DOMContentLoaded + repli taille sur window.innerWidth/Height. Dégradé aurore
posé aussi sur body (repli si le canvas échoue). Script copié côté account.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-07-04 09:44:42 -04:00
parent e12c40e00c
commit 0419114755
3 changed files with 167 additions and 155 deletions

View file

@ -1,93 +1,97 @@
/* Alliance Boréale champ d'étoiles + constellation, adapté au login Keycloak. /* Alliance Boréale champ d'étoiles + constellation, adapté au login Keycloak.
Vanilla JS, aucune dépendance. Crée son propre ciel en fond (le template Vanilla JS, aucune dépendance. Crée son propre ciel en fond (le template Keycloak
Keycloak n'a pas de <canvas>). Respecte prefers-reduced-motion. n'a pas de <canvas>). Attend le DOM (le script est injecté dans <head>).
Métaphore : chaque artisan est une étoile ; reliés, ils forment la constellation. */ Respecte prefers-reduced-motion. Métaphore : chaque artisan est une étoile ;
reliés, ils forment la constellation. */
(function () { (function () {
"use strict"; "use strict";
if (document.querySelector(".ab-sky")) return;
var sky = document.createElement("div"); function init() {
sky.className = "ab-sky"; if (!document.body || document.querySelector(".ab-sky")) return;
sky.setAttribute("aria-hidden", "true");
var canvas = document.createElement("canvas");
canvas.id = "ab-constellation";
var aurora = document.createElement("div");
aurora.className = "ab-aurora";
sky.appendChild(canvas);
sky.appendChild(aurora);
document.body.insertBefore(sky, document.body.firstChild);
var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; var sky = document.createElement("div");
var ctx = canvas.getContext("2d"); sky.className = "ab-sky";
var stars = []; sky.setAttribute("aria-hidden", "true");
var w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2); var canvas = document.createElement("canvas");
var LINK_DIST = 130; canvas.id = "ab-constellation";
var aurora = document.createElement("div");
aurora.className = "ab-aurora";
sky.appendChild(canvas);
sky.appendChild(aurora);
document.body.insertBefore(sky, document.body.firstChild);
function resize() { var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
w = canvas.clientWidth; var ctx = canvas.getContext("2d");
h = canvas.clientHeight; var stars = [];
canvas.width = w * dpr; var w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.height = h * dpr; var LINK_DIST = 130;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
seed();
}
function seed() { function resize() {
var count = Math.round((w * h) / 14000); w = canvas.clientWidth || window.innerWidth;
count = Math.max(40, Math.min(160, count)); h = canvas.clientHeight || window.innerHeight;
stars = []; canvas.width = w * dpr;
for (var i = 0; i < count; i++) { canvas.height = h * dpr;
stars.push({ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
x: Math.random() * w, seed();
y: Math.random() * h,
r: Math.random() * 1.3 + 0.4,
vx: (Math.random() - 0.5) * 0.12,
vy: (Math.random() - 0.5) * 0.12,
tw: Math.random() * Math.PI * 2
});
} }
}
function draw() { function seed() {
ctx.clearRect(0, 0, w, h); var count = Math.round((w * h) / 14000);
for (var i = 0; i < stars.length; i++) { count = Math.max(40, Math.min(160, count));
for (var j = i + 1; j < stars.length; j++) { stars = [];
var dx = stars[i].x - stars[j].x; for (var i = 0; i < count; i++) {
var dy = stars[i].y - stars[j].y; stars.push({
var d = Math.sqrt(dx * dx + dy * dy); x: Math.random() * w, y: Math.random() * h,
if (d < LINK_DIST) { r: Math.random() * 1.3 + 0.4,
var a = (1 - d / LINK_DIST) * 0.22; vx: (Math.random() - 0.5) * 0.12, vy: (Math.random() - 0.5) * 0.12,
ctx.strokeStyle = "rgba(120, 200, 255," + a + ")"; tw: Math.random() * Math.PI * 2
ctx.lineWidth = 1; });
ctx.beginPath(); }
ctx.moveTo(stars[i].x, stars[i].y); }
ctx.lineTo(stars[j].x, stars[j].y);
ctx.stroke(); function draw() {
ctx.clearRect(0, 0, w, h);
for (var i = 0; i < stars.length; i++) {
for (var j = i + 1; j < stars.length; j++) {
var dx = stars[i].x - stars[j].x, dy = stars[i].y - stars[j].y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < LINK_DIST) {
ctx.strokeStyle = "rgba(120, 200, 255," + (1 - d / LINK_DIST) * 0.22 + ")";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(stars[i].x, stars[i].y);
ctx.lineTo(stars[j].x, stars[j].y);
ctx.stroke();
}
} }
} }
} for (var k = 0; k < stars.length; k++) {
for (var k = 0; k < stars.length; k++) { var s = stars[k];
var s = stars[k]; s.tw += 0.02;
s.tw += 0.02; ctx.beginPath();
var glow = 0.6 + Math.sin(s.tw) * 0.4; ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.beginPath(); ctx.fillStyle = "rgba(255,255,255," + (0.6 + Math.sin(s.tw) * 0.4) + ")";
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.shadowColor = "rgba(160,220,255,0.9)";
ctx.fillStyle = "rgba(255,255,255," + glow + ")"; ctx.shadowBlur = 6;
ctx.shadowColor = "rgba(160,220,255,0.9)"; ctx.fill();
ctx.shadowBlur = 6; ctx.shadowBlur = 0;
ctx.fill(); if (!reduce) {
ctx.shadowBlur = 0; s.x += s.vx; s.y += s.vy;
if (!reduce) { if (s.x < 0 || s.x > w) s.vx *= -1;
s.x += s.vx; if (s.y < 0 || s.y > h) s.vy *= -1;
s.y += s.vy; }
if (s.x < 0 || s.x > w) s.vx *= -1;
if (s.y < 0 || s.y > h) s.vy *= -1;
} }
if (!reduce) requestAnimationFrame(draw);
} }
if (!reduce) requestAnimationFrame(draw);
window.addEventListener("resize", resize);
resize();
draw();
} }
window.addEventListener("resize", resize); if (document.readyState === "loading") {
resize(); document.addEventListener("DOMContentLoaded", init);
draw(); } else {
init();
}
})(); })();

View file

@ -22,7 +22,11 @@ html, body {
height: 100%; height: 100%;
font-family: var(--font); font-family: var(--font);
color: var(--ink); color: var(--ink);
background: #05060f !important; /* Dégradé aurore en repli (visible même si le canvas ne se crée pas). */
background:
radial-gradient(1200px 700px at 80% -10%, #131a44 0%, transparent 60%),
radial-gradient(900px 600px at 10% 0%, #0e1640 0%, transparent 55%),
linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 70%) fixed !important;
} }
.login-pf-page, .login-pf-page,
body .login-pf-page { body .login-pf-page {

View file

@ -1,93 +1,97 @@
/* Alliance Boréale champ d'étoiles + constellation, adapté au login Keycloak. /* Alliance Boréale champ d'étoiles + constellation, adapté au login Keycloak.
Vanilla JS, aucune dépendance. Crée son propre ciel en fond (le template Vanilla JS, aucune dépendance. Crée son propre ciel en fond (le template Keycloak
Keycloak n'a pas de <canvas>). Respecte prefers-reduced-motion. n'a pas de <canvas>). Attend le DOM (le script est injecté dans <head>).
Métaphore : chaque artisan est une étoile ; reliés, ils forment la constellation. */ Respecte prefers-reduced-motion. Métaphore : chaque artisan est une étoile ;
reliés, ils forment la constellation. */
(function () { (function () {
"use strict"; "use strict";
if (document.querySelector(".ab-sky")) return;
var sky = document.createElement("div"); function init() {
sky.className = "ab-sky"; if (!document.body || document.querySelector(".ab-sky")) return;
sky.setAttribute("aria-hidden", "true");
var canvas = document.createElement("canvas");
canvas.id = "ab-constellation";
var aurora = document.createElement("div");
aurora.className = "ab-aurora";
sky.appendChild(canvas);
sky.appendChild(aurora);
document.body.insertBefore(sky, document.body.firstChild);
var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; var sky = document.createElement("div");
var ctx = canvas.getContext("2d"); sky.className = "ab-sky";
var stars = []; sky.setAttribute("aria-hidden", "true");
var w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2); var canvas = document.createElement("canvas");
var LINK_DIST = 130; canvas.id = "ab-constellation";
var aurora = document.createElement("div");
aurora.className = "ab-aurora";
sky.appendChild(canvas);
sky.appendChild(aurora);
document.body.insertBefore(sky, document.body.firstChild);
function resize() { var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
w = canvas.clientWidth; var ctx = canvas.getContext("2d");
h = canvas.clientHeight; var stars = [];
canvas.width = w * dpr; var w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.height = h * dpr; var LINK_DIST = 130;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
seed();
}
function seed() { function resize() {
var count = Math.round((w * h) / 14000); w = canvas.clientWidth || window.innerWidth;
count = Math.max(40, Math.min(160, count)); h = canvas.clientHeight || window.innerHeight;
stars = []; canvas.width = w * dpr;
for (var i = 0; i < count; i++) { canvas.height = h * dpr;
stars.push({ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
x: Math.random() * w, seed();
y: Math.random() * h,
r: Math.random() * 1.3 + 0.4,
vx: (Math.random() - 0.5) * 0.12,
vy: (Math.random() - 0.5) * 0.12,
tw: Math.random() * Math.PI * 2
});
} }
}
function draw() { function seed() {
ctx.clearRect(0, 0, w, h); var count = Math.round((w * h) / 14000);
for (var i = 0; i < stars.length; i++) { count = Math.max(40, Math.min(160, count));
for (var j = i + 1; j < stars.length; j++) { stars = [];
var dx = stars[i].x - stars[j].x; for (var i = 0; i < count; i++) {
var dy = stars[i].y - stars[j].y; stars.push({
var d = Math.sqrt(dx * dx + dy * dy); x: Math.random() * w, y: Math.random() * h,
if (d < LINK_DIST) { r: Math.random() * 1.3 + 0.4,
var a = (1 - d / LINK_DIST) * 0.22; vx: (Math.random() - 0.5) * 0.12, vy: (Math.random() - 0.5) * 0.12,
ctx.strokeStyle = "rgba(120, 200, 255," + a + ")"; tw: Math.random() * Math.PI * 2
ctx.lineWidth = 1; });
ctx.beginPath(); }
ctx.moveTo(stars[i].x, stars[i].y); }
ctx.lineTo(stars[j].x, stars[j].y);
ctx.stroke(); function draw() {
ctx.clearRect(0, 0, w, h);
for (var i = 0; i < stars.length; i++) {
for (var j = i + 1; j < stars.length; j++) {
var dx = stars[i].x - stars[j].x, dy = stars[i].y - stars[j].y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < LINK_DIST) {
ctx.strokeStyle = "rgba(120, 200, 255," + (1 - d / LINK_DIST) * 0.22 + ")";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(stars[i].x, stars[i].y);
ctx.lineTo(stars[j].x, stars[j].y);
ctx.stroke();
}
} }
} }
} for (var k = 0; k < stars.length; k++) {
for (var k = 0; k < stars.length; k++) { var s = stars[k];
var s = stars[k]; s.tw += 0.02;
s.tw += 0.02; ctx.beginPath();
var glow = 0.6 + Math.sin(s.tw) * 0.4; ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.beginPath(); ctx.fillStyle = "rgba(255,255,255," + (0.6 + Math.sin(s.tw) * 0.4) + ")";
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.shadowColor = "rgba(160,220,255,0.9)";
ctx.fillStyle = "rgba(255,255,255," + glow + ")"; ctx.shadowBlur = 6;
ctx.shadowColor = "rgba(160,220,255,0.9)"; ctx.fill();
ctx.shadowBlur = 6; ctx.shadowBlur = 0;
ctx.fill(); if (!reduce) {
ctx.shadowBlur = 0; s.x += s.vx; s.y += s.vy;
if (!reduce) { if (s.x < 0 || s.x > w) s.vx *= -1;
s.x += s.vx; if (s.y < 0 || s.y > h) s.vy *= -1;
s.y += s.vy; }
if (s.x < 0 || s.x > w) s.vx *= -1;
if (s.y < 0 || s.y > h) s.vy *= -1;
} }
if (!reduce) requestAnimationFrame(draw);
} }
if (!reduce) requestAnimationFrame(draw);
window.addEventListener("resize", resize);
resize();
draw();
} }
window.addEventListener("resize", resize); if (document.readyState === "loading") {
resize(); document.addEventListener("DOMContentLoaded", init);
draw(); } else {
init();
}
})(); })();