- Implemented a service worker (sw.js) to handle push notifications with dynamic options and notification click events. - Created a calendar layout in test.html with a grid system for displaying events across days and times. - Developed a visually engaging WLAN QR code page (wlan.html) with animated backgrounds, particle effects, and tips for connecting to the network.
624 lines
16 KiB
HTML
624 lines
16 KiB
HTML
<!doctype html>
|
||
<html lang="de">
|
||
|
||
<head>
|
||
|
||
<meta name="robots" content="noindex">
|
||
<meta name="robots" content="nofollow">
|
||
|
||
<link rel="icon" href="https://fsae41.de/schule.ico" type="image/x-icon">
|
||
|
||
<script defer src="https://analytics.fsae41.de/script.js" data-website-id="257da02e-d678-47b6-b036-e3bdabaf1405"></script>
|
||
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>FSAE41 WLAN QR-Code</title>
|
||
<meta name="description" content="Bunt, unnötig animiert, zeigt den WLAN-QR-Code der Klasse FSAE41." />
|
||
<style>
|
||
:root {
|
||
--glowA: 0 0 20px rgba(0, 255, 255, .8);
|
||
--glowB: 0 0 28px rgba(255, 0, 255, .8);
|
||
--glowC: 0 0 36px rgba(255, 255, 0, .7);
|
||
--card: rgba(10, 10, 20, .55);
|
||
--card2: rgba(255, 255, 255, .08);
|
||
--white: rgba(255, 255, 255, .92);
|
||
}
|
||
|
||
/* ====== Hintergrund: animierter Regenbogen + Noise + Scanlines ====== */
|
||
body {
|
||
margin: 0;
|
||
min-height: 100vh;
|
||
overflow: hidden;
|
||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", "Helvetica Neue", sans-serif;
|
||
color: var(--white);
|
||
background:
|
||
radial-gradient(circle at 20% 20%, rgba(255, 0, 200, .35), transparent 45%),
|
||
radial-gradient(circle at 80% 30%, rgba(0, 255, 200, .35), transparent 45%),
|
||
radial-gradient(circle at 40% 85%, rgba(255, 255, 0, .25), transparent 50%),
|
||
linear-gradient(120deg, #ff0080, #00e5ff, #ffee00, #8a2be2, #00ff8a, #ff3d00);
|
||
background-size: 200% 200%;
|
||
animation: bgShift 9s ease-in-out infinite alternate;
|
||
}
|
||
|
||
@keyframes bgShift {
|
||
0% {
|
||
background-position: 0% 30%;
|
||
filter: hue-rotate(0deg) saturate(1.2);
|
||
}
|
||
|
||
50% {
|
||
background-position: 70% 70%;
|
||
filter: hue-rotate(120deg) saturate(1.7);
|
||
}
|
||
|
||
100% {
|
||
background-position: 100% 0%;
|
||
filter: hue-rotate(260deg) saturate(1.35);
|
||
}
|
||
}
|
||
|
||
/* Scanlines */
|
||
.scanlines {
|
||
position: fixed;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
background: repeating-linear-gradient(to bottom,
|
||
rgba(255, 255, 255, .06) 0px,
|
||
rgba(255, 255, 255, .06) 1px,
|
||
rgba(0, 0, 0, 0) 3px,
|
||
rgba(0, 0, 0, 0) 6px);
|
||
mix-blend-mode: overlay;
|
||
opacity: .22;
|
||
animation: scanFlicker 2.8s infinite;
|
||
}
|
||
|
||
@keyframes scanFlicker {
|
||
|
||
0%,
|
||
100% {
|
||
opacity: .15;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
50% {
|
||
opacity: .28;
|
||
transform: translateY(1px);
|
||
}
|
||
}
|
||
|
||
/* Noise */
|
||
.noise {
|
||
position: fixed;
|
||
inset: -50%;
|
||
pointer-events: none;
|
||
background-image:
|
||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.35'/%3E%3C/svg%3E");
|
||
mix-blend-mode: overlay;
|
||
opacity: .18;
|
||
transform: rotate(8deg);
|
||
animation: noiseMove 7s linear infinite;
|
||
}
|
||
|
||
@keyframes noiseMove {
|
||
from {
|
||
transform: translate3d(-2%, -2%, 0) rotate(8deg);
|
||
}
|
||
|
||
to {
|
||
transform: translate3d(2%, 2%, 0) rotate(8deg);
|
||
}
|
||
}
|
||
|
||
/* ====== Layout ====== */
|
||
.wrap {
|
||
position: relative;
|
||
min-height: 100vh;
|
||
display: grid;
|
||
place-items: center;
|
||
padding: 24px;
|
||
z-index: 2;
|
||
}
|
||
|
||
.card {
|
||
width: min(860px, 92vw);
|
||
display: grid;
|
||
grid-template-columns: 1.1fr .9fr;
|
||
gap: 22px;
|
||
padding: 22px;
|
||
border-radius: 26px;
|
||
background: linear-gradient(180deg, var(--card), rgba(10, 10, 20, .28));
|
||
border: 1px solid rgba(255, 255, 255, .18);
|
||
box-shadow:
|
||
0 25px 80px rgba(0, 0, 0, .45),
|
||
var(--glowA),
|
||
var(--glowB);
|
||
backdrop-filter: blur(12px);
|
||
position: relative;
|
||
overflow: hidden;
|
||
animation: cardFloat 4.2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes cardFloat {
|
||
|
||
0%,
|
||
100% {
|
||
transform: translateY(0) rotate(-.2deg);
|
||
}
|
||
|
||
50% {
|
||
transform: translateY(-10px) rotate(.2deg);
|
||
}
|
||
}
|
||
|
||
/* Glitzerband im Card-Hintergrund */
|
||
.card::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: -60%;
|
||
background: conic-gradient(from 0deg,
|
||
rgba(255, 0, 150, .0),
|
||
rgba(255, 255, 0, .25),
|
||
rgba(0, 255, 255, .25),
|
||
rgba(140, 0, 255, .25),
|
||
rgba(0, 255, 140, .25),
|
||
rgba(255, 80, 0, .25),
|
||
rgba(255, 0, 150, .0));
|
||
filter: blur(14px);
|
||
opacity: .7;
|
||
animation: spin 5.5s linear infinite;
|
||
pointer-events: none;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.card::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background: radial-gradient(circle at 30% 20%, rgba(255, 255, 255, .10), transparent 45%),
|
||
radial-gradient(circle at 70% 80%, rgba(255, 255, 255, .08), transparent 55%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
@media (max-width: 780px) {
|
||
.card {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.left {
|
||
position: relative;
|
||
padding: 10px 10px 10px 6px;
|
||
z-index: 1;
|
||
}
|
||
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 14px;
|
||
border-radius: 999px;
|
||
background: linear-gradient(90deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, .07));
|
||
border: 1px solid rgba(255, 255, 255, .18);
|
||
box-shadow: var(--glowC);
|
||
font-weight: 700;
|
||
letter-spacing: .04em;
|
||
text-transform: uppercase;
|
||
font-size: 13px;
|
||
width: fit-content;
|
||
animation: badgeWiggle 2.2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes badgeWiggle {
|
||
|
||
0%,
|
||
100% {
|
||
transform: rotate(-1deg) scale(1);
|
||
}
|
||
|
||
50% {
|
||
transform: rotate(1deg) scale(1.04);
|
||
}
|
||
}
|
||
|
||
h1 {
|
||
margin: 14px 0 10px;
|
||
font-size: clamp(34px, 4.6vw, 56px);
|
||
line-height: 1.02;
|
||
letter-spacing: -0.02em;
|
||
text-shadow: 0 10px 40px rgba(0, 0, 0, .45);
|
||
animation: titleHue 3.6s linear infinite;
|
||
}
|
||
|
||
@keyframes titleHue {
|
||
0% {
|
||
filter: hue-rotate(0deg);
|
||
}
|
||
|
||
100% {
|
||
filter: hue-rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.sub {
|
||
margin: 0 0 14px;
|
||
font-size: clamp(14px, 2.0vw, 18px);
|
||
opacity: .92;
|
||
}
|
||
|
||
.tips {
|
||
display: grid;
|
||
gap: 10px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.tip {
|
||
padding: 12px 14px;
|
||
border-radius: 18px;
|
||
background: linear-gradient(180deg, rgba(255, 255, 255, .11), rgba(255, 255, 255, .06));
|
||
border: 1px solid rgba(255, 255, 255, .15);
|
||
box-shadow: 0 12px 40px rgba(0, 0, 0, .25);
|
||
transform-origin: left center;
|
||
animation: tipPop 2.8s ease-in-out infinite;
|
||
}
|
||
|
||
.tip:nth-child(2) {
|
||
animation-delay: .35s;
|
||
}
|
||
|
||
.tip:nth-child(3) {
|
||
animation-delay: .7s;
|
||
}
|
||
|
||
@keyframes tipPop {
|
||
|
||
0%,
|
||
100% {
|
||
transform: translateY(0) rotate(-.15deg) scale(1);
|
||
}
|
||
|
||
50% {
|
||
transform: translateY(-6px) rotate(.15deg) scale(1.02);
|
||
}
|
||
}
|
||
|
||
.right {
|
||
position: relative;
|
||
display: grid;
|
||
place-items: center;
|
||
z-index: 1;
|
||
}
|
||
|
||
.qr-frame {
|
||
width: min(340px, 72vw);
|
||
aspect-ratio: 1 / 1;
|
||
border-radius: 28px;
|
||
padding: 18px;
|
||
background: linear-gradient(135deg, rgba(255, 255, 255, .18), rgba(255, 255, 255, .06));
|
||
border: 1px solid rgba(255, 255, 255, .22);
|
||
box-shadow:
|
||
0 18px 55px rgba(0, 0, 0, .35),
|
||
var(--glowA),
|
||
var(--glowB),
|
||
var(--glowC);
|
||
position: relative;
|
||
overflow: hidden;
|
||
animation: framePulse 1.8s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes framePulse {
|
||
|
||
0%,
|
||
100% {
|
||
transform: rotate(-.4deg) scale(1);
|
||
}
|
||
|
||
50% {
|
||
transform: rotate(.4deg) scale(1.03);
|
||
}
|
||
}
|
||
|
||
/* Regenbogenrand-Licht */
|
||
.qr-frame::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: -40%;
|
||
background: conic-gradient(from 180deg,
|
||
#ff004c, #ffea00, #00ffb7, #00b3ff, #a100ff, #ff004c);
|
||
opacity: .55;
|
||
filter: blur(18px);
|
||
animation: spin 3.2s linear infinite reverse;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.qr {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 18px;
|
||
background: #fff;
|
||
display: grid;
|
||
place-items: center;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* QR selbst */
|
||
.qr img {
|
||
width: 92%;
|
||
height: 92%;
|
||
object-fit: contain;
|
||
image-rendering: -webkit-optimize-contrast;
|
||
image-rendering: crisp-edges;
|
||
transform-origin: center;
|
||
animation: qrWobble 1.4s ease-in-out infinite;
|
||
filter: drop-shadow(0 12px 22px rgba(0, 0, 0, .18));
|
||
}
|
||
|
||
@keyframes qrWobble {
|
||
|
||
0%,
|
||
100% {
|
||
transform: rotate(-.7deg) scale(1);
|
||
}
|
||
|
||
50% {
|
||
transform: rotate(.7deg) scale(1.02);
|
||
}
|
||
}
|
||
|
||
/* Shine-Sweep */
|
||
.shine {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(120deg,
|
||
rgba(255, 255, 255, 0) 0%,
|
||
rgba(255, 255, 255, .55) 45%,
|
||
rgba(255, 255, 255, 0) 70%);
|
||
transform: translateX(-140%) rotate(18deg);
|
||
mix-blend-mode: screen;
|
||
animation: sweep 2.4s ease-in-out infinite;
|
||
pointer-events: none;
|
||
}
|
||
|
||
@keyframes sweep {
|
||
0% {
|
||
transform: translateX(-140%) rotate(18deg);
|
||
opacity: 0;
|
||
}
|
||
|
||
18% {
|
||
opacity: .75;
|
||
}
|
||
|
||
55% {
|
||
opacity: .55;
|
||
}
|
||
|
||
100% {
|
||
transform: translateX(140%) rotate(18deg);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
/* ====== Partikel-Canvas ====== */
|
||
canvas#party {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 1;
|
||
pointer-events: none;
|
||
mix-blend-mode: screen;
|
||
opacity: .85;
|
||
}
|
||
|
||
/* ====== Footer Bling ====== */
|
||
.footer {
|
||
margin-top: 16px;
|
||
font-size: 12px;
|
||
opacity: .85;
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
|
||
.pill {
|
||
padding: 8px 10px;
|
||
border-radius: 999px;
|
||
border: 1px solid rgba(255, 255, 255, .18);
|
||
background: rgba(255, 255, 255, .08);
|
||
animation: pillBounce 1.7s ease-in-out infinite;
|
||
}
|
||
|
||
.pill:nth-child(2) {
|
||
animation-delay: .2s;
|
||
}
|
||
|
||
.pill:nth-child(3) {
|
||
animation-delay: .4s;
|
||
}
|
||
|
||
@keyframes pillBounce {
|
||
|
||
0%,
|
||
100% {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
50% {
|
||
transform: translateY(-6px);
|
||
}
|
||
}
|
||
|
||
/* Accessibility-ish: weniger Bewegung */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
* {
|
||
animation: none !important;
|
||
transition: none !important;
|
||
}
|
||
|
||
body {
|
||
background-size: 100% 100%;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<canvas id="party"></canvas>
|
||
<div class="noise"></div>
|
||
<div class="scanlines"></div>
|
||
|
||
<main class="wrap">
|
||
<section class="card" aria-label="FSAE41 WLAN QR Code">
|
||
<div class="left">
|
||
<div class="badge">📶 FSAE41 • Schnelles-WLAN! • QR-Code</div>
|
||
<h1>Scan mich<br />für WLAN ✨</h1>
|
||
<p class="sub">
|
||
JETZT QR<br>Code scannen und mit bis zu 1Gbit/s <em>los surfen!</em>.
|
||
</p>
|
||
|
||
<div class="tips">
|
||
<div class="tip">✅ Kamera-App öffnen → QR scannen → verbinden</div>
|
||
<div class="tip">💡 Wenn’s nicht klappt: Abstand ändern / Licht an</div>
|
||
<div class="tip">🚀 Bonus: <b>SSID: FSAE41.de | Pass: FSAE41@bbs (WPA2)</b></div>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<span class="pill"><b><a href="https://www.fsae41.de">HOME</a></b></span>
|
||
<span class="pill"><b><a href="https://www.lifab.de/OT">die OT</a></b></span>
|
||
<span class="pill" id="ip">⏱️</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="right">
|
||
<div class="qr-frame" title="QR-Code: qr-code_fsae41.png">
|
||
<div class="qr">
|
||
<img src="static/qr-code_fsae41.png" alt="QR Code für das Klassen-WLAN FSAE41" />
|
||
<div class="shine"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<script>
|
||
// IP anzeigen
|
||
fetch("https://api.ipify.org?format=json")
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
document.getElementById("ip").textContent = data.ip;
|
||
})
|
||
.catch(() => {
|
||
document.getElementById("ip").textContent = "Fehler beim Laden";
|
||
});
|
||
|
||
// ====== Partikel-Party im Canvas ======
|
||
const canvas = document.getElementById("party");
|
||
const ctx = canvas.getContext("2d", { alpha: true });
|
||
|
||
function resize() {
|
||
const dpr = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
|
||
canvas.width = Math.floor(window.innerWidth * dpr);
|
||
canvas.height = Math.floor(window.innerHeight * dpr);
|
||
canvas.style.width = window.innerWidth + "px";
|
||
canvas.style.height = window.innerHeight + "px";
|
||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||
}
|
||
window.addEventListener("resize", resize);
|
||
resize();
|
||
|
||
const rand = (a, b) => a + Math.random() * (b - a);
|
||
|
||
// Bunte „Konfetti“-Partikel + Orbit-Bubbles
|
||
const particles = [];
|
||
const N = Math.min(180, Math.floor((window.innerWidth * window.innerHeight) / 12000));
|
||
|
||
function makeParticle() {
|
||
const type = Math.random() < 0.72 ? "confetti" : "bubble";
|
||
return {
|
||
type,
|
||
x: rand(0, window.innerWidth),
|
||
y: rand(0, window.innerHeight),
|
||
vx: rand(-0.6, 0.6),
|
||
vy: rand(-1.2, -0.2),
|
||
size: type === "confetti" ? rand(2, 6) : rand(6, 16),
|
||
rot: rand(0, Math.PI * 2),
|
||
vr: rand(-0.08, 0.08),
|
||
hue: rand(0, 360),
|
||
life: rand(220, 520),
|
||
t: 0,
|
||
wobble: rand(0.6, 2.2),
|
||
phase: rand(0, Math.PI * 2),
|
||
};
|
||
}
|
||
|
||
for (let i = 0; i < N; i++) particles.push(makeParticle());
|
||
|
||
let mouseX = window.innerWidth / 2, mouseY = window.innerHeight / 2;
|
||
window.addEventListener("pointermove", (e) => {
|
||
mouseX = e.clientX;
|
||
mouseY = e.clientY;
|
||
}, { passive: true });
|
||
|
||
function draw() {
|
||
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
|
||
|
||
// Leichte „Aura“ um den Mauszeiger (komplett unnötig)
|
||
const grad = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, 180);
|
||
grad.addColorStop(0, "rgba(255,255,255,0.22)");
|
||
grad.addColorStop(1, "rgba(255,255,255,0)");
|
||
ctx.fillStyle = grad;
|
||
ctx.fillRect(mouseX - 180, mouseY - 180, 360, 360);
|
||
|
||
for (const p of particles) {
|
||
p.t += 1;
|
||
p.life -= 1;
|
||
p.rot += p.vr;
|
||
|
||
// Bewegung
|
||
const wob = Math.sin((p.t * 0.03) + p.phase) * p.wobble;
|
||
p.x += p.vx + wob * 0.05;
|
||
p.y += p.vy + Math.cos((p.t * 0.02) + p.phase) * 0.08;
|
||
|
||
// Wieder oben rein
|
||
if (p.y < -40 || p.x < -60 || p.x > window.innerWidth + 60 || p.life <= 0) {
|
||
Object.assign(p, makeParticle(), { y: window.innerHeight + rand(0, 120) });
|
||
}
|
||
|
||
// Zeichnen
|
||
const a = 0.55 + 0.35 * Math.sin(p.t * 0.02 + p.phase);
|
||
if (p.type === "confetti") {
|
||
ctx.save();
|
||
ctx.translate(p.x, p.y);
|
||
ctx.rotate(p.rot);
|
||
ctx.fillStyle = `hsla(${p.hue}, 95%, 60%, ${a})`;
|
||
ctx.fillRect(-p.size, -p.size / 2, p.size * 2.2, p.size);
|
||
ctx.restore();
|
||
} else {
|
||
ctx.beginPath();
|
||
ctx.fillStyle = `hsla(${p.hue}, 95%, 65%, ${a * 0.55})`;
|
||
ctx.arc(p.x, p.y, p.size * (0.55 + 0.25 * Math.sin(p.t * 0.03)), 0, Math.PI * 2);
|
||
ctx.fill();
|
||
}
|
||
}
|
||
|
||
requestAnimationFrame(draw);
|
||
}
|
||
draw();
|
||
|
||
// Kleines Easter Egg: Space = "Turbo Disco"
|
||
let turbo = false;
|
||
window.addEventListener("keydown", (e) => {
|
||
if (e.code === "Space") {
|
||
turbo = !turbo;
|
||
document.body.style.animationDuration = turbo ? "2.8s" : "9s";
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
|
||
</html> |