Files
Website-Fa/index.html
cemGr ac2c2be559 -
2026-05-18 22:26:32 +02:00

2376 lines
92 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Frontier Algorithmics KI & Technology Consulting</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
<style>
:root {
--fa-navy: #1A1F5C;
--fa-navy-deep: #0F1340;
--fa-blue: #6B8FD4;
--fa-blue-light: #A8C0E8;
--fa-blue-pale: #EEF3FB;
--fa-off-white: #F7F8FC;
--fa-mid: #6B7280;
--fa-border: rgba(26,31,92,0.1);
/* Horizontal content padding: scales with viewport,
caps content at ~1500px on huge screens */
--content-pad: max(1.25rem, calc((100vw - 1500px) / 2));
}
@media (min-width: 720px) { :root { --content-pad: max(2.5rem, calc((100vw - 1500px) / 2)); } }
@media (min-width: 1100px) { :root { --content-pad: max(3.5rem, calc((100vw - 1500px) / 2)); } }
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; scroll-padding-top: 70px; }
body {
font-family: 'DM Sans', sans-serif;
color: var(--fa-navy);
background: #fff;
overflow-x: hidden;
}
/* ── NAV ── */
nav.fa-nav {
display: flex; align-items: center; justify-content: space-between;
padding: 1.1rem var(--content-pad);
border-bottom: 0.5px solid var(--fa-border);
background: rgba(255,255,255,0.97);
position: sticky; top: 0; z-index: 200;
backdrop-filter: blur(8px);
gap: 1.5rem;
}
.fa-logo-area { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.fa-logo-mark { font-family:'Syne',sans-serif; font-weight:800; font-size:24px; color:var(--fa-blue); letter-spacing:-1px; line-height:1; }
.fa-logo-divider { width:1px; height:30px; background:var(--fa-border); }
.fa-logo-text { font-family:'Syne',sans-serif; font-weight:600; font-size:12px; color:var(--fa-navy); text-transform:uppercase; letter-spacing:2.5px; line-height:1.3; }
.fa-nav-links { display:flex; gap:0.5rem; list-style:none; align-items: center; }
.fa-nav-links a { font-size:14px; font-weight:400; color:var(--fa-navy); text-decoration:none; letter-spacing:0.3px; opacity:0.65; transition:opacity 0.2s; white-space: nowrap; padding:0.6rem 0.75rem; display:inline-flex; align-items:center; min-height:44px; }
.fa-nav-links a:hover { opacity:1; }
.fa-nav-links a.lang { opacity: 0.35; font-family: 'Syne', sans-serif; font-size: 12px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; cursor: default; }
.fa-nav-links a.lang:hover { opacity: 0.5; }
.fa-nav-links a.lang .active { color: var(--fa-blue); opacity: 1; }
.fa-nav-cta {
font-family:'DM Sans',sans-serif; font-size:14px; font-weight:500;
color:#fff; background:var(--fa-navy); border:none;
padding:0.65rem 1.4rem; cursor:pointer; transition:background 0.2s;
text-decoration: none; white-space: nowrap; display: inline-flex; align-items: center; gap: 8px;
flex-shrink: 0;
}
.fa-nav-cta:hover { background:var(--fa-blue); }
.fa-nav-cta::before {
content: ''; width: 6px; height: 6px; border-radius: 50%;
background: #6FD49A; box-shadow: 0 0 0 2px rgba(111,212,154,0.25);
}
/* Mobile menu button */
.fa-nav-menu {
display: none; background: none; border: none; cursor: pointer;
padding: 8px; gap: 4px; flex-direction: column;
}
.fa-nav-menu span {
display: block; width: 22px; height: 1.5px;
background: var(--fa-navy); transition: transform 0.25s, opacity 0.25s;
}
.fa-nav.menu-open .fa-nav-menu span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.fa-nav.menu-open .fa-nav-menu span:nth-child(2) { opacity: 0; }
.fa-nav.menu-open .fa-nav-menu span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }
/* ── HERO ── */
.fa-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;
}
.fa-hero.no-visual {
grid-template-columns: 1fr;
padding: 6rem var(--content-pad) 5.5rem;
}
.fa-hero.no-visual .fa-hero-content {
padding-right: 0;
max-width: 880px;
}
.fa-hero.no-visual h1 {
font-size: clamp(2.8rem, 5.4vw, 5rem);
}
.fa-hero.no-visual .fa-hero-sub {
max-width: 540px; font-size: 17px;
}
/* Canvas background */
#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; }
/* Mask: fade the GL canvas away on the left side where the headline sits */
#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%);
}
/* Soft white wash behind headline for extra legibility when GL is active */
.fa-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;
}
/* When GL is off, no wash */
#hero-canvas.hidden { opacity: 0; }
.fa-hero-content { position:relative; z-index:2; padding-right:3.5rem; max-width: 720px; }
.fa-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;
}
.fa-eyebrow::before { content:''; display:block; width:36px; height:1px; background:var(--fa-blue); }
/* AI badge in hero */
.fa-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;
}
.fa-ai-dot {
width: 7px; height: 7px; border-radius: 50%; background: var(--fa-blue);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%,100% { opacity:1; transform:scale(1); }
50% { opacity:0.5; transform:scale(0.7); }
}
.fa-hero 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;
}
.fa-hero h1 em {
font-style:normal; color:var(--fa-blue);
position: relative; display: inline-block;
/* Scroll progress drives saturation → use a color blend toward a richer blue */
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;
}
@keyframes faUnderlineDraw {
0% { transform: scaleX(0); }
100% { transform: scaleX(1); }
}
/* Base underline (faded) — draws in on load */
.fa-hero 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;
}
/* Brighter overlay underline — grows with scroll progress */
.fa-hero 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;
}
.fa-hero h1.flat em { color: var(--fa-navy); }
.fa-hero h1.flat em::after, .fa-hero h1.flat em::before { display: none; }
.fa-hero-sub {
font-size:16px; font-weight:300; line-height:1.8;
color:var(--fa-mid); max-width:460px; margin-bottom:3rem;
}
.fa-hero-actions { display:flex; align-items:center; gap:1.75rem; }
.fa-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;
}
.fa-btn-primary:hover { background:var(--fa-blue); }
.fa-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;
}
.fa-btn-ghost:hover { opacity:1; }
/* Hero right side capability stack */
.fa-hero-visual {
position:relative; z-index:2;
display:flex; flex-direction:column; align-items:stretch;
max-width: 380px; margin-left: auto;
}
.fa-cap-card {
background: var(--fa-navy); color: #fff;
border: 0.5px solid rgba(107,143,212,0.25);
}
.fa-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);
}
.fa-cap-head-mark {
display:flex; align-items:center; gap:6px;
color: var(--fa-blue-light);
}
.fa-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;
}
.fa-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;
}
.fa-cap-row:last-of-type { border-bottom: none; }
.fa-cap-row:hover { background: rgba(107,143,212,0.06); }
.fa-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;
}
.fa-cap-body {}
.fa-cap-title {
font-family:'Syne',sans-serif; font-size: 15px; font-weight: 600;
color: #fff; letter-spacing: -0.2px;
margin-bottom: 4px;
}
.fa-cap-keys {
font-size: 12px; font-weight: 300; line-height: 1.5;
color: rgba(255,255,255,0.68);
letter-spacing: 0.2px;
}
.fa-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);
}
.fa-cap-foot-dot {
display:inline-flex; align-items:center; gap:8px;
color: rgba(255,255,255,0.7);
}
.fa-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);
}
/* Original stat cards (kept for tweak toggle) */
.fa-stat-card {
background:var(--fa-navy); color:#fff;
padding:1.75rem 2.25rem; width:290px;
}
.fa-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;
}
.fa-stat-card .label { font-size:13px; font-weight:300; opacity:0.65; line-height:1.5; }
.fa-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);
}
.fa-stat-card.light .num { color:var(--fa-navy); }
.fa-stat-card.light .label { opacity:0.55; color:var(--fa-navy); }
.fa-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;
}
.fa-hero-visual.legacy { gap:1.25rem; align-items:flex-end; max-width: none; }
/* ── TICKER / EXPERTISE BANNER ── */
.fa-ticker-wrap {
background: var(--fa-navy);
padding: 0;
overflow: hidden;
position: relative;
}
.fa-ticker-inner {
display: flex;
width: max-content;
animation: ticker 75s linear infinite;
padding: 1.6rem 0;
}
.fa-ticker-wrap:hover .fa-ticker-inner { animation-play-state: paused; }
@keyframes ticker {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
.fa-ticker-item {
display: flex; align-items: center; gap: 1.8rem;
padding: 0 2.2rem;
font-family: 'Syne', sans-serif;
font-size: 16px; font-weight: 600;
letter-spacing: 3px; text-transform: uppercase;
color: rgba(255,255,255,0.65);
white-space: nowrap;
}
.fa-ticker-dot {
width: 5px; height: 5px; border-radius: 50%;
background: var(--fa-blue); flex-shrink: 0;
}
/* Mobile: keep compact + a bit faster so it's not boring on small screens */
@media (max-width: 720px) {
.fa-ticker-inner { animation-duration: 45s; padding: 1.1rem 0; }
.fa-ticker-item { font-size: 11px; letter-spacing: 2px; padding: 0 1.5rem; gap: 1.25rem; }
}
/* Fade edges */
.fa-ticker-wrap::before,
.fa-ticker-wrap::after {
content: '';
position: absolute; top: 0; bottom: 0; width: 120px; z-index: 2;
pointer-events: none;
}
.fa-ticker-wrap::before { left:0; background: linear-gradient(to right, var(--fa-navy), transparent); }
.fa-ticker-wrap::after { right:0; background: linear-gradient(to left, var(--fa-navy), transparent); }
/* ── SERVICES ── */
.fa-services { padding:7rem var(--content-pad); background:var(--fa-off-white); }
.fa-section-header {
display:grid; grid-template-columns:1fr 1fr;
gap:2rem; margin-bottom:4.5rem; align-items:end;
}
.fa-section-tag {
font-size:11px; font-weight:500; letter-spacing:3px; text-transform:uppercase;
color:var(--fa-blue); margin-bottom:1rem;
display:flex; align-items:center; gap:10px;
}
.fa-section-tag::before { content:''; display:block; width:24px; height:1px; background:var(--fa-blue); }
.fa-section-header h2 {
font-family:'Syne',sans-serif;
font-size:clamp(2rem,3vw,2.8rem); font-weight:700;
line-height:1.1; letter-spacing:-1.2px; color:var(--fa-navy);
}
.fa-section-desc {
font-size:15px; font-weight:300; line-height:1.8;
color:var(--fa-mid); max-width:420px; align-self:end;
}
/* (KI pills removed — cleaner cards) */
.fa-services-grid {
display:grid; grid-template-columns:repeat(4,1fr);
gap:1px; background:var(--fa-border); border:0.5px solid var(--fa-border);
}
.fa-service-card {
background:#fff; padding:2.25rem 2rem;
cursor:pointer; transition:background 0.25s; position:relative;
}
.fa-service-card:hover { background:var(--fa-navy); }
.fa-service-card:hover .fa-sc-num { color:var(--fa-blue-light); }
.fa-service-card:hover .fa-sc-title,
.fa-service-card:hover .fa-sc-desc { color:rgba(255,255,255,0.85); }
.fa-service-card:hover .fa-sc-tags span { color:rgba(255,255,255,0.6); border-color:rgba(255,255,255,0.15); }
.fa-sc-num {
font-family:'Syne',sans-serif; font-size:12px; font-weight:700;
letter-spacing:2px; color:var(--fa-blue); margin-bottom:1.25rem; transition:color 0.25s;
}
.fa-sc-title {
font-family:'Syne',sans-serif; font-size:16px; font-weight:700;
color:var(--fa-navy); margin-bottom:0.75rem; line-height:1.25; transition:color 0.25s;
}
.fa-sc-desc {
font-size:14px; font-weight:300; line-height:1.7;
color:var(--fa-mid); margin-bottom:1.25rem; transition:color 0.25s;
}
.fa-sc-tags { display:flex; flex-wrap:wrap; gap:5px; }
.fa-sc-tags span {
font-size:11px; letter-spacing:0.5px; color:var(--fa-navy);
border:0.5px solid var(--fa-border); padding:3px 9px; opacity:0.7;
transition:color 0.25s, border-color 0.25s;
}
/* ── KI HIGHLIGHT SECTION ── */
.fa-ai-section {
padding: 7rem var(--content-pad);
background: var(--fa-navy);
display: grid;
grid-template-columns: 1fr 1fr;
gap: 5rem;
align-items: center;
position: relative;
overflow: hidden;
}
.fa-ai-section::before {
content: 'AI';
font-family: 'Syne', sans-serif;
font-size: 22rem;
font-weight: 800;
color: rgba(107,143,212,0.04);
position: absolute;
right: -2rem; bottom: -4rem;
line-height: 1;
pointer-events: none;
letter-spacing: -10px;
}
.fa-ai-content { position: relative; z-index: 1; }
.fa-ai-tag {
font-size:11px; font-weight:500; letter-spacing:3px; text-transform:uppercase;
color:var(--fa-blue-light); margin-bottom:1rem;
display:flex; align-items:center; gap:10px;
}
.fa-ai-tag::before { content:''; display:block; width:24px; height:1px; background:var(--fa-blue-light); }
.fa-ai-content h2 {
font-family:'Syne',sans-serif;
font-size:clamp(1.8rem,2.8vw,2.6rem); font-weight:700;
letter-spacing:-1px; line-height:1.1; color:#fff; margin-bottom:1.5rem;
}
.fa-ai-content h2 em { font-style:normal; color:var(--fa-blue-light); }
.fa-ai-content p {
font-size:15px; font-weight:300; line-height:1.8;
color:rgba(255,255,255,0.72); margin-bottom:2.5rem; max-width:440px;
}
.fa-ai-capabilities {
display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
background: rgba(107,143,212,0.15);
border: 0.5px solid rgba(107,143,212,0.2);
position: relative; z-index: 1;
}
.fa-ai-cap {
background: rgba(26,31,92,0.6);
padding: 1.75rem;
transition: background 0.2s;
}
.fa-ai-cap:hover { background: rgba(107,143,212,0.12); }
.fa-ai-cap-icon {
width: 32px; height: 32px;
background: rgba(107,143,212,0.15);
border: 0.5px solid rgba(107,143,212,0.3);
display: flex; align-items: center; justify-content: center;
margin-bottom: 0.85rem;
font-size: 14px; color: var(--fa-blue-light);
}
.fa-ai-cap h4 {
font-family:'Syne',sans-serif; font-size:14px; font-weight:700;
color:#fff; margin-bottom:0.4rem; letter-spacing:-0.2px;
}
.fa-ai-cap p {
font-size:13px; font-weight:300; line-height:1.6;
color:rgba(255,255,255,0.65); margin-bottom:0;
}
/* ── WHY ── */
.fa-why {
padding:7rem var(--content-pad);
display:grid; grid-template-columns:1fr 1fr;
gap:5rem; align-items:center; background:#fff;
}
.fa-why-visual {
position: relative;
aspect-ratio: 2 / 3;
overflow: hidden;
}
.fa-why-img {
position: absolute; inset: 0;
width: 100%; height: 100%;
object-fit: cover;
object-position: center;
display: block;
}
/* Subtle gradient so text overlays stay readable */
.fa-why-visual::after {
content: ''; position: absolute; inset: 0;
background: linear-gradient(to bottom,
rgba(15,19,64,0.0) 30%,
rgba(15,19,64,0.18) 65%,
rgba(15,19,64,0.55) 100%);
pointer-events: none;
}
.fa-why-block {
position: absolute;
left: 1.5rem; right: 1.5rem; bottom: 1.5rem;
background: var(--fa-navy);
padding: 2rem 2.25rem;
color: #fff;
z-index: 2;
box-shadow: 0 20px 50px -20px rgba(15,19,64,0.45);
}
.fa-why-block-quote {
font-family:'Syne',sans-serif; font-size:1.25rem; font-weight:600;
line-height:1.35; letter-spacing:-0.3px; color:#fff; margin-bottom:1rem;
}
.fa-why-block-quote em { font-style:normal; color:var(--fa-blue-light); }
.fa-why-block-sub {
font-size:13px; font-weight:300; line-height:1.7;
color:rgba(255,255,255,0.72);
}
.fa-accent-bar {
position: absolute;
top: 1.5rem; right: 1.5rem;
width: 110px; height: 110px;
background: rgba(255,255,255,0.92);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
border: 0.5px solid rgba(26,31,92,0.08);
display: flex; align-items: center; justify-content: center;
flex-direction: column; gap: 3px;
z-index: 2;
}
.fa-accent-bar .city {
font-family:'Syne',sans-serif; font-size:13px; font-weight:800;
color:var(--fa-navy); letter-spacing:2.5px; text-transform:uppercase;
}
.fa-accent-bar .tagline {
font-size:9px; color:var(--fa-mid);
letter-spacing:1.5px; text-transform:uppercase;
}
.fa-photo-credit {
position: absolute; bottom: 6px; right: 10px; z-index: 2;
font-family: 'DM Sans', sans-serif;
font-size: 9px; letter-spacing: 1px;
color: rgba(255,255,255,0.7);
text-shadow: 0 1px 4px rgba(0,0,0,0.6);
text-decoration: none;
}
.fa-photo-credit:hover { color: #fff; }
.fa-why-content h2 {
font-family:'Syne',sans-serif; font-size:clamp(1.8rem,2.8vw,2.4rem);
font-weight:700; letter-spacing:-0.8px; line-height:1.15; color:var(--fa-navy); margin-bottom:2.75rem;
}
.fa-points { display:flex; flex-direction:column; gap:1.75rem; }
.fa-point { display:grid; grid-template-columns:2.5rem 1fr; gap:1rem; align-items:start; }
.fa-point-num { font-family:'Syne',sans-serif; font-size:12px; font-weight:700; color:var(--fa-blue); letter-spacing:1px; padding-top:3px; }
.fa-point-text strong { display:block; font-size:15px; font-weight:500; color:var(--fa-navy); margin-bottom:5px; }
.fa-point-text p { font-size:14px; font-weight:300; line-height:1.7; color:var(--fa-mid); }
/* ── BOOKING SECTION (Beratungstermin) ── */
.fa-booking {
padding: 6rem var(--content-pad);
background: #fff;
border-top: 0.5px solid var(--fa-border);
border-bottom: 0.5px solid var(--fa-border);
}
.fa-booking-inner {
display: grid; grid-template-columns: 1fr 1.05fr;
gap: 4rem; align-items: stretch;
}
.fa-booking-content { display: flex; flex-direction: column; }
.fa-booking-tag {
display: inline-flex; align-items: center; gap: 10px;
font-size:11px; font-weight:500; letter-spacing:3px; text-transform:uppercase;
color:var(--fa-blue); margin-bottom:1rem;
}
.fa-booking-tag::before { content:''; display:block; width:24px; height:1px; background:var(--fa-blue); }
.fa-booking-content h2 {
font-family:'Syne',sans-serif;
font-size: clamp(1.7rem, 2.6vw, 2.4rem); font-weight:700;
line-height:1.1; letter-spacing:-1px; color:var(--fa-navy);
margin-bottom: 1.25rem;
}
.fa-booking-content h2 em { font-style:normal; color:var(--fa-blue); }
.fa-booking-content > p {
font-size:15px; font-weight:300; line-height:1.75;
color:var(--fa-mid); margin-bottom:2.25rem; max-width:440px;
}
.fa-booking-list {
list-style: none; display: flex; flex-direction: column; gap: 1.5rem;
margin-top: auto;
}
.fa-booking-list li { display: grid; grid-template-columns: 2.5rem 1fr; gap: 1rem; }
.fa-booking-li-num {
font-family:'Syne',sans-serif; font-size:12px; font-weight:700;
color:var(--fa-blue); letter-spacing:1.5px; padding-top:3px;
}
.fa-booking-list strong {
display:block; font-size:15px; font-weight:500;
color:var(--fa-navy); margin-bottom:5px;
}
.fa-booking-list p { font-size:14px; font-weight:300; line-height:1.65; color:var(--fa-mid); }
/* Right column: calendar embed slot */
.fa-booking-cal {
background: var(--fa-navy);
color: #fff;
min-height: 460px;
position: relative;
overflow: hidden;
display: flex;
}
.fa-booking-cal iframe { width: 100%; height: 100%; border: 0; }
.fa-cal-placeholder {
flex: 1; display: flex; flex-direction: column;
align-items: flex-start; justify-content: center;
padding: 3rem 2.75rem; gap: 0.5rem;
position: relative;
}
.fa-cal-placeholder::before {
/* Subtle grid pattern */
content: ''; position: absolute; inset: 0;
background-image:
linear-gradient(rgba(168,192,232,0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(168,192,232,0.06) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
}
.fa-cal-icon {
width: 44px; height: 44px;
border: 0.5px solid rgba(168,192,232,0.4);
display: flex; align-items: center; justify-content: center;
margin-bottom: 1.25rem;
position: relative; z-index: 1;
}
.fa-cal-icon svg { width: 22px; height: 22px; stroke: var(--fa-blue-light); }
.fa-cal-label {
font-family:'Syne',sans-serif; font-size:11px; font-weight:600;
letter-spacing:2.5px; text-transform:uppercase;
color: var(--fa-blue-light); position: relative; z-index: 1;
}
.fa-cal-title {
font-family:'Syne',sans-serif; font-size: 1.6rem; font-weight: 700;
color: #fff; letter-spacing: -0.5px; line-height: 1.1;
margin: 0.25rem 0 0.75rem; position: relative; z-index: 1;
}
.fa-cal-sub {
font-size: 14px; font-weight: 300; color: rgba(255,255,255,0.72);
line-height: 1.6; max-width: 320px; margin-bottom: 1.75rem;
position: relative; z-index: 1;
}
.fa-cal-actions {
display: flex; flex-direction: column; gap: 0.75rem;
position: relative; z-index: 1;
}
.fa-btn-cal {
font-family:'DM Sans',sans-serif; font-size:13px; font-weight:500;
color: var(--fa-navy); background: #fff; border: none;
padding: 0.85rem 1.6rem; cursor: pointer; transition: background 0.2s;
text-decoration: none; display: inline-flex; align-items: center; gap: 8px;
white-space: nowrap;
}
.fa-btn-cal:hover { background: var(--fa-blue-light); }
.fa-cal-tiny {
font-size: 12px; color: rgba(255,255,255,0.65);
letter-spacing: 0.3px; line-height: 1.5;
}
.fa-cal-tiny a { color: var(--fa-blue-light); text-decoration: none; border-bottom: 0.5px solid rgba(168,192,232,0.3); }
/* ── KONTORION ── */
.fa-showcase {
padding:7rem var(--content-pad); background:var(--fa-off-white);
display:grid; grid-template-columns:1fr 1fr; gap:5rem; align-items:center;
}
.fa-showcase-label { font-size:10px; font-weight:500; letter-spacing:3px; text-transform:uppercase; color:var(--fa-mid); margin-bottom:0.9rem; }
.fa-showcase h2 { font-family:'Syne',sans-serif; font-size:clamp(1.7rem,2.5vw,2.2rem); font-weight:700; letter-spacing:-0.8px; line-height:1.15; color:var(--fa-navy); margin-bottom:1.25rem; }
.fa-showcase > div > p { font-size:15px; font-weight:300; line-height:1.8; color:var(--fa-mid); margin-bottom:2.25rem; max-width:430px; }
.fa-kontorion-card { background:var(--fa-navy); padding:3rem; color:#fff; position:relative; overflow:hidden; }
.fa-kontorion-card::before { content:'K'; font-family:'Syne',sans-serif; font-size:16rem; font-weight:800; color:rgba(107,143,212,0.06); position:absolute; right:-1.5rem; top:-2rem; line-height:1; pointer-events:none; }
.fa-k-label { font-size:11px; font-weight:500; letter-spacing:3px; text-transform:uppercase; color:var(--fa-blue-light); margin-bottom:1rem; opacity:0.9; }
.fa-k-title { font-family:'Syne',sans-serif; font-size:1.8rem; font-weight:700; letter-spacing:-0.5px; margin-bottom:0.75rem; color:#fff; }
.fa-k-desc { font-size:14px; font-weight:300; line-height:1.75; color:rgba(255,255,255,0.68); margin-bottom:1.75rem; max-width:310px; }
.fa-k-pills { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:2rem; }
.fa-k-pill { font-size:11px; letter-spacing:1.5px; text-transform:uppercase; color:var(--fa-blue-light); border:0.5px solid rgba(107,143,212,0.35); padding:4px 10px; }
.fa-k-link { display:inline-flex; align-items:center; gap:8px; font-size:13px; font-weight:500; letter-spacing:1px; text-transform:uppercase; color:#fff; text-decoration:none; border-bottom:1px solid rgba(255,255,255,0.2); padding-bottom:3px; transition:border-color 0.2s; }
.fa-k-link:hover { border-color:rgba(255,255,255,0.6); }
/* ── CTA ── */
.fa-cta { background:var(--fa-navy); padding:7rem var(--content-pad); display:grid; grid-template-columns:1fr auto; align-items:center; gap:4rem; }
.fa-cta h2 { font-family:'Syne',sans-serif; font-size:clamp(1.8rem,3vw,2.8rem); font-weight:700; letter-spacing:-1.2px; line-height:1.08; color:#fff; }
.fa-cta h2 em { font-style:normal; color:var(--fa-blue-light); }
.fa-cta-sub { font-size:14px; font-weight:300; color:rgba(255,255,255,0.65); margin-top:0.9rem; }
.fa-cta-actions { display:flex; flex-direction:column; gap:0.9rem; align-items:flex-end; }
.fa-btn-white { font-family:'DM Sans',sans-serif; font-size:14px; font-weight:500; color:var(--fa-navy); background:#fff; border:none; padding:0.9rem 2.25rem; cursor:pointer; white-space:nowrap; transition:background 0.2s; text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; }
.fa-btn-white:hover { background:var(--fa-blue-light); }
.fa-cta-loc { font-size:11px; color:rgba(255,255,255,0.55); letter-spacing:2px; text-transform:uppercase; }
/* ── FOOTER ── */
footer.fa-footer { background:var(--fa-navy-deep); padding:2rem var(--content-pad); display:flex; align-items:center; justify-content:space-between; gap: 1.5rem; flex-wrap: wrap; }
.fa-footer-left { font-family:'Syne',sans-serif; font-size:13px; font-weight:600; letter-spacing:2px; text-transform:uppercase; color:rgba(255,255,255,0.55); }
.fa-footer-links { display:flex; gap:1.75rem; }
.fa-footer-links a { font-size:13px; color:rgba(255,255,255,0.55); text-decoration:none; letter-spacing:0.5px; transition:color 0.2s; }
.fa-footer-links a:hover { color:rgba(255,255,255,0.9); }
/* ── SCROLL REVEAL ── */
[data-reveal] {
opacity: 0;
transform: translateY(22px);
transition: opacity 0.5s cubic-bezier(0.22, 0.61, 0.36, 1),
transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal].is-visible {
opacity: 1;
transform: none;
}
/* Don't animate on reduced-motion */
@media (prefers-reduced-motion: reduce) {
[data-reveal] { opacity: 1; transform: none; transition: none; }
}
/* ── RESPONSIVE ── */
/* Small laptops & narrow desktops (1024-1280): keep paddings tighter so content breathes */
@media (max-width: 1280px) {
.fa-hero, .fa-services, .fa-ai-section, .fa-why, .fa-showcase, .fa-booking, .fa-cta {
padding-top: 5.5rem; padding-bottom: 5.5rem;
}
.fa-services-grid { grid-template-columns: repeat(2, 1fr); }
.fa-booking-inner { gap: 3rem; }
.fa-section-header { gap: 1.5rem; margin-bottom: 3.5rem; }
}
/* Tablet & smaller (≤ 900px): stack 2-col layouts */
@media (max-width: 900px) {
.fa-hero, .fa-why, .fa-showcase, .fa-ai-section, .fa-booking-inner, .fa-cta {
grid-template-columns: 1fr;
}
.fa-hero { min-height: auto; padding: 4rem var(--content-pad); }
.fa-hero-content { padding-right: 0; }
.fa-hero-visual { display: none; }
.fa-section-header { grid-template-columns: 1fr; }
.fa-why, .fa-showcase, .fa-ai-section, .fa-booking { padding: 4.5rem var(--content-pad); gap: 3rem; }
.fa-services { padding: 4.5rem var(--content-pad); }
.fa-cta { padding: 4.5rem var(--content-pad); }
.fa-cta-actions { align-items: flex-start; }
.fa-ai-capabilities { grid-template-columns: 1fr; }
.fa-why-visual { aspect-ratio: 4 / 5; max-height: 70vh; }
/* Reverse stacking order so photo comes after content on mobile? Keep visual-first; user can reorder later */
.fa-booking-cal { min-height: 380px; }
}
/* Small tablet / large phone (≤ 720px) */
@media (max-width: 720px) {
nav.fa-nav { padding: 0.9rem var(--content-pad); gap: 0.75rem; }
.fa-nav-links { display: none; }
.fa-nav-links.menu-open {
display: flex; flex-direction: column;
position: fixed; top: 56px; left: 0; right: 0; bottom: 0;
background: rgba(255,255,255,0.98); backdrop-filter: blur(10px);
padding: 2rem var(--content-pad); gap: 1.5rem;
align-items: flex-start; z-index: 199;
}
.fa-nav-links.menu-open a { font-size: 18px; opacity: 0.85; }
.fa-nav-menu { display: flex; }
.fa-logo-text { font-size: 10px; letter-spacing: 1.5px; }
.fa-nav-cta { padding: 0.55rem 1rem; font-size: 12px; }
}
/* Phone (≤ 480px) */
@media (max-width: 480px) {
.fa-hero h1 { font-size: clamp(1.85rem, 8.5vw, 2.6rem); letter-spacing: -1.2px; }
.fa-hero .fa-eyebrow { font-size: 10px; letter-spacing: 2px; margin-bottom: 1.25rem; }
.fa-hero-sub { font-size: 15px; margin-bottom: 2rem; }
.fa-hero-actions { flex-wrap: wrap; gap: 1rem; }
.fa-btn-primary { padding: 0.85rem 1.6rem; }
.fa-hero { padding: 3rem var(--content-pad) 3.5rem; }
.fa-services-grid { grid-template-columns: 1fr; }
.fa-section-header h2 { font-size: clamp(1.6rem, 7vw, 2rem); }
.fa-service-card { padding: 1.75rem 1.5rem; }
.fa-ai-section { padding: 4rem var(--content-pad); gap: 2rem; }
.fa-ai-section::before { font-size: 12rem; right: -1rem; bottom: -2rem; }
.fa-ai-content h2 { font-size: clamp(1.5rem, 6.5vw, 2rem); }
.fa-why-block { left: 0.75rem; right: 0.75rem; bottom: 0.75rem; padding: 1.5rem 1.5rem; }
.fa-why-block-quote { font-size: 1.05rem; }
.fa-why-block-sub { font-size: 11px; }
.fa-accent-bar { top: 0.75rem; right: 0.75rem; width: 88px; height: 88px; }
.fa-accent-bar .city { font-size: 11px; letter-spacing: 2px; }
.fa-accent-bar .tagline { font-size: 8px; }
.fa-why-content h2 { font-size: clamp(1.6rem, 7vw, 2rem); }
.fa-showcase h2 { font-size: clamp(1.5rem, 6.5vw, 1.9rem); }
.fa-kontorion-card { padding: 2rem 1.75rem; }
.fa-kontorion-card::before { font-size: 11rem; }
.fa-booking-cal { min-height: 320px; }
.fa-cal-placeholder { padding: 2rem 1.75rem; }
.fa-cal-title { font-size: 1.3rem; }
.fa-cta h2 { font-size: clamp(1.6rem, 7vw, 2.2rem); }
.fa-cta-actions { width: 100%; }
.fa-btn-white { width: 100%; text-align: center; justify-content: center; }
footer.fa-footer { padding: 1.5rem var(--content-pad); flex-direction: column; gap: 1rem; align-items: flex-start; }
.fa-footer-links { flex-wrap: wrap; gap: 1rem 1.5rem; }
/* Hero canvases: don't dominate phones */
#hero-canvas, #hero-canvas-gl { opacity: 0.5; }
}
/* Touch devices: cursor effects fall back to ambient motion (already handled by mouseSmooth drifting to center). Hide tweaks panel auto-hint for hover. */
@media (hover: none) and (pointer: coarse) {
.fa-service-card:hover { background: #fff; } /* avoid sticky hover state on touch */
.fa-service-card:hover .fa-sc-num { color: var(--fa-blue); }
.fa-service-card:hover .fa-sc-title,
.fa-service-card:hover .fa-sc-desc { color: var(--fa-navy); }
.fa-service-card:hover .fa-sc-tags span { color: var(--fa-navy); border-color: var(--fa-border); }
.fa-service-card:hover .fa-sc-desc { color: var(--fa-mid); }
}
</style>
</head>
<body>
<!-- NAV -->
<nav class="fa-nav" id="fa-nav">
<a href="#top" class="fa-logo-area" style="text-decoration:none;">
<div class="fa-logo-mark">FA</div>
<div class="fa-logo-divider"></div>
<div class="fa-logo-text">Frontier<br>Algorithmics</div>
</a>
<ul class="fa-nav-links" id="fa-nav-links">
<li><a href="#services">Leistungen</a></li>
<li><a href="#ki">KI &amp; Daten</a></li>
<li><a href="#kontorion">Kontorion</a></li>
<li><a href="#why">Über uns</a></li>
<li><a href="#" class="lang" aria-disabled="true" title="English version coming soon"><span class="active">DE</span>&nbsp;/&nbsp;EN</a></li>
</ul>
<button class="fa-nav-menu" id="fa-nav-menu" aria-label="Menu" aria-expanded="false">
<span></span><span></span><span></span>
</button>
<a href="#kontakt" class="fa-nav-cta">Beratungstermin</a>
</nav>
<!-- HERO -->
<section class="fa-hero" id="top">
<canvas id="hero-canvas"></canvas>
<canvas id="hero-canvas-gl"></canvas>
<div class="fa-hero-content">
<div class="fa-ai-badge">
<div class="fa-ai-dot"></div>
KI-getriebene Technologieberatung
</div>
<div class="fa-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="fa-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="fa-hero-actions">
<a href="#kontakt" class="fa-btn-primary">Beratungstermin vereinbaren</a>
<a href="#kontorion" class="fa-btn-ghost">Kontorion ansehen ↗</a>
</div>
</div>
<div class="fa-hero-visual" id="fa-hero-visual">
<!-- Capability stack (default) -->
<div class="fa-cap-card" id="fa-cap-card">
<div class="fa-cap-head">
<span>Kern-Disziplinen</span>
<span class="fa-cap-head-mark">Live</span>
</div>
<div class="fa-cap-row">
<div class="fa-cap-tag">AI</div>
<div class="fa-cap-body">
<div class="fa-cap-title">Generative KI &amp; Agents</div>
<div class="fa-cap-keys">LLM · RAG · Computer Vision · MLOps</div>
</div>
</div>
<div class="fa-cap-row">
<div class="fa-cap-tag">MD</div>
<div class="fa-cap-body">
<div class="fa-cap-title">Regulierte Software</div>
<div class="fa-cap-keys">MDR · IEC 62304 · ISO 13485 · FHIR / DICOM</div>
</div>
</div>
<div class="fa-cap-row">
<div class="fa-cap-tag">SW</div>
<div class="fa-cap-body">
<div class="fa-cap-title">Engineering &amp; Architektur</div>
<div class="fa-cap-keys">Apps · Backend · APIs · Tech Consulting</div>
</div>
</div>
<div class="fa-cap-foot">
<span class="fa-cap-foot-dot">Regensburg · DE</span>
<span>Seit&nbsp;2020</span>
</div>
</div>
<!-- Original stat cards (toggle via Tweaks) -->
<div id="fa-stat-stack" style="display:none; flex-direction:column; gap:1.25rem; align-items:flex-end; width:100%;">
<div class="fa-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="fa-stat-card light">
<div class="num">MDR</div>
<div class="label">IEC 62304 · HL7 · FHIR · DICOM<br>Regulatorische Expertise</div>
</div>
<div class="fa-hero-badge">Seit Regensburg</div>
</div>
</div>
</section>
<!-- TICKER -->
<div class="fa-ticker-wrap" aria-hidden="true">
<div class="fa-ticker-inner" id="ticker">
<!-- Items doubled for seamless loop -->
<span class="fa-ticker-item">KI-Strategie<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">LLM-Agents<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Computer Vision<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Predictive Analytics<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">RAG-Systeme<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">App-Entwicklung<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Regulatory Affairs<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">HL7 / FHIR<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">DICOM<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Software-Architektur<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Agile Methoden<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Data Science<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">MLOps<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">MDR / IEC 62304<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Backend &amp; APIs<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Technologieberatung<div class="fa-ticker-dot"></div></span>
<!-- duplicate for seamless -->
<span class="fa-ticker-item">KI-Strategie<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">LLM-Agents<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Computer Vision<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Predictive Analytics<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">RAG-Systeme<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">App-Entwicklung<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Regulatory Affairs<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">HL7 / FHIR<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">DICOM<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Software-Architektur<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Agile Methoden<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Data Science<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">MLOps<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">MDR / IEC 62304<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Backend &amp; APIs<div class="fa-ticker-dot"></div></span>
<span class="fa-ticker-item">Technologieberatung<div class="fa-ticker-dot"></div></span>
</div>
</div>
<!-- SERVICES -->
<section class="fa-services" id="services">
<div class="fa-section-header">
<div>
<div class="fa-section-tag" data-reveal>Was wir tun</div>
<h2 data-reveal style="--reveal-delay:80ms">Vier Disziplinen.<br>Ein Partner.</h2>
</div>
<p class="fa-section-desc" data-reveal style="--reveal-delay:160ms">KI ist kein Add-on sie ist der Antrieb hinter jedem unserer Leistungsbereiche. Wir decken den gesamten Projektzyklus ab, von der ersten Idee bis zur regulierten Lösung.</p>
</div>
<div class="fa-services-grid">
<div class="fa-service-card" data-reveal style="--reveal-delay:0ms">
<div class="fa-sc-num">01</div>
<div class="fa-sc-title">Data Science &amp; KI</div>
<p class="fa-sc-desc">LLM-Agents, RAG-Systeme, Computer Vision und Predictive Analytics von der Datenpipeline bis zum produktiven KI-System.</p>
<div class="fa-sc-tags">
<span>LLM / RAG</span><span>Agents</span><span>Computer Vision</span><span>Forecasting</span>
</div>
</div>
<div class="fa-service-card" data-reveal style="--reveal-delay:80ms">
<div class="fa-sc-num">02</div>
<div class="fa-sc-title">Regulatorik</div>
<p class="fa-sc-desc">Medizinproduktesoftware nach MDR, ISO 13485 und IEC 62304 von der technischen Dokumentation bis zur erfolgreichen Zulassung.</p>
<div class="fa-sc-tags">
<span>MDR</span><span>HL7 / FHIR</span><span>DICOM</span><span>IEC 62304</span>
</div>
</div>
<div class="fa-service-card" data-reveal style="--reveal-delay:160ms">
<div class="fa-sc-num">03</div>
<div class="fa-sc-title">App-Entwicklung</div>
<p class="fa-sc-desc">Native und Cross-Platform Apps, Backend-Systeme und Web-Frontends präzise implementiert, skalierbar gebaut, KI-ready von Anfang an.</p>
<div class="fa-sc-tags">
<span>Cross-Platform</span><span>Backend</span><span>Frontend</span><span>APIs</span>
</div>
</div>
<div class="fa-service-card" data-reveal style="--reveal-delay:240ms">
<div class="fa-sc-num">04</div>
<div class="fa-sc-title">Software Engineering</div>
<p class="fa-sc-desc">Systemarchitektur, Agile Methoden und technische Beratung für Projekte, bei denen Struktur über Erfolg oder Scheitern entscheidet.</p>
<div class="fa-sc-tags">
<span>Architektur</span><span>Scrum / SAFe</span><span>Tech Consulting</span><span>Reviews</span>
</div>
</div>
</div>
</section>
<!-- BOOKING / BERATUNGSTERMIN -->
<section class="fa-booking" id="kontakt">
<div class="fa-booking-inner">
<div class="fa-booking-content">
<div class="fa-booking-tag" data-reveal>Beratungstermin · 30 Min</div>
<h2 data-reveal style="--reveal-delay:80ms">In 30 Minuten sprechen wir<br>über <em>Ihren Use Case</em>.</h2>
<p data-reveal style="--reveal-delay:160ms">Kein Pitch, kein Verkaufsgespräch. Wir hören zu, stellen Fragen, und sagen Ihnen ehrlich, ob wir der richtige Partner sind oder nicht.</p>
<ul class="fa-booking-list" data-reveal style="--reveal-delay:240ms">
<li>
<span class="fa-booking-li-num">01</span>
<div>
<strong>Konkrete Machbarkeit</strong>
<p>Wir schätzen Aufwand, Risiken und Zeitrahmen Ihres Projekts ein nicht in Phrasen, sondern in Wochen.</p>
</div>
</li>
<li>
<span class="fa-booking-li-num">02</span>
<div>
<strong>Erste Architektur-Skizze</strong>
<p>Welche Methoden, Modelle und Werkzeuge wir für Ihre Anforderungen einsetzen würden.</p>
</div>
</li>
<li>
<span class="fa-booking-li-num">03</span>
<div>
<strong>Klare nächste Schritte</strong>
<p>Mit oder ohne uns weiter Sie verlassen das Gespräch mit einer Empfehlung, die unabhängig vom Auftrag funktioniert.</p>
</div>
</li>
</ul>
</div>
<!-- CALENDAR EMBED SLOT
To activate: replace the .fa-cal-placeholder block below with your booking iframe, e.g.
<iframe src="https://cal.com/your-handle/30min" style="width:100%;height:100%;border:0;"></iframe>
-->
<div class="fa-booking-cal" data-reveal style="--reveal-delay:120ms">
<div class="fa-cal-placeholder">
<div class="fa-cal-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="5" width="18" height="16" rx="1"></rect>
<line x1="3" y1="10" x2="21" y2="10"></line>
<line x1="8" y1="3" x2="8" y2="7"></line>
<line x1="16" y1="3" x2="16" y2="7"></line>
<circle cx="8" cy="15" r="1" fill="currentColor"></circle>
<circle cx="12" cy="15" r="1" fill="currentColor"></circle>
<circle cx="16" cy="15" r="1" fill="currentColor"></circle>
</svg>
</div>
<div class="fa-cal-label">Termin wählen</div>
<div class="fa-cal-title">30-Minuten-Slot</div>
<div class="fa-cal-sub">Hier läuft Ihr Booking-Embed (Cal.com, Calendly, o.ä.). Bis dahin schreiben Sie uns direkt wir antworten innerhalb eines Werktags.</div>
<div class="fa-cal-actions">
<a href="mailto:contact@frontieralgorithmics.com?subject=Beratungstermin%20%20Frontier%20Algorithmics" class="fa-btn-cal">Per E-Mail anfragen ↗</a>
<div class="fa-cal-tiny">
oder direkt: <a href="mailto:contact@frontieralgorithmics.com">contact@frontieralgorithmics.com</a>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- KI HIGHLIGHT -->
<section class="fa-ai-section" id="ki">
<div class="fa-ai-content">
<div class="fa-ai-tag" data-reveal>KI-Expertise</div>
<h2 data-reveal style="--reveal-delay:80ms">Intelligente Systeme,<br>die <em>echte Arbeit</em> leisten.</h2>
<p data-reveal style="--reveal-delay:160ms">Wir entwickeln keine Demos wir bauen KI-Systeme, die in produktiven Umgebungen funktionieren. Von autonomen Agenten bis zur regulierten medizinischen Bildanalyse.</p>
<a href="#kontakt" class="fa-btn-primary" data-reveal style="--reveal-delay:240ms; background:#fff; color:var(--fa-navy); text-decoration:none;">Beratungstermin vereinbaren</a>
</div>
<div class="fa-ai-capabilities" data-reveal style="--reveal-delay:200ms">
<div class="fa-ai-cap">
<div class="fa-ai-cap-icon"></div>
<h4>LLM-Agents &amp; RAG</h4>
<p>Autonome Agenten und retrieval-gestützte Systeme für komplexe Unternehmensanwendungen.</p>
</div>
<div class="fa-ai-cap">
<div class="fa-ai-cap-icon"></div>
<h4>Computer Vision</h4>
<p>Bildanalyse und Objekterkennung auch in regulierten Bereichen wie der Medizintechnik.</p>
</div>
<div class="fa-ai-cap">
<div class="fa-ai-cap-icon"></div>
<h4>Predictive Analytics</h4>
<p>Forecasting-Modelle und Anomalieerkennung auf Basis realer Produktionsdaten.</p>
</div>
<div class="fa-ai-cap">
<div class="fa-ai-cap-icon"></div>
<h4>MLOps &amp; Deployment</h4>
<p>Vom Notebook zur skalierbaren Produktionsinfrastruktur zuverlässig und wartbar.</p>
</div>
</div>
</section>
<!-- WHY -->
<section class="fa-why" id="why">
<div class="fa-why-visual" data-reveal>
<img src="assets/regensburg.jpg"
alt="Regensburg — Steinerne Brücke und Dom St. Peter"
class="fa-why-img"
loading="lazy">
<div class="fa-accent-bar">
<div class="city">RGN</div>
<div class="tagline">Bayern · DE</div>
</div>
<div class="fa-why-block">
<div class="fa-why-block-quote">„Wir bauen nicht nur Software wir tragen <em>Verantwortung</em> für das System dahinter."</div>
<div class="fa-why-block-sub">Frontier Algorithmics verbindet Ingenieursdenken mit unternehmerischer Verantwortung. Regensburg ist unser Zuhause Europa und darüber hinaus unser Markt.</div>
</div>
<a class="fa-photo-credit"
href="https://unsplash.com/de/@lecreusois"
target="_blank" rel="noopener">Foto · L. Niederwimmer / Unsplash</a>
</div>
<div class="fa-why-content">
<h2 data-reveal style="--reveal-delay:120ms">Warum Frontier<br>Algorithmics?</h2>
<div class="fa-points">
<div class="fa-point" data-reveal style="--reveal-delay:200ms">
<div class="fa-point-num">01</div>
<div class="fa-point-text">
<strong>KI mit Substanz, nicht mit Buzzwords</strong>
<p>Wir implementieren LLM-Agents, RAG-Systeme und Computer-Vision-Lösungen, die in der Praxis funktionieren nicht nur in der Demo.</p>
</div>
</div>
<div class="fa-point" data-reveal style="--reveal-delay:280ms">
<div class="fa-point-num">02</div>
<div class="fa-point-text">
<strong>Regulatorik trifft Technologie</strong>
<p>Wir sind eine der wenigen Firmen, die KI-Expertise und regulatorisches Know-how (MDR, IEC 62304) unter einem Dach vereinen.</p>
</div>
</div>
<div class="fa-point" data-reveal style="--reveal-delay:360ms">
<div class="fa-point-num">03</div>
<div class="fa-point-text">
<strong>Beweis statt Versprechen: Kontorion</strong>
<p>Wir haben ein eigenes Produkt entwickelt und am Markt platziert. Wir denken wie Unternehmer, nicht wie Dienstleister.</p>
</div>
</div>
</div>
</div>
</section>
<!-- KONTORION -->
<section class="fa-showcase" id="kontorion">
<div>
<div class="fa-showcase-label" data-reveal>Eigenes Produkt · Referenz</div>
<h2 data-reveal style="--reveal-delay:80ms">Kontorion unsere Billing-Plattform für den EU-Markt.</h2>
<p data-reveal style="--reveal-delay:160ms">Kontorion ist der Beweis unserer Leistungsfähigkeit: eine vollständig intern entwickelte SaaS-Plattform für Subscription-Billing, EU-Steuern, SEPA und Revenue Recognition gebaut für die regulatorische Realität von 27 Mitgliedsstaaten.</p>
<a href="https://kontorion.eu" target="_blank" rel="noopener" class="fa-btn-primary" data-reveal style="--reveal-delay:240ms">Mehr über Kontorion ↗</a>
</div>
<div class="fa-kontorion-card" data-reveal style="--reveal-delay:120ms">
<div class="fa-k-label">Produkt · kontorion.eu</div>
<div class="fa-k-title">Kontorion</div>
<p class="fa-k-desc">Subscription-Billing, EU-VAT/OSS, XRechnung, SEPA und IFRS 15 in einer Plattform. Nativ für Europa gebaut, nicht nachgerüstet.</p>
<div class="fa-k-pills">
<span class="fa-k-pill">EU-VAT / OSS</span>
<span class="fa-k-pill">SEPA</span>
<span class="fa-k-pill">XRechnung</span>
<span class="fa-k-pill">IFRS 15</span>
</div>
<a class="fa-k-link" href="https://kontorion.eu" target="_blank">kontorion.eu besuchen →</a>
</div>
</section>
<!-- CTA -->
<section class="fa-cta">
<div>
<h2 data-reveal>Bereit, das <em>nächste<br>komplexe Projekt</em> anzugehen?</h2>
<p class="fa-cta-sub" data-reveal style="--reveal-delay:100ms">Wir hören zu, bevor wir reden. Kein Proposal ohne Verständnis.</p>
</div>
<div class="fa-cta-actions" data-reveal style="--reveal-delay:200ms">
<a href="#kontakt" class="fa-btn-white">Beratungstermin vereinbaren</a>
<div class="fa-cta-loc">Regensburg · Bayern · International</div>
</div>
</section>
<!-- FOOTER -->
<footer class="fa-footer">
<div class="fa-footer-left">© 2025 Frontier Algorithmics · Regensburg</div>
<div class="fa-footer-links">
<a href="#">Impressum</a>
<a href="#">Datenschutz</a>
<a href="#">LinkedIn</a>
<a href="#">kontorion.eu</a>
</div>
</footer>
<script>
/* ============================================================
MOBILE NAV MENU TOGGLE
============================================================ */
(function() {
const nav = document.getElementById('fa-nav');
const btn = document.getElementById('fa-nav-menu');
const links = document.getElementById('fa-nav-links');
if (!nav || !btn || !links) return;
btn.addEventListener('click', () => {
const open = nav.classList.toggle('menu-open');
links.classList.toggle('menu-open', open);
btn.setAttribute('aria-expanded', String(open));
});
// Close on link click
links.querySelectorAll('a').forEach(a => {
a.addEventListener('click', () => {
nav.classList.remove('menu-open');
links.classList.remove('menu-open');
btn.setAttribute('aria-expanded', 'false');
});
});
})();
/* ============================================================
HERO SCROLL PROGRESS
Drives --hero-progress (0..1) as the user scrolls through the hero.
Used by the headline emphasis (underline grows, color saturates).
============================================================ */
(function() {
const hero = document.querySelector('.fa-hero');
if (!hero) return;
const root = document.documentElement;
let ticking = false;
function update() {
const rect = hero.getBoundingClientRect();
const h = rect.height;
// 0 when hero top hits viewport top; 1 when hero bottom hits viewport top.
// Begin advancing only after a small lead-in (~12%) so the underline
// entrance animation finishes cleanly before scroll takes over.
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 — two modes
- 'constellation' (default refined): 4 anchored hub nodes with
real labels, drifting around their anchors; sparse filler nodes;
edges only to nearest 2 neighbors.
- 'network' (original): bouncing dots, all-pairs short edges.
============================================================ */
(function() {
const canvas = document.getElementById('hero-canvas');
const ctx = canvas.getContext('2d');
const NAVY = '26, 31, 92';
const BLUE = '107, 143, 212';
const BLUE_LIGHT = '168, 192, 232';
let W, H, nodes = [], hubs = [];
let mode = 'constellation';
let mouse = { x: -9999, y: -9999 };
let rafId = null;
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 MODE ------- */
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) {
// Hubs drift around their anchor (spring physics)
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;
}
// Filler nodes drift with edge-wrapping
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);
// 1. Edges from each filler node to its nearest 2 neighbors
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;
const a = (1 - d / 160) * 0.25;
ctx.strokeStyle = `rgba(${BLUE}, ${a})`;
ctx.beginPath();
ctx.moveTo(n.x, n.y);
ctx.lineTo(o.x, o.y);
ctx.stroke();
}
}
// 2. Stronger edges between hubs and their nearest filler nodes
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;
const a = (1 - d / 260) * 0.55;
ctx.strokeStyle = `rgba(${BLUE}, ${a})`;
ctx.beginPath();
ctx.moveTo(h.x, h.y);
ctx.lineTo(o.x, o.y);
ctx.stroke();
}
}
// 3. Mouse pulls a bright connection to the 4 nearest nodes
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;
const a = (1 - d / 220) * 0.7;
ctx.strokeStyle = `rgba(${BLUE}, ${a})`;
ctx.beginPath();
ctx.moveTo(mouse.x, mouse.y);
ctx.lineTo(o.x, o.y);
ctx.stroke();
}
}
// 4. Filler dots — blue-tinted for visibility
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();
}
// 5. Hub nodes — halo + core + double pulse ring + label
const t = performance.now();
for (const h of hubs) {
// Outer pulse ring (slow, large)
const pulseT = ((t * 0.0007 + h.phase) % 1);
const pulseR = 8 + pulseT * 52;
ctx.beginPath();
ctx.arc(h.x, h.y, pulseR, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - pulseT) * 0.65})`;
ctx.lineWidth = 1.2;
ctx.stroke();
// Inner pulse ring (faster, smaller, offset phase)
const pulseT2 = ((t * 0.0011 + h.phase + 0.5) % 1);
const pulseR2 = 5 + pulseT2 * 28;
ctx.beginPath();
ctx.arc(h.x, h.y, pulseR2, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - pulseT2) * 0.45})`;
ctx.lineWidth = 0.8;
ctx.stroke();
// Glow halo
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();
// Core dot
ctx.beginPath();
ctx.arc(h.x, h.y, h.r, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${BLUE}, 1)`;
ctx.fill();
// Inner ring
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();
// Outer ring
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();
// Label tag
ctx.font = '700 11px "Syne", sans-serif';
const tw = ctx.measureText(h.label).width;
const lx = h.x + h.r + 12;
const ly = h.y - 5;
// tag background pill
ctx.fillStyle = `rgba(${BLUE}, 0.12)`;
ctx.fillRect(lx - 4, ly - 2, tw + 8, 16);
// tag border
ctx.strokeStyle = `rgba(${BLUE}, 0.4)`;
ctx.lineWidth = 0.6;
ctx.strokeRect(lx - 4, ly - 2, tw + 8, 16);
// label text
ctx.fillStyle = `rgba(${NAVY}, 0.95)`;
ctx.fillText(h.label, lx, ly + 9);
}
}
/* ------- ORIGINAL NETWORK MODE ------- */
function initNetwork() {
nodes = [];
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)]
});
}
hubs = [];
}
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;
const dy = nodes[i].y - nodes[j].y;
const d = Math.sqrt(dx*dx + dy*dy);
if (d < 130) {
const a = (1 - d / 130) * 0.18;
ctx.strokeStyle = `rgba(${BLUE}, ${a})`;
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;
const mdy = nodes[i].y - mouse.y;
const md = Math.sqrt(mdx*mdx + mdy*mdy);
if (md < 160) {
const a = (1 - md / 160) * 0.35;
ctx.strokeStyle = `rgba(${BLUE}, ${a})`;
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 MODE ------- */
let flowParticles = [];
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),
});
}
// Paint a fresh white so we don't carry over residue from another mode
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, W, H);
}
function flowAngle(x, y, t) {
const a = Math.sin(x * 0.0045 + t * 0.00025);
const b = Math.cos(y * 0.0042 - t * 0.00030);
const c = Math.sin((x + y) * 0.0023 + t * 0.00040);
return (a + b + c) * 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;
// Mouse repulsion
if (mouse.x > -9000) {
const dx = p.x - mouse.x, dy = p.y - mouse.y;
const 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() {
// Soft fade of previous frame instead of clear -> trails
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;
const a = Math.sin(lr * Math.PI) * 0.7; // fade in & out
// Mix navy + blue based on life position
const useBlue = ((p.x + p.y) | 0) % 3 !== 0;
ctx.strokeStyle = useBlue
? `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 MODE ------- */
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;
const 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];
// Center thresholds slightly stronger
const mid = (thresholds.length - 1) / 2;
const closeness = 1 - Math.abs(ti - mid) / mid;
const a = 0.13 + closeness * 0.18;
ctx.strokeStyle = `rgba(${BLUE}, ${a})`;
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];
const tr = topoField[j * topoCols + i + 1];
const br = topoField[(j + 1) * topoCols + i + 1];
const 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;
// Linear-interpolated mid-edge crossings
const tx = x + s * (thr - tl) / (tr - tl);
const rx = x + s, ry = y + s * (thr - tr) / (br - tr);
const bx = x + s * (thr - bl) / (br - bl), by = y + s;
const 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();
}
// Subtle elevation markers at field peaks (just a few small dots)
ctx.fillStyle = `rgba(${BLUE}, 0.55)`;
for (let j = 2; j < topoRows - 2; j += 4) {
for (let i = 2; i < topoCols - 2; i += 4) {
const v = topoField[j * topoCols + i];
if (v > 1.8) {
ctx.beginPath();
ctx.arc(i * TOPO_RES, j * TOPO_RES, 2, 0, Math.PI * 2);
ctx.fill();
}
}
}
}
/* ------- LOOP & PUBLIC 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');
const cnvGL = document.getElementById('hero-canvas-gl');
const hero = document.querySelector('.fa-hero');
// GL-driven modes
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;
}
// 2D modes
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 — Plasma + Mercury, brand-tuned
Renders into a SECOND canvas overlaid on the 2D one.
Outputs RGBA (alpha-blended) so effects sit over the white hero.
============================================================ */
(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);
vec2 u = f*f*(3.0-2.0*f);
float a = hash21(i);
float b = hash21(i + vec2(1.0, 0.0));
float c = hash21(i + vec2(0.0, 1.0));
float d = hash21(i + vec2(1.0, 1.0));
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;
}
`;
// ── PLASMA HERO (navy/blue glow following cursor over white) ──
const FS_PLASMA = `
void main() {
vec2 frag = gl_FragCoord.xy;
float scale = min(uRes.x, uRes.y);
vec2 uv = (frag - 0.5 * uRes) / scale;
vec2 m = (uMouse - 0.5 * uRes) / scale;
// Orb anchored softly to cursor (drift to center if cursor leaves)
vec2 center = m * 0.55;
vec2 p = uv - center;
float r = length(p);
float a = atan(p.y, p.x);
// Corona: polar-domain warped noise
float 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));
float outer = exp(-r * 2.8) * (0.55 + 0.5 * corona);
// Solar arc filaments (subtle)
float 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);
// Brand palette
vec3 navy = vec3(0.102, 0.122, 0.361);
vec3 blue = vec3(0.420, 0.561, 0.831);
vec3 lblue = vec3(0.659, 0.753, 0.910);
// Color: deep navy at core, blue corona, light-blue filaments
vec3 col = mix(blue, navy, smoothstep(0.0, 0.25, r));
col = mix(col, lblue, fil);
// Intensity controls alpha (so the white hero shows through)
float a1 = core * 0.65 + outer * 0.55 + fil * 0.45;
a1 = clamp(a1, 0.0, 0.85);
gl_FragColor = vec4(col, a1);
}
`;
// ── CRYSTAL HERO (voronoi lattice tuned to brand on white) ──
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));
vec2 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;
vec2 m = uMouse / scale;
vec2 d = uv - m;
float warp = 0.45 * exp(-length(d) * 2.2);
vec2 uvw = uv + d * warp;
vec3 vd = voronoiB(uvw * 3.5);
float d1 = vd.x, d2 = vd.y, cellId = vd.z;
float edge = d2 - d1;
vec3 navy = vec3(0.102, 0.122, 0.361);
vec3 blue = vec3(0.420, 0.561, 0.831);
vec3 lblue = vec3(0.659, 0.753, 0.910);
vec3 pale = vec3(0.85, 0.90, 0.97);
vec3 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);
float caustic = smoothstep(0.5, 0.95, sin(d1 * 28.0 + uTime + cellId * 14.0));
caustic *= (1.0 - edgeMask) * 0.3;
vec3 col = fill + edgeCol * edgeMask * 1.0 + lblue * caustic;
col = mix(col, mix(blue, lblue, 0.4), hovered * (1.0 - edgeMask) * 0.45);
float fillAlpha = 0.18 + boundary * 0.18 + hovered * 0.12;
float a1 = max(fillAlpha, edgeMask * 0.85);
a1 = clamp(a1, 0.0, 0.85);
gl_FragColor = vec4(col, a1);
}
`;
// ── MERCURY HERO (navy/blue metaballs over white) ──
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;
vec2 m = (uMouse - 0.5 * uRes) / scale;
// Six drifting metaballs + one tied to cursor + one mirror
vec2 b0 = vec2(sin(uTime * 0.27) * 0.42, cos(uTime * 0.23) * 0.28);
vec2 b1 = vec2(cos(uTime * 0.35) * 0.48, sin(uTime * 0.21) * 0.26);
vec2 b2 = vec2(sin(uTime * 0.19 + 2.0) * 0.5, cos(uTime * 0.31 + 1.0) * 0.36);
vec2 b3 = vec2(cos(uTime * 0.25 + 4.0) * 0.36, sin(uTime * 0.17 + 3.0) * 0.44);
vec2 b4 = m;
vec2 b5 = -m * 0.4;
float f = 0.0;
f += metaball(uv, b0, 0.16);
f += metaball(uv, b1, 0.18);
f += metaball(uv, b2, 0.13);
f += metaball(uv, b3, 0.15);
f += metaball(uv, b4, 0.22); // cursor blob — biggest
f += metaball(uv, b5, 0.10);
float surface = smoothstep(2.3, 2.85, f);
float halo = smoothstep(1.6, 2.3, f) - surface;
// Normal approximation via finite difference
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;
float 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));
// Brand palette
vec3 navy = vec3(0.102, 0.122, 0.361);
vec3 blue = vec3(0.420, 0.561, 0.831);
vec3 lblue = vec3(0.659, 0.753, 0.910);
vec3 pale = vec3(0.93, 0.95, 0.99);
// Iridescence: navy → blue → light-blue based on normal angle
float ang = atan(n.y, n.x);
float t1 = sin(ang * 2.0 + uTime * 0.5) * 0.5 + 0.5;
float 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);
// Fresnel-ish edge highlight
float fres = pow(1.0 - abs(n.y), 1.6);
surfCol = mix(surfCol, pale, fres * 0.5);
// Spec highlight
float spec = pow(max(dot(n, normalize(vec2(0.5, 0.8))), 0.0), 18.0);
surfCol += vec3(spec) * 0.4;
// Edge ring (subtle blue glow around the merge surface)
vec3 haloCol = mix(blue, lblue, 0.6);
// Composition: opaque over white where surface is solid; soft halo around
vec3 col = mix(haloCol, surfCol, surface);
float a1 = surface * 0.92 + halo * 0.35;
a1 = clamp(a1, 0.0, 0.95);
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);
const 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 mode = 'off';
let running = false;
let rafId = null;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const mouse = { x: -9999, y: -9999 };
const mouseSmooth = { x: 0, y: 0 };
function resize() {
const rect = canvas.parentElement.getBoundingClientRect();
const w = Math.floor(rect.width * dpr);
const 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;
// GL origin is bottom-left
mouse.y = (rect.height - (e.clientY - rect.top)) * dpr;
});
canvas.parentElement.addEventListener('mouseleave', () => {
mouse.x = -9999; mouse.y = -9999;
});
// Initialize smooth target to center so first frame isn't off-screen
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 loop() {
if (!running) return;
resize();
const now = performance.now();
const dt = (now - lastFrame) / 1000;
lastFrame = now;
const t = (now - startMs) / 1000;
// Smooth mouse (or drift toward center when out)
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(mode);
if (!p) { rafId = requestAnimationFrame(loop); 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);
rafId = requestAnimationFrame(loop);
}
function setGLMode(m) {
if (m === mode) return;
mode = m;
if (m === 'off') {
running = false;
cancelAnimationFrame(rafId); rafId = null;
gl.clear(gl.COLOR_BUFFER_BIT);
return;
}
if (m === 'plasma' || m === 'mercury' || m === 'crystal') {
getProgram(m); // compile if needed
initCenterMouse();
if (!running) {
running = true;
lastFrame = performance.now();
cancelAnimationFrame(rafId);
loop();
}
}
}
window.__faSetGLMode = setGLMode;
window.addEventListener('resize', resize);
resize();
})();
/* ============================================================
TWEAKS PANEL
============================================================ */
(function() {
const DEFAULTS = /*EDITMODE-BEGIN*/{
"canvasMode": "mercury",
"visualMode": "none",
"emphasis": "underline"
}/*EDITMODE-END*/;
const state = { ...DEFAULTS };
// Panel
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() {
// Canvas
if (window.__faSetCanvasMode) window.__faSetCanvasMode(state.canvasMode);
// Visual
const cap = document.getElementById('fa-cap-card');
const stats = document.getElementById('fa-stat-stack');
const visual = document.getElementById('fa-hero-visual');
const hero = document.querySelector('.fa-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 { // none
visual.style.display = 'none';
hero.classList.add('no-visual');
}
// Trigger canvas resize so it spans the new hero width
window.dispatchEvent(new Event('resize'));
// Emphasis
const h1 = document.getElementById('fa-headline');
const 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');
// Highlight active buttons
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');
const key = seg.dataset.key;
state[key] = btn.dataset.v;
apply();
try {
window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [key]: btn.dataset.v } }, '*');
} catch(e) {}
});
});
// Close button
document.getElementById('fa-tweaks-close').addEventListener('click', () => {
panel.style.display = 'none';
try { window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*'); } catch(e){}
});
// Drag
(function makeDraggable() {
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);
})();
// Edit-mode protocol
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 initial state on load
// (canvas already initialized; this aligns visual + emphasis)
apply();
})();
/* ============================================================
SCROLL REVEAL — IntersectionObserver
============================================================ */
(function() {
const observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('[data-reveal]').forEach(function(el) {
observer.observe(el);
});
})();
</script>
</body>
</html>