FeedGine launch

This commit is contained in:
2026-04-22 10:57:37 +02:00
commit 32b6ceba80
26 changed files with 4620 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
{
"permissions": {
"allow": [
"Bash(wc -l \"/c/projekt/Profice WebSite\"/style/*.css \"/c/projekt/Profice WebSite\"/scripts/*.js)",
"Bash(find '/c/projekt/Profice WebSite' -name *.js -o -name *.css)",
"Bash(xargs wc:*)",
"Bash(grep -E \"\\\\.html$\")",
"Bash(cp 'C:/projekt/Profice WebSite/images/logo/logo-01-complete.png' C:/Users/mvita/Desktop/Alles/FEEDGINE/images/logo/)",
"Bash(cp 'C:/projekt/Profice WebSite/images/logo/Appicon 1024X1024-01.png' C:/Users/mvita/Desktop/Alles/FEEDGINE/images/logo/)",
"Bash(cp 'C:/projekt/Profice WebSite/images/icons/spider.png' C:/Users/mvita/Desktop/Alles/FEEDGINE/images/icons/)",
"Bash(cp \"C:/projekt/Profice WebSite/images/icons/email_white.png\" \"C:/Users/mvita/Desktop/Alles/FEEDGINE/images/icons/\")",
"Bash(cp \"C:/projekt/Profice WebSite/images/icons/instagram.png\" \"C:/Users/mvita/Desktop/Alles/FEEDGINE/images/icons/\")",
"Bash(cp \"C:/projekt/Profice WebSite/images/icons/facebook.png\" \"C:/Users/mvita/Desktop/Alles/FEEDGINE/images/icons/\")",
"Bash(cp \"C:/projekt/Profice WebSite/images/icons/KI.png\" \"C:/Users/mvita/Desktop/Alles/FEEDGINE/images/icons/\")",
"Bash(cp \"C:/projekt/Profice WebSite/images/additional/cursor.png\" \"C:/Users/mvita/Desktop/Alles/FEEDGINE/images/additional/\")",
"Bash(cp \"C:/projekt/Profice WebSite/scripts/hex-background.js\" \"C:/Users/mvita/Desktop/Alles/FEEDGINE/scripts/\")",
"Bash(cp \"C:/projekt/Profice WebSite/scripts/feed-calculator.js\" \"C:/Users/mvita/Desktop/Alles/FEEDGINE/scripts/\")",
"Bash(cp:*)"
]
}
}

51
.htaccess Normal file
View File

@@ -0,0 +1,51 @@
# Feedgine — Apache Configuration
# Security: disable directory listing
Options -Indexes
# Flowise chat proxy
RewriteEngine On
RewriteRule ^api/v1/prediction/(.*)$ scripts/add/flowise-proxy.php?path=$1 [L,QSA]
# HTTPS redirect (enable in production)
# RewriteCond %{HTTPS} off
# RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# GZIP compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json
AddOutputFilterByType DEFLATE image/svg+xml font/ttf font/woff font/woff2
</IfModule>
# Browser caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType font/ttf "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 month"
ExpiresByType text/html "access plus 1 hour"
</IfModule>
# Security headers
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# X-Frame-Options set per-page where needed
</IfModule>
# Protect config file
<Files "config.php">
Order allow,deny
Deny from all
</Files>
# Protect data directory
<FilesMatch "\.json$">
Order allow,deny
Deny from all
</FilesMatch>

7
Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY . /usr/share/nginx/html
EXPOSE 80

68
config.php Normal file
View File

@@ -0,0 +1,68 @@
<?php
/**
* Feedgine — Centralized Credentials & Configuration
* ALL SENSITIVE DATA IS STORED HERE — NOT VISIBLE TO CLIENT
*
* Include this file in send.php: require_once dirname(__DIR__, 2) . '/config.php';
*/
// ============================================================
// ENVIRONMENT
// ============================================================
define('USE_PRODUCTION', false); // set to true on live server
define('DEBUG_MODE', true); // set to false on live server
// ============================================================
// N8N WEBHOOKS
// ============================================================
define('FEEDGINE_WEBHOOK_TEST', 'https://n8n.support-space.de/webhook-test/REPLACE_WITH_FEEDGINE_WEBHOOK');
define('FEEDGINE_WEBHOOK_PROD', 'https://n8n.support-space.de/webhook/REPLACE_WITH_FEEDGINE_WEBHOOK');
define('FEEDGINE_WEBHOOK_URL', USE_PRODUCTION ? FEEDGINE_WEBHOOK_PROD : FEEDGINE_WEBHOOK_TEST);
// KI Chat Webhook
define('KI_CHAT_WEBHOOK_TEST', 'https://n8n.support-space.de/webhook-test/8a25bce2-ff83-4676-a3a2-a0e1174fcffe');
define('KI_CHAT_WEBHOOK_PROD', 'https://n8n.support-space.de/webhook/8a25bce2-ff83-4676-a3a2-a0e1174fcffe');
define('KI_CHAT_WEBHOOK_URL', USE_PRODUCTION ? KI_CHAT_WEBHOOK_PROD : KI_CHAT_WEBHOOK_TEST);
// ============================================================
// GOOGLE
// ============================================================
define('GA_MEASUREMENT_ID', 'G-XXXXXXXXXX'); // Google Analytics 4 Measurement ID
define('GA_API_SECRET', ''); // GA4 Measurement Protocol API Secret
define('GTM_CONTAINER_ID', 'GTM-XXXXXXX'); // Google Tag Manager container ID
define('GADS_CONVERSION_ID', 'AW-XXXXXXXXXX'); // Google Ads Conversion ID
define('GADS_CONVERSION_LABEL', ''); // Google Ads Conversion Label
// Flowise Chatbot
define('FLOWISE_CHATFLOW_ID', 'd63d3d02-b5fa-482c-9161-c21c615fb625');
define('FLOWISE_API_HOST', 'https://flowise.profice.de');
// ============================================================
// META / FACEBOOK
// ============================================================
define('FB_PIXEL_ID', ''); // Facebook Pixel ID
define('FB_ACCESS_TOKEN', ''); // Facebook Conversions API Access Token
// ============================================================
// LINKEDIN
// ============================================================
define('LINKEDIN_PARTNER_ID', '');
// ============================================================
// GENERAL API SECURITY
// ============================================================
define('API_SECRET_KEY', 'REPLACE_WITH_STRONG_RANDOM_KEY');
define('RATE_LIMIT_REQUESTS', 100); // max requests per window
define('RATE_LIMIT_WINDOW', 3600); // window in seconds (1 hour)
// ============================================================
// ALLOWED ORIGINS (CORS)
// ============================================================
define('ALLOWED_ORIGINS', serialize([
'https://feedgine.de',
'https://www.feedgine.de',
'https://profice.ai',
'https://www.profice.ai',
'http://localhost',
'http://127.0.0.1',
]));

BIN
fonts/montserrat-bold.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
images/icons/KI.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
images/icons/facebook.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 MiB

BIN
images/icons/instagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 MiB

BIN
images/icons/spider.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

908
index.html Normal file
View File

@@ -0,0 +1,908 @@
<!DOCTYPE html>
<html lang="de">
<head>
<!-- Cookiebot — consent management -->
<script id="Cookiebot" src="https://consent.cookiebot.com/uc.js"
data-cbid="d6499c25-b3c2-41ac-8edf-60a4209f74c9"
data-blockingmode="auto" type="text/javascript"></script>
<script>
window.addEventListener('CookiebotOnDialogDisplay', function () {
var d = document.getElementById('CybotCookiebotDialog');
if (d) {
d.style.setProperty('position', 'fixed', 'important');
d.style.setProperty('bottom', '0', 'important');
d.style.setProperty('top', 'auto', 'important');
d.style.setProperty('left', '0', 'important');
d.style.setProperty('right', '0', 'important');
d.style.setProperty('width', '100%', 'important');
d.style.setProperty('transform','none', 'important');
d.style.setProperty('margin', '0', 'important');
}
});
</script>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-PNJK5FN7');</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#2E2929">
<meta name="description" content="Feedgine — Profit Intelligence Platform. Verbindet JTL-Wawi-Margen mit Google Shopping, Meta &amp; Microsoft Ads. POAS statt ROAS. Server-Side Tracking. Ab €999/Monat.">
<meta name="keywords" content="Feedgine, POAS, Profit Intelligence, Google Shopping, Server Side Tracking, Feed Optimierung, E-Commerce">
<meta name="author" content="Profice GmbH">
<meta property="og:title" content="Feedgine — Profit Intelligence Platform">
<meta property="og:description" content="ROAS lügt. Profit zählt. Feedgine berechnet echte Produktmargen und optimiert Feeds für POAS. +31% profitabler Umsatz bei lkw-teile24.de.">
<meta property="og:type" content="website">
<meta name="robots" content="index, follow">
<title>Feedgine — Profit Intelligence Platform by Profice</title>
<!-- Favicons -->
<link rel="icon" type="image/png" sizes="32x32" href="images/logo/Appicon 1024X1024-01.png">
<link rel="apple-touch-icon" sizes="180x180" href="images/logo/Appicon 1024X1024-01.png">
<link rel="shortcut icon" href="images/logo/Appicon 1024X1024-01.png">
<!-- Fonts: local Montserrat + Google Fonts -->
<link rel="stylesheet" href="style/fonts.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Raleway:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
/* ================================================================
FEEDGINE — Profice Home Design System
Palette: Warm Dark · Amber · Teal · Green
================================================================ */
/* ─── ROOT ─── */
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
:root{
--charcoal:#4F4747;--slate:#777764;--ivory:#EBEBDE;
--amber:#F57C00;--teal:#26A69A;--green:#66BB6A;
--bg:#0e0d0c;--bg2:#161514;--bg3:#1c1b19;
--text:#d4d0c8;--text-dim:#7a7668;--text-bright:#f5f2ea;
--mono:'Space Mono','Courier New',monospace;
--head:'Montserrat','Segoe UI',sans-serif;
--body:'Raleway','Montserrat',sans-serif;
}
html{scroll-behavior:smooth;font-size:16px}
body{background:var(--bg);color:var(--text);font-family:var(--body);overflow-x:hidden;-webkit-font-smoothing:antialiased}
::selection{background:var(--amber);color:var(--bg)}
/* ─── GRAIN OVERLAY ─── */
body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:9999;
background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
opacity:.35}
/* ─── NAV ─── */
nav{position:fixed;top:0;left:0;right:0;z-index:100;padding:0.6rem 3rem;display:flex;justify-content:space-between;align-items:center;
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
background:rgba(14,13,12,.75);border-bottom:1px solid rgba(235,235,222,.05)}
.nav-logo{text-decoration:none;display:flex;align-items:center}
.nav-logo img{height:44px;display:block;width:auto}
nav ul{list-style:none;display:flex;gap:2rem;align-items:center}
nav a{color:var(--text-dim);text-decoration:none;font-family:var(--head);font-size:.78rem;letter-spacing:.1em;text-transform:uppercase;transition:color .3s}
nav a:hover{color:var(--amber)}
.nav-cta{color:var(--amber)!important;border:1px solid var(--amber);padding:.45rem 1.2rem;transition:all .3s}
.nav-cta:hover{background:var(--amber);color:var(--bg)!important}
/* Hamburger */
.hamburger{display:none;flex-direction:column;gap:5px;background:transparent;border:1px solid rgba(235,235,222,.25);
border-radius:6px;cursor:pointer;padding:8px 10px;width:42px;height:42px;transition:border-color .2s}
.hamburger:hover{border-color:var(--amber)}
.hamburger span{display:block;width:18px;height:2px;background:var(--text);border-radius:1px;transition:transform .3s,opacity .3s;transform-origin:center}
.hamburger.open span:nth-child(1){transform:translateY(7px) rotate(45deg)}
.hamburger.open span:nth-child(2){opacity:0}
.hamburger.open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
/* Mobile nav */
.mobile-nav{display:none;position:fixed;top:62px;left:0;right:0;background:rgba(14,13,12,.97);
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
z-index:99;flex-direction:column;gap:0;border-bottom:1px solid rgba(235,235,222,.08);
padding:1rem 1.5rem 1.5rem}
.mobile-nav.open{display:flex}
.mobile-nav a{display:block;padding:.8rem .5rem;color:var(--text-dim);text-decoration:none;font-family:var(--head);
font-size:.85rem;letter-spacing:.08em;text-transform:uppercase;border-bottom:1px solid rgba(235,235,222,.04);transition:color .2s}
.mobile-nav a:hover{color:var(--amber)}
.mobile-nav .mobile-cta{margin-top:.75rem;text-align:center;border:1px solid var(--amber);
color:var(--amber)!important;border-bottom:1px solid var(--amber)!important;padding:.8rem!important}
.overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:90}
.overlay.active{display:block}
/* ─── HERO ─── */
.hero{min-height:100vh;display:grid;grid-template-columns:1fr 400px;gap:4rem;align-items:center;
padding:8rem 3rem 4rem;max-width:1240px;margin:0 auto;position:relative}
.hero::before{content:'';position:absolute;top:-20%;right:-10%;width:70vw;height:70vw;
background:radial-gradient(circle,rgba(245,124,0,.05) 0%,transparent 65%);pointer-events:none}
.hero-label{font-family:var(--head);font-size:.7rem;letter-spacing:.3em;text-transform:uppercase;color:var(--amber);
margin-bottom:2rem;display:flex;align-items:center;gap:1rem}
.hero-label::before{content:'';width:3rem;height:1px;background:var(--amber)}
.hero h1{font-family:var(--head);font-weight:900;font-size:clamp(2.8rem,7vw,5.5rem);line-height:1.05;
color:var(--text-bright);max-width:14ch;margin-bottom:2rem}
.hero h1 em{font-style:normal;color:var(--amber);position:relative}
.hero h1 em::after{content:'';position:absolute;bottom:.08em;left:0;right:0;height:3px;background:var(--amber);opacity:.35}
.hero-sub{font-family:var(--body);font-size:1.1rem;line-height:1.8;color:var(--text-dim);max-width:44ch;margin-bottom:2.5rem;font-weight:300}
.hero-sub strong{color:var(--text);font-weight:600}
.hero-actions{display:flex;gap:1rem;flex-wrap:wrap}
/* Buttons */
.cta-btn{display:inline-block;font-family:var(--head);font-weight:700;font-size:.82rem;letter-spacing:.12em;text-transform:uppercase;
text-decoration:none;color:var(--bg);background:var(--amber);padding:1rem 2.5rem;transition:all .3s;border:none;cursor:pointer}
.cta-btn:hover{background:var(--text-bright);color:var(--bg)}
.cta-btn::after{content:'→';margin-left:.75rem;transition:transform .3s;display:inline-block}
.cta-btn:hover::after{transform:translateX(4px)}
.btn-ghost{display:inline-block;font-family:var(--head);font-weight:600;font-size:.82rem;letter-spacing:.1em;text-transform:uppercase;
text-decoration:none;color:var(--text-dim);border:1px solid rgba(235,235,222,.2);padding:1rem 2rem;transition:all .3s}
.btn-ghost:hover{color:var(--amber);border-color:var(--amber)}
/* ─── TERMINAL CARD (hero right) ─── */
.hero-terminal{background:var(--bg2);border:1px solid rgba(235,235,222,.08);padding:1.75rem;border-radius:4px}
.t-header{display:flex;align-items:center;gap:.45rem;margin-bottom:1.5rem;padding-bottom:1rem;border-bottom:1px solid rgba(235,235,222,.06)}
.t-dot{width:8px;height:8px;border-radius:50%}
.t-dot.r{background:#FF5F57}.t-dot.y{background:#FEBC2E}.t-dot.g{background:#28C840}
.t-label{font-family:var(--mono);font-size:.62rem;color:var(--text-dim);margin-left:auto;letter-spacing:.08em}
.t-row{display:flex;justify-content:space-between;align-items:center;padding:.65rem 0;
border-bottom:1px solid rgba(235,235,222,.04);font-family:var(--mono);font-size:.73rem}
.t-row:last-of-type{border-bottom:none}
.t-key{color:var(--text-dim)}.t-val{font-weight:700;color:var(--text)}
.t-val.g{color:var(--green)}.t-val.a{color:var(--amber)}.t-val.r{color:#ef9a9a}
.t-tag{font-family:var(--mono);font-size:.58rem;padding:2px 7px;font-weight:700;letter-spacing:.04em;margin-left:6px;border-radius:2px}
.t-tag.pos{background:rgba(102,187,106,.12);color:var(--green)}
.t-tag.warn{background:rgba(245,124,0,.12);color:var(--amber)}
.t-tag.neg{background:rgba(239,154,154,.12);color:#ef9a9a}
.t-footer{margin-top:1.25rem;padding-top:1rem;border-top:1px solid rgba(235,235,222,.06);
display:flex;justify-content:space-between;font-family:var(--mono);font-size:.65rem}
.t-footer .key{color:var(--text-dim)}.t-footer .val{color:var(--teal);font-weight:700}
/* ─── PROOF STRIP ─── */
.proof-strip{background:var(--bg3);border-top:1px solid rgba(235,235,222,.04);border-bottom:1px solid rgba(235,235,222,.04);padding:4rem 3rem}
.proof-inner{max-width:1240px;margin:0 auto;display:grid;grid-template-columns:repeat(4,1fr);gap:2rem}
.proof-label{width:100%;text-align:center;font-family:var(--mono);font-size:.68rem;color:var(--text-dim);
margin-bottom:1.5rem;letter-spacing:.08em;text-transform:uppercase;grid-column:1/-1}
.proof-stat{}
.proof-num{font-family:var(--head);font-size:2.4rem;font-weight:900;color:var(--amber);letter-spacing:-.03em;line-height:1;margin-bottom:.4rem}
.proof-desc{font-family:var(--body);font-size:.83rem;color:var(--text-dim);line-height:1.5;font-weight:300}
/* ─── DIVIDER ─── */
hr.divider{border:none;border-top:1px solid rgba(235,235,222,.06);margin:0}
/* ─── SECTIONS ─── */
.section{max-width:1240px;margin:0 auto;padding:6rem 3rem}
.label-tag{font-family:var(--head);font-size:.68rem;letter-spacing:.3em;text-transform:uppercase;color:var(--amber);
margin-bottom:1.5rem;display:flex;align-items:center;gap:1rem}
.label-tag::before{content:'';width:3rem;height:1px;background:var(--amber)}
.section-title{font-family:var(--head);font-weight:800;font-size:clamp(1.8rem,4vw,3rem);color:var(--text-bright);
line-height:1.15;margin-bottom:1.25rem}
.section-title em{font-style:normal;color:var(--amber)}
.section-lead{font-family:var(--body);font-size:1.05rem;color:var(--text-dim);max-width:52ch;line-height:1.8;
font-weight:300;margin-bottom:3.5rem}
/* ─── PROBLEM / PROFILE CARDS ─── */
.problem-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:2rem}
.prob-card{border-left:2px solid var(--slate);padding:2rem 2rem 2rem 2.5rem;transition:border-color .3s}
.prob-card:hover{border-color:var(--amber)}
.prob-num{font-family:var(--head);font-size:.65rem;letter-spacing:.2em;text-transform:uppercase;color:var(--amber);
opacity:.75;margin-bottom:1.25rem}
.prob-icon{width:52px;height:52px;border-radius:6px;background:rgba(245,124,0,.06);border:1px solid rgba(245,124,0,.15);
display:flex;align-items:center;justify-content:center;margin-bottom:1.25rem;color:var(--amber)}
.prob-title{font-family:var(--head);font-weight:700;font-size:1.1rem;color:var(--text-bright);margin-bottom:.75rem}
.prob-text{font-family:var(--body);font-size:.88rem;color:var(--text-dim);line-height:1.75;font-weight:300}
/* ─── HOW IT WORKS ─── */
.how-grid{display:grid;grid-template-columns:1fr 360px;gap:4rem;align-items:start;margin-top:3rem}
.how-step{display:flex;gap:1.5rem;padding:1.25rem 0;border-bottom:1px solid rgba(235,235,222,.05);transition:border-color .2s}
.how-step:last-child{border-bottom:none}
.how-step:hover{border-color:rgba(245,124,0,.2)}
.step-num{font-family:var(--mono);font-size:.62rem;color:var(--amber);min-width:22px;padding-top:4px;letter-spacing:.06em}
.step-title{font-family:var(--head);font-weight:700;font-size:.95rem;color:var(--text-bright);margin-bottom:.35rem}
.step-desc{font-family:var(--body);font-size:.87rem;color:var(--text-dim);line-height:1.65;font-weight:300}
/* Visual / POAS panel */
.how-visual{background:var(--bg2);border:1px solid rgba(235,235,222,.07);padding:2rem;position:sticky;top:5rem}
.vis-label{font-family:var(--mono);font-size:.6rem;color:var(--text-dim);letter-spacing:.1em;text-transform:uppercase;margin-bottom:1.5rem}
.poas-wrap{margin-bottom:1.1rem}
.poas-bar-label{display:flex;justify-content:space-between;font-family:var(--mono);font-size:.68rem;color:var(--text-dim);margin-bottom:.4rem}
.poas-bar-label span:last-child{color:var(--text)}
.poas-track{height:4px;background:rgba(235,235,222,.07);overflow:hidden;border-radius:2px}
.poas-fill{height:100%;border-radius:2px;transition:width 1.4s cubic-bezier(.16,1,.3,1)}
.poas-fill.g{background:var(--green)}
.poas-fill.a{background:var(--amber)}
.poas-fill.r{background:#ef9a9a;width:18%!important}
.vis-divider{height:1px;background:rgba(235,235,222,.06);margin:1.5rem 0}
.vis-row{display:flex;justify-content:space-between;padding:.38rem 0;font-family:var(--mono);font-size:.7rem}
.vis-k{color:var(--text-dim)}.vis-v{color:var(--text);font-weight:700}
.vis-v.g{color:var(--green)}.vis-v.r{color:#ef9a9a}
/* ─── MODULES ─── */
.modules-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:rgba(235,235,222,.06)}
.module-card{background:var(--bg);padding:3rem 2.5rem;position:relative;transition:background .3s}
.module-card:hover{background:var(--bg2)}
.module-num{font-family:var(--head);font-size:.62rem;letter-spacing:.2em;text-transform:uppercase;color:var(--amber);opacity:.7;margin-bottom:1.5rem}
.module-stack-num{font-family:var(--head);font-weight:800;font-size:3rem;color:rgba(245,124,0,.08);
position:absolute;top:1.5rem;right:1.5rem;line-height:1;user-select:none}
.module-title{font-family:var(--head);font-weight:700;font-size:1.1rem;color:var(--text-bright);margin-bottom:.75rem}
.module-desc{font-family:var(--body);font-size:.87rem;color:var(--text-dim);line-height:1.75;font-weight:300;margin-bottom:1.25rem}
.module-features{list-style:none;display:flex;flex-direction:column;gap:.4rem}
.module-features li{font-family:var(--mono);font-size:.75rem;color:var(--text-dim);padding-left:1.25rem;position:relative;font-weight:400}
.module-features li::before{content:'→';position:absolute;left:0;color:var(--amber)}
/* ─── COMPARE TABLE ─── */
.compare-wrap{background:var(--bg2);border:1px solid rgba(235,235,222,.06);overflow:hidden;overflow-x:auto;-webkit-overflow-scrolling:touch}
.compare-table{width:100%;border-collapse:collapse;font-size:.87rem;min-width:420px}
.compare-table th{padding:1rem 1.5rem;text-align:left;font-family:var(--head);font-size:.65rem;font-weight:700;
color:var(--text-dim);letter-spacing:.1em;text-transform:uppercase;border-bottom:1px solid rgba(235,235,222,.06);background:rgba(235,235,222,.02)}
.compare-table th.accent{color:var(--amber);background:rgba(245,124,0,.04)}
.compare-table td{padding:1rem 1.5rem;border-bottom:1px solid rgba(235,235,222,.04);color:var(--text-dim);font-size:.85rem}
.compare-table tr:last-child td{border-bottom:none}
.compare-table td:first-child{color:var(--text);font-family:var(--head);font-weight:600}
.compare-table td.accent-col{background:rgba(245,124,0,.03)}
.chk{color:var(--green);font-weight:700}.crs{color:#ef9a9a;font-weight:700}
/* ─── PRICING ─── */
.pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:2rem}
.price-card{border-left:2px solid var(--slate);padding:2.5rem 2rem;position:relative;transition:border-color .3s}
.price-card:hover{border-color:var(--text-dim)}
.price-card.featured{border-left-color:var(--amber)}
.price-badge{position:absolute;top:0;right:0;background:var(--amber);color:var(--bg);
font-family:var(--head);font-size:.6rem;font-weight:800;padding:4px 10px;letter-spacing:.08em;text-transform:uppercase}
.price-tier{font-family:var(--head);font-size:.65rem;font-weight:700;color:var(--text-dim);
letter-spacing:.2em;text-transform:uppercase;margin-bottom:1.25rem}
.price-amount{font-family:var(--head);font-size:3rem;font-weight:900;letter-spacing:-.03em;color:var(--text-bright);line-height:1;margin-bottom:.2rem}
.price-period{font-family:var(--mono);font-size:.68rem;color:var(--text-dim);margin-bottom:.4rem}
.price-setup{font-family:var(--mono);font-size:.68rem;color:var(--amber);margin-bottom:2rem}
.price-features{list-style:none;display:flex;flex-direction:column;gap:.55rem;margin-bottom:2rem}
.price-features li{font-family:var(--body);font-size:.85rem;color:var(--text-dim);display:flex;gap:.6rem;align-items:flex-start;font-weight:300}
.price-features li span:first-child{color:var(--green);font-weight:700;flex-shrink:0;margin-top:1px}
.price-cta{display:block;text-align:center;padding:.85rem;font-family:var(--head);font-size:.75rem;font-weight:700;
letter-spacing:.1em;text-transform:uppercase;text-decoration:none;border:1px solid rgba(235,235,222,.15);
color:var(--text-dim);transition:all .3s}
.price-cta:hover{border-color:var(--amber);color:var(--amber)}
.price-card.featured .price-cta{border-color:var(--amber);color:var(--amber)}
.price-card.featured .price-cta:hover{background:var(--amber);color:var(--bg)}
/* ─── CALCULATOR ─── */
.calculator-wrapper{max-width:1100px;margin:0 auto;padding:6rem 3rem}
.calculator-section{display:grid;grid-template-columns:1fr 1fr;gap:2.5rem;align-items:start;margin-top:2.5rem}
.calc-inputs{background:var(--bg2);border:1px solid rgba(235,235,222,.07);padding:2.5rem 2rem}
.calc-inputs h3{font-family:var(--head);font-size:1rem;font-weight:700;color:var(--text-bright);margin-bottom:2rem}
.calc-field{margin-bottom:1.5rem}
.calc-field:last-child{margin-bottom:0}
.calc-field label{display:flex;justify-content:space-between;align-items:baseline;font-family:var(--head);
font-size:.78rem;font-weight:600;color:var(--text-dim);margin-bottom:.6rem;letter-spacing:.02em}
.calc-field label span{font-size:.82rem;font-weight:700;color:var(--text-bright)}
.calc-slider-row{display:flex;align-items:center;gap:.75rem}
.calc-slider-row input[type=range]{flex:1;-webkit-appearance:none;appearance:none;height:3px;border-radius:2px;
background:rgba(235,235,222,.1);outline:none;cursor:pointer}
.calc-slider-row input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;border-radius:50%;
background:var(--amber);cursor:pointer;border:2px solid rgba(0,0,0,.3)}
.calc-slider-row input[type=range]::-moz-range-thumb{width:16px;height:16px;border-radius:50%;
background:var(--amber);cursor:pointer;border:2px solid rgba(0,0,0,.3)}
.calc-num-input{width:80px;padding:6px 10px;border:1px solid rgba(235,235,222,.12);font-size:.82rem;font-weight:600;
color:var(--text-bright);text-align:right;background:rgba(235,235,222,.04);outline:none;font-family:inherit}
.calc-num-input:focus{border-color:var(--amber)}
.calc-outputs{display:flex;flex-direction:column;gap:1rem}
.calc-out-card{background:var(--bg2);border:1px solid rgba(235,235,222,.07);padding:1.75rem}
.calc-out-card.highlight{background:var(--bg3);border-color:rgba(38,166,154,.3)}
.calc-out-label{font-family:var(--head);font-size:.62rem;font-weight:700;letter-spacing:.15em;text-transform:uppercase;
color:var(--text-dim);margin-bottom:.5rem}
.calc-out-card.highlight .calc-out-label{color:var(--teal)}
.calc-out-value{font-family:var(--head);font-size:2.2rem;font-weight:700;color:var(--text-bright);line-height:1.1;margin-bottom:.25rem}
.calc-out-card.highlight .calc-out-value{color:var(--teal)}
.calc-out-sub{font-size:.8rem;color:var(--text-dim)}
.calc-hebel-row{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}
.calc-hebel-chip{display:inline-flex;align-items:center;background:rgba(102,187,106,.08);border:1px solid rgba(102,187,106,.2);
padding:5px 12px;font-size:.87rem;font-weight:700;color:var(--green)}
/* ─── CTA SECTION ─── */
.cta-section{padding:8rem 3rem;text-align:center;position:relative;background:var(--bg3)}
.cta-section::before{content:'';position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:60vw;height:60vw;
background:radial-gradient(circle,rgba(245,124,0,.04) 0%,transparent 60%);pointer-events:none}
.cta-section h2{font-family:var(--head);font-weight:900;font-size:clamp(2rem,5vw,3.2rem);color:var(--text-bright);
margin-bottom:1.5rem;line-height:1.1}
.cta-section h2 em{font-style:normal;color:var(--amber)}
.cta-section p{font-size:1rem;color:var(--text-dim);max-width:44ch;margin:0 auto 3rem;line-height:1.8;font-weight:300}
.cta-eyebrow{font-family:var(--head);font-size:.65rem;letter-spacing:.3em;text-transform:uppercase;color:var(--amber);margin-bottom:1.5rem}
/* ─── FOOTER ─── */
footer{background:var(--bg3);border-top:1px solid rgba(235,235,222,.05);padding:4rem 3rem 2rem}
.footer-grid{max-width:1240px;margin:0 auto;display:grid;grid-template-columns:repeat(4,1fr);gap:3rem;margin-bottom:3rem}
.footer-brand img{height:40px;display:block;filter:brightness(0) invert(1);opacity:.8;margin-bottom:1rem}
.footer-brand p{font-family:var(--body);font-size:.85rem;color:var(--text-dim);line-height:1.6;font-weight:300}
.footer-col h4{font-family:var(--head);font-size:.65rem;letter-spacing:.2em;text-transform:uppercase;color:var(--amber);margin-bottom:1.25rem;font-weight:700}
.footer-col a{display:block;color:var(--text-dim);text-decoration:none;font-size:.85rem;margin-bottom:.6rem;transition:color .3s}
.footer-col a:hover{color:var(--amber)}
.footer-contact-item{display:flex;align-items:center;gap:.75rem;margin-bottom:.75rem;font-size:.85rem;color:var(--text-dim)}
.footer-contact-item a{color:var(--text-dim);text-decoration:none;transition:color .3s}
.footer-contact-item a:hover{color:var(--amber)}
.social-icons{display:flex;gap:1rem;margin-top:.5rem}
.social-link{color:var(--text-dim);transition:color .3s,transform .3s;opacity:.6}
.social-link:hover{color:var(--amber);opacity:1;transform:translateY(-2px)}
.footer-bottom{max-width:1240px;margin:0 auto;padding-top:2rem;border-top:1px solid rgba(235,235,222,.05);
display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:.75rem}
.footer-bottom p{font-size:.78rem;color:var(--text-dim);opacity:.5}
.footer-bottom-links{display:flex;gap:1.5rem}
.footer-bottom-links a{font-size:.78rem;color:var(--text-dim);text-decoration:none;transition:color .3s}
.footer-bottom-links a:hover{color:var(--amber)}
/* ─── REVEAL ANIMATIONS ─── */
.reveal{opacity:0;transform:translateY(28px);transition:opacity .8s ease,transform .8s ease}
.reveal.visible{opacity:1;transform:translateY(0)}
.reveal-delay-1{transition-delay:.1s}
.reveal-delay-2{transition-delay:.2s}
/* ─── RESPONSIVE ─── */
@media(max-width:1024px){
nav ul{gap:1.2rem}
.how-grid{grid-template-columns:1fr;gap:2.5rem}
.how-visual{position:static}
.footer-grid{grid-template-columns:repeat(2,1fr);gap:2rem}
}
@media(max-width:900px){
nav{padding:.6rem 1.5rem}
nav ul{display:none}
.hamburger{display:flex}
.hero{grid-template-columns:1fr;gap:2.5rem;padding:7rem 1.5rem 3rem}
.proof-strip{padding:3rem 1.5rem}
.proof-inner{grid-template-columns:repeat(2,1fr)}
.section{padding:4rem 1.5rem}
.problem-grid{grid-template-columns:1fr;gap:1.25rem}
.modules-grid{grid-template-columns:1fr;gap:1px}
.pricing-grid{grid-template-columns:1fr;gap:1.25rem}
}
@media(max-width:768px){
nav{padding:.5rem 1.25rem}
.hero{padding:6.5rem 1.25rem 3rem}
.hero h1{font-size:clamp(2.2rem,8vw,3.5rem)}
.section{padding:3.5rem 1.25rem}
.calculator-wrapper{padding:3.5rem 1.25rem}
.calculator-section{grid-template-columns:1fr;gap:1.25rem}
.cta-section{padding:5rem 1.25rem}
footer{padding:3rem 1.25rem 1.5rem}
.footer-grid{grid-template-columns:1fr 1fr;gap:1.75rem}
.footer-bottom{flex-direction:column;align-items:flex-start}
}
@media(max-width:480px){
.proof-inner{grid-template-columns:1fr 1fr}
.hero-actions{flex-direction:column;align-items:stretch}
.hero-actions .cta-btn,.hero-actions .btn-ghost{text-align:center}
.footer-grid{grid-template-columns:1fr;gap:1.5rem}
}
</style>
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PNJK5FN7"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- ── NAV ── -->
<nav>
<a href="https://profice.ai/" class="nav-logo" title="Profice GmbH">
<img src="images/logo/logo-01-complete.png" alt="Profice">
</a>
<ul>
<li><a href="#problem">Problem</a></li>
<li><a href="#solution">Lösung</a></li>
<li><a href="#modules">Module</a></li>
<li><a href="#pricing">Pricing</a></li>
<li><a href="#calculator">Kalkulator</a></li>
<li><a href="https://profice.ai/">↗ Profice</a></li>
<li><a href="https://termine.profice.de" target="_blank" rel="noopener" class="nav-cta">Profit-Analyse buchen</a></li>
</ul>
<button class="hamburger" id="hamburger" aria-label="Menü öffnen">
<span></span><span></span><span></span>
</button>
</nav>
<!-- Mobile Nav -->
<div class="mobile-nav" id="mobileNav">
<a href="#problem">Problem</a>
<a href="#solution">Lösung</a>
<a href="#modules">Module</a>
<a href="#pricing">Pricing</a>
<a href="#calculator">Kalkulator</a>
<a href="https://profice.ai/" target="_blank" rel="noopener">↗ KI Systeme (Profice)</a>
<a href="https://termine.profice.de" target="_blank" rel="noopener" class="mobile-cta">Profit-Analyse buchen →</a>
</div>
<div class="overlay" id="overlay"></div>
<!-- ── HERO ── -->
<section>
<div class="hero">
<div class="hero-left">
<div class="hero-label">Profit Intelligence Platform</div>
<h1>ROAS lügt.<br><em>Profit zählt.</em></h1>
<p class="hero-sub">
Feedgine verbindet deine <strong>JTL-Wawi-Margen</strong> mit Google Shopping, Meta und Microsoft Ads. Ergebnis bei lkw-teile24.de: <strong>+31 % profitabler Umsatz</strong>, 23 % verschwendetes Budget.
</p>
<div class="hero-actions">
<a href="https://termine.profice.de" target="_blank" rel="noopener" class="cta-btn">Profit-Analyse buchen</a>
<a href="#solution" class="btn-ghost">Wie es funktioniert</a>
</div>
</div>
<div class="hero-terminal">
<div class="t-header">
<div class="t-dot r"></div>
<div class="t-dot y"></div>
<div class="t-dot g"></div>
<span class="t-label">LIVE · feedgine.engine</span>
</div>
<div class="t-row">
<span class="t-key">Produkt A — Werkzeug</span>
<span class="t-val g">POAS 4.2 <span class="t-tag pos">SKALIEREN</span></span>
</div>
<div class="t-row">
<span class="t-key">Produkt B — Zubehör</span>
<span class="t-val a">POAS 1.1 <span class="t-tag warn">PRÜFEN</span></span>
</div>
<div class="t-row">
<span class="t-key">Produkt C — Sonder</span>
<span class="t-val r">POAS 0.6 <span class="t-tag neg">PAUSIEREN</span></span>
</div>
<div class="t-row">
<span class="t-key">Produkt D — OEM-Teil</span>
<span class="t-val g">POAS 3.8 <span class="t-tag pos">+BUDGET</span></span>
</div>
<div class="t-footer">
<span class="key">Tracking Coverage (SST)</span>
<span class="val">97.4 %</span>
</div>
</div>
</div>
</section>
<!-- ── PROOF STRIP ── -->
<div class="proof-strip">
<div class="proof-inner">
<div class="proof-label">Live-Referenz: lkw-teile24.de — 230.000 Produkte · JTL-Wawi · Google PMax</div>
<div class="proof-stat">
<div class="proof-num">230k</div>
<div class="proof-desc">Produkte live in Referenz-Deployment lkw-teile24.de</div>
</div>
<div class="proof-stat">
<div class="proof-num">97%</div>
<div class="proof-desc">Tracking-Coverage durch Server Side Tracking</div>
</div>
<div class="proof-stat">
<div class="proof-num">23%</div>
<div class="proof-desc">Ads-Budget auf unprofitable Produkte nach POAS-Umstellung</div>
</div>
<div class="proof-stat">
<div class="proof-num">+31%</div>
<div class="proof-desc">Umsatz mit profitablen Produkten im selben Zeitraum</div>
</div>
</div>
</div>
<hr class="divider">
<!-- ── PROBLEM ── -->
<section id="problem" class="section">
<div class="label-tag reveal">Das Problem</div>
<h2 class="section-title reveal">Dein ROAS <em>lügt dich an.</em></h2>
<p class="section-lead reveal">Klassische Tools zeigen dir Umsatz — nicht Marge. Du weißt nicht welche Produkte wirklich Geld verdienen, und welche dein Budget verbrennen.</p>
<div class="problem-grid">
<div class="prob-card reveal">
<div class="prob-num">01 — PROBLEM</div>
<div class="prob-icon">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>
</div>
<div class="prob-title">Feed-Tools kennen keinen EK</div>
<p class="prob-text">Channable, DataFeedWatch &amp; Co. syndizieren Daten. Sie wissen nichts über deine Einkaufspreise, Versandkosten oder echte Marge pro Produkt.</p>
</div>
<div class="prob-card reveal reveal-delay-1">
<div class="prob-num">02 — PROBLEM</div>
<div class="prob-icon">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="1" y1="1" x2="23" y2="23"/><path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"/><path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39"/><path d="M10.71 5.05A16 16 0 0 1 22.56 9"/><path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88"/><path d="M8.53 16.11a6 6 0 0 1 6.95 0"/><line x1="12" y1="20" x2="12.01" y2="20"/></svg>
</div>
<div class="prob-title">Tracking bricht täglich</div>
<p class="prob-text">Adblocker, ITP, Cookie-Verweigerer. Client-Side Tracking verliert 2040% der Conversions. Du optimierst auf Phantomdaten.</p>
</div>
<div class="prob-card reveal reveal-delay-2">
<div class="prob-num">03 — PROBLEM</div>
<div class="prob-icon">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="9" x2="19" y2="9"/><line x1="5" y1="15" x2="19" y2="15"/><line x1="18" y1="4" x2="6" y2="20"/></svg>
</div>
<div class="prob-title">ROAS ≠ Profitabilität</div>
<p class="prob-text">ROAS 5 auf einem Produkt mit 8% Marge. Minus. Du weißt es nicht — und Google bietet weiter drauf. Das ist das eigentliche Problem.</p>
</div>
</div>
</section>
<hr class="divider">
<!-- ── SOLUTION ── -->
<section id="solution" class="section">
<div class="label-tag reveal">Die Lösung</div>
<h2 class="section-title reveal">Profit-Intelligenz,<br><em>nicht Feed-Sync.</em></h2>
<div class="how-grid">
<div class="how-steps reveal">
<div class="how-step">
<span class="step-num">01</span>
<div>
<div class="step-title">Datenbasis aus deinem WaWi</div>
<p class="step-desc">Feedgine liest EK, Versandkosten und alle relevanten Margen-Felder direkt aus JTL-Wawi. Keine manuelle Pflege.</p>
</div>
</div>
<div class="how-step">
<span class="step-num">02</span>
<div>
<div class="step-title">Bereinigung &amp; Margenkalkulation</div>
<p class="step-desc">Jedes Produkt bekommt eine echte Deckungsbeitragsberechnung. Daten werden bereinigt, normiert und POAS-tauglich gemacht.</p>
</div>
</div>
<div class="how-step">
<span class="step-num">03</span>
<div>
<div class="step-title">POAS Custom Labels</div>
<p class="step-desc">Produkte werden automatisch in Profit-Tiers eingeteilt. Google Shopping und PMax bieten auf Basis echter Marge — nicht Umsatz.</p>
</div>
</div>
<div class="how-step">
<span class="step-num">04</span>
<div>
<div class="step-title">Server Side Tracking</div>
<p class="step-desc">Cookie-optional. First-Party. Tracking-Coverage über 95% — unabhängig von Browser, Adblocker oder Consent-Rate.</p>
</div>
</div>
<div class="how-step">
<span class="step-num">05</span>
<div>
<div class="step-title">Multi-Channel Ausgabe</div>
<p class="step-desc">Google Shopping, Meta CAPI und Microsoft Ads — täglich aktualisiert, ohne Drittanbieter-Abo.</p>
</div>
</div>
</div>
<div class="how-visual reveal">
<div class="vis-label">Profit-Analyse · Live</div>
<div class="poas-wrap">
<div class="poas-bar-label"><span>Top-Seller → Werkzeuge</span><span>POAS 4.2</span></div>
<div class="poas-track"><div class="poas-fill g" style="width:84%"></div></div>
</div>
<div class="poas-wrap">
<div class="poas-bar-label"><span>Mittelfeld → Zubehör</span><span>POAS 2.8</span></div>
<div class="poas-track"><div class="poas-fill a" style="width:56%"></div></div>
</div>
<div class="poas-wrap">
<div class="poas-bar-label"><span>Verlust → Sonderartikel</span><span>POAS 0.6</span></div>
<div class="poas-track"><div class="poas-fill r"></div></div>
</div>
<div class="vis-divider"></div>
<div class="vis-row"><span class="vis-k">Tracking vorher</span><span class="vis-v r">61%</span></div>
<div class="vis-row"><span class="vis-k">Tracking nachher (SST)</span><span class="vis-v g">97%</span></div>
<div class="vis-row"><span class="vis-k">Ads-Budget eingespart</span><span class="vis-v g">23%</span></div>
<div class="vis-row"><span class="vis-k">Profit. Umsatz</span><span class="vis-v g">+31%</span></div>
<div class="vis-divider"></div>
<div style="font-family:var(--mono);font-size:.6rem;color:var(--text-dim);line-height:1.6">
Referenz: lkw-teile24.de<br>230.000 Produkte · JTL-Wawi · Google PMax
</div>
</div>
</div>
</section>
<hr class="divider">
<!-- ── MODULES ── -->
<section id="modules" class="section">
<div class="label-tag reveal">Was enthalten ist</div>
<h2 class="section-title reveal">Drei Module.<br><em>Ein System.</em></h2>
<p class="section-lead reveal">Kein Flickenteppich aus Einzellösungen. Feedgine verbindet Profitdaten, Feed-Intelligence und Tracking in einer Pipeline.</p>
<div class="modules-grid">
<div class="module-card reveal">
<div class="module-stack-num">01</div>
<div class="module-num">MODUL 01 — PROFIT ENGINE</div>
<div class="module-title">Margenkalkulation</div>
<p class="module-desc">EK, Versand, Gebühren — alles berücksichtigt. POAS Custom Labels für Google Shopping und PMax auf Basis echter Deckungsbeiträge.</p>
<ul class="module-features">
<li>EK-Daten aus JTL-Wawi / MSSQL</li>
<li>Automatische DB-Berechnung</li>
<li>POAS-Tiering (Skala 15)</li>
<li>Täglicher Update-Zyklus</li>
</ul>
</div>
<div class="module-card reveal reveal-delay-1">
<div class="module-stack-num">02</div>
<div class="module-num">MODUL 02 — FEED INTELLIGENCE</div>
<div class="module-title">Datenpipeline</div>
<p class="module-desc">Datenbereinigungs-Pipeline für Google, Meta und Microsoft. Kein Channable. Direkt aus deiner Datenbasis — täglich, automatisch.</p>
<ul class="module-features">
<li>Google Shopping / PMax</li>
<li>Meta Product Catalog (CAPI)</li>
<li>Microsoft Shopping Ads</li>
<li>OE/OEM Nummer-Handling</li>
</ul>
</div>
<div class="module-card reveal reveal-delay-2">
<div class="module-stack-num">03</div>
<div class="module-num">MODUL 03 — SERVER SIDE TRACKING</div>
<div class="module-title">First-Party Tracking</div>
<p class="module-desc">Cookie-optionales Tracking auf EU-Infrastruktur. Kein Datenverlust durch Adblocker oder ITP. DSGVO-konform by design.</p>
<ul class="module-features">
<li>Google Ads Conversions (enhanced)</li>
<li>Meta Conversion API</li>
<li>GA4 Server Container</li>
<li>Consent Mode v2 ready</li>
</ul>
</div>
</div>
</section>
<hr class="divider">
<!-- ── COMPARISON ── -->
<section class="section">
<div class="label-tag reveal">Marktvergleich</div>
<h2 class="section-title reveal">Was andere kosten.<br><em>Was du bekommst.</em></h2>
<div class="compare-wrap reveal" style="margin-top:2.5rem">
<table class="compare-table">
<thead>
<tr>
<th>Feature</th>
<th>Klassische Agentur</th>
<th class="accent">Feedgine</th>
</tr>
</thead>
<tbody>
<tr>
<td>Echte Margenkalkulation (EK)</td>
<td><span class="crs"></span></td>
<td class="accent-col"><span class="chk"></span></td>
</tr>
<tr>
<td>POAS Custom Labels</td>
<td>Manuell</td>
<td class="accent-col"><span class="chk">✓ Automatisch</span></td>
</tr>
<tr>
<td>Server Side Tracking</td>
<td>Extra Kosten</td>
<td class="accent-col"><span class="chk">✓ Enthalten</span></td>
</tr>
<tr>
<td>Multi-Channel (Google + Meta + MS)</td>
<td>Teilweise</td>
<td class="accent-col"><span class="chk"></span></td>
</tr>
<tr>
<td>JTL-Wawi Integration</td>
<td><span class="crs"></span></td>
<td class="accent-col"><span class="chk">✓ Nativ</span></td>
</tr>
<tr>
<td>EU-Hosting / DSGVO</td>
<td>Teilweise</td>
<td class="accent-col"><span class="chk">✓ 100%</span></td>
</tr>
<tr>
<td>Monatliche Kosten</td>
<td>€2.0005.000+</td>
<td class="accent-col" style="color:var(--amber);font-weight:700">ab €999</td>
</tr>
</tbody>
</table>
</div>
</section>
<hr class="divider">
<!-- ── PRICING ── -->
<section id="pricing" class="section">
<div class="label-tag reveal">Pricing</div>
<h2 class="section-title reveal">Kein Lock-in.<br><em>Kein Bullshit.</em></h2>
<p class="section-lead reveal">Managed oder Self-Hosted — du entscheidest. Alle Preise netto zzgl. MwSt.</p>
<div class="pricing-grid">
<div class="price-card featured reveal">
<div class="price-badge">Empfohlen</div>
<div class="price-tier">Gewinn-Check</div>
<div class="price-amount">€1.000</div>
<div class="price-period">einmalig</div>
<ul class="price-features">
<li><span></span><span>Analyse deiner Ads &amp; Daten</span></li>
<li><span></span><span>Identifikation von Verlusten</span></li>
<li><span></span><span>konkrete Maßnahmen</span></li>
</ul>
<a href="https://termine.profice.de" target="_blank" rel="noopener" class="price-cta">Jetzt starten →</a>
</div>
<div class="price-card reveal reveal-delay-1">
<div class="price-tier">Feedgine Setup</div>
<div class="price-amount">€4.999</div>
<div class="price-period">einmalig</div>
<ul class="price-features">
<li><span></span><span>Feed + POAS Setup</span></li>
<li><span></span><span>Tracking Fix</span></li>
<li><span></span><span>Margensteuerung</span></li>
</ul>
<a href="https://termine.profice.de" target="_blank" rel="noopener" class="price-cta">Setup anfragen →</a>
</div>
<div class="price-card reveal reveal-delay-2">
<div class="price-tier">Feedgine Betrieb</div>
<div class="price-amount">ab €1.500</div>
<div class="price-period">/Monat</div>
<ul class="price-features">
<li><span></span><span>Monitoring</span></li>
<li><span></span><span>Optimierung</span></li>
<li><span></span><span>Skalierung</span></li>
</ul>
<a href="https://termine.profice.de" target="_blank" rel="noopener" class="price-cta">Starten →</a>
</div>
</div>
</section>
<hr class="divider">
<!-- ── TARGET AUDIENCE ── -->
<section class="section">
<div class="label-tag reveal">Zielgruppe</div>
<h2 class="section-title reveal">Feedgine ist gebaut für<br><em>E-Commerce mit Ambition.</em></h2>
<div class="problem-grid" style="margin-top:2rem">
<div class="prob-card reveal">
<div class="prob-num">PROFIL 01</div>
<div class="prob-title">JTL-Shop mit &gt;10k Produkten</div>
<p class="prob-text">Du nutzt JTL-Wawi und hast einen aktiven Google-Shopping-Feed. Du weißt, dass dein Feed besser sein könnte — aber dir fehlt die Marge-Transparenz.</p>
</div>
<div class="prob-card reveal reveal-delay-1">
<div class="prob-num">PROFIL 02</div>
<div class="prob-title">Adspend &gt;€5k/Monat</div>
<p class="prob-text">Du investierst signifikant in Google Ads oder Meta — und willst endlich wissen, welche Produkte echten Profit liefern und welche Budget verbrennen.</p>
</div>
<div class="prob-card reveal reveal-delay-2">
<div class="prob-num">PROFIL 03</div>
<div class="prob-title">ROAS-Reporting reicht nicht mehr</div>
<p class="prob-text">Du hast das Gefühl, dass deine Agentur auf Umsatz optimiert statt auf Gewinn. Du brauchst Transparenz — keine Dashboards mit Vanity-Metriken.</p>
</div>
</div>
</section>
<hr class="divider">
<!-- ── CALCULATOR ── -->
<section id="calculator">
<div class="calculator-wrapper">
<div class="label-tag reveal">Kalkulator</div>
<h2 class="section-title reveal">Ihr persönlicher <em>Gewinn-Rechner</em></h2>
<p class="section-lead reveal" style="margin-bottom:0">Geben Sie Ihre Zahlen ein — und sehen Sie sofort, wie viel mehr Profit durch optimierte Feed-Steuerung möglich ist.</p>
<div class="calculator-section">
<div class="calc-inputs">
<h3>Ihre Kennzahlen</h3>
<div class="calc-field">
<label>Werbebudget <span id="lbl-budget">10.000 €</span></label>
<div class="calc-slider-row">
<input type="range" id="sl-budget" min="1000" max="100000" step="500" value="10000">
<input type="number" class="calc-num-input" id="num-budget" min="1000" max="100000" step="500" value="10000">
</div>
</div>
<div class="calc-field">
<label>Monatlicher Umsatz <span id="lbl-umsatz">50.000 €</span></label>
<div class="calc-slider-row">
<input type="range" id="sl-umsatz" min="5000" max="500000" step="1000" value="50000">
<input type="number" class="calc-num-input" id="num-umsatz" min="5000" max="500000" step="1000" value="50000">
</div>
</div>
<div class="calc-field">
<label>Bruttomarge <span id="lbl-marge">30 %</span></label>
<div class="calc-slider-row">
<input type="range" id="sl-marge" min="10" max="80" step="1" value="30">
<input type="number" class="calc-num-input" id="num-marge" min="10" max="80" step="1" value="30">
</div>
</div>
</div>
<div class="calc-outputs">
<div class="calc-out-card">
<div class="calc-out-label">Bisheriger Profit</div>
<div class="calc-out-value" id="out-profit-alt">5.000 €</div>
<div class="calc-out-sub">Vor der Feed-Optimierung</div>
</div>
<div class="calc-out-card highlight">
<div class="calc-out-label">Neuer Profit</div>
<div class="calc-out-value" id="out-profit-neu"></div>
<div class="calc-out-sub">Mit optimiertem Feed</div>
</div>
<div class="calc-out-card">
<div class="calc-out-label">Ihr Hebel</div>
<div class="calc-hebel-row">
<div class="calc-hebel-chip" id="out-hebel-eur"></div>
<div class="calc-hebel-chip" id="out-hebel-pct"></div>
</div>
<div class="calc-out-sub" style="margin-top:10px">Mehr Gewinn — gleiches Budget</div>
</div>
</div>
</div>
</div>
</section>
<!-- ── CTA ── -->
<div id="contact" class="cta-section">
<div class="cta-eyebrow">Bereit?</div>
<h2>Wisse endlich, welche<br>Produkte <em>Geld verdienen.</em></h2>
<p>Kein Sales-Funnel. Kein Massen-Onboarding. Wir arbeiten mit ausgewählten E-Commerce-Betreibern — direkt, konkret, auf Augenhöhe.</p>
<a href="https://termine.profice.de" target="_blank" rel="noopener" class="cta-btn" style="margin-top:2rem">Profit-Analyse buchen</a>
</div>
<!-- ── FOOTER ── -->
<footer>
<div class="footer-grid">
<div class="footer-brand">
<img src="images/logo/logo-01-complete.png" alt="Profice Logo">
<p>Profit Intelligence für E-Commerce.<br>POAS · Feed · Server-Side Tracking.</p>
</div>
<div class="footer-col">
<h4>Feedgine</h4>
<a href="#problem">Das Problem</a>
<a href="#solution">Die Lösung</a>
<a href="#modules">Module</a>
<a href="#pricing">Pricing</a>
<a href="#calculator">POAS-Kalkulator</a>
</div>
<div class="footer-col">
<h4>Rechtliches</h4>
<a href="https://profice.ai/sites/datenschutz.html" target="_blank" rel="noopener">Datenschutzerklärung</a>
<a href="https://profice.ai/sites/impressum.html" target="_blank" rel="noopener">Impressum</a>
</div>
<div class="footer-col">
<h4>Kontakt</h4>
<div class="footer-contact-item">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>
<a href="mailto:hello@profice.ai">hello@profice.ai</a>
</div>
<div class="social-icons">
<a href="https://instagram.com" target="_blank" rel="noopener noreferrer" class="social-link" title="Instagram">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg>
</a>
<a href="https://facebook.com" target="_blank" rel="noopener noreferrer" class="social-link" title="Facebook">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
</a>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2026 Profice GmbH. Alle Rechte vorbehalten.</p>
<div class="footer-bottom-links">
<a href="https://profice.ai/sites/datenschutz.html" target="_blank" rel="noopener">Datenschutz</a>
<a href="https://profice.ai/sites/impressum.html" target="_blank" rel="noopener">Impressum</a>
<a href="https://profice.ai/" target="_blank" rel="noopener">profice.ai</a>
</div>
</div>
</footer>
<!-- ── SCRIPTS ── -->
<script src="scripts/feed-calculator.js" defer></script>
<script>
// Reveal on scroll
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible') });
}, { threshold: 0.12 });
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
// Mobile nav toggle
const hamburger = document.getElementById('hamburger');
const mobileNav = document.getElementById('mobileNav');
const overlay = document.getElementById('overlay');
function toggleNav() {
hamburger.classList.toggle('open');
mobileNav.classList.toggle('open');
overlay.classList.toggle('active');
}
hamburger?.addEventListener('click', toggleNav);
overlay?.addEventListener('click', toggleNav);
mobileNav?.querySelectorAll('a').forEach(a => a.addEventListener('click', () => {
if (mobileNav.classList.contains('open')) toggleNav();
}));
</script>
</body>
</html>

25
nginx.conf Normal file
View File

@@ -0,0 +1,25 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Redirect /index.html to /
location = /index.html {
return 301 /;
}
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
gzip on;
gzip_types text/plain text/css application/javascript image/svg+xml;
}

196
scripts/add/send.php Normal file
View File

@@ -0,0 +1,196 @@
<?php
/**
* Feedgine Web API Handler
* ALL SENSITIVE DATA IS IN config.php — NOT HERE
* Handles: contact forms, KI chat, cookie consent, tracking events
*/
require_once dirname(__DIR__, 2) . '/config.php';
// ============================================================
// SECURITY HEADERS
// ============================================================
header('Content-Type: application/json');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: strict-origin-when-cross-origin');
// CORS
$allowedOrigins = unserialize(ALLOWED_ORIGINS);
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins)) {
header("Access-Control-Allow-Origin: $origin");
} else {
header('Access-Control-Allow-Origin: *');
}
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Access-Control-Max-Age: 86400');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
// ============================================================
// HELPER FUNCTIONS
// ============================================================
function sendResponse($success, $message, $data = null, $statusCode = 200) {
http_response_code($statusCode);
$response = ['success' => $success, 'message' => $message, 'timestamp' => date('c')];
if ($data !== null && (!USE_PRODUCTION || DEBUG_MODE)) {
$response['data'] = $data;
}
echo json_encode($response);
exit();
}
function sanitizeInput($input) {
return htmlspecialchars(trim($input ?? ''), ENT_QUOTES, 'UTF-8');
}
function getClientIP() {
foreach (['HTTP_CF_CONNECTING_IP','HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP','REMOTE_ADDR'] as $key) {
if (!empty($_SERVER[$key])) {
$ip = explode(',', $_SERVER[$key])[0];
return filter_var(trim($ip), FILTER_VALIDATE_IP) ?: 'unknown';
}
}
return 'unknown';
}
function checkRateLimit($ip) {
$file = __DIR__ . '/data/rate_limits.json';
$limits = file_exists($file) ? json_decode(file_get_contents($file), true) : [];
$now = time();
// Clean old entries
foreach ($limits as $k => $v) {
if ($now - $v['first'] > RATE_LIMIT_WINDOW) unset($limits[$k]);
}
if (!isset($limits[$ip])) {
$limits[$ip] = ['count' => 1, 'first' => $now];
} else {
$limits[$ip]['count']++;
if ($limits[$ip]['count'] > RATE_LIMIT_REQUESTS) {
file_put_contents($file, json_encode($limits));
return false;
}
}
file_put_contents($file, json_encode($limits));
return true;
}
function sendToWebhook($data, $webhookUrl) {
$payload = json_encode($data);
if (function_exists('curl_init')) {
$ch = curl_init($webhookUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: ' . strlen($payload)],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return ['success' => $httpCode >= 200 && $httpCode < 300, 'http_code' => $httpCode, 'response' => $response, 'error' => $error];
}
// Fallback
$ctx = stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\nContent-Length: " . strlen($payload) . "\r\n",
'content' => $payload,
'timeout' => 15,
]]);
$result = @file_get_contents($webhookUrl, false, $ctx);
return ['success' => $result !== false, 'response' => $result, 'method' => 'file_get_contents'];
}
function storeLead($data) {
$file = __DIR__ . '/data/leads.json';
$leads = file_exists($file) ? json_decode(file_get_contents($file), true) : [];
$leads[] = array_merge($data, ['timestamp' => date('c'), 'ip' => getClientIP()]);
if (count($leads) > 200) $leads = array_slice($leads, -200);
file_put_contents($file, json_encode($leads, JSON_PRETTY_PRINT));
}
// ============================================================
// REQUEST HANDLERS
// ============================================================
function handleContactForm($data) {
$name = sanitizeInput($data['name'] ?? '');
$contact = sanitizeInput($data['contact'] ?? '');
$message = sanitizeInput($data['message'] ?? '');
$company = sanitizeInput($data['company'] ?? '');
if (empty($name) || empty($contact)) {
sendResponse(false, 'Name und Kontakt sind Pflichtfelder.', null, 400);
}
$payload = ['source' => 'feedgine.de', 'name' => $name, 'contact' => $contact,
'message' => $message, 'company' => $company, 'timestamp' => date('c'), 'ip' => getClientIP()];
storeLead($payload);
sendToWebhook($payload, FEEDGINE_WEBHOOK_URL);
sendResponse(true, 'Anfrage erfolgreich übermittelt. Wir melden uns in Kürze.');
}
function handleChatMessage($data) {
$message = sanitizeInput($data['message'] ?? '');
$session_id = sanitizeInput($data['session_id'] ?? '');
if (empty($message)) {
sendResponse(false, 'Nachricht darf nicht leer sein.', null, 400);
}
$payload = ['message' => $message, 'session_id' => $session_id, 'source' => 'feedgine.de'];
$result = sendToWebhook($payload, KI_CHAT_WEBHOOK_URL);
$botReply = 'Vielen Dank für Ihre Nachricht. Unser Team meldet sich schnellstmöglich.';
if ($result['success'] && !empty($result['response'])) {
$decoded = json_decode($result['response'], true);
foreach (['message','output','text','response','answer','result'] as $key) {
if (!empty($decoded[$key])) { $botReply = $decoded[$key]; break; }
}
}
sendResponse(true, 'OK', ['message' => $botReply, 'session_id' => $session_id]);
}
function handleCookieConsent($data) {
$file = __DIR__ . '/data/cookie_consent.json';
$records = file_exists($file) ? json_decode(file_get_contents($file), true) : [];
$records[] = ['consent' => $data, 'timestamp' => date('c'), 'ip' => getClientIP()];
if (count($records) > 1000) $records = array_slice($records, -1000);
file_put_contents($file, json_encode($records));
sendResponse(true, 'Consent gespeichert.');
}
// ============================================================
// ROUTER
// ============================================================
$ip = getClientIP();
if (!checkRateLimit($ip)) {
sendResponse(false, 'Zu viele Anfragen. Bitte warten.', null, 429);
}
$raw = file_get_contents('php://input');
$body = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE || empty($body)) {
sendResponse(false, 'Ungültige Anfrage.', null, 400);
}
$type = sanitizeInput($body['type'] ?? '');
switch ($type) {
case 'contact': handleContactForm($body); break;
case 'chat': handleChatMessage($body); break;
case 'cookie_consent': handleCookieConsent($body); break;
default: sendResponse(false, "Unbekannter Typ: $type", null, 400);
}

256
scripts/cursor.js Normal file
View File

@@ -0,0 +1,256 @@
// cursor.js — Venom/Spider Cursor · Space Edition (white + neon)
document.addEventListener("DOMContentLoaded", function () {
if (window.matchMedia("(pointer: coarse)").matches) return;
/* ── CONFIG ── */
const CONFIG = {
tentacleCount: 10,
triggerDist: 8,
maxLength: 300,
connectionDist: 150,
prediction: 3.5,
// Idle: cold silver-white
idleStroke: { r: 210, g: 228, b: 255 },
idleGlow: { r: 160, g: 200, b: 255 },
// Hover: neon orange (matches site accent)
hoverStroke: { r: 255, g: 102, b: 0 },
hoverGlow: { r: 255, g: 80, b: 0 },
};
/* ── toggle ── */
const toggleBtn = document.getElementById('cursorToggle');
const body = document.body;
let isCursorDisabled = localStorage.getItem('venomCursorDisabled') !== 'false';
function updateCursorState() {
if (isCursorDisabled) {
body.classList.add('system-cursor');
document.documentElement.style.cursor = '';
if (toggleBtn) {
toggleBtn.classList.remove('active');
const icon = toggleBtn.querySelector('.cursor-icon');
if (icon && icon.tagName === 'IMG') {
icon.src = 'images/icons/spider.png';
icon.alt = 'Spider Cursor';
}
}
} else {
body.classList.remove('system-cursor');
document.documentElement.style.cursor = 'none';
if (toggleBtn) {
toggleBtn.classList.add('active');
const icon = toggleBtn.querySelector('.cursor-icon');
if (icon && icon.tagName === 'IMG') {
icon.src = 'images/additional/cursor.png';
icon.alt = 'Custom Cursor';
}
}
}
}
updateCursorState();
if (toggleBtn) {
toggleBtn.addEventListener('click', () => {
isCursorDisabled = !isCursorDisabled;
localStorage.setItem('venomCursorDisabled', isCursorDisabled);
updateCursorState();
});
}
/* ── canvas ── */
const canvas = document.createElement('canvas');
canvas.id = 'venom-cursor';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
/* ── state ── */
const mouse = { x: 0, y: 0 };
const oldMouse = { x: 0, y: 0 };
let isHover = false;
let colorT = 0; // 0 = idle, 1 = hover (lerped)
const tentacles = [];
let rotation = 0; // reticle ring slow spin
/* ── hover selector ── */
const HOVER_SEL = [
'a', 'button', '[role="button"]', 'select', 'label',
'.nav-link', '.btn-primary', '.btn-ghost', '.nav-demo-btn',
'.price-cta', '.prob-card', '.module-card', '.how-step',
'.dropdown-item', '.mobile-nav-link', '.mobile-nav-cta',
'[onclick]', '.clickable'
].join(',');
document.addEventListener('mousemove', (e) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
const el = document.elementFromPoint(e.clientX, e.clientY);
isHover = !!el && (el.matches(HOVER_SEL) || !!el.closest(HOVER_SEL));
}, { passive: true });
/* ── colour helper ── */
function lerpColor(a, b, t) {
return {
r: a.r + (b.r - a.r) * t | 0,
g: a.g + (b.g - a.g) * t | 0,
b: a.b + (b.b - a.b) * t | 0,
};
}
/* ── Tentacle ── */
class Tentacle {
constructor(targetX, targetY) {
this.anchor = { x: targetX, y: targetY };
this.dead = false;
this.dist = 0;
this.age = 0;
}
update() {
const dx = mouse.x - this.anchor.x;
const dy = mouse.y - this.anchor.y;
this.dist = Math.sqrt(dx * dx + dy * dy);
this.age++;
if (this.dist > CONFIG.maxLength) this.dead = true;
}
draw(stroke, glow) {
if (this.dead) return;
const tension = Math.min(this.dist / CONFIG.maxLength, 1);
// Fade in over first 10 frames
const fadeIn = Math.min(this.age / 10, 1);
const lineAlpha = fadeIn * (1 - tension * 0.85) * 0.70;
const dotAlpha = fadeIn * (1 - tension) * 0.90;
const lw = Math.max(0.2, 1.1 * (1 - tension * 0.80));
// Glow pass (wide, soft)
ctx.save();
ctx.shadowColor = `rgba(${glow.r},${glow.g},${glow.b},${lineAlpha * 0.55})`;
ctx.shadowBlur = 6;
ctx.beginPath();
ctx.moveTo(mouse.x, mouse.y);
ctx.lineTo(this.anchor.x, this.anchor.y);
ctx.strokeStyle = `rgba(${stroke.r},${stroke.g},${stroke.b},${lineAlpha})`;
ctx.lineWidth = lw;
ctx.lineCap = 'round';
ctx.stroke();
ctx.restore();
// Anchor dot
ctx.beginPath();
ctx.arc(this.anchor.x, this.anchor.y, 1.8 * (1 - tension), 0, Math.PI * 2);
ctx.fillStyle = `rgba(${stroke.r},${stroke.g},${stroke.b},${dotAlpha})`;
ctx.shadowColor = `rgba(${glow.r},${glow.g},${glow.b},${dotAlpha * 0.6})`;
ctx.shadowBlur = 4;
ctx.fill();
ctx.shadowBlur = 0;
}
}
/* ── render ── */
function render() {
if (isCursorDisabled) {
requestAnimationFrame(render);
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Lerp color T
colorT += (isHover ? 1 : 0 - colorT) * 0.12;
const stroke = lerpColor(CONFIG.idleStroke, CONFIG.hoverStroke, colorT);
const glow = lerpColor(CONFIG.idleGlow, CONFIG.hoverGlow, colorT);
/* spawn tentacle on movement */
const moved = Math.hypot(mouse.x - oldMouse.x, mouse.y - oldMouse.y);
if (moved > CONFIG.triggerDist) {
const vx = mouse.x - oldMouse.x;
const vy = mouse.y - oldMouse.y;
const tx = mouse.x + vx * CONFIG.prediction + (Math.random() - 0.5) * 60;
const ty = mouse.y + vy * CONFIG.prediction + (Math.random() - 0.5) * 60;
tentacles.push(new Tentacle(tx, ty));
oldMouse.x = mouse.x;
oldMouse.y = mouse.y;
}
if (tentacles.length > CONFIG.tentacleCount) tentacles.shift();
/* update + draw tentacles */
for (let i = tentacles.length - 1; i >= 0; i--) {
tentacles[i].update();
if (tentacles[i].dead) tentacles.splice(i, 1);
else tentacles[i].draw(stroke, glow);
}
/* web connections between close anchor pairs */
for (let i = 0; i < tentacles.length; i++) {
for (let j = i + 1; j < tentacles.length; j++) {
const a = tentacles[i].anchor;
const b = tentacles[j].anchor;
const d = Math.hypot(a.x - b.x, a.y - b.y);
if (d < CONFIG.connectionDist) {
const alpha = (1 - d / CONFIG.connectionDist) * 0.28;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.strokeStyle = `rgba(${stroke.r},${stroke.g},${stroke.b},${alpha})`;
ctx.lineWidth = 0.5;
ctx.stroke();
}
}
}
/* cursor shape — segmented reticle ring */
rotation += isHover ? 0.040 : 0.016;
const ringR = isHover ? 9 : 7;
const gapHalf = 0.28; // gap half-angle in radians at each cardinal point
const segments = [
[rotation + gapHalf, rotation + Math.PI * 0.5 - gapHalf],
[rotation + Math.PI * 0.5 + gapHalf, rotation + Math.PI - gapHalf],
[rotation + Math.PI + gapHalf, rotation + Math.PI * 1.5 - gapHalf],
[rotation + Math.PI * 1.5 + gapHalf, rotation + Math.PI * 2.0 - gapHalf],
];
ctx.save();
ctx.shadowColor = `rgba(${glow.r},${glow.g},${glow.b},0.75)`;
ctx.shadowBlur = isHover ? 14 : 8;
ctx.strokeStyle = `rgba(${stroke.r},${stroke.g},${stroke.b},0.90)`;
ctx.lineWidth = isHover ? 1.4 : 1.1;
ctx.lineCap = 'round';
for (const [start, end] of segments) {
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, ringR, start, end);
ctx.stroke();
}
ctx.restore();
// Center dot — exact cursor tip
ctx.save();
ctx.shadowColor = `rgba(${glow.r},${glow.g},${glow.b},0.90)`;
ctx.shadowBlur = isHover ? 10 : 6;
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, isHover ? 2.2 : 1.6, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${stroke.r},${stroke.g},${stroke.b},1)`;
ctx.fill();
ctx.restore();
requestAnimationFrame(render);
}
render();
});

View File

@@ -0,0 +1,85 @@
/**
* Gewinn-Calculator — Feed Page
* Real-time profit calculator with synchronized sliders and number inputs.
*/
(function () {
function fmt(n) {
return n.toLocaleString('de-DE', { maximumFractionDigits: 0 }) + ' €';
}
function fmtPct(n) {
return (n >= 0 ? '+' : '') + n.toLocaleString('de-DE', { maximumFractionDigits: 0 }) + ' %';
}
var WASTE = 0.20; // Anteil Budget-Fresser — fixed at 20%
function calc() {
var B = +document.getElementById('sl-budget').value;
var U = +document.getElementById('sl-umsatz').value;
var m = +document.getElementById('sl-marge').value / 100;
var w = WASTE;
var P_alt = U * m - B;
var roas_core = U / (B * (1 - w));
var U_neu = U + B * w * roas_core;
var P_neu = U_neu * m - B;
var wachstum = P_alt !== 0 ? ((P_neu - P_alt) / Math.abs(P_alt)) * 100 : 0;
var hebel = P_neu - P_alt;
document.getElementById('out-profit-alt').textContent = fmt(Math.round(P_alt));
document.getElementById('out-profit-neu').textContent = fmt(Math.round(P_neu));
document.getElementById('out-hebel-eur').textContent = (hebel >= 0 ? '+' : '') + fmt(Math.round(hebel)).replace(' €', '') + ' €';
document.getElementById('out-hebel-pct').textContent = fmtPct(Math.round(wachstum));
}
function syncFromSlider(slId, numId, lblId, unit) {
var sl = document.getElementById(slId);
var num = document.getElementById(numId);
var lbl = document.getElementById(lblId);
function updateLbl(v) {
if (unit === '€') {
lbl.textContent = Number(v).toLocaleString('de-DE') + ' €';
} else {
lbl.textContent = v + ' %';
}
}
sl.addEventListener('input', function () {
num.value = sl.value;
updateLbl(sl.value);
calc();
});
num.addEventListener('input', function () {
var v = Math.min(+num.max, Math.max(+num.min, +num.value || +num.min));
sl.value = v;
updateLbl(v);
calc();
});
num.addEventListener('change', function () {
var v = Math.min(+num.max, Math.max(+num.min, +num.value || +num.min));
num.value = v;
sl.value = v;
updateLbl(v);
calc();
});
}
function init() {
if (!document.getElementById('sl-budget')) return;
syncFromSlider('sl-budget', 'num-budget', 'lbl-budget', '€');
syncFromSlider('sl-umsatz', 'num-umsatz', 'lbl-umsatz', '€');
syncFromSlider('sl-marge', 'num-marge', 'lbl-marge', '%');
calc();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

131
scripts/feedgine.js Normal file
View File

@@ -0,0 +1,131 @@
/**
* feedgine.js — Main page interactions
* - Scroll animations (IntersectionObserver)
* - Bar animation for the profit visual
* - Terminal pulse effect
* - Smooth scroll for anchor links
*/
(function () {
'use strict';
// ---- SCROLL ANIMATIONS ----
const obs = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('visible');
obs.unobserve(e.target);
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.animate-in').forEach(el => obs.observe(el));
// ---- BAR ANIMATION ----
const barObs = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.querySelectorAll('.poas-fill').forEach(bar => {
const w = bar.style.width;
bar.style.width = '0%';
setTimeout(() => { bar.style.width = w; }, 150);
});
barObs.unobserve(e.target);
}
});
}, { threshold: 0.3 });
document.querySelectorAll('.how-visual').forEach(el => barObs.observe(el));
// ---- TERMINAL PULSE ----
setInterval(() => {
document.querySelectorAll('.t-val').forEach(v => {
v.style.transition = 'opacity 0.3s';
v.style.opacity = '0.4';
setTimeout(() => { v.style.opacity = '1'; }, 300);
});
}, 3500);
// ---- HAMBURGER MENU ----
const hamburger = document.getElementById('hamburger');
const mobileNav = document.getElementById('mobileNav');
function closeMobileNav() {
if (!hamburger || !mobileNav) return;
hamburger.classList.remove('open');
hamburger.setAttribute('aria-expanded', 'false');
mobileNav.classList.remove('open');
mobileNav.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
}
if (hamburger && mobileNav) {
hamburger.addEventListener('click', () => {
const isOpen = hamburger.classList.contains('open');
if (isOpen) {
closeMobileNav();
} else {
hamburger.classList.add('open');
hamburger.setAttribute('aria-expanded', 'true');
mobileNav.classList.add('open');
mobileNav.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
}
});
// Close on any nav link click
mobileNav.querySelectorAll('a').forEach(a => {
a.addEventListener('click', closeMobileNav);
});
// Close on overlay click or Escape key
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeMobileNav(); });
}
// ---- SMOOTH SCROLL ----
document.querySelectorAll('a[href^="#"]').forEach(a => {
a.addEventListener('click', (e) => {
const href = a.getAttribute('href');
if (!href || href === '#') return;
const target = document.querySelector(href);
if (!target) return;
e.preventDefault();
const offset = 100;
const top = target.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({ top, behavior: 'smooth' });
});
});
// ---- CONTACT FORM SUBMIT ----
const form = document.getElementById('contactForm');
if (form) {
form.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = form.querySelector('button[type="submit"]');
if (btn) { btn.disabled = true; btn.textContent = 'Wird gesendet…'; }
const data = {
type: 'contact',
name: form.querySelector('[name="name"]')?.value || '',
contact: form.querySelector('[name="contact"]')?.value || '',
message: form.querySelector('[name="message"]')?.value || '',
company: form.querySelector('[name="company"]')?.value || '',
};
try {
const res = await fetch('scripts/add/send.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const json = await res.json();
const msg = form.querySelector('.form-success');
if (msg) { msg.style.display = 'block'; }
form.reset();
} catch (err) {
console.error('Form error:', err);
} finally {
if (btn) { btn.disabled = false; btn.textContent = 'Demo anfragen →'; }
}
});
}
})();

531
scripts/hex-background.js Normal file
View File

@@ -0,0 +1,531 @@
/**
* Space Background — Glass Shards · Animated Nebula · Drifting Stars · Shooting Stars
* No hex grid. Cursor scatters floating glass fragments.
*/
(function () {
'use strict';
let canvas, ctx;
let W = 0, H = 0;
let mouse = { x: -2000, y: -2000 };
let rafId;
let initialized = false;
let time = 0;
/* ──────────────────────────────────────────────
NEBULA BLOBS (animated radial gradients) — dimmed
────────────────────────────────────────────── */
const NEBULAS = [
{ px: 0.12, py: 0.25, pr: 0.50, cr: 255, cg: 80, cb: 0, a: 0.040, spx: 0.00014, spy: 0.00007, phase: 0.0 },
{ px: 0.82, py: 0.55, pr: 0.55, cr: 0, cg: 255, cb: 136, a: 0.028, spx:-0.00009, spy: 0.00011, phase: 2.1 },
{ px: 0.50, py: 0.88, pr: 0.42, cr: 0, cg: 180, cb: 255, a: 0.022, spx: 0.00007, spy:-0.00009, phase: 4.3 },
{ px: 0.72, py: 0.12, pr: 0.38, cr: 160, cg: 0, cb: 255, a: 0.016, spx:-0.00010, spy: 0.00005, phase: 1.5 },
{ px: 0.35, py: 0.65, pr: 0.32, cr: 255, cg: 140, cb: 0, a: 0.016, spx: 0.00005, spy:-0.00006, phase: 3.7 },
];
function drawNebulas() {
for (let i = 0; i < NEBULAS.length; i++) {
const n = NEBULAS[i];
const ox = Math.sin(time * n.spx * 800 + n.phase) * W * 0.10;
const oy = Math.cos(time * n.spy * 800 + n.phase + 1.2) * H * 0.10;
const cx = n.px * W + ox;
const cy = n.py * H + oy;
const rad = n.pr * Math.min(W, H);
const pa = n.a * (0.65 + 0.35 * Math.sin(time * 0.28 + n.phase));
const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, rad);
g.addColorStop(0, `rgba(${n.cr},${n.cg},${n.cb},${pa})`);
g.addColorStop(0.45,`rgba(${n.cr},${n.cg},${n.cb},${pa * 0.35})`);
g.addColorStop(1, `rgba(${n.cr},${n.cg},${n.cb},0)`);
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(cx, cy, rad, 0, Math.PI * 2);
ctx.fill();
}
}
/* ──────────────────────────────────────────────
DRIFTING STARS — dimmed halos
────────────────────────────────────────────── */
const STAR_COUNT = 220;
let stars = [];
function initStars() {
stars = [];
for (let i = 0; i < STAR_COUNT; i++) {
stars.push({
x: Math.random() * W,
y: Math.random() * H,
r: 0.35 + Math.random() * 1.65,
vx: (Math.random() - 0.5) * 0.055,
vy: 0.018 + Math.random() * 0.055,
phase: Math.random() * Math.PI * 2,
twinkleSpeed: 0.4 + Math.random() * 1.6,
// 0=white 1=neon-green 2=neon-teal
type: Math.random() < 0.78 ? 0 : (Math.random() < 0.5 ? 1 : 2),
});
}
}
function drawStars() {
for (let i = 0; i < stars.length; i++) {
const s = stars[i];
const tw = 0.25 + 0.75 * (0.5 + 0.5 * Math.sin(time * s.twinkleSpeed + s.phase));
s.x += s.vx;
s.y += s.vy;
if (s.y > H + 2) { s.y = -2; s.x = Math.random() * W; }
if (s.x < -2) s.x = W + 2;
if (s.x > W + 2) s.x = -2;
const clr = s.type === 0
? `rgba(255,255,255,${tw * 0.70})`
: s.type === 1
? `rgba(0,255,136,${tw * 0.50})`
: `rgba(0,220,255,${tw * 0.50})`;
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fillStyle = clr;
ctx.fill();
// Halo only on larger stars, dimmed
if (s.r > 1.1) {
const halo = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, s.r * 3.5);
const ha = tw * (s.type === 0 ? 0.14 : 0.10);
halo.addColorStop(0, s.type === 0
? `rgba(255,255,255,${ha})`
: s.type === 1
? `rgba(0,255,136,${ha})`
: `rgba(0,220,255,${ha})`);
halo.addColorStop(1, 'rgba(0,0,0,0)');
ctx.beginPath();
ctx.arc(s.x, s.y, s.r * 3.5, 0, Math.PI * 2);
ctx.fillStyle = halo;
ctx.fill();
}
}
}
/* ──────────────────────────────────────────────
SHOOTING STARS
────────────────────────────────────────────── */
const MAX_SHOOTING_STARS = 3;
let shootingStars = [];
let nextShootingStarAt = 0; // time value when next star spawns
function spawnShootingStar() {
// Spawn from top or right edge, travel down-left or down-right
const fromRight = Math.random() < 0.5;
const startX = fromRight ? W * (0.5 + Math.random() * 0.6) : W * Math.random() * 0.7;
const startY = Math.random() * H * 0.45;
const angle = (Math.PI / 4) + (Math.random() - 0.5) * 0.6; // ~45° downward
const speed = 6 + Math.random() * 9;
const length = 80 + Math.random() * 160;
shootingStars.push({
x: startX,
y: startY,
vx: Math.cos(angle) * speed * (fromRight ? -1 : 1),
vy: Math.sin(angle) * speed,
length,
alpha: 0,
fadeIn: true,
life: 0,
maxLife: (length / speed) * 1.6, // frames to live
});
}
function updateDrawShootingStars() {
// Possibly spawn a new one
if (shootingStars.length < MAX_SHOOTING_STARS && time > nextShootingStarAt) {
spawnShootingStar();
// Next star between 414 seconds of time units (time += 0.016/frame)
nextShootingStarAt = time + 4 + Math.random() * 10;
}
for (let i = shootingStars.length - 1; i >= 0; i--) {
const s = shootingStars[i];
s.life++;
// Fade in quickly, fade out near end
if (s.life < 8) {
s.alpha = s.life / 8;
} else if (s.life > s.maxLife - 10) {
s.alpha = Math.max(0, (s.maxLife - s.life) / 10);
} else {
s.alpha = 1;
}
s.x += s.vx;
s.y += s.vy;
// Draw trail
const tailX = s.x - (s.vx / Math.hypot(s.vx, s.vy)) * s.length;
const tailY = s.y - (s.vy / Math.hypot(s.vx, s.vy)) * s.length;
const grad = ctx.createLinearGradient(s.x, s.y, tailX, tailY);
grad.addColorStop(0, `rgba(255,255,255,${s.alpha * 0.90})`);
grad.addColorStop(0.15,`rgba(220,235,255,${s.alpha * 0.55})`);
grad.addColorStop(1, `rgba(180,210,255,0)`);
ctx.beginPath();
ctx.moveTo(s.x, s.y);
ctx.lineTo(tailX, tailY);
ctx.strokeStyle = grad;
ctx.lineWidth = 1.5;
ctx.stroke();
// Bright head dot
ctx.beginPath();
ctx.arc(s.x, s.y, 1.4, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255,255,255,${s.alpha * 0.95})`;
ctx.fill();
// Remove if expired or off-screen
if (s.life >= s.maxLife || s.x < -50 || s.x > W + 50 || s.y > H + 50) {
shootingStars.splice(i, 1);
}
}
}
/* ──────────────────────────────────────────────
GLASS SHARDS — fixed physics
────────────────────────────────────────────── */
const SHARD_COUNT = 30;
const SHARD_RADIUS = 230; // mouse influence radius
const SCATTER_FORCE = 3.0;
const ROT_V_MAX = 0.018; // cap on rotation speed
const SHARD_COLORS = [
{ r: 255, g: 102, b: 0 }, // neon orange
{ r: 0, g: 255, b: 136 }, // neon green
{ r: 0, g: 220, b: 255 }, // neon teal
{ r: 255, g: 255, b: 255 }, // white
{ r: 255, g: 180, b: 0 }, // amber
];
let shards = [];
function buildVerts(size, sides) {
const verts = [];
const base = Math.random() * Math.PI * 2;
for (let i = 0; i < sides; i++) {
const a = base + (Math.PI * 2 * i / sides) + (Math.random() - 0.5) * 0.65;
const r = size * (0.55 + Math.random() * 0.45);
verts.push([Math.cos(a) * r, Math.sin(a) * r]);
}
return verts;
}
function initShards() {
shards = [];
for (let i = 0; i < SHARD_COUNT; i++) {
const size = 11 + Math.random() * 42;
const sides = 3 + Math.floor(Math.random() * 3); // 35 sides
const col = SHARD_COLORS[Math.floor(Math.random() * SHARD_COLORS.length)];
shards.push({
x: Math.random() * W,
y: Math.random() * H,
vx: (Math.random() - 0.5) * 0.22,
vy: (Math.random() - 0.5) * 0.22,
rot: Math.random() * Math.PI * 2,
rotV: (Math.random() - 0.5) * 0.003, // very gentle initial spin
verts: buildVerts(size, sides),
size, col,
alpha: 0.18 + Math.random() * 0.24,
alphaTarget: 0.18 + Math.random() * 0.24,
svx: 0, svy: 0,
phase: Math.random() * Math.PI * 2,
floatSpeed: 0.20 + Math.random() * 0.45, // slower float
});
}
}
function drawShard(s) {
ctx.save();
ctx.translate(s.x, s.y);
ctx.rotate(s.rot);
const { r, g, b } = s.col;
const al = s.alpha;
ctx.beginPath();
ctx.moveTo(s.verts[0][0], s.verts[0][1]);
for (let i = 1; i < s.verts.length; i++) ctx.lineTo(s.verts[i][0], s.verts[i][1]);
ctx.closePath();
// Glass fill
const fill = ctx.createLinearGradient(-s.size, -s.size, s.size * 0.6, s.size * 0.6);
fill.addColorStop(0, `rgba(${r},${g},${b},${al * 0.18})`);
fill.addColorStop(0.45,`rgba(255,255,255,${al * 0.08})`);
fill.addColorStop(1, `rgba(${r},${g},${b},${al * 0.03})`);
ctx.fillStyle = fill;
ctx.fill();
// Neon outline — dimmed
ctx.shadowColor = `rgba(${r},${g},${b},0.45)`;
ctx.shadowBlur = 7;
ctx.strokeStyle = `rgba(${r},${g},${b},${al * 0.70})`;
ctx.lineWidth = 1.0;
ctx.stroke();
// Soft outer glow — dimmed
ctx.shadowBlur = 14;
ctx.strokeStyle = `rgba(${r},${g},${b},${al * 0.18})`;
ctx.lineWidth = 2.0;
ctx.stroke();
ctx.shadowBlur = 0;
// Inner highlight
if (s.verts.length >= 2) {
ctx.beginPath();
ctx.moveTo(s.verts[0][0] * 0.55, s.verts[0][1] * 0.55);
ctx.lineTo(s.verts[1][0] * 0.55, s.verts[1][1] * 0.55);
ctx.strokeStyle = `rgba(255,255,255,${al * 0.40})`;
ctx.lineWidth = 0.8;
ctx.shadowBlur = 3;
ctx.shadowColor = 'rgba(255,255,255,0.3)';
ctx.stroke();
ctx.shadowBlur = 0;
}
ctx.restore();
}
function updateShards() {
const infSq = SHARD_RADIUS * SHARD_RADIUS;
for (let i = 0; i < shards.length; i++) {
const s = shards[i];
// Gentle float bob
s.vy += Math.sin(time * s.floatSpeed + s.phase) * 0.002;
// Mouse scatter
if (mouse.x > -1000) {
const dx = s.x - mouse.x;
const dy = s.y - mouse.y;
const distSq = dx * dx + dy * dy;
if (distSq < infSq) {
const dist = Math.sqrt(distSq);
const force = (1 - dist / SHARD_RADIUS) * SCATTER_FORCE;
const ang = Math.atan2(dy, dx);
s.svx += Math.cos(ang) * force * 0.065;
s.svy += Math.sin(ang) * force * 0.065;
// Small nudge to rotation — NOT multiplicative
s.rotV += (Math.random() - 0.5) * 0.004 * (1 - dist / SHARD_RADIUS);
// Brighten on interaction
s.alphaTarget = Math.min(0.75, s.alphaTarget + 0.03);
}
}
// Rotation damping — keeps shards from spinning endlessly
s.rotV *= 0.96;
// Hard cap on rotation speed
if (s.rotV > ROT_V_MAX) s.rotV = ROT_V_MAX;
if (s.rotV < -ROT_V_MAX) s.rotV = -ROT_V_MAX;
// Scatter velocity damping
s.svx *= 0.92;
s.svy *= 0.92;
s.vx *= 0.998;
s.vy *= 0.998;
// Apply
s.x += s.vx + s.svx;
s.y += s.vy + s.svy;
s.rot += s.rotV;
// Fade alpha toward target
s.alpha += (s.alphaTarget - s.alpha) * 0.025;
// Slowly restore target
const base = 0.18;
if (s.alphaTarget > base) s.alphaTarget -= 0.006;
// Edge wrap
const m = s.size + 10;
if (s.x < -m) s.x = W + m;
if (s.x > W + m) s.x = -m;
if (s.y < -m) s.y = H + m;
if (s.y > H + m) s.y = -m;
}
}
/* ──────────────────────────────────────────────
CURSOR AURA — dimmed
────────────────────────────────────────────── */
function drawCursorAura() {
const r = 130 + Math.sin(time * 1.8) * 22;
const aura = ctx.createRadialGradient(mouse.x, mouse.y, 0, mouse.x, mouse.y, r);
aura.addColorStop(0, 'rgba(255,102,0,0.04)');
aura.addColorStop(0.5, 'rgba(255,102,0,0.012)');
aura.addColorStop(1, 'rgba(255,102,0,0)');
ctx.fillStyle = aura;
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, r, 0, Math.PI * 2);
ctx.fill();
}
/* ──────────────────────────────────────────────
MAIN LOOP
────────────────────────────────────────────── */
function animate() {
if (!initialized) return;
time += 0.016;
ctx.clearRect(0, 0, W, H);
drawNebulas();
drawStars();
updateDrawShootingStars();
updateShards();
for (let i = 0; i < shards.length; i++) drawShard(shards[i]);
if (mouse.x > -1000) drawCursorAura();
rafId = requestAnimationFrame(animate);
}
/* ──────────────────────────────────────────────
MOBILE — one-shot static render
────────────────────────────────────────────── */
function initMobileStatic() {
const c = document.getElementById('hexCanvas');
if (!c) return;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const w = window.innerWidth;
const h = window.innerHeight;
c.width = w * dpr;
c.height = h * dpr;
c.style.width = w + 'px';
c.style.height = h + 'px';
const cx = c.getContext('2d');
cx.scale(dpr, dpr);
// Nebula blobs — dimmed
const g1 = cx.createRadialGradient(w*0.15, h*0.3, 0, w*0.15, h*0.3, w*0.5);
g1.addColorStop(0, 'rgba(255,80,0,0.04)'); g1.addColorStop(1, 'rgba(255,80,0,0)');
cx.fillStyle = g1; cx.fillRect(0, 0, w, h);
const g2 = cx.createRadialGradient(w*0.82, h*0.65, 0, w*0.82, h*0.65, w*0.45);
g2.addColorStop(0, 'rgba(0,255,136,0.03)'); g2.addColorStop(1, 'rgba(0,255,136,0)');
cx.fillStyle = g2; cx.fillRect(0, 0, w, h);
const g3 = cx.createRadialGradient(w*0.5, h*0.85, 0, w*0.5, h*0.85, w*0.4);
g3.addColorStop(0, 'rgba(0,180,255,0.025)'); g3.addColorStop(1, 'rgba(0,180,255,0)');
cx.fillStyle = g3; cx.fillRect(0, 0, w, h);
// Stars
for (let i = 0; i < 130; i++) {
const sx = Math.random() * w;
const sy = Math.random() * h;
const sr = 0.35 + Math.random() * 1.5;
const sa = 0.20 + Math.random() * 0.55;
cx.beginPath();
cx.arc(sx, sy, sr, 0, Math.PI * 2);
cx.fillStyle = `rgba(255,255,255,${sa})`;
cx.fill();
}
// Glass shards
const MCOLS = [[255,102,0],[0,255,136],[0,220,255],[255,255,255],[255,180,0]];
for (let i = 0; i < 14; i++) {
const sx = Math.random() * w;
const sy = Math.random() * h;
const ss = 10 + Math.random() * 35;
const sides = 3 + Math.floor(Math.random() * 3);
const [cr,cg,cb] = MCOLS[Math.floor(Math.random() * MCOLS.length)];
cx.save();
cx.translate(sx, sy);
cx.rotate(Math.random() * Math.PI * 2);
cx.beginPath();
for (let j = 0; j < sides; j++) {
const a = (Math.PI * 2 * j / sides) + (Math.random()-0.5)*0.6;
const r = ss * (0.55 + Math.random() * 0.45);
j === 0 ? cx.moveTo(Math.cos(a)*r, Math.sin(a)*r)
: cx.lineTo(Math.cos(a)*r, Math.sin(a)*r);
}
cx.closePath();
cx.fillStyle = `rgba(${cr},${cg},${cb},0.04)`;
cx.strokeStyle = `rgba(${cr},${cg},${cb},0.38)`;
cx.lineWidth = 1;
cx.fill();
cx.shadowColor = `rgba(${cr},${cg},${cb},0.45)`;
cx.shadowBlur = 6;
cx.stroke();
cx.shadowBlur = 0;
cx.restore();
}
}
/* ──────────────────────────────────────────────
INIT
────────────────────────────────────────────── */
function init() {
if (window.innerWidth <= 768 || ('ontouchstart' in window)) {
initMobileStatic();
return;
}
canvas = document.getElementById('hexCanvas');
if (!canvas) return;
ctx = canvas.getContext('2d', { alpha: true });
resize();
window.addEventListener('resize', debounce(resize, 200));
document.addEventListener('mousemove', e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
}, { passive: true });
document.addEventListener('mouseleave', () => {
mouse.x = -2000;
mouse.y = -2000;
});
// First shooting star after a short delay
nextShootingStarAt = 3 + Math.random() * 5;
initialized = true;
animate();
}
function resize() {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
canvas.style.position = 'fixed';
canvas.style.top = '0';
canvas.style.left = '0';
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.scale(dpr, dpr);
initStars();
initShards();
}
function debounce(fn, ms) {
let t;
return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); };
}
function destroy() {
if (rafId) cancelAnimationFrame(rafId);
initialized = false;
stars = []; shards = []; shootingStars = [];
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.HexBackground = { init, destroy };
})();

194
scripts/ki-chat-bubble.js Normal file
View File

@@ -0,0 +1,194 @@
// KI Chat Bubble — Flowise embed (Feedgine-branded)
// To remove: delete this file and its <script> tag in index.html.
import Chatbot from "https://cdn.jsdelivr.net/npm/flowise-embed/dist/web.js";
// Read from config - on localhost talk directly to Flowise, on prod use proxy.
const isLocal = ['localhost', '127.0.0.1'].includes(window.location.hostname);
Chatbot.init({
chatflowid: isLocal ? "d63d3d02-b5fa-482c-9161-c21c615fb625" : "chat",
apiHost: isLocal ? "https://flowise.profice.de" : window.location.origin,
theme: {
button: {
backgroundColor: "#05050d",
right: 24,
bottom: 24,
size: "medium",
iconColor: "#ff6600"
},
chatWindow: {
showTitle: true,
title: "Feedgine Assistent",
titleBackgroundColor: "#05050d",
titleTextColor: "#e8e8f2",
welcomeMessage: "Hallo! Ich bin der KI-Assistent von Feedgine.\n\nIch beantworte Ihre Fragen rund um Profit Intelligence, POAS-Optimierung und Server-Side Tracking.",
backgroundColor: "#0d0d1a",
fontSize: 15,
showAgentMessages: true,
poweredByTextColor: "#0d0d1a",
botMessage: {
backgroundColor: "rgba(255,255,255,0.07)",
textColor: "#e8e8f2",
showAvatar: true,
avatarSrc: "/images/icons/KI.png"
},
userMessage: {
backgroundColor: "rgba(255,102,0,0.18)",
textColor: "#e8e8f2",
showAvatar: false
},
textInput: {
placeholder: "Ihre Nachricht...",
backgroundColor: "rgba(255,255,255,0.04)",
textColor: "#e8e8f2",
sendButtonColor: "#ff6600"
},
footer: {
textColor: "#8888a8",
text: "KI-System · Keine sensiblen Daten eingeben.",
company: " ",
companyLink: ""
}
}
}
});
// Inject orange border around the chat header via shadow DOM
function injectHeaderStyle() {
const flowise = document.querySelector('flowise-chatbot');
if (!flowise?.shadowRoot) return false;
const shadow = flowise.shadowRoot;
if (shadow.querySelector('#feedgine-header-applied')) return true;
const all = [...shadow.querySelectorAll('*')];
const header = all.find(el => {
const bg = getComputedStyle(el).backgroundColor;
return bg === 'rgb(5, 5, 13)' && el.textContent.includes('Feedgine Assistent');
});
if (!header) return false;
header.style.borderTop = '1px solid rgba(255,102,0,0.5)';
header.style.borderLeft = '1px solid rgba(255,102,0,0.5)';
header.style.borderRight = '1px solid rgba(255,102,0,0.5)';
header.style.borderBottom = '1px solid rgba(255,102,0,0.25)';
header.style.boxShadow = '0 0 10px rgba(255,102,0,0.28), 0 0 24px rgba(255,102,0,0.10)';
header.style.boxSizing = 'border-box';
const chatWindow = header.parentElement;
if (chatWindow) {
chatWindow.style.borderBottomLeftRadius = '12px';
chatWindow.style.borderBottomRightRadius = '12px';
chatWindow.style.border = '1px solid rgba(255,102,0,0.25)';
chatWindow.style.boxShadow = '0 0 30px rgba(255,102,0,0.08)';
chatWindow.style.overflow = 'hidden';
}
const marker = document.createElement('span');
marker.id = 'feedgine-header-applied';
marker.style.display = 'none';
shadow.appendChild(marker);
return true;
}
let attempts = 0;
const interval = setInterval(() => {
if (injectHeaderStyle() || ++attempts > 20) clearInterval(interval);
}, 300);
// Keep shadow DOM cursor in sync with the main-page cursor toggle.
// CSS from the outer document cannot penetrate shadow DOM, so we inject
// a <style> tag directly and update it whenever body's class changes.
function updateShadowCursor(shadow) {
let style = shadow.querySelector('#feedgine-cursor-style');
if (!style) {
style = document.createElement('style');
style.id = 'feedgine-cursor-style';
shadow.appendChild(style);
}
style.textContent = document.body.classList.contains('system-cursor')
? ''
: '* { cursor: none !important; }';
}
function initShadowCursorSync() {
const host = document.querySelector('flowise-chatbot');
if (!host?.shadowRoot) return false;
updateShadowCursor(host.shadowRoot);
new MutationObserver(() => updateShadowCursor(host.shadowRoot))
.observe(document.body, { attributes: true, attributeFilter: ['class'] });
return true;
}
let cursorAttempts = 0;
const cursorInterval = setInterval(() => {
if (initShadowCursorSync() || ++cursorAttempts > 20) clearInterval(cursorInterval);
}, 300);
// Global helper: open the Flowise chat and automatically send a message.
// Called by CTA buttons across the page.
window.openChatWithMessage = function (message) {
// Defer by one tick so Flowise's own click-outside handler runs first
// (otherwise it would close the chat immediately after we open it).
setTimeout(() => {
const chatbot = document.querySelector('flowise-chatbot');
if (!chatbot || !chatbot.shadowRoot) return;
const shadow = chatbot.shadowRoot;
// Open chat window if not yet visible
if (!shadow.querySelector('textarea')) {
const toggleBtn = shadow.querySelector('button[part="button"]') || shadow.querySelector('button');
if (toggleBtn) toggleBtn.click();
}
// Poll until the textarea is available, then fill + send via Enter key
let tries = 0;
const poll = setInterval(() => {
if (++tries > 30) { clearInterval(poll); return; }
const ta = shadow.querySelector('textarea');
if (!ta) return;
clearInterval(poll);
// Use execCommand('insertText') — fires a real, trusted InputEvent
// that Solid.js signal tracking actually picks up (unlike synthetic events).
ta.focus();
ta.select();
document.execCommand('delete', false);
document.execCommand('insertText', false, message);
// Find and click the send button.
// Walk UP from the textarea; the send button is a sibling in the
// same input-row container. Fall back to the last non-toggle button
// in the entire shadow DOM (send button is always last in source order).
setTimeout(() => {
const toggleBtn = shadow.querySelector('button[part="button"]');
let sendBtn = null;
let el = ta;
for (let i = 0; i < 6 && !sendBtn; i++) {
el = el.parentElement;
if (!el) break;
const candidate = [...el.querySelectorAll('button')]
.find(b => b !== toggleBtn);
if (candidate) sendBtn = candidate;
}
// Fallback: the send button is the last button in the shadow DOM
if (!sendBtn) {
const all = [...shadow.querySelectorAll('button')]
.filter(b => b !== toggleBtn);
sendBtn = all[all.length - 1] || null;
}
if (sendBtn) sendBtn.click();
}, 300);
}, 150);
}, 200); // 200 ms lets the click-outside event fully propagate before we open
};

64
scripts/scroll-header.js Normal file
View File

@@ -0,0 +1,64 @@
/**
* Scroll Header — adapted from Profice WebSite
* Handles sticky header state changes on scroll.
*/
document.addEventListener("DOMContentLoaded", function () {
const topBanner = document.querySelector('.top-banner');
if (!topBanner) return;
const scrollThreshold = 50;
const rafDelay = 8;
let isScrolled = false;
let lastScrollY = 0;
let rafId = null;
let lastUpdateTime = 0;
let lastScrollTime = 0;
function calculateVelocity(currentScrollY, currentTime) {
if (lastScrollTime === 0) { lastScrollTime = currentTime; return 0; }
const timeDelta = currentTime - lastScrollTime;
const scrollDelta = Math.abs(currentScrollY - lastScrollY);
lastScrollTime = currentTime;
return scrollDelta / timeDelta;
}
function updateHeaderState(scrolled, velocity = 0) {
if (scrolled === isScrolled) return;
if (velocity > 5) {
topBanner.classList.add('fast-scroll');
} else {
topBanner.classList.remove('fast-scroll');
}
requestAnimationFrame(() => {
if (scrolled) {
topBanner.classList.add('scrolled');
} else {
topBanner.classList.remove('scrolled');
}
});
isScrolled = scrolled;
}
function handleScroll(currentTime) {
if (currentTime - lastUpdateTime < rafDelay) {
rafId = requestAnimationFrame(handleScroll);
return;
}
lastUpdateTime = currentTime;
const currentScrollY = window.pageYOffset || document.documentElement.scrollTop;
const velocity = calculateVelocity(currentScrollY, currentTime);
updateHeaderState(currentScrollY > scrollThreshold, velocity);
lastScrollY = currentScrollY;
rafId = null;
}
window.addEventListener('scroll', () => {
if (!rafId) rafId = requestAnimationFrame(handleScroll);
}, { passive: true });
// Initial state
if (window.pageYOffset > scrollThreshold) updateHeaderState(true);
});

103
style/cursor.css Normal file
View File

@@ -0,0 +1,103 @@
/* cursor.css — Venom/Spider Cursor · Space Edition */
/* ── Default: system cursor ── */
body.system-cursor,
body.system-cursor *,
body.system-cursor a,
body.system-cursor button,
body.system-cursor input,
body.system-cursor textarea,
body.system-cursor select,
body.system-cursor label,
body.system-cursor [role="button"],
body.system-cursor [onclick],
body.system-cursor .clickable {
cursor: auto !important;
}
/* ── Custom cursor: hide native cursor everywhere ── */
body:not(.system-cursor),
body:not(.system-cursor) * {
cursor: none !important;
}
/* Flowise toggle button is exposed via part="button" — pierce shadow DOM */
body:not(.system-cursor) flowise-chatbot::part(button) {
cursor: none !important;
}
/* ── Canvas overlay ── */
#venom-cursor {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 2147483647;
display: none;
}
body:not(.system-cursor) #venom-cursor {
display: block;
}
body.system-cursor #venom-cursor {
display: none !important;
opacity: 0;
pointer-events: none;
}
/* ── Touch devices: revert ── */
@media (pointer: coarse) {
body, html, a, button, input, textarea, select {
cursor: auto !important;
}
#venom-cursor {
display: none !important;
}
}
/* ── Cursor toggle button ── */
#cursorToggle {
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid rgba(255,255,255,0.18);
color: rgba(255,255,255,0.70);
width: 46px;
height: 46px;
border-radius: 10px;
cursor: auto;
transition: border-color 0.25s, background 0.25s, box-shadow 0.25s;
padding: 0;
overflow: hidden;
}
#cursorToggle:hover {
border-color: rgba(255,255,255,0.40);
background: rgba(255,255,255,0.05);
}
#cursorToggle.active {
border-color: rgba(255,102,0,0.45);
background: rgba(255,102,0,0.06);
box-shadow: 0 0 8px rgba(255,102,0,0.12);
}
#cursorToggle .cursor-icon {
width: 28px;
height: 28px;
object-fit: contain;
display: block;
filter: brightness(0) invert(1);
opacity: 0.65;
transition: opacity 0.2s;
}
#cursorToggle:hover .cursor-icon,
#cursorToggle.active .cursor-icon {
opacity: 0.95;
}

1964
style/design.css Normal file

File diff suppressed because it is too large Load Diff

16
style/fonts.css Normal file
View File

@@ -0,0 +1,16 @@
/* Local Montserrat fonts — loaded before any external request */
@font-face {
font-family: 'Montserrat';
src: url('../fonts/montserrat-regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Montserrat';
src: url('../fonts/montserrat-bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
font-display: swap;
}