-
This commit is contained in:
622
src/components/Hero.astro
Normal file
622
src/components/Hero.astro
Normal file
@@ -0,0 +1,622 @@
|
||||
---
|
||||
---
|
||||
<section class="hero" id="top">
|
||||
<canvas id="hero-canvas"></canvas>
|
||||
<canvas id="hero-canvas-gl"></canvas>
|
||||
|
||||
<div class="hero-content">
|
||||
<div class="ai-badge">
|
||||
<div class="ai-dot"></div>
|
||||
KI-getriebene Technologieberatung
|
||||
</div>
|
||||
<div class="eyebrow">Regensburg · Bayern · International</div>
|
||||
<h1 id="fa-headline">Komplexe Systeme.<br /><em class="underline">Klare Lösungen.</em><br />Echter Fortschritt.</h1>
|
||||
<p class="hero-sub">Von intelligenten KI-Agenten bis zur regulierten Medizinsoftware – wir verbinden modernste Algorithmen mit der Präzision, die kritische Systeme verlangen.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="#kontakt" class="btn-primary">Beratungstermin vereinbaren</a>
|
||||
<a href="#kontorion" class="btn-ghost">Kontorion ansehen ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-visual" id="fa-hero-visual">
|
||||
<div class="cap-card" id="fa-cap-card">
|
||||
<div class="cap-head">
|
||||
<span>Kern-Disziplinen</span>
|
||||
<span class="cap-head-mark">Live</span>
|
||||
</div>
|
||||
<div class="cap-row">
|
||||
<div class="cap-tag">AI</div>
|
||||
<div class="cap-body">
|
||||
<div class="cap-title">Generative KI & Agents</div>
|
||||
<div class="cap-keys">LLM · RAG · Computer Vision · MLOps</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap-row">
|
||||
<div class="cap-tag">MD</div>
|
||||
<div class="cap-body">
|
||||
<div class="cap-title">Regulierte Software</div>
|
||||
<div class="cap-keys">MDR · IEC 62304 · ISO 13485 · FHIR / DICOM</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap-row">
|
||||
<div class="cap-tag">SW</div>
|
||||
<div class="cap-body">
|
||||
<div class="cap-title">Engineering & Architektur</div>
|
||||
<div class="cap-keys">Apps · Backend · APIs · Tech Consulting</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap-foot">
|
||||
<span class="cap-foot-dot">Regensburg · DE</span>
|
||||
<span>Seit 2020</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fa-stat-stack" style="display:none; flex-direction:column; gap:1.25rem; align-items:flex-end; width:100%;">
|
||||
<div class="stat-card">
|
||||
<div class="num">LLM</div>
|
||||
<div class="label">Agents · RAG · Fine-Tuning<br />Generative KI für Ihr Unternehmen</div>
|
||||
</div>
|
||||
<div class="stat-card light">
|
||||
<div class="num">MDR</div>
|
||||
<div class="label">IEC 62304 · HL7 · FHIR · DICOM<br />Regulatorische Expertise</div>
|
||||
</div>
|
||||
<div class="hero-badge">Seit Regensburg</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
min-height: 88vh;
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 0.85fr;
|
||||
align-items: center;
|
||||
padding: 5rem var(--content-pad) 4.5rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
.hero.no-visual {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 6rem var(--content-pad) 5.5rem;
|
||||
}
|
||||
.hero.no-visual .hero-content { padding-right: 0; max-width: 880px; }
|
||||
.hero.no-visual h1 { font-size: clamp(2.8rem, 5.4vw, 5rem); }
|
||||
.hero.no-visual .hero-sub { max-width: 540px; font-size: 17px; }
|
||||
|
||||
#hero-canvas, #hero-canvas-gl {
|
||||
position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0;
|
||||
}
|
||||
#hero-canvas { opacity: 1; }
|
||||
#hero-canvas-gl { opacity: 0; transition: opacity 0.35s ease; pointer-events: none; }
|
||||
#hero-canvas-gl.active { opacity: 1; }
|
||||
#hero-canvas-gl.active {
|
||||
-webkit-mask-image: linear-gradient(to right, transparent 0%, transparent 18%, rgba(0,0,0,0.18) 30%, rgba(0,0,0,0.55) 45%, rgba(0,0,0,0.9) 60%, #000 75%);
|
||||
mask-image: linear-gradient(to right, transparent 0%, transparent 18%, rgba(0,0,0,0.18) 30%, rgba(0,0,0,0.55) 45%, rgba(0,0,0,0.9) 60%, #000 75%);
|
||||
}
|
||||
.hero.gl-active::before {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background: linear-gradient(to right, rgba(255,255,255,0.88) 0%, rgba(255,255,255,0.78) 22%, rgba(255,255,255,0.45) 42%, rgba(255,255,255,0.1) 60%, rgba(255,255,255,0) 75%);
|
||||
z-index: 1; pointer-events: none; transition: opacity 0.35s ease;
|
||||
}
|
||||
#hero-canvas.hidden { opacity: 0; }
|
||||
|
||||
.hero-content { position: relative; z-index: 2; padding-right: 3.5rem; max-width: 720px; }
|
||||
|
||||
.eyebrow {
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 3px; text-transform: uppercase;
|
||||
color: var(--fa-blue); margin-bottom: 1.75rem;
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
}
|
||||
.eyebrow::before { content: ''; display: block; width: 36px; height: 1px; background: var(--fa-blue); }
|
||||
|
||||
.ai-badge {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
background: var(--fa-navy); color: #fff;
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 1.5px; text-transform: uppercase;
|
||||
padding: 0.45rem 1rem 0.45rem 0.7rem; margin-bottom: 1.5rem;
|
||||
}
|
||||
.ai-dot {
|
||||
width: 7px; height: 7px; border-radius: 50%; background: var(--fa-blue);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: clamp(2.6rem, 4.4vw, 4.2rem);
|
||||
font-weight: 700; line-height: 0.98;
|
||||
color: var(--fa-navy); letter-spacing: -2.5px; margin-bottom: 2.25rem;
|
||||
text-wrap: balance;
|
||||
}
|
||||
h1 em {
|
||||
font-style: normal; color: var(--fa-blue);
|
||||
position: relative; display: inline-block;
|
||||
color: color-mix(in oklch, var(--fa-blue), oklch(0.55 0.22 255) calc(var(--hero-progress, 0) * 100%));
|
||||
transition: color 0.05s linear;
|
||||
}
|
||||
h1 em.underline::after {
|
||||
content: ''; position: absolute; left: 0; right: 0; bottom: 0.06em;
|
||||
height: 0.08em; background: var(--fa-blue); opacity: 0.35;
|
||||
transform-origin: left center;
|
||||
animation: faUnderlineDraw 1.2s cubic-bezier(0.22, 0.61, 0.36, 1) 0.6s both;
|
||||
}
|
||||
h1 em.underline::before {
|
||||
content: ''; position: absolute; left: 0; bottom: 0.04em; height: 0.14em;
|
||||
width: calc(var(--hero-progress, 0) * 100%);
|
||||
background: linear-gradient(90deg, oklch(0.62 0.18 250) 0%, oklch(0.70 0.22 260) 100%);
|
||||
opacity: 0.85; border-radius: 1px;
|
||||
box-shadow: 0 0 12px oklch(0.65 0.22 255 / 0.4);
|
||||
transition: width 0.05s linear; pointer-events: none;
|
||||
}
|
||||
h1.flat em { color: var(--fa-navy); }
|
||||
h1.flat em::after, h1.flat em::before { display: none; }
|
||||
|
||||
.hero-sub {
|
||||
font-size: 16px; font-weight: 300; line-height: 1.8;
|
||||
color: var(--fa-mid); max-width: 460px; margin-bottom: 3rem;
|
||||
}
|
||||
.hero-actions { display: flex; align-items: center; gap: 1.75rem; }
|
||||
|
||||
.btn-primary {
|
||||
font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 500;
|
||||
color: #fff; background: var(--fa-navy); border: none;
|
||||
padding: 0.9rem 2.25rem; cursor: pointer; transition: background 0.2s;
|
||||
text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-primary:hover { background: var(--fa-blue); }
|
||||
.btn-ghost {
|
||||
font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 400;
|
||||
color: var(--fa-navy); background: none; border: none; cursor: pointer;
|
||||
opacity: 0.6; transition: opacity 0.2s;
|
||||
text-decoration: none; display: inline-flex; align-items: center;
|
||||
}
|
||||
.btn-ghost:hover { opacity: 1; }
|
||||
|
||||
/* Hero visual */
|
||||
.hero-visual {
|
||||
position: relative; z-index: 2;
|
||||
display: flex; flex-direction: column; align-items: stretch;
|
||||
max-width: 380px; margin-left: auto;
|
||||
}
|
||||
.cap-card {
|
||||
background: var(--fa-navy); color: #fff;
|
||||
border: 0.5px solid rgba(107,143,212,0.25);
|
||||
}
|
||||
.cap-head {
|
||||
padding: 1.1rem 1.5rem;
|
||||
border-bottom: 0.5px solid rgba(107,143,212,0.18);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
font-family: 'Syne', sans-serif; font-size: 11px; font-weight: 600;
|
||||
letter-spacing: 2.5px; text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.68);
|
||||
}
|
||||
.cap-head-mark {
|
||||
display: flex; align-items: center; gap: 6px; color: var(--fa-blue-light);
|
||||
}
|
||||
.cap-head-mark::before {
|
||||
content: ''; width: 5px; height: 5px; border-radius: 50%;
|
||||
background: var(--fa-blue-light); animation: pulse 2.4s ease-in-out infinite;
|
||||
}
|
||||
.cap-row {
|
||||
display: grid; grid-template-columns: 44px 1fr;
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-bottom: 0.5px solid rgba(107,143,212,0.12);
|
||||
gap: 1rem; align-items: center; transition: background 0.25s;
|
||||
}
|
||||
.cap-row:last-of-type { border-bottom: none; }
|
||||
.cap-row:hover { background: rgba(107,143,212,0.06); }
|
||||
.cap-tag {
|
||||
font-family: 'Syne', sans-serif; font-size: 12px; font-weight: 700;
|
||||
letter-spacing: 1.5px; color: var(--fa-blue-light);
|
||||
border: 0.5px solid rgba(168,192,232,0.35); padding: 6px 0; text-align: center;
|
||||
}
|
||||
.cap-title {
|
||||
font-family: 'Syne', sans-serif; font-size: 15px; font-weight: 600;
|
||||
color: #fff; letter-spacing: -0.2px; margin-bottom: 4px;
|
||||
}
|
||||
.cap-keys {
|
||||
font-size: 12px; font-weight: 300; line-height: 1.5;
|
||||
color: rgba(255,255,255,0.68); letter-spacing: 0.2px;
|
||||
}
|
||||
.cap-foot {
|
||||
padding: 0.85rem 1.5rem; background: rgba(0,0,0,0.18);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
font-size: 11px; letter-spacing: 1.8px; text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.65);
|
||||
}
|
||||
.cap-foot-dot { display: inline-flex; align-items: center; gap: 8px; color: rgba(255,255,255,0.7); }
|
||||
.cap-foot-dot::before {
|
||||
content: ''; width: 6px; height: 6px; border-radius: 50%;
|
||||
background: #6FD49A; box-shadow: 0 0 0 3px rgba(111,212,154,0.18);
|
||||
}
|
||||
|
||||
/* Stat cards */
|
||||
.stat-card {
|
||||
background: var(--fa-navy); color: #fff;
|
||||
padding: 1.75rem 2.25rem; width: 290px;
|
||||
}
|
||||
.stat-card .num {
|
||||
font-family: 'Syne', sans-serif; font-size: 2rem; font-weight: 800;
|
||||
letter-spacing: -0.5px; color: var(--fa-blue-light); line-height: 1; margin-bottom: 0.4rem;
|
||||
}
|
||||
.stat-card .label { font-size: 13px; font-weight: 300; opacity: 0.65; line-height: 1.5; }
|
||||
.stat-card.light {
|
||||
background: rgba(238,243,251,0.9); color: var(--fa-navy);
|
||||
border: 0.5px solid var(--fa-border); margin-right: 2rem;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.stat-card.light .num { color: var(--fa-navy); }
|
||||
.stat-card.light .label { opacity: 0.55; color: var(--fa-navy); }
|
||||
.hero-badge {
|
||||
background: var(--fa-blue); color: #fff;
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 2px; text-transform: uppercase;
|
||||
padding: 0.55rem 1.1rem; align-self: flex-start; margin-left: 1rem;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 900px) {
|
||||
.hero { grid-template-columns: 1fr; min-height: auto; padding: 4rem var(--content-pad); }
|
||||
.hero-content { padding-right: 0; }
|
||||
.hero-visual { display: none; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
h1 { font-size: clamp(1.85rem, 8.5vw, 2.6rem); letter-spacing: -1.2px; }
|
||||
.eyebrow { font-size: 10px; letter-spacing: 2px; margin-bottom: 1.25rem; }
|
||||
.hero-sub { font-size: 15px; margin-bottom: 2rem; }
|
||||
.hero-actions { flex-wrap: wrap; gap: 1rem; }
|
||||
.btn-primary { padding: 0.85rem 1.6rem; }
|
||||
.hero { padding: 3rem var(--content-pad) 3.5rem; }
|
||||
#hero-canvas, #hero-canvas-gl { opacity: 0.5; }
|
||||
}
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
/* No hover states on touch */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
/* ============================================================
|
||||
HERO SCROLL PROGRESS
|
||||
============================================================ */
|
||||
(function() {
|
||||
const hero = document.querySelector('.hero');
|
||||
if (!hero) return;
|
||||
const root = document.documentElement;
|
||||
let ticking = false;
|
||||
function update() {
|
||||
const rect = hero.getBoundingClientRect();
|
||||
const h = rect.height;
|
||||
let p = -rect.top / h;
|
||||
p = Math.max(0, Math.min(1, (p - 0.12) / 0.7));
|
||||
root.style.setProperty('--hero-progress', p.toFixed(3));
|
||||
ticking = false;
|
||||
}
|
||||
function onScroll() { if (!ticking) { requestAnimationFrame(update); ticking = true; } }
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
window.addEventListener('resize', onScroll);
|
||||
update();
|
||||
})();
|
||||
|
||||
/* ============================================================
|
||||
HERO CANVAS — 2D modes
|
||||
============================================================ */
|
||||
(function() {
|
||||
const canvas = document.getElementById('hero-canvas');
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const BLUE = '107,143,212', NAVY = '26,31,92';
|
||||
let W = 0, H = 0, nodes = [], hubs = [], flowParticles = [];
|
||||
let mode = 'constellation', rafId = null;
|
||||
const mouse = { x: -9999, y: -9999 };
|
||||
let running = true;
|
||||
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||
|
||||
function resize() {
|
||||
const rect = canvas.parentElement.getBoundingClientRect();
|
||||
W = rect.width; H = rect.height;
|
||||
canvas.width = W * dpr; canvas.height = H * dpr;
|
||||
canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
function rand(a, b) { return a + Math.random() * (b - a); }
|
||||
|
||||
/* CONSTELLATION */
|
||||
function initConstellation() {
|
||||
hubs = [
|
||||
{ ax: 0.18, ay: 0.30, label: 'LLM · RAG' },
|
||||
{ ax: 0.62, ay: 0.18, label: 'COMPUTER VISION' },
|
||||
{ ax: 0.78, ay: 0.62, label: 'MDR · 62304' },
|
||||
{ ax: 0.32, ay: 0.82, label: 'FHIR · DICOM' },
|
||||
].map((h, i) => ({ ...h, x: h.ax * W, y: h.ay * H, vx: 0, vy: 0, r: 4.5, phase: i * 1.7 }));
|
||||
nodes = [];
|
||||
const count = Math.max(28, Math.floor((W * H) / 22000));
|
||||
for (let i = 0; i < count; i++) {
|
||||
nodes.push({ x: rand(0, W), y: rand(0, H), vx: rand(-0.18, 0.18), vy: rand(-0.15, 0.15), r: rand(1.2, 2.4) });
|
||||
}
|
||||
}
|
||||
function updateConstellation(t) {
|
||||
for (const h of hubs) {
|
||||
const targetX = h.ax * W + Math.sin(t * 0.0004 + h.phase) * 18;
|
||||
const targetY = h.ay * H + Math.cos(t * 0.0005 + h.phase * 1.3) * 14;
|
||||
h.vx += (targetX - h.x) * 0.008; h.vy += (targetY - h.y) * 0.008;
|
||||
h.vx *= 0.92; h.vy *= 0.92; h.x += h.vx; h.y += h.vy;
|
||||
}
|
||||
for (const n of nodes) {
|
||||
n.x += n.vx; n.y += n.vy;
|
||||
if (n.x < -10) n.x = W + 10; if (n.x > W + 10) n.x = -10;
|
||||
if (n.y < -10) n.y = H + 10; if (n.y > H + 10) n.y = -10;
|
||||
}
|
||||
}
|
||||
function nearestK(node, others, k) {
|
||||
const arr = [];
|
||||
for (const o of others) {
|
||||
if (o === node) continue;
|
||||
const dx = node.x - o.x, dy = node.y - o.y;
|
||||
arr.push({ o, d: Math.sqrt(dx*dx + dy*dy) });
|
||||
}
|
||||
arr.sort((a, b) => a.d - b.d);
|
||||
return arr.slice(0, k);
|
||||
}
|
||||
function drawConstellation() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
ctx.lineWidth = 0.9;
|
||||
for (const n of nodes) {
|
||||
const nn = nearestK(n, nodes, 2);
|
||||
for (const { o, d } of nn) {
|
||||
if (d > 160) continue;
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - d / 160) * 0.25})`;
|
||||
ctx.beginPath(); ctx.moveTo(n.x, n.y); ctx.lineTo(o.x, o.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
ctx.lineWidth = 1.3;
|
||||
for (const h of hubs) {
|
||||
const nn = nearestK(h, nodes, 6);
|
||||
for (const { o, d } of nn) {
|
||||
if (d > 260) continue;
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - d / 260) * 0.55})`;
|
||||
ctx.beginPath(); ctx.moveTo(h.x, h.y); ctx.lineTo(o.x, o.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
if (mouse.x > -9000) {
|
||||
const closest = nearestK({ x: mouse.x, y: mouse.y }, nodes.concat(hubs), 4);
|
||||
ctx.lineWidth = 1.4;
|
||||
for (const { o, d } of closest) {
|
||||
if (d > 220) continue;
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - d / 220) * 0.7})`;
|
||||
ctx.beginPath(); ctx.moveTo(mouse.x, mouse.y); ctx.lineTo(o.x, o.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
for (const n of nodes) {
|
||||
const g2 = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r * 3);
|
||||
g2.addColorStop(0, `rgba(${BLUE}, 0.18)`); g2.addColorStop(1, `rgba(${BLUE}, 0)`);
|
||||
ctx.beginPath(); ctx.arc(n.x, n.y, n.r * 3, 0, Math.PI * 2); ctx.fillStyle = g2; ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2); ctx.fillStyle = `rgba(${BLUE}, 0.45)`; ctx.fill();
|
||||
}
|
||||
const t = performance.now();
|
||||
for (const h of hubs) {
|
||||
const pulseT = ((t * 0.0007 + h.phase) % 1);
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, 8 + pulseT * 52, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - pulseT) * 0.65})`; ctx.lineWidth = 1.2; ctx.stroke();
|
||||
const pulseT2 = ((t * 0.0011 + h.phase + 0.5) % 1);
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, 5 + pulseT2 * 28, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - pulseT2) * 0.45})`; ctx.lineWidth = 0.8; ctx.stroke();
|
||||
const g = ctx.createRadialGradient(h.x, h.y, 0, h.x, h.y, 42);
|
||||
g.addColorStop(0, `rgba(${BLUE}, 0.45)`); g.addColorStop(0.5, `rgba(${BLUE}, 0.12)`); g.addColorStop(1, `rgba(${BLUE}, 0)`);
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, 42, 0, Math.PI * 2); ctx.fillStyle = g; ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, h.r, 0, Math.PI * 2); ctx.fillStyle = `rgba(${BLUE}, 1)`; ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, h.r + 3.5, 0, Math.PI * 2); ctx.strokeStyle = `rgba(${BLUE}, 0.75)`; ctx.lineWidth = 1.2; ctx.stroke();
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, h.r + 8, 0, Math.PI * 2); ctx.strokeStyle = `rgba(${BLUE}, 0.3)`; ctx.lineWidth = 0.8; ctx.stroke();
|
||||
ctx.font = '700 11px "Syne", sans-serif';
|
||||
const tw = ctx.measureText(h.label).width;
|
||||
const lx = h.x + h.r + 12, ly = h.y - 5;
|
||||
ctx.fillStyle = `rgba(${BLUE}, 0.12)`; ctx.fillRect(lx - 4, ly - 2, tw + 8, 16);
|
||||
ctx.strokeStyle = `rgba(${BLUE}, 0.4)`; ctx.lineWidth = 0.6; ctx.strokeRect(lx - 4, ly - 2, tw + 8, 16);
|
||||
ctx.fillStyle = `rgba(${NAVY}, 0.95)`; ctx.fillText(h.label, lx, ly + 9);
|
||||
}
|
||||
}
|
||||
|
||||
/* NETWORK */
|
||||
function initNetwork() {
|
||||
nodes = []; hubs = [];
|
||||
const count = Math.max(50, Math.floor((W * H) / 14000));
|
||||
for (let i = 0; i < count; i++) {
|
||||
nodes.push({ x: rand(0, W), y: rand(0, H), vx: rand(-0.25, 0.25), vy: rand(-0.2, 0.2), r: rand(2, 5), isKey: Math.random() < 0.12, label: ['LLM','RAG','CV','AI','ML','API','MDR'][Math.floor(Math.random()*7)] });
|
||||
}
|
||||
}
|
||||
function updateNetwork() {
|
||||
for (const n of nodes) { n.x += n.vx; n.y += n.vy; if (n.x < 0 || n.x > W) n.vx *= -1; if (n.y < 0 || n.y > H) n.vy *= -1; }
|
||||
}
|
||||
function drawNetwork() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
for (let j = i + 1; j < nodes.length; j++) {
|
||||
const dx = nodes[i].x - nodes[j].x, dy = nodes[i].y - nodes[j].y;
|
||||
const d = Math.sqrt(dx*dx + dy*dy);
|
||||
if (d < 130) { ctx.strokeStyle = `rgba(${BLUE}, ${(1-d/130)*0.18})`; ctx.lineWidth = 0.6; ctx.beginPath(); ctx.moveTo(nodes[i].x,nodes[i].y); ctx.lineTo(nodes[j].x,nodes[j].y); ctx.stroke(); }
|
||||
}
|
||||
const mdx = nodes[i].x - mouse.x, mdy = nodes[i].y - mouse.y, md = Math.sqrt(mdx*mdx+mdy*mdy);
|
||||
if (md < 160) { ctx.strokeStyle = `rgba(${BLUE}, ${(1-md/160)*0.35})`; ctx.lineWidth = 0.8; ctx.beginPath(); ctx.moveTo(nodes[i].x,nodes[i].y); ctx.lineTo(mouse.x,mouse.y); ctx.stroke(); }
|
||||
}
|
||||
for (const n of nodes) {
|
||||
const g = ctx.createRadialGradient(n.x,n.y,0,n.x,n.y,n.r*4); g.addColorStop(0,`rgba(${BLUE},0.12)`); g.addColorStop(1,`rgba(${BLUE},0)`);
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,n.r*4,0,Math.PI*2); ctx.fillStyle = g; ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,n.r,0,Math.PI*2); ctx.fillStyle = n.isKey ? `rgba(${BLUE},0.75)` : `rgba(${NAVY},0.35)`; ctx.fill();
|
||||
if (n.isKey) { ctx.font = '600 9px "Syne",sans-serif'; ctx.fillStyle = `rgba(${BLUE},0.55)`; ctx.fillText(n.label, n.x+n.r+4, n.y+3); }
|
||||
}
|
||||
}
|
||||
|
||||
/* FLOW FIELD */
|
||||
function initFlowfield() {
|
||||
flowParticles = [];
|
||||
const count = Math.max(160, Math.floor((W * H) / 4500));
|
||||
for (let i = 0; i < count; i++) flowParticles.push({ x: rand(0,W), y: rand(0,H), px:0, py:0, life: rand(0,200), maxLife: 220+rand(0,280), speed: 0.35+rand(0,0.7) });
|
||||
ctx.fillStyle = '#fff'; ctx.fillRect(0,0,W,H);
|
||||
}
|
||||
function flowAngle(x, y, t) {
|
||||
return (Math.sin(x*0.0045+t*0.00025) + Math.cos(y*0.0042-t*0.00030) + Math.sin((x+y)*0.0023+t*0.00040)) * Math.PI;
|
||||
}
|
||||
function updateFlowfield(t) {
|
||||
for (const p of flowParticles) {
|
||||
p.px = p.x; p.py = p.y;
|
||||
const ang = flowAngle(p.x, p.y, t);
|
||||
p.x += Math.cos(ang)*p.speed; p.y += Math.sin(ang)*p.speed;
|
||||
if (mouse.x > -9000) { const dx=p.x-mouse.x,dy=p.y-mouse.y,d=Math.sqrt(dx*dx+dy*dy); if (d<130&&d>0.1) { const f=(1-d/130)*1.6; p.x+=(dx/d)*f; p.y+=(dy/d)*f; } }
|
||||
p.life++;
|
||||
if (p.life>p.maxLife||p.x<-5||p.x>W+5||p.y<-5||p.y>H+5) { p.x=rand(0,W); p.y=rand(0,H); p.life=0; p.px=p.x; p.py=p.y; }
|
||||
}
|
||||
}
|
||||
function drawFlowfield() {
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.018)'; ctx.fillRect(0,0,W,H);
|
||||
ctx.lineWidth = 1.0; ctx.lineCap = 'round';
|
||||
for (const p of flowParticles) {
|
||||
const lr = p.life/p.maxLife, a = Math.sin(lr*Math.PI)*0.7;
|
||||
ctx.strokeStyle = ((p.x+p.y)|0)%3!==0 ? `rgba(${BLUE},${a})` : `rgba(${NAVY},${a*0.7})`;
|
||||
ctx.beginPath(); ctx.moveTo(p.px,p.py); ctx.lineTo(p.x,p.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/* TOPOGRAPHY */
|
||||
const TOPO_RES = 26;
|
||||
let topoField = null, topoCols = 0, topoRows = 0;
|
||||
function initTopo() {
|
||||
topoCols = Math.ceil(W/TOPO_RES)+2; topoRows = Math.ceil(H/TOPO_RES)+2;
|
||||
topoField = new Float32Array(topoCols*topoRows);
|
||||
}
|
||||
function topoNoise(x,y,t) { return Math.sin(x*0.011+t*0.00028)+Math.cos(y*0.013-t*0.00022)+0.6*Math.sin((x+y)*0.0075+t*0.00036)+0.4*Math.cos((x-y)*0.0098-t*0.00042); }
|
||||
function updateTopo(t) {
|
||||
const mx=mouse.x,my=mouse.y,hasMouse=mx>-9000;
|
||||
for (let j=0;j<topoRows;j++) { const y=j*TOPO_RES; for (let i=0;i<topoCols;i++) { const x=i*TOPO_RES; let v=topoNoise(x,y,t); if (hasMouse) { const dx=x-mx,dy=y-my,d2=dx*dx+dy*dy; if (d2<60000) v+=1.6*Math.exp(-d2/9000); } topoField[j*topoCols+i]=v; } }
|
||||
}
|
||||
function drawTopo() {
|
||||
ctx.clearRect(0,0,W,H);
|
||||
const thresholds=[-1.6,-1.0,-0.4,0.2,0.8,1.4,2.0];
|
||||
for (let ti=0;ti<thresholds.length;ti++) {
|
||||
const thr=thresholds[ti],mid=(thresholds.length-1)/2,closeness=1-Math.abs(ti-mid)/mid;
|
||||
ctx.strokeStyle=`rgba(${BLUE},${0.13+closeness*0.18})`; ctx.lineWidth=closeness>0.7?0.95:0.55; ctx.beginPath();
|
||||
for (let j=0;j<topoRows-1;j++) { for (let i=0;i<topoCols-1;i++) { const tl=topoField[j*topoCols+i],tr=topoField[j*topoCols+i+1],br=topoField[(j+1)*topoCols+i+1],bl=topoField[(j+1)*topoCols+i]; let idx=0; if(tl>thr)idx|=1;if(tr>thr)idx|=2;if(br>thr)idx|=4;if(bl>thr)idx|=8; if(idx===0||idx===15)continue; const x=i*TOPO_RES,y=j*TOPO_RES,s=TOPO_RES,tx=x+s*(thr-tl)/(tr-tl),rx=x+s,ry=y+s*(thr-tr)/(br-tr),bx=x+s*(thr-bl)/(br-bl),by=y+s,lx=x,ly=y+s*(thr-tl)/(bl-tl); switch(idx){case 1:case 14:ctx.moveTo(lx,ly);ctx.lineTo(tx,y);break;case 2:case 13:ctx.moveTo(tx,y);ctx.lineTo(rx,ry);break;case 3:case 12:ctx.moveTo(lx,ly);ctx.lineTo(rx,ry);break;case 4:case 11:ctx.moveTo(rx,ry);ctx.lineTo(bx,by);break;case 5:ctx.moveTo(lx,ly);ctx.lineTo(tx,y);ctx.moveTo(rx,ry);ctx.lineTo(bx,by);break;case 6:case 9:ctx.moveTo(tx,y);ctx.lineTo(bx,by);break;case 7:case 8:ctx.moveTo(lx,ly);ctx.lineTo(bx,by);break;case 10:ctx.moveTo(lx,ly);ctx.lineTo(bx,by);ctx.moveTo(tx,y);ctx.lineTo(rx,ry);break;} } } ctx.stroke();
|
||||
}
|
||||
ctx.fillStyle=`rgba(${BLUE},0.55)`;
|
||||
for (let j=2;j<topoRows-2;j+=4) { for (let i=2;i<topoCols-2;i+=4) { if(topoField[j*topoCols+i]>1.8){ctx.beginPath();ctx.arc(i*TOPO_RES,j*TOPO_RES,2,0,Math.PI*2);ctx.fill();} } }
|
||||
}
|
||||
|
||||
/* LOOP & API */
|
||||
function loop() {
|
||||
if (!running) return;
|
||||
const t = performance.now();
|
||||
if (mode==='constellation'){updateConstellation(t);drawConstellation();}
|
||||
else if (mode==='network'){updateNetwork();drawNetwork();}
|
||||
else if (mode==='flow'){updateFlowfield(t);drawFlowfield();}
|
||||
else if (mode==='topo'){updateTopo(t);drawTopo();}
|
||||
rafId = requestAnimationFrame(loop);
|
||||
}
|
||||
function setMode(m) {
|
||||
mode = m;
|
||||
const cnv2d=document.getElementById('hero-canvas'), cnvGL=document.getElementById('hero-canvas-gl'), hero=document.querySelector('.hero');
|
||||
if (m==='plasma'||m==='mercury'||m==='crystal') { running=false; cancelAnimationFrame(rafId); rafId=null; ctx.clearRect(0,0,W,H); cnv2d.classList.add('hidden'); cnvGL.classList.add('active'); hero.classList.add('gl-active'); if(window.__faSetGLMode)window.__faSetGLMode(m); return; }
|
||||
cnv2d.classList.remove('hidden'); cnvGL.classList.remove('active'); hero.classList.remove('gl-active'); if(window.__faSetGLMode)window.__faSetGLMode('off');
|
||||
if (m==='off'){running=false;cancelAnimationFrame(rafId);rafId=null;ctx.clearRect(0,0,W,H);cnv2d.classList.add('hidden');return;}
|
||||
if(m==='constellation')initConstellation(); else if(m==='network')initNetwork(); else if(m==='flow')initFlowfield(); else if(m==='topo')initTopo();
|
||||
running=true; cancelAnimationFrame(rafId); rafId=null; loop();
|
||||
}
|
||||
window.__faSetCanvasMode = setMode;
|
||||
window.addEventListener('resize', () => { resize(); if(mode==='constellation')initConstellation(); else if(mode==='network')initNetwork(); else if(mode==='flow')initFlowfield(); else if(mode==='topo')initTopo(); });
|
||||
canvas.parentElement.addEventListener('mousemove', e => { const rect=canvas.getBoundingClientRect(); mouse.x=e.clientX-rect.left; mouse.y=e.clientY-rect.top; });
|
||||
canvas.parentElement.addEventListener('mouseleave', () => { mouse.x=-9999; mouse.y=-9999; });
|
||||
resize(); initConstellation(); loop();
|
||||
})();
|
||||
|
||||
/* ============================================================
|
||||
HERO WEBGL
|
||||
============================================================ */
|
||||
(function() {
|
||||
const canvas = document.getElementById('hero-canvas-gl');
|
||||
const gl = canvas.getContext('webgl', { antialias: false, alpha: true, premultipliedAlpha: false });
|
||||
if (!gl) { window.__faSetGLMode = () => {}; return; }
|
||||
const VS_SRC = `attribute vec2 a_pos; void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }`;
|
||||
const FRAG_HEADER = `
|
||||
precision highp float;
|
||||
uniform vec2 uRes; uniform float uTime; uniform vec2 uMouse;
|
||||
float hash21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+45.32);return fract(p.x*p.y);}
|
||||
float vnoise(vec2 p){vec2 i=floor(p),f=fract(p),u=f*f*(3.0-2.0*f);float a=hash21(i),b=hash21(i+vec2(1,0)),c=hash21(i+vec2(0,1)),d=hash21(i+vec2(1,1));return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);}
|
||||
float fbm(vec2 p){float v=0.0,a=0.5;mat2 r=mat2(0.8,-0.6,0.6,0.8);for(int i=0;i<4;i++){v+=a*vnoise(p);p=r*p*2.02;a*=0.5;}return v;}
|
||||
`;
|
||||
const FS_PLASMA = `void main(){vec2 frag=gl_FragCoord.xy;float scale=min(uRes.x,uRes.y);vec2 uv=(frag-0.5*uRes)/scale,m=(uMouse-0.5*uRes)/scale,center=m*0.55,p=uv-center;float r=length(p),a=atan(p.y,p.x),corona=0.0;for(int i=0;i<3;i++){float fi=float(i);corona+=fbm(vec2(a*(3.0+fi)+uTime*(0.18+fi*0.07),r*4.5-uTime*0.25))/(fi+1.0);}corona/=1.6;float core=exp(-pow(r*4.0,2.0)),outer=exp(-r*2.8)*(0.55+0.5*corona),fil=smoothstep(0.6,0.92,fbm(vec2(a*6.0,r*7.0+uTime*0.4)));fil*=smoothstep(0.08,0.3,r)*smoothstep(0.7,0.25,r);vec3 navy=vec3(0.102,0.122,0.361),blue=vec3(0.420,0.561,0.831),lblue=vec3(0.659,0.753,0.910),col=mix(blue,navy,smoothstep(0.0,0.25,r));col=mix(col,lblue,fil);float a1=clamp(core*0.65+outer*0.55+fil*0.45,0.0,0.85);gl_FragColor=vec4(col,a1);}`;
|
||||
const FS_MERCURY = `float metaball(vec2 p,vec2 c,float r){return r/max(length(p-c),0.0001);}void main(){vec2 frag=gl_FragCoord.xy;float scale=min(uRes.x,uRes.y);vec2 uv=(frag-0.5*uRes)/scale,m=(uMouse-0.5*uRes)/scale,b0=vec2(sin(uTime*0.27)*0.42,cos(uTime*0.23)*0.28),b1=vec2(cos(uTime*0.35)*0.48,sin(uTime*0.21)*0.26),b2=vec2(sin(uTime*0.19+2.0)*0.5,cos(uTime*0.31+1.0)*0.36),b3=vec2(cos(uTime*0.25+4.0)*0.36,sin(uTime*0.17+3.0)*0.44),b4=m,b5=-m*0.4;float f=metaball(uv,b0,0.16)+metaball(uv,b1,0.18)+metaball(uv,b2,0.13)+metaball(uv,b3,0.15)+metaball(uv,b4,0.22)+metaball(uv,b5,0.10);float surface=smoothstep(2.3,2.85,f),halo=smoothstep(1.6,2.3,f)-surface;vec2 e=vec2(0.004,0.0);float fx=(metaball(uv+e.xy,b0,0.16)+metaball(uv+e.xy,b1,0.18)+metaball(uv+e.xy,b2,0.13)+metaball(uv+e.xy,b3,0.15)+metaball(uv+e.xy,b4,0.22)+metaball(uv+e.xy,b5,0.10))-f,fy=(metaball(uv+e.yx,b0,0.16)+metaball(uv+e.yx,b1,0.18)+metaball(uv+e.yx,b2,0.13)+metaball(uv+e.yx,b3,0.15)+metaball(uv+e.yx,b4,0.22)+metaball(uv+e.yx,b5,0.10))-f;vec2 n=normalize(vec2(fx,fy)+vec2(1e-5));vec3 navy=vec3(0.102,0.122,0.361),blue=vec3(0.420,0.561,0.831),lblue=vec3(0.659,0.753,0.910),pale=vec3(0.93,0.95,0.99);float ang=atan(n.y,n.x),t1=sin(ang*2.0+uTime*0.5)*0.5+0.5,t2=sin(ang*3.0-uTime*0.4)*0.5+0.5;vec3 surfCol=mix(navy,blue,t1);surfCol=mix(surfCol,lblue,t2*0.55);float fres=pow(1.0-abs(n.y),1.6);surfCol=mix(surfCol,pale,fres*0.5);float spec=pow(max(dot(n,normalize(vec2(0.5,0.8))),0.0),18.0);surfCol+=vec3(spec)*0.4;vec3 haloCol=mix(blue,lblue,0.6),col=mix(haloCol,surfCol,surface);float a1=clamp(surface*0.92+halo*0.35,0.0,0.95);gl_FragColor=vec4(col,a1);}`;
|
||||
const FS_CRYSTAL = `vec2 hash22b(vec2 p){vec3 p3=fract(vec3(p.xyx)*vec3(0.1031,0.1030,0.0973));p3+=dot(p3,p3.yzx+33.33);return fract((p3.xx+p3.yz)*p3.zy);}vec3 voronoiB(vec2 p){vec2 i=floor(p),f=fract(p);float d1=1e9,d2=1e9;vec2 ic=vec2(0.0);for(int y=-1;y<=1;y++){for(int x=-1;x<=1;x++){vec2 g=vec2(float(x),float(y)),o=hash22b(i+g);o=0.5+0.5*sin(uTime*0.5+6.28*o);vec2 r=g+o-f;float d=dot(r,r);if(d<d1){d2=d1;d1=d;ic=i+g;}else if(d<d2)d2=d;}}return vec3(sqrt(d1),sqrt(d2),hash21(ic));}void main(){vec2 frag=gl_FragCoord.xy;float scale=min(uRes.x,uRes.y);vec2 uv=frag/scale,m=uMouse/scale,d=uv-m,uvw=uv+d*0.45*exp(-length(d)*2.2);vec3 vd=voronoiB(uvw*3.5);float d1=vd.x,d2=vd.y,cellId=vd.z,edge=d2-d1;vec3 navy=vec3(0.102,0.122,0.361),blue=vec3(0.420,0.561,0.831),lblue=vec3(0.659,0.753,0.910),pale=vec3(0.85,0.90,0.97),fill=mix(pale,lblue,cellId*0.7);float boundary=smoothstep(0.0,0.35,d1);fill=mix(fill,blue,boundary*0.55);float edgeMask=1.0-smoothstep(0.0,0.035,edge);vec3 edgeCol=mix(navy,blue,sin(uTime*0.3+cellId*8.0)*0.5+0.5);vec3 mvd=voronoiB(m*3.5);float hovered=step(abs(mvd.z-cellId),0.001),caustic=smoothstep(0.5,0.95,sin(d1*28.0+uTime+cellId*14.0))*(1.0-edgeMask)*0.3,col_=fill+edgeCol*edgeMask+lblue*caustic;vec3 col=mix(col_,mix(blue,lblue,0.4),hovered*(1.0-edgeMask)*0.45);float fillAlpha=0.18+boundary*0.18+hovered*0.12,a1=clamp(max(fillAlpha,edgeMask*0.85),0.0,0.85);gl_FragColor=vec4(col,a1);}`;
|
||||
|
||||
function compile(type,src){const s=gl.createShader(type);gl.shaderSource(s,src);gl.compileShader(s);if(!gl.getShaderParameter(s,gl.COMPILE_STATUS)){console.error('GL compile:',gl.getShaderInfoLog(s));return null;}return s;}
|
||||
function makeProgram(fsSrc){const vs=compile(gl.VERTEX_SHADER,VS_SRC),fs=compile(gl.FRAGMENT_SHADER,FRAG_HEADER+'\n'+fsSrc);if(!vs||!fs)return null;const p=gl.createProgram();gl.attachShader(p,vs);gl.attachShader(p,fs);gl.linkProgram(p);if(!gl.getProgramParameter(p,gl.LINK_STATUS)){console.error('GL link:',gl.getProgramInfoLog(p));return null;}return p;}
|
||||
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,1,-1,1,1]),gl.STATIC_DRAW);
|
||||
const programs={};
|
||||
function getProgram(name){if(programs[name])return programs[name];let src;if(name==='plasma')src=FS_PLASMA;else if(name==='mercury')src=FS_MERCURY;else if(name==='crystal')src=FS_CRYSTAL;else return null;const p=makeProgram(src);if(!p)return null;programs[name]={prog:p,uRes:gl.getUniformLocation(p,'uRes'),uTime:gl.getUniformLocation(p,'uTime'),uMouse:gl.getUniformLocation(p,'uMouse'),a_pos:gl.getAttribLocation(p,'a_pos')};return programs[name];}
|
||||
gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
|
||||
let glMode='off',glRunning=false,glRafId=null;
|
||||
const dpr=Math.min(window.devicePixelRatio||1,2),mouse={x:-9999,y:-9999},mouseSmooth={x:0,y:0};
|
||||
function resize(){const rect=canvas.parentElement.getBoundingClientRect();const w=Math.floor(rect.width*dpr),h=Math.floor(rect.height*dpr);if(canvas.width!==w||canvas.height!==h){canvas.width=w;canvas.height=h;canvas.style.width=rect.width+'px';canvas.style.height=rect.height+'px';gl.viewport(0,0,w,h);}}
|
||||
canvas.parentElement.addEventListener('mousemove',e=>{const rect=canvas.getBoundingClientRect();mouse.x=(e.clientX-rect.left)*dpr;mouse.y=(rect.height-(e.clientY-rect.top))*dpr;});
|
||||
canvas.parentElement.addEventListener('mouseleave',()=>{mouse.x=-9999;mouse.y=-9999;});
|
||||
function initCenterMouse(){const rect=canvas.parentElement.getBoundingClientRect();if(mouse.x<-9000){mouseSmooth.x=(rect.width/2)*dpr;mouseSmooth.y=(rect.height/2)*dpr;}}
|
||||
const startMs=performance.now();let lastFrame=startMs;
|
||||
function glLoop(){if(!glRunning)return;resize();const now=performance.now(),dt=(now-lastFrame)/1000;lastFrame=now;const t=(now-startMs)/1000;const target=mouse.x<-9000?{x:canvas.width/2,y:canvas.height/2}:mouse;mouseSmooth.x+=(target.x-mouseSmooth.x)*Math.min(1,dt*5);mouseSmooth.y+=(target.y-mouseSmooth.y)*Math.min(1,dt*5);const p=getProgram(glMode);if(!p){glRafId=requestAnimationFrame(glLoop);return;}gl.useProgram(p.prog);gl.bindBuffer(gl.ARRAY_BUFFER,buf);gl.enableVertexAttribArray(p.a_pos);gl.vertexAttribPointer(p.a_pos,2,gl.FLOAT,false,0,0);gl.uniform2f(p.uRes,canvas.width,canvas.height);gl.uniform1f(p.uTime,t);gl.uniform2f(p.uMouse,mouseSmooth.x,mouseSmooth.y);gl.clearColor(0,0,0,0);gl.clear(gl.COLOR_BUFFER_BIT);gl.drawArrays(gl.TRIANGLES,0,6);glRafId=requestAnimationFrame(glLoop);}
|
||||
function setGLMode(m){if(m===glMode)return;glMode=m;if(m==='off'){glRunning=false;cancelAnimationFrame(glRafId);glRafId=null;gl.clear(gl.COLOR_BUFFER_BIT);return;}if(m==='plasma'||m==='mercury'||m==='crystal'){getProgram(m);initCenterMouse();if(!glRunning){glRunning=true;lastFrame=performance.now();cancelAnimationFrame(glRafId);glLoop();}}}
|
||||
window.__faSetGLMode=setGLMode;
|
||||
window.addEventListener('resize',resize);resize();
|
||||
})();
|
||||
|
||||
/* ============================================================
|
||||
TWEAKS PANEL
|
||||
============================================================ */
|
||||
(function() {
|
||||
const DEFAULTS = {"canvasMode":"mercury","visualMode":"none","emphasis":"underline"};
|
||||
const state = { ...DEFAULTS };
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'fa-tweaks';
|
||||
panel.style.cssText = 'position:fixed;bottom:24px;right:24px;z-index:9999;width:280px;background:#fff;color:#1A1F5C;border:0.5px solid rgba(26,31,92,0.15);box-shadow:0 20px 60px -20px rgba(26,31,92,0.35),0 4px 12px -4px rgba(26,31,92,0.1);font-family:"DM Sans",sans-serif;display:none;';
|
||||
panel.innerHTML = `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:0.5px solid rgba(26,31,92,0.1);cursor:move;" id="fa-tweaks-head">
|
||||
<div style="font-family:'Syne',sans-serif;font-size:11px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:#1A1F5C;">Tweaks</div>
|
||||
<button id="fa-tweaks-close" style="background:none;border:none;cursor:pointer;font-size:18px;line-height:1;color:#6B7280;padding:0;">×</button>
|
||||
</div>
|
||||
<div style="padding:16px 18px;display:flex;flex-direction:column;gap:18px;">
|
||||
<div><div style="font-size:10px;font-weight:500;letter-spacing:2px;text-transform:uppercase;color:#6B7280;margin-bottom:8px;">Hero Canvas</div>
|
||||
<div class="fa-seg" data-key="canvasMode" style="display:grid;grid-template-columns:1fr 1fr;gap:1px;background:rgba(26,31,92,0.1);border:0.5px solid rgba(26,31,92,0.1);">
|
||||
<button data-v="constellation">Constellation</button><button data-v="network">Network</button>
|
||||
<button data-v="flow">Flow field</button><button data-v="topo">Topografie</button>
|
||||
<button data-v="plasma">Plasma</button><button data-v="mercury">Mercury</button>
|
||||
<button data-v="crystal" style="grid-column:span 2;">Crystal</button>
|
||||
<button data-v="off" style="grid-column:span 2;">Off</button>
|
||||
</div></div>
|
||||
<div><div style="font-size:10px;font-weight:500;letter-spacing:2px;text-transform:uppercase;color:#6B7280;margin-bottom:8px;">Right Panel</div>
|
||||
<div class="fa-seg" data-key="visualMode" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1px;background:rgba(26,31,92,0.1);border:0.5px solid rgba(26,31,92,0.1);">
|
||||
<button data-v="stack">Stack</button><button data-v="stats">Cards</button><button data-v="none">Keine</button>
|
||||
</div></div>
|
||||
<div><div style="font-size:10px;font-weight:500;letter-spacing:2px;text-transform:uppercase;color:#6B7280;margin-bottom:8px;">Headline Emphasis</div>
|
||||
<div class="fa-seg" data-key="emphasis" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1px;background:rgba(26,31,92,0.1);border:0.5px solid rgba(26,31,92,0.1);">
|
||||
<button data-v="underline">Blue+line</button><button data-v="blue">Blue</button><button data-v="flat">All navy</button>
|
||||
</div></div>
|
||||
</div>
|
||||
<style>#fa-tweaks .fa-seg button{background:#fff;border:none;padding:9px 6px;font-family:"DM Sans",sans-serif;font-size:11px;font-weight:500;color:#6B7280;cursor:pointer;transition:all 0.15s;letter-spacing:0.3px;}#fa-tweaks .fa-seg button:hover{color:#1A1F5C;}#fa-tweaks .fa-seg button.active{background:#1A1F5C;color:#fff;}</style>
|
||||
`;
|
||||
document.body.appendChild(panel);
|
||||
function apply() {
|
||||
if(window.__faSetCanvasMode)window.__faSetCanvasMode(state.canvasMode);
|
||||
const cap=document.getElementById('fa-cap-card'),stats=document.getElementById('fa-stat-stack'),visual=document.getElementById('fa-hero-visual'),hero=document.querySelector('.hero');
|
||||
hero.classList.remove('no-visual');
|
||||
if(state.visualMode==='stack'){cap.style.display='block';stats.style.display='none';visual.style.display='flex';visual.classList.remove('legacy');}
|
||||
else if(state.visualMode==='stats'){cap.style.display='none';stats.style.display='flex';visual.style.display='flex';visual.classList.add('legacy');}
|
||||
else{visual.style.display='none';hero.classList.add('no-visual');}
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
const h1=document.getElementById('fa-headline'),em=h1.querySelector('em');
|
||||
h1.classList.remove('flat');em.classList.remove('underline');
|
||||
if(state.emphasis==='underline')em.classList.add('underline');else if(state.emphasis==='flat')h1.classList.add('flat');
|
||||
panel.querySelectorAll('.fa-seg').forEach(seg=>{const key=seg.dataset.key;seg.querySelectorAll('button').forEach(b=>{b.classList.toggle('active',b.dataset.v===state[key]);});});
|
||||
}
|
||||
panel.querySelectorAll('.fa-seg button').forEach(btn=>{btn.addEventListener('click',()=>{const seg=btn.closest('.fa-seg');state[seg.dataset.key]=btn.dataset.v;apply();try{window.parent.postMessage({type:'__edit_mode_set_keys',edits:{[seg.dataset.key]:btn.dataset.v}},'*');}catch(e){}});});
|
||||
document.getElementById('fa-tweaks-close').addEventListener('click',()=>{panel.style.display='none';try{window.parent.postMessage({type:'__edit_mode_dismissed'},'*');}catch(e){}});
|
||||
(function(){const head=document.getElementById('fa-tweaks-head');let dragging=false,sx=0,sy=0,px=0,py=0;head.addEventListener('mousedown',e=>{if(e.target.id==='fa-tweaks-close')return;dragging=true;sx=e.clientX;sy=e.clientY;const r=panel.getBoundingClientRect();px=r.left;py=r.top;panel.style.right='auto';panel.style.bottom='auto';e.preventDefault();});window.addEventListener('mousemove',e=>{if(!dragging)return;panel.style.left=(px+e.clientX-sx)+'px';panel.style.top=(py+e.clientY-sy)+'px';});window.addEventListener('mouseup',()=>dragging=false);})();
|
||||
window.addEventListener('message',e=>{if(!e.data)return;if(e.data.type==='__activate_edit_mode')panel.style.display='block';if(e.data.type==='__deactivate_edit_mode')panel.style.display='none';});
|
||||
try{window.parent.postMessage({type:'__edit_mode_available'},'*');}catch(e){}
|
||||
apply();
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user