-
This commit is contained in:
181
src/components/Booking.astro
Normal file
181
src/components/Booking.astro
Normal file
@@ -0,0 +1,181 @@
|
||||
---
|
||||
---
|
||||
<section class="booking-section" id="kontakt">
|
||||
<div class="booking-inner">
|
||||
<div class="booking-content">
|
||||
<div class="booking-tag" data-reveal>Beratungstermin · 30 Min</div>
|
||||
<h2 data-reveal style="--reveal-delay:80ms">In 30 Minuten sprechen wir<br>über <em>Ihren Use Case</em>.</h2>
|
||||
<p data-reveal style="--reveal-delay:160ms">Kein Pitch, kein Verkaufsgespräch. Wir hören zu, stellen Fragen, und sagen Ihnen ehrlich, ob wir der richtige Partner sind – oder nicht.</p>
|
||||
<ul class="booking-list" data-reveal style="--reveal-delay:240ms">
|
||||
<li>
|
||||
<span class="li-num">01</span>
|
||||
<div>
|
||||
<strong>Konkrete Machbarkeit</strong>
|
||||
<p>Wir schätzen Aufwand, Risiken und Zeitrahmen Ihres Projekts ein – nicht in Phrasen, sondern in Wochen.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="li-num">02</span>
|
||||
<div>
|
||||
<strong>Erste Architektur-Skizze</strong>
|
||||
<p>Welche Methoden, Modelle und Werkzeuge wir für Ihre Anforderungen einsetzen würden.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="li-num">03</span>
|
||||
<div>
|
||||
<strong>Klare nächste Schritte</strong>
|
||||
<p>Mit oder ohne uns weiter – Sie verlassen das Gespräch mit einer Empfehlung, die unabhängig vom Auftrag funktioniert.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="booking-cal" data-reveal style="--reveal-delay:120ms">
|
||||
<div class="cal-placeholder">
|
||||
<div class="cal-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="5" width="18" height="16" rx="1"></rect>
|
||||
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||||
<line x1="8" y1="3" x2="8" y2="7"></line>
|
||||
<line x1="16" y1="3" x2="16" y2="7"></line>
|
||||
<circle cx="8" cy="15" r="1" fill="currentColor"></circle>
|
||||
<circle cx="12" cy="15" r="1" fill="currentColor"></circle>
|
||||
<circle cx="16" cy="15" r="1" fill="currentColor"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cal-label">Termin wählen</div>
|
||||
<div class="cal-title">30-Minuten-Slot</div>
|
||||
<div class="cal-sub">Hier läuft Ihr Booking-Embed (Cal.com, Calendly, o.ä.). Bis dahin schreiben Sie uns direkt – wir antworten innerhalb eines Werktags.</div>
|
||||
<div class="cal-actions">
|
||||
<a href="mailto:contact@frontieralgorithmics.com?subject=Beratungstermin%20–%20Frontier%20Algorithmics" class="btn-cal">Per E-Mail anfragen ↗</a>
|
||||
<div class="cal-tiny">
|
||||
oder direkt: <a href="mailto:contact@frontieralgorithmics.com">contact@frontieralgorithmics.com</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.booking-section {
|
||||
padding: 6rem var(--content-pad);
|
||||
background: #fff;
|
||||
border-top: 0.5px solid var(--fa-border);
|
||||
border-bottom: 0.5px solid var(--fa-border);
|
||||
}
|
||||
.booking-inner {
|
||||
display: grid; grid-template-columns: 1fr 1.05fr;
|
||||
gap: 4rem; align-items: stretch;
|
||||
}
|
||||
.booking-content { display: flex; flex-direction: column; }
|
||||
.booking-tag {
|
||||
display: inline-flex; align-items: center; gap: 10px;
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 3px; text-transform: uppercase;
|
||||
color: var(--fa-blue); margin-bottom: 1rem;
|
||||
}
|
||||
.booking-tag::before { content: ''; display: block; width: 24px; height: 1px; background: var(--fa-blue); }
|
||||
.booking-content h2 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: clamp(1.7rem,2.6vw,2.4rem); font-weight: 700;
|
||||
line-height: 1.1; letter-spacing: -1px; color: var(--fa-navy);
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.booking-content h2 em { font-style: normal; color: var(--fa-blue); }
|
||||
.booking-content > p {
|
||||
font-size: 15px; font-weight: 300; line-height: 1.75;
|
||||
color: var(--fa-mid); margin-bottom: 2.25rem; max-width: 440px;
|
||||
}
|
||||
.booking-list {
|
||||
list-style: none; display: flex; flex-direction: column; gap: 1.5rem;
|
||||
margin-top: auto;
|
||||
}
|
||||
.booking-list li { display: grid; grid-template-columns: 2.5rem 1fr; gap: 1rem; }
|
||||
.li-num {
|
||||
font-family: 'Syne', sans-serif; font-size: 12px; font-weight: 700;
|
||||
color: var(--fa-blue); letter-spacing: 1.5px; padding-top: 3px;
|
||||
}
|
||||
.booking-list strong {
|
||||
display: block; font-size: 15px; font-weight: 500;
|
||||
color: var(--fa-navy); margin-bottom: 5px;
|
||||
}
|
||||
.booking-list p { font-size: 14px; font-weight: 300; line-height: 1.65; color: var(--fa-mid); }
|
||||
|
||||
.booking-cal {
|
||||
background: var(--fa-navy);
|
||||
color: #fff;
|
||||
min-height: 460px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
.cal-placeholder {
|
||||
flex: 1; display: flex; flex-direction: column;
|
||||
align-items: flex-start; justify-content: center;
|
||||
padding: 3rem 2.75rem; gap: 0.5rem;
|
||||
position: relative;
|
||||
}
|
||||
.cal-placeholder::before {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(168,192,232,0.06) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(168,192,232,0.06) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.cal-icon {
|
||||
width: 44px; height: 44px;
|
||||
border: 0.5px solid rgba(168,192,232,0.4);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: 1.25rem; position: relative; z-index: 1;
|
||||
}
|
||||
.cal-icon svg { width: 22px; height: 22px; stroke: var(--fa-blue-light); }
|
||||
.cal-label {
|
||||
font-family: 'Syne', sans-serif; font-size: 11px; font-weight: 600;
|
||||
letter-spacing: 2.5px; text-transform: uppercase;
|
||||
color: var(--fa-blue-light); position: relative; z-index: 1;
|
||||
}
|
||||
.cal-title {
|
||||
font-family: 'Syne', sans-serif; font-size: 1.6rem; font-weight: 700;
|
||||
color: #fff; letter-spacing: -0.5px; line-height: 1.1;
|
||||
margin: 0.25rem 0 0.75rem; position: relative; z-index: 1;
|
||||
}
|
||||
.cal-sub {
|
||||
font-size: 14px; font-weight: 300; color: rgba(255,255,255,0.72);
|
||||
line-height: 1.6; max-width: 320px; margin-bottom: 1.75rem;
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
.cal-actions {
|
||||
display: flex; flex-direction: column; gap: 0.75rem;
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
.btn-cal {
|
||||
font-family: 'DM Sans', sans-serif; font-size: 13px; font-weight: 500;
|
||||
color: var(--fa-navy); background: #fff; border: none;
|
||||
padding: 0.85rem 1.6rem; cursor: pointer; transition: background 0.2s;
|
||||
text-decoration: none; display: inline-flex; align-items: center; gap: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-cal:hover { background: var(--fa-blue-light); }
|
||||
.cal-tiny {
|
||||
font-size: 12px; color: rgba(255,255,255,0.65);
|
||||
letter-spacing: 0.3px; line-height: 1.5;
|
||||
}
|
||||
.cal-tiny a { color: var(--fa-blue-light); text-decoration: none; border-bottom: 0.5px solid rgba(168,192,232,0.3); }
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.booking-section { padding-top: 5.5rem; padding-bottom: 5.5rem; }
|
||||
.booking-inner { gap: 3rem; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.booking-section { padding: 4.5rem var(--content-pad); gap: 3rem; }
|
||||
.booking-inner { grid-template-columns: 1fr; }
|
||||
.booking-cal { min-height: 380px; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.booking-cal { min-height: 320px; }
|
||||
.cal-placeholder { padding: 2rem 1.75rem; }
|
||||
.cal-title { font-size: 1.3rem; }
|
||||
}
|
||||
</style>
|
||||
49
src/components/CTA.astro
Normal file
49
src/components/CTA.astro
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
---
|
||||
<section class="cta-section">
|
||||
<div>
|
||||
<h2 data-reveal>Bereit, das <em>nächste<br>komplexe Projekt</em> anzugehen?</h2>
|
||||
<p class="cta-sub" data-reveal style="--reveal-delay:100ms">Wir hören zu, bevor wir reden. Kein Proposal ohne Verständnis.</p>
|
||||
</div>
|
||||
<div class="cta-actions" data-reveal style="--reveal-delay:200ms">
|
||||
<a href="#kontakt" class="btn-white">Beratungstermin vereinbaren</a>
|
||||
<div class="cta-loc">Regensburg · Bayern · International</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.cta-section {
|
||||
background: var(--fa-navy); padding: 7rem var(--content-pad);
|
||||
display: grid; grid-template-columns: 1fr auto;
|
||||
align-items: center; gap: 4rem;
|
||||
}
|
||||
.cta-section h2 {
|
||||
font-family: 'Syne', sans-serif; font-size: clamp(1.8rem,3vw,2.8rem);
|
||||
font-weight: 700; letter-spacing: -1.2px; line-height: 1.08; color: #fff;
|
||||
}
|
||||
.cta-section h2 em { font-style: normal; color: var(--fa-blue-light); }
|
||||
.cta-sub { font-size: 14px; font-weight: 300; color: rgba(255,255,255,0.65); margin-top: 0.9rem; }
|
||||
.cta-actions { display: flex; flex-direction: column; gap: 0.9rem; align-items: flex-end; }
|
||||
.btn-white {
|
||||
font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 500;
|
||||
color: var(--fa-navy); background: #fff; border: none;
|
||||
padding: 0.9rem 2.25rem; cursor: pointer; white-space: nowrap;
|
||||
transition: background 0.2s; text-decoration: none;
|
||||
display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
}
|
||||
.btn-white:hover { background: var(--fa-blue-light); }
|
||||
.cta-loc { font-size: 11px; color: rgba(255,255,255,0.55); letter-spacing: 2px; text-transform: uppercase; }
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.cta-section { padding-top: 5.5rem; padding-bottom: 5.5rem; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.cta-section { grid-template-columns: 1fr; padding: 4.5rem var(--content-pad); }
|
||||
.cta-actions { align-items: flex-start; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.cta-section h2 { font-size: clamp(1.6rem, 7vw, 2.2rem); }
|
||||
.cta-actions { width: 100%; }
|
||||
.btn-white { width: 100%; text-align: center; justify-content: center; }
|
||||
}
|
||||
</style>
|
||||
35
src/components/Footer.astro
Normal file
35
src/components/Footer.astro
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
---
|
||||
<footer class="site-footer">
|
||||
<div class="footer-left">© 2025 Frontier Algorithmics · Regensburg</div>
|
||||
<div class="footer-links">
|
||||
<a href="#">Impressum</a>
|
||||
<a href="#">Datenschutz</a>
|
||||
<a href="#">LinkedIn</a>
|
||||
<a href="https://kontorion.eu" target="_blank" rel="noopener">kontorion.eu</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
.site-footer {
|
||||
background: var(--fa-navy-deep);
|
||||
padding: 2rem var(--content-pad);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 1.5rem; flex-wrap: wrap;
|
||||
}
|
||||
.footer-left {
|
||||
font-family: 'Syne', sans-serif; font-size: 13px; font-weight: 600;
|
||||
letter-spacing: 2px; text-transform: uppercase; color: rgba(255,255,255,0.55);
|
||||
}
|
||||
.footer-links { display: flex; gap: 1.75rem; }
|
||||
.footer-links a {
|
||||
font-size: 13px; color: rgba(255,255,255,0.55); text-decoration: none;
|
||||
letter-spacing: 0.5px; transition: color 0.2s;
|
||||
}
|
||||
.footer-links a:hover { color: rgba(255,255,255,0.9); }
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.site-footer { padding: 1.5rem var(--content-pad); flex-direction: column; gap: 1rem; align-items: flex-start; }
|
||||
.footer-links { flex-wrap: wrap; gap: 1rem 1.5rem; }
|
||||
}
|
||||
</style>
|
||||
622
src/components/Hero.astro
Normal file
622
src/components/Hero.astro
Normal file
@@ -0,0 +1,622 @@
|
||||
---
|
||||
---
|
||||
<section class="hero" id="top">
|
||||
<canvas id="hero-canvas"></canvas>
|
||||
<canvas id="hero-canvas-gl"></canvas>
|
||||
|
||||
<div class="hero-content">
|
||||
<div class="ai-badge">
|
||||
<div class="ai-dot"></div>
|
||||
KI-getriebene Technologieberatung
|
||||
</div>
|
||||
<div class="eyebrow">Regensburg · Bayern · International</div>
|
||||
<h1 id="fa-headline">Komplexe Systeme.<br /><em class="underline">Klare Lösungen.</em><br />Echter Fortschritt.</h1>
|
||||
<p class="hero-sub">Von intelligenten KI-Agenten bis zur regulierten Medizinsoftware – wir verbinden modernste Algorithmen mit der Präzision, die kritische Systeme verlangen.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="#kontakt" class="btn-primary">Beratungstermin vereinbaren</a>
|
||||
<a href="#kontorion" class="btn-ghost">Kontorion ansehen ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-visual" id="fa-hero-visual">
|
||||
<div class="cap-card" id="fa-cap-card">
|
||||
<div class="cap-head">
|
||||
<span>Kern-Disziplinen</span>
|
||||
<span class="cap-head-mark">Live</span>
|
||||
</div>
|
||||
<div class="cap-row">
|
||||
<div class="cap-tag">AI</div>
|
||||
<div class="cap-body">
|
||||
<div class="cap-title">Generative KI & Agents</div>
|
||||
<div class="cap-keys">LLM · RAG · Computer Vision · MLOps</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap-row">
|
||||
<div class="cap-tag">MD</div>
|
||||
<div class="cap-body">
|
||||
<div class="cap-title">Regulierte Software</div>
|
||||
<div class="cap-keys">MDR · IEC 62304 · ISO 13485 · FHIR / DICOM</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap-row">
|
||||
<div class="cap-tag">SW</div>
|
||||
<div class="cap-body">
|
||||
<div class="cap-title">Engineering & Architektur</div>
|
||||
<div class="cap-keys">Apps · Backend · APIs · Tech Consulting</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap-foot">
|
||||
<span class="cap-foot-dot">Regensburg · DE</span>
|
||||
<span>Seit 2020</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fa-stat-stack" style="display:none; flex-direction:column; gap:1.25rem; align-items:flex-end; width:100%;">
|
||||
<div class="stat-card">
|
||||
<div class="num">LLM</div>
|
||||
<div class="label">Agents · RAG · Fine-Tuning<br />Generative KI für Ihr Unternehmen</div>
|
||||
</div>
|
||||
<div class="stat-card light">
|
||||
<div class="num">MDR</div>
|
||||
<div class="label">IEC 62304 · HL7 · FHIR · DICOM<br />Regulatorische Expertise</div>
|
||||
</div>
|
||||
<div class="hero-badge">Seit Regensburg</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
min-height: 88vh;
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 0.85fr;
|
||||
align-items: center;
|
||||
padding: 5rem var(--content-pad) 4.5rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
.hero.no-visual {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 6rem var(--content-pad) 5.5rem;
|
||||
}
|
||||
.hero.no-visual .hero-content { padding-right: 0; max-width: 880px; }
|
||||
.hero.no-visual h1 { font-size: clamp(2.8rem, 5.4vw, 5rem); }
|
||||
.hero.no-visual .hero-sub { max-width: 540px; font-size: 17px; }
|
||||
|
||||
#hero-canvas, #hero-canvas-gl {
|
||||
position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0;
|
||||
}
|
||||
#hero-canvas { opacity: 1; }
|
||||
#hero-canvas-gl { opacity: 0; transition: opacity 0.35s ease; pointer-events: none; }
|
||||
#hero-canvas-gl.active { opacity: 1; }
|
||||
#hero-canvas-gl.active {
|
||||
-webkit-mask-image: linear-gradient(to right, transparent 0%, transparent 18%, rgba(0,0,0,0.18) 30%, rgba(0,0,0,0.55) 45%, rgba(0,0,0,0.9) 60%, #000 75%);
|
||||
mask-image: linear-gradient(to right, transparent 0%, transparent 18%, rgba(0,0,0,0.18) 30%, rgba(0,0,0,0.55) 45%, rgba(0,0,0,0.9) 60%, #000 75%);
|
||||
}
|
||||
.hero.gl-active::before {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background: linear-gradient(to right, rgba(255,255,255,0.88) 0%, rgba(255,255,255,0.78) 22%, rgba(255,255,255,0.45) 42%, rgba(255,255,255,0.1) 60%, rgba(255,255,255,0) 75%);
|
||||
z-index: 1; pointer-events: none; transition: opacity 0.35s ease;
|
||||
}
|
||||
#hero-canvas.hidden { opacity: 0; }
|
||||
|
||||
.hero-content { position: relative; z-index: 2; padding-right: 3.5rem; max-width: 720px; }
|
||||
|
||||
.eyebrow {
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 3px; text-transform: uppercase;
|
||||
color: var(--fa-blue); margin-bottom: 1.75rem;
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
}
|
||||
.eyebrow::before { content: ''; display: block; width: 36px; height: 1px; background: var(--fa-blue); }
|
||||
|
||||
.ai-badge {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
background: var(--fa-navy); color: #fff;
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 1.5px; text-transform: uppercase;
|
||||
padding: 0.45rem 1rem 0.45rem 0.7rem; margin-bottom: 1.5rem;
|
||||
}
|
||||
.ai-dot {
|
||||
width: 7px; height: 7px; border-radius: 50%; background: var(--fa-blue);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: clamp(2.6rem, 4.4vw, 4.2rem);
|
||||
font-weight: 700; line-height: 0.98;
|
||||
color: var(--fa-navy); letter-spacing: -2.5px; margin-bottom: 2.25rem;
|
||||
text-wrap: balance;
|
||||
}
|
||||
h1 em {
|
||||
font-style: normal; color: var(--fa-blue);
|
||||
position: relative; display: inline-block;
|
||||
color: color-mix(in oklch, var(--fa-blue), oklch(0.55 0.22 255) calc(var(--hero-progress, 0) * 100%));
|
||||
transition: color 0.05s linear;
|
||||
}
|
||||
h1 em.underline::after {
|
||||
content: ''; position: absolute; left: 0; right: 0; bottom: 0.06em;
|
||||
height: 0.08em; background: var(--fa-blue); opacity: 0.35;
|
||||
transform-origin: left center;
|
||||
animation: faUnderlineDraw 1.2s cubic-bezier(0.22, 0.61, 0.36, 1) 0.6s both;
|
||||
}
|
||||
h1 em.underline::before {
|
||||
content: ''; position: absolute; left: 0; bottom: 0.04em; height: 0.14em;
|
||||
width: calc(var(--hero-progress, 0) * 100%);
|
||||
background: linear-gradient(90deg, oklch(0.62 0.18 250) 0%, oklch(0.70 0.22 260) 100%);
|
||||
opacity: 0.85; border-radius: 1px;
|
||||
box-shadow: 0 0 12px oklch(0.65 0.22 255 / 0.4);
|
||||
transition: width 0.05s linear; pointer-events: none;
|
||||
}
|
||||
h1.flat em { color: var(--fa-navy); }
|
||||
h1.flat em::after, h1.flat em::before { display: none; }
|
||||
|
||||
.hero-sub {
|
||||
font-size: 16px; font-weight: 300; line-height: 1.8;
|
||||
color: var(--fa-mid); max-width: 460px; margin-bottom: 3rem;
|
||||
}
|
||||
.hero-actions { display: flex; align-items: center; gap: 1.75rem; }
|
||||
|
||||
.btn-primary {
|
||||
font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 500;
|
||||
color: #fff; background: var(--fa-navy); border: none;
|
||||
padding: 0.9rem 2.25rem; cursor: pointer; transition: background 0.2s;
|
||||
text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-primary:hover { background: var(--fa-blue); }
|
||||
.btn-ghost {
|
||||
font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 400;
|
||||
color: var(--fa-navy); background: none; border: none; cursor: pointer;
|
||||
opacity: 0.6; transition: opacity 0.2s;
|
||||
text-decoration: none; display: inline-flex; align-items: center;
|
||||
}
|
||||
.btn-ghost:hover { opacity: 1; }
|
||||
|
||||
/* Hero visual */
|
||||
.hero-visual {
|
||||
position: relative; z-index: 2;
|
||||
display: flex; flex-direction: column; align-items: stretch;
|
||||
max-width: 380px; margin-left: auto;
|
||||
}
|
||||
.cap-card {
|
||||
background: var(--fa-navy); color: #fff;
|
||||
border: 0.5px solid rgba(107,143,212,0.25);
|
||||
}
|
||||
.cap-head {
|
||||
padding: 1.1rem 1.5rem;
|
||||
border-bottom: 0.5px solid rgba(107,143,212,0.18);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
font-family: 'Syne', sans-serif; font-size: 11px; font-weight: 600;
|
||||
letter-spacing: 2.5px; text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.68);
|
||||
}
|
||||
.cap-head-mark {
|
||||
display: flex; align-items: center; gap: 6px; color: var(--fa-blue-light);
|
||||
}
|
||||
.cap-head-mark::before {
|
||||
content: ''; width: 5px; height: 5px; border-radius: 50%;
|
||||
background: var(--fa-blue-light); animation: pulse 2.4s ease-in-out infinite;
|
||||
}
|
||||
.cap-row {
|
||||
display: grid; grid-template-columns: 44px 1fr;
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-bottom: 0.5px solid rgba(107,143,212,0.12);
|
||||
gap: 1rem; align-items: center; transition: background 0.25s;
|
||||
}
|
||||
.cap-row:last-of-type { border-bottom: none; }
|
||||
.cap-row:hover { background: rgba(107,143,212,0.06); }
|
||||
.cap-tag {
|
||||
font-family: 'Syne', sans-serif; font-size: 12px; font-weight: 700;
|
||||
letter-spacing: 1.5px; color: var(--fa-blue-light);
|
||||
border: 0.5px solid rgba(168,192,232,0.35); padding: 6px 0; text-align: center;
|
||||
}
|
||||
.cap-title {
|
||||
font-family: 'Syne', sans-serif; font-size: 15px; font-weight: 600;
|
||||
color: #fff; letter-spacing: -0.2px; margin-bottom: 4px;
|
||||
}
|
||||
.cap-keys {
|
||||
font-size: 12px; font-weight: 300; line-height: 1.5;
|
||||
color: rgba(255,255,255,0.68); letter-spacing: 0.2px;
|
||||
}
|
||||
.cap-foot {
|
||||
padding: 0.85rem 1.5rem; background: rgba(0,0,0,0.18);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
font-size: 11px; letter-spacing: 1.8px; text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.65);
|
||||
}
|
||||
.cap-foot-dot { display: inline-flex; align-items: center; gap: 8px; color: rgba(255,255,255,0.7); }
|
||||
.cap-foot-dot::before {
|
||||
content: ''; width: 6px; height: 6px; border-radius: 50%;
|
||||
background: #6FD49A; box-shadow: 0 0 0 3px rgba(111,212,154,0.18);
|
||||
}
|
||||
|
||||
/* Stat cards */
|
||||
.stat-card {
|
||||
background: var(--fa-navy); color: #fff;
|
||||
padding: 1.75rem 2.25rem; width: 290px;
|
||||
}
|
||||
.stat-card .num {
|
||||
font-family: 'Syne', sans-serif; font-size: 2rem; font-weight: 800;
|
||||
letter-spacing: -0.5px; color: var(--fa-blue-light); line-height: 1; margin-bottom: 0.4rem;
|
||||
}
|
||||
.stat-card .label { font-size: 13px; font-weight: 300; opacity: 0.65; line-height: 1.5; }
|
||||
.stat-card.light {
|
||||
background: rgba(238,243,251,0.9); color: var(--fa-navy);
|
||||
border: 0.5px solid var(--fa-border); margin-right: 2rem;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.stat-card.light .num { color: var(--fa-navy); }
|
||||
.stat-card.light .label { opacity: 0.55; color: var(--fa-navy); }
|
||||
.hero-badge {
|
||||
background: var(--fa-blue); color: #fff;
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 2px; text-transform: uppercase;
|
||||
padding: 0.55rem 1.1rem; align-self: flex-start; margin-left: 1rem;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 900px) {
|
||||
.hero { grid-template-columns: 1fr; min-height: auto; padding: 4rem var(--content-pad); }
|
||||
.hero-content { padding-right: 0; }
|
||||
.hero-visual { display: none; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
h1 { font-size: clamp(1.85rem, 8.5vw, 2.6rem); letter-spacing: -1.2px; }
|
||||
.eyebrow { font-size: 10px; letter-spacing: 2px; margin-bottom: 1.25rem; }
|
||||
.hero-sub { font-size: 15px; margin-bottom: 2rem; }
|
||||
.hero-actions { flex-wrap: wrap; gap: 1rem; }
|
||||
.btn-primary { padding: 0.85rem 1.6rem; }
|
||||
.hero { padding: 3rem var(--content-pad) 3.5rem; }
|
||||
#hero-canvas, #hero-canvas-gl { opacity: 0.5; }
|
||||
}
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
/* No hover states on touch */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
/* ============================================================
|
||||
HERO SCROLL PROGRESS
|
||||
============================================================ */
|
||||
(function() {
|
||||
const hero = document.querySelector('.hero');
|
||||
if (!hero) return;
|
||||
const root = document.documentElement;
|
||||
let ticking = false;
|
||||
function update() {
|
||||
const rect = hero.getBoundingClientRect();
|
||||
const h = rect.height;
|
||||
let p = -rect.top / h;
|
||||
p = Math.max(0, Math.min(1, (p - 0.12) / 0.7));
|
||||
root.style.setProperty('--hero-progress', p.toFixed(3));
|
||||
ticking = false;
|
||||
}
|
||||
function onScroll() { if (!ticking) { requestAnimationFrame(update); ticking = true; } }
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
window.addEventListener('resize', onScroll);
|
||||
update();
|
||||
})();
|
||||
|
||||
/* ============================================================
|
||||
HERO CANVAS — 2D modes
|
||||
============================================================ */
|
||||
(function() {
|
||||
const canvas = document.getElementById('hero-canvas');
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const BLUE = '107,143,212', NAVY = '26,31,92';
|
||||
let W = 0, H = 0, nodes = [], hubs = [], flowParticles = [];
|
||||
let mode = 'constellation', rafId = null;
|
||||
const mouse = { x: -9999, y: -9999 };
|
||||
let running = true;
|
||||
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||
|
||||
function resize() {
|
||||
const rect = canvas.parentElement.getBoundingClientRect();
|
||||
W = rect.width; H = rect.height;
|
||||
canvas.width = W * dpr; canvas.height = H * dpr;
|
||||
canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
function rand(a, b) { return a + Math.random() * (b - a); }
|
||||
|
||||
/* CONSTELLATION */
|
||||
function initConstellation() {
|
||||
hubs = [
|
||||
{ ax: 0.18, ay: 0.30, label: 'LLM · RAG' },
|
||||
{ ax: 0.62, ay: 0.18, label: 'COMPUTER VISION' },
|
||||
{ ax: 0.78, ay: 0.62, label: 'MDR · 62304' },
|
||||
{ ax: 0.32, ay: 0.82, label: 'FHIR · DICOM' },
|
||||
].map((h, i) => ({ ...h, x: h.ax * W, y: h.ay * H, vx: 0, vy: 0, r: 4.5, phase: i * 1.7 }));
|
||||
nodes = [];
|
||||
const count = Math.max(28, Math.floor((W * H) / 22000));
|
||||
for (let i = 0; i < count; i++) {
|
||||
nodes.push({ x: rand(0, W), y: rand(0, H), vx: rand(-0.18, 0.18), vy: rand(-0.15, 0.15), r: rand(1.2, 2.4) });
|
||||
}
|
||||
}
|
||||
function updateConstellation(t) {
|
||||
for (const h of hubs) {
|
||||
const targetX = h.ax * W + Math.sin(t * 0.0004 + h.phase) * 18;
|
||||
const targetY = h.ay * H + Math.cos(t * 0.0005 + h.phase * 1.3) * 14;
|
||||
h.vx += (targetX - h.x) * 0.008; h.vy += (targetY - h.y) * 0.008;
|
||||
h.vx *= 0.92; h.vy *= 0.92; h.x += h.vx; h.y += h.vy;
|
||||
}
|
||||
for (const n of nodes) {
|
||||
n.x += n.vx; n.y += n.vy;
|
||||
if (n.x < -10) n.x = W + 10; if (n.x > W + 10) n.x = -10;
|
||||
if (n.y < -10) n.y = H + 10; if (n.y > H + 10) n.y = -10;
|
||||
}
|
||||
}
|
||||
function nearestK(node, others, k) {
|
||||
const arr = [];
|
||||
for (const o of others) {
|
||||
if (o === node) continue;
|
||||
const dx = node.x - o.x, dy = node.y - o.y;
|
||||
arr.push({ o, d: Math.sqrt(dx*dx + dy*dy) });
|
||||
}
|
||||
arr.sort((a, b) => a.d - b.d);
|
||||
return arr.slice(0, k);
|
||||
}
|
||||
function drawConstellation() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
ctx.lineWidth = 0.9;
|
||||
for (const n of nodes) {
|
||||
const nn = nearestK(n, nodes, 2);
|
||||
for (const { o, d } of nn) {
|
||||
if (d > 160) continue;
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - d / 160) * 0.25})`;
|
||||
ctx.beginPath(); ctx.moveTo(n.x, n.y); ctx.lineTo(o.x, o.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
ctx.lineWidth = 1.3;
|
||||
for (const h of hubs) {
|
||||
const nn = nearestK(h, nodes, 6);
|
||||
for (const { o, d } of nn) {
|
||||
if (d > 260) continue;
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - d / 260) * 0.55})`;
|
||||
ctx.beginPath(); ctx.moveTo(h.x, h.y); ctx.lineTo(o.x, o.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
if (mouse.x > -9000) {
|
||||
const closest = nearestK({ x: mouse.x, y: mouse.y }, nodes.concat(hubs), 4);
|
||||
ctx.lineWidth = 1.4;
|
||||
for (const { o, d } of closest) {
|
||||
if (d > 220) continue;
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - d / 220) * 0.7})`;
|
||||
ctx.beginPath(); ctx.moveTo(mouse.x, mouse.y); ctx.lineTo(o.x, o.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
for (const n of nodes) {
|
||||
const g2 = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r * 3);
|
||||
g2.addColorStop(0, `rgba(${BLUE}, 0.18)`); g2.addColorStop(1, `rgba(${BLUE}, 0)`);
|
||||
ctx.beginPath(); ctx.arc(n.x, n.y, n.r * 3, 0, Math.PI * 2); ctx.fillStyle = g2; ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2); ctx.fillStyle = `rgba(${BLUE}, 0.45)`; ctx.fill();
|
||||
}
|
||||
const t = performance.now();
|
||||
for (const h of hubs) {
|
||||
const pulseT = ((t * 0.0007 + h.phase) % 1);
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, 8 + pulseT * 52, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - pulseT) * 0.65})`; ctx.lineWidth = 1.2; ctx.stroke();
|
||||
const pulseT2 = ((t * 0.0011 + h.phase + 0.5) % 1);
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, 5 + pulseT2 * 28, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = `rgba(${BLUE}, ${(1 - pulseT2) * 0.45})`; ctx.lineWidth = 0.8; ctx.stroke();
|
||||
const g = ctx.createRadialGradient(h.x, h.y, 0, h.x, h.y, 42);
|
||||
g.addColorStop(0, `rgba(${BLUE}, 0.45)`); g.addColorStop(0.5, `rgba(${BLUE}, 0.12)`); g.addColorStop(1, `rgba(${BLUE}, 0)`);
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, 42, 0, Math.PI * 2); ctx.fillStyle = g; ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, h.r, 0, Math.PI * 2); ctx.fillStyle = `rgba(${BLUE}, 1)`; ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, h.r + 3.5, 0, Math.PI * 2); ctx.strokeStyle = `rgba(${BLUE}, 0.75)`; ctx.lineWidth = 1.2; ctx.stroke();
|
||||
ctx.beginPath(); ctx.arc(h.x, h.y, h.r + 8, 0, Math.PI * 2); ctx.strokeStyle = `rgba(${BLUE}, 0.3)`; ctx.lineWidth = 0.8; ctx.stroke();
|
||||
ctx.font = '700 11px "Syne", sans-serif';
|
||||
const tw = ctx.measureText(h.label).width;
|
||||
const lx = h.x + h.r + 12, ly = h.y - 5;
|
||||
ctx.fillStyle = `rgba(${BLUE}, 0.12)`; ctx.fillRect(lx - 4, ly - 2, tw + 8, 16);
|
||||
ctx.strokeStyle = `rgba(${BLUE}, 0.4)`; ctx.lineWidth = 0.6; ctx.strokeRect(lx - 4, ly - 2, tw + 8, 16);
|
||||
ctx.fillStyle = `rgba(${NAVY}, 0.95)`; ctx.fillText(h.label, lx, ly + 9);
|
||||
}
|
||||
}
|
||||
|
||||
/* NETWORK */
|
||||
function initNetwork() {
|
||||
nodes = []; hubs = [];
|
||||
const count = Math.max(50, Math.floor((W * H) / 14000));
|
||||
for (let i = 0; i < count; i++) {
|
||||
nodes.push({ x: rand(0, W), y: rand(0, H), vx: rand(-0.25, 0.25), vy: rand(-0.2, 0.2), r: rand(2, 5), isKey: Math.random() < 0.12, label: ['LLM','RAG','CV','AI','ML','API','MDR'][Math.floor(Math.random()*7)] });
|
||||
}
|
||||
}
|
||||
function updateNetwork() {
|
||||
for (const n of nodes) { n.x += n.vx; n.y += n.vy; if (n.x < 0 || n.x > W) n.vx *= -1; if (n.y < 0 || n.y > H) n.vy *= -1; }
|
||||
}
|
||||
function drawNetwork() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
for (let j = i + 1; j < nodes.length; j++) {
|
||||
const dx = nodes[i].x - nodes[j].x, dy = nodes[i].y - nodes[j].y;
|
||||
const d = Math.sqrt(dx*dx + dy*dy);
|
||||
if (d < 130) { ctx.strokeStyle = `rgba(${BLUE}, ${(1-d/130)*0.18})`; ctx.lineWidth = 0.6; ctx.beginPath(); ctx.moveTo(nodes[i].x,nodes[i].y); ctx.lineTo(nodes[j].x,nodes[j].y); ctx.stroke(); }
|
||||
}
|
||||
const mdx = nodes[i].x - mouse.x, mdy = nodes[i].y - mouse.y, md = Math.sqrt(mdx*mdx+mdy*mdy);
|
||||
if (md < 160) { ctx.strokeStyle = `rgba(${BLUE}, ${(1-md/160)*0.35})`; ctx.lineWidth = 0.8; ctx.beginPath(); ctx.moveTo(nodes[i].x,nodes[i].y); ctx.lineTo(mouse.x,mouse.y); ctx.stroke(); }
|
||||
}
|
||||
for (const n of nodes) {
|
||||
const g = ctx.createRadialGradient(n.x,n.y,0,n.x,n.y,n.r*4); g.addColorStop(0,`rgba(${BLUE},0.12)`); g.addColorStop(1,`rgba(${BLUE},0)`);
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,n.r*4,0,Math.PI*2); ctx.fillStyle = g; ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,n.r,0,Math.PI*2); ctx.fillStyle = n.isKey ? `rgba(${BLUE},0.75)` : `rgba(${NAVY},0.35)`; ctx.fill();
|
||||
if (n.isKey) { ctx.font = '600 9px "Syne",sans-serif'; ctx.fillStyle = `rgba(${BLUE},0.55)`; ctx.fillText(n.label, n.x+n.r+4, n.y+3); }
|
||||
}
|
||||
}
|
||||
|
||||
/* FLOW FIELD */
|
||||
function initFlowfield() {
|
||||
flowParticles = [];
|
||||
const count = Math.max(160, Math.floor((W * H) / 4500));
|
||||
for (let i = 0; i < count; i++) flowParticles.push({ x: rand(0,W), y: rand(0,H), px:0, py:0, life: rand(0,200), maxLife: 220+rand(0,280), speed: 0.35+rand(0,0.7) });
|
||||
ctx.fillStyle = '#fff'; ctx.fillRect(0,0,W,H);
|
||||
}
|
||||
function flowAngle(x, y, t) {
|
||||
return (Math.sin(x*0.0045+t*0.00025) + Math.cos(y*0.0042-t*0.00030) + Math.sin((x+y)*0.0023+t*0.00040)) * Math.PI;
|
||||
}
|
||||
function updateFlowfield(t) {
|
||||
for (const p of flowParticles) {
|
||||
p.px = p.x; p.py = p.y;
|
||||
const ang = flowAngle(p.x, p.y, t);
|
||||
p.x += Math.cos(ang)*p.speed; p.y += Math.sin(ang)*p.speed;
|
||||
if (mouse.x > -9000) { const dx=p.x-mouse.x,dy=p.y-mouse.y,d=Math.sqrt(dx*dx+dy*dy); if (d<130&&d>0.1) { const f=(1-d/130)*1.6; p.x+=(dx/d)*f; p.y+=(dy/d)*f; } }
|
||||
p.life++;
|
||||
if (p.life>p.maxLife||p.x<-5||p.x>W+5||p.y<-5||p.y>H+5) { p.x=rand(0,W); p.y=rand(0,H); p.life=0; p.px=p.x; p.py=p.y; }
|
||||
}
|
||||
}
|
||||
function drawFlowfield() {
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.018)'; ctx.fillRect(0,0,W,H);
|
||||
ctx.lineWidth = 1.0; ctx.lineCap = 'round';
|
||||
for (const p of flowParticles) {
|
||||
const lr = p.life/p.maxLife, a = Math.sin(lr*Math.PI)*0.7;
|
||||
ctx.strokeStyle = ((p.x+p.y)|0)%3!==0 ? `rgba(${BLUE},${a})` : `rgba(${NAVY},${a*0.7})`;
|
||||
ctx.beginPath(); ctx.moveTo(p.px,p.py); ctx.lineTo(p.x,p.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/* TOPOGRAPHY */
|
||||
const TOPO_RES = 26;
|
||||
let topoField = null, topoCols = 0, topoRows = 0;
|
||||
function initTopo() {
|
||||
topoCols = Math.ceil(W/TOPO_RES)+2; topoRows = Math.ceil(H/TOPO_RES)+2;
|
||||
topoField = new Float32Array(topoCols*topoRows);
|
||||
}
|
||||
function topoNoise(x,y,t) { return Math.sin(x*0.011+t*0.00028)+Math.cos(y*0.013-t*0.00022)+0.6*Math.sin((x+y)*0.0075+t*0.00036)+0.4*Math.cos((x-y)*0.0098-t*0.00042); }
|
||||
function updateTopo(t) {
|
||||
const mx=mouse.x,my=mouse.y,hasMouse=mx>-9000;
|
||||
for (let j=0;j<topoRows;j++) { const y=j*TOPO_RES; for (let i=0;i<topoCols;i++) { const x=i*TOPO_RES; let v=topoNoise(x,y,t); if (hasMouse) { const dx=x-mx,dy=y-my,d2=dx*dx+dy*dy; if (d2<60000) v+=1.6*Math.exp(-d2/9000); } topoField[j*topoCols+i]=v; } }
|
||||
}
|
||||
function drawTopo() {
|
||||
ctx.clearRect(0,0,W,H);
|
||||
const thresholds=[-1.6,-1.0,-0.4,0.2,0.8,1.4,2.0];
|
||||
for (let ti=0;ti<thresholds.length;ti++) {
|
||||
const thr=thresholds[ti],mid=(thresholds.length-1)/2,closeness=1-Math.abs(ti-mid)/mid;
|
||||
ctx.strokeStyle=`rgba(${BLUE},${0.13+closeness*0.18})`; ctx.lineWidth=closeness>0.7?0.95:0.55; ctx.beginPath();
|
||||
for (let j=0;j<topoRows-1;j++) { for (let i=0;i<topoCols-1;i++) { const tl=topoField[j*topoCols+i],tr=topoField[j*topoCols+i+1],br=topoField[(j+1)*topoCols+i+1],bl=topoField[(j+1)*topoCols+i]; let idx=0; if(tl>thr)idx|=1;if(tr>thr)idx|=2;if(br>thr)idx|=4;if(bl>thr)idx|=8; if(idx===0||idx===15)continue; const x=i*TOPO_RES,y=j*TOPO_RES,s=TOPO_RES,tx=x+s*(thr-tl)/(tr-tl),rx=x+s,ry=y+s*(thr-tr)/(br-tr),bx=x+s*(thr-bl)/(br-bl),by=y+s,lx=x,ly=y+s*(thr-tl)/(bl-tl); switch(idx){case 1:case 14:ctx.moveTo(lx,ly);ctx.lineTo(tx,y);break;case 2:case 13:ctx.moveTo(tx,y);ctx.lineTo(rx,ry);break;case 3:case 12:ctx.moveTo(lx,ly);ctx.lineTo(rx,ry);break;case 4:case 11:ctx.moveTo(rx,ry);ctx.lineTo(bx,by);break;case 5:ctx.moveTo(lx,ly);ctx.lineTo(tx,y);ctx.moveTo(rx,ry);ctx.lineTo(bx,by);break;case 6:case 9:ctx.moveTo(tx,y);ctx.lineTo(bx,by);break;case 7:case 8:ctx.moveTo(lx,ly);ctx.lineTo(bx,by);break;case 10:ctx.moveTo(lx,ly);ctx.lineTo(bx,by);ctx.moveTo(tx,y);ctx.lineTo(rx,ry);break;} } } ctx.stroke();
|
||||
}
|
||||
ctx.fillStyle=`rgba(${BLUE},0.55)`;
|
||||
for (let j=2;j<topoRows-2;j+=4) { for (let i=2;i<topoCols-2;i+=4) { if(topoField[j*topoCols+i]>1.8){ctx.beginPath();ctx.arc(i*TOPO_RES,j*TOPO_RES,2,0,Math.PI*2);ctx.fill();} } }
|
||||
}
|
||||
|
||||
/* LOOP & API */
|
||||
function loop() {
|
||||
if (!running) return;
|
||||
const t = performance.now();
|
||||
if (mode==='constellation'){updateConstellation(t);drawConstellation();}
|
||||
else if (mode==='network'){updateNetwork();drawNetwork();}
|
||||
else if (mode==='flow'){updateFlowfield(t);drawFlowfield();}
|
||||
else if (mode==='topo'){updateTopo(t);drawTopo();}
|
||||
rafId = requestAnimationFrame(loop);
|
||||
}
|
||||
function setMode(m) {
|
||||
mode = m;
|
||||
const cnv2d=document.getElementById('hero-canvas'), cnvGL=document.getElementById('hero-canvas-gl'), hero=document.querySelector('.hero');
|
||||
if (m==='plasma'||m==='mercury'||m==='crystal') { running=false; cancelAnimationFrame(rafId); rafId=null; ctx.clearRect(0,0,W,H); cnv2d.classList.add('hidden'); cnvGL.classList.add('active'); hero.classList.add('gl-active'); if(window.__faSetGLMode)window.__faSetGLMode(m); return; }
|
||||
cnv2d.classList.remove('hidden'); cnvGL.classList.remove('active'); hero.classList.remove('gl-active'); if(window.__faSetGLMode)window.__faSetGLMode('off');
|
||||
if (m==='off'){running=false;cancelAnimationFrame(rafId);rafId=null;ctx.clearRect(0,0,W,H);cnv2d.classList.add('hidden');return;}
|
||||
if(m==='constellation')initConstellation(); else if(m==='network')initNetwork(); else if(m==='flow')initFlowfield(); else if(m==='topo')initTopo();
|
||||
running=true; cancelAnimationFrame(rafId); rafId=null; loop();
|
||||
}
|
||||
window.__faSetCanvasMode = setMode;
|
||||
window.addEventListener('resize', () => { resize(); if(mode==='constellation')initConstellation(); else if(mode==='network')initNetwork(); else if(mode==='flow')initFlowfield(); else if(mode==='topo')initTopo(); });
|
||||
canvas.parentElement.addEventListener('mousemove', e => { const rect=canvas.getBoundingClientRect(); mouse.x=e.clientX-rect.left; mouse.y=e.clientY-rect.top; });
|
||||
canvas.parentElement.addEventListener('mouseleave', () => { mouse.x=-9999; mouse.y=-9999; });
|
||||
resize(); initConstellation(); loop();
|
||||
})();
|
||||
|
||||
/* ============================================================
|
||||
HERO WEBGL
|
||||
============================================================ */
|
||||
(function() {
|
||||
const canvas = document.getElementById('hero-canvas-gl');
|
||||
const gl = canvas.getContext('webgl', { antialias: false, alpha: true, premultipliedAlpha: false });
|
||||
if (!gl) { window.__faSetGLMode = () => {}; return; }
|
||||
const VS_SRC = `attribute vec2 a_pos; void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }`;
|
||||
const FRAG_HEADER = `
|
||||
precision highp float;
|
||||
uniform vec2 uRes; uniform float uTime; uniform vec2 uMouse;
|
||||
float hash21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+45.32);return fract(p.x*p.y);}
|
||||
float vnoise(vec2 p){vec2 i=floor(p),f=fract(p),u=f*f*(3.0-2.0*f);float a=hash21(i),b=hash21(i+vec2(1,0)),c=hash21(i+vec2(0,1)),d=hash21(i+vec2(1,1));return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);}
|
||||
float fbm(vec2 p){float v=0.0,a=0.5;mat2 r=mat2(0.8,-0.6,0.6,0.8);for(int i=0;i<4;i++){v+=a*vnoise(p);p=r*p*2.02;a*=0.5;}return v;}
|
||||
`;
|
||||
const FS_PLASMA = `void main(){vec2 frag=gl_FragCoord.xy;float scale=min(uRes.x,uRes.y);vec2 uv=(frag-0.5*uRes)/scale,m=(uMouse-0.5*uRes)/scale,center=m*0.55,p=uv-center;float r=length(p),a=atan(p.y,p.x),corona=0.0;for(int i=0;i<3;i++){float fi=float(i);corona+=fbm(vec2(a*(3.0+fi)+uTime*(0.18+fi*0.07),r*4.5-uTime*0.25))/(fi+1.0);}corona/=1.6;float core=exp(-pow(r*4.0,2.0)),outer=exp(-r*2.8)*(0.55+0.5*corona),fil=smoothstep(0.6,0.92,fbm(vec2(a*6.0,r*7.0+uTime*0.4)));fil*=smoothstep(0.08,0.3,r)*smoothstep(0.7,0.25,r);vec3 navy=vec3(0.102,0.122,0.361),blue=vec3(0.420,0.561,0.831),lblue=vec3(0.659,0.753,0.910),col=mix(blue,navy,smoothstep(0.0,0.25,r));col=mix(col,lblue,fil);float a1=clamp(core*0.65+outer*0.55+fil*0.45,0.0,0.85);gl_FragColor=vec4(col,a1);}`;
|
||||
const FS_MERCURY = `float metaball(vec2 p,vec2 c,float r){return r/max(length(p-c),0.0001);}void main(){vec2 frag=gl_FragCoord.xy;float scale=min(uRes.x,uRes.y);vec2 uv=(frag-0.5*uRes)/scale,m=(uMouse-0.5*uRes)/scale,b0=vec2(sin(uTime*0.27)*0.42,cos(uTime*0.23)*0.28),b1=vec2(cos(uTime*0.35)*0.48,sin(uTime*0.21)*0.26),b2=vec2(sin(uTime*0.19+2.0)*0.5,cos(uTime*0.31+1.0)*0.36),b3=vec2(cos(uTime*0.25+4.0)*0.36,sin(uTime*0.17+3.0)*0.44),b4=m,b5=-m*0.4;float f=metaball(uv,b0,0.16)+metaball(uv,b1,0.18)+metaball(uv,b2,0.13)+metaball(uv,b3,0.15)+metaball(uv,b4,0.22)+metaball(uv,b5,0.10);float surface=smoothstep(2.3,2.85,f),halo=smoothstep(1.6,2.3,f)-surface;vec2 e=vec2(0.004,0.0);float fx=(metaball(uv+e.xy,b0,0.16)+metaball(uv+e.xy,b1,0.18)+metaball(uv+e.xy,b2,0.13)+metaball(uv+e.xy,b3,0.15)+metaball(uv+e.xy,b4,0.22)+metaball(uv+e.xy,b5,0.10))-f,fy=(metaball(uv+e.yx,b0,0.16)+metaball(uv+e.yx,b1,0.18)+metaball(uv+e.yx,b2,0.13)+metaball(uv+e.yx,b3,0.15)+metaball(uv+e.yx,b4,0.22)+metaball(uv+e.yx,b5,0.10))-f;vec2 n=normalize(vec2(fx,fy)+vec2(1e-5));vec3 navy=vec3(0.102,0.122,0.361),blue=vec3(0.420,0.561,0.831),lblue=vec3(0.659,0.753,0.910),pale=vec3(0.93,0.95,0.99);float ang=atan(n.y,n.x),t1=sin(ang*2.0+uTime*0.5)*0.5+0.5,t2=sin(ang*3.0-uTime*0.4)*0.5+0.5;vec3 surfCol=mix(navy,blue,t1);surfCol=mix(surfCol,lblue,t2*0.55);float fres=pow(1.0-abs(n.y),1.6);surfCol=mix(surfCol,pale,fres*0.5);float spec=pow(max(dot(n,normalize(vec2(0.5,0.8))),0.0),18.0);surfCol+=vec3(spec)*0.4;vec3 haloCol=mix(blue,lblue,0.6),col=mix(haloCol,surfCol,surface);float a1=clamp(surface*0.92+halo*0.35,0.0,0.95);gl_FragColor=vec4(col,a1);}`;
|
||||
const FS_CRYSTAL = `vec2 hash22b(vec2 p){vec3 p3=fract(vec3(p.xyx)*vec3(0.1031,0.1030,0.0973));p3+=dot(p3,p3.yzx+33.33);return fract((p3.xx+p3.yz)*p3.zy);}vec3 voronoiB(vec2 p){vec2 i=floor(p),f=fract(p);float d1=1e9,d2=1e9;vec2 ic=vec2(0.0);for(int y=-1;y<=1;y++){for(int x=-1;x<=1;x++){vec2 g=vec2(float(x),float(y)),o=hash22b(i+g);o=0.5+0.5*sin(uTime*0.5+6.28*o);vec2 r=g+o-f;float d=dot(r,r);if(d<d1){d2=d1;d1=d;ic=i+g;}else if(d<d2)d2=d;}}return vec3(sqrt(d1),sqrt(d2),hash21(ic));}void main(){vec2 frag=gl_FragCoord.xy;float scale=min(uRes.x,uRes.y);vec2 uv=frag/scale,m=uMouse/scale,d=uv-m,uvw=uv+d*0.45*exp(-length(d)*2.2);vec3 vd=voronoiB(uvw*3.5);float d1=vd.x,d2=vd.y,cellId=vd.z,edge=d2-d1;vec3 navy=vec3(0.102,0.122,0.361),blue=vec3(0.420,0.561,0.831),lblue=vec3(0.659,0.753,0.910),pale=vec3(0.85,0.90,0.97),fill=mix(pale,lblue,cellId*0.7);float boundary=smoothstep(0.0,0.35,d1);fill=mix(fill,blue,boundary*0.55);float edgeMask=1.0-smoothstep(0.0,0.035,edge);vec3 edgeCol=mix(navy,blue,sin(uTime*0.3+cellId*8.0)*0.5+0.5);vec3 mvd=voronoiB(m*3.5);float hovered=step(abs(mvd.z-cellId),0.001),caustic=smoothstep(0.5,0.95,sin(d1*28.0+uTime+cellId*14.0))*(1.0-edgeMask)*0.3,col_=fill+edgeCol*edgeMask+lblue*caustic;vec3 col=mix(col_,mix(blue,lblue,0.4),hovered*(1.0-edgeMask)*0.45);float fillAlpha=0.18+boundary*0.18+hovered*0.12,a1=clamp(max(fillAlpha,edgeMask*0.85),0.0,0.85);gl_FragColor=vec4(col,a1);}`;
|
||||
|
||||
function compile(type,src){const s=gl.createShader(type);gl.shaderSource(s,src);gl.compileShader(s);if(!gl.getShaderParameter(s,gl.COMPILE_STATUS)){console.error('GL compile:',gl.getShaderInfoLog(s));return null;}return s;}
|
||||
function makeProgram(fsSrc){const vs=compile(gl.VERTEX_SHADER,VS_SRC),fs=compile(gl.FRAGMENT_SHADER,FRAG_HEADER+'\n'+fsSrc);if(!vs||!fs)return null;const p=gl.createProgram();gl.attachShader(p,vs);gl.attachShader(p,fs);gl.linkProgram(p);if(!gl.getProgramParameter(p,gl.LINK_STATUS)){console.error('GL link:',gl.getProgramInfoLog(p));return null;}return p;}
|
||||
const buf=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buf);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]),gl.STATIC_DRAW);
|
||||
const programs={};
|
||||
function getProgram(name){if(programs[name])return programs[name];let src;if(name==='plasma')src=FS_PLASMA;else if(name==='mercury')src=FS_MERCURY;else if(name==='crystal')src=FS_CRYSTAL;else return null;const p=makeProgram(src);if(!p)return null;programs[name]={prog:p,uRes:gl.getUniformLocation(p,'uRes'),uTime:gl.getUniformLocation(p,'uTime'),uMouse:gl.getUniformLocation(p,'uMouse'),a_pos:gl.getAttribLocation(p,'a_pos')};return programs[name];}
|
||||
gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
|
||||
let glMode='off',glRunning=false,glRafId=null;
|
||||
const dpr=Math.min(window.devicePixelRatio||1,2),mouse={x:-9999,y:-9999},mouseSmooth={x:0,y:0};
|
||||
function resize(){const rect=canvas.parentElement.getBoundingClientRect();const w=Math.floor(rect.width*dpr),h=Math.floor(rect.height*dpr);if(canvas.width!==w||canvas.height!==h){canvas.width=w;canvas.height=h;canvas.style.width=rect.width+'px';canvas.style.height=rect.height+'px';gl.viewport(0,0,w,h);}}
|
||||
canvas.parentElement.addEventListener('mousemove',e=>{const rect=canvas.getBoundingClientRect();mouse.x=(e.clientX-rect.left)*dpr;mouse.y=(rect.height-(e.clientY-rect.top))*dpr;});
|
||||
canvas.parentElement.addEventListener('mouseleave',()=>{mouse.x=-9999;mouse.y=-9999;});
|
||||
function initCenterMouse(){const rect=canvas.parentElement.getBoundingClientRect();if(mouse.x<-9000){mouseSmooth.x=(rect.width/2)*dpr;mouseSmooth.y=(rect.height/2)*dpr;}}
|
||||
const startMs=performance.now();let lastFrame=startMs;
|
||||
function glLoop(){if(!glRunning)return;resize();const now=performance.now(),dt=(now-lastFrame)/1000;lastFrame=now;const t=(now-startMs)/1000;const target=mouse.x<-9000?{x:canvas.width/2,y:canvas.height/2}:mouse;mouseSmooth.x+=(target.x-mouseSmooth.x)*Math.min(1,dt*5);mouseSmooth.y+=(target.y-mouseSmooth.y)*Math.min(1,dt*5);const p=getProgram(glMode);if(!p){glRafId=requestAnimationFrame(glLoop);return;}gl.useProgram(p.prog);gl.bindBuffer(gl.ARRAY_BUFFER,buf);gl.enableVertexAttribArray(p.a_pos);gl.vertexAttribPointer(p.a_pos,2,gl.FLOAT,false,0,0);gl.uniform2f(p.uRes,canvas.width,canvas.height);gl.uniform1f(p.uTime,t);gl.uniform2f(p.uMouse,mouseSmooth.x,mouseSmooth.y);gl.clearColor(0,0,0,0);gl.clear(gl.COLOR_BUFFER_BIT);gl.drawArrays(gl.TRIANGLES,0,6);glRafId=requestAnimationFrame(glLoop);}
|
||||
function setGLMode(m){if(m===glMode)return;glMode=m;if(m==='off'){glRunning=false;cancelAnimationFrame(glRafId);glRafId=null;gl.clear(gl.COLOR_BUFFER_BIT);return;}if(m==='plasma'||m==='mercury'||m==='crystal'){getProgram(m);initCenterMouse();if(!glRunning){glRunning=true;lastFrame=performance.now();cancelAnimationFrame(glRafId);glLoop();}}}
|
||||
window.__faSetGLMode=setGLMode;
|
||||
window.addEventListener('resize',resize);resize();
|
||||
})();
|
||||
|
||||
/* ============================================================
|
||||
TWEAKS PANEL
|
||||
============================================================ */
|
||||
(function() {
|
||||
const DEFAULTS = {"canvasMode":"mercury","visualMode":"none","emphasis":"underline"};
|
||||
const state = { ...DEFAULTS };
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'fa-tweaks';
|
||||
panel.style.cssText = 'position:fixed;bottom:24px;right:24px;z-index:9999;width:280px;background:#fff;color:#1A1F5C;border:0.5px solid rgba(26,31,92,0.15);box-shadow:0 20px 60px -20px rgba(26,31,92,0.35),0 4px 12px -4px rgba(26,31,92,0.1);font-family:"DM Sans",sans-serif;display:none;';
|
||||
panel.innerHTML = `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:0.5px solid rgba(26,31,92,0.1);cursor:move;" id="fa-tweaks-head">
|
||||
<div style="font-family:'Syne',sans-serif;font-size:11px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:#1A1F5C;">Tweaks</div>
|
||||
<button id="fa-tweaks-close" style="background:none;border:none;cursor:pointer;font-size:18px;line-height:1;color:#6B7280;padding:0;">×</button>
|
||||
</div>
|
||||
<div style="padding:16px 18px;display:flex;flex-direction:column;gap:18px;">
|
||||
<div><div style="font-size:10px;font-weight:500;letter-spacing:2px;text-transform:uppercase;color:#6B7280;margin-bottom:8px;">Hero Canvas</div>
|
||||
<div class="fa-seg" data-key="canvasMode" style="display:grid;grid-template-columns:1fr 1fr;gap:1px;background:rgba(26,31,92,0.1);border:0.5px solid rgba(26,31,92,0.1);">
|
||||
<button data-v="constellation">Constellation</button><button data-v="network">Network</button>
|
||||
<button data-v="flow">Flow field</button><button data-v="topo">Topografie</button>
|
||||
<button data-v="plasma">Plasma</button><button data-v="mercury">Mercury</button>
|
||||
<button data-v="crystal" style="grid-column:span 2;">Crystal</button>
|
||||
<button data-v="off" style="grid-column:span 2;">Off</button>
|
||||
</div></div>
|
||||
<div><div style="font-size:10px;font-weight:500;letter-spacing:2px;text-transform:uppercase;color:#6B7280;margin-bottom:8px;">Right Panel</div>
|
||||
<div class="fa-seg" data-key="visualMode" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1px;background:rgba(26,31,92,0.1);border:0.5px solid rgba(26,31,92,0.1);">
|
||||
<button data-v="stack">Stack</button><button data-v="stats">Cards</button><button data-v="none">Keine</button>
|
||||
</div></div>
|
||||
<div><div style="font-size:10px;font-weight:500;letter-spacing:2px;text-transform:uppercase;color:#6B7280;margin-bottom:8px;">Headline Emphasis</div>
|
||||
<div class="fa-seg" data-key="emphasis" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1px;background:rgba(26,31,92,0.1);border:0.5px solid rgba(26,31,92,0.1);">
|
||||
<button data-v="underline">Blue+line</button><button data-v="blue">Blue</button><button data-v="flat">All navy</button>
|
||||
</div></div>
|
||||
</div>
|
||||
<style>#fa-tweaks .fa-seg button{background:#fff;border:none;padding:9px 6px;font-family:"DM Sans",sans-serif;font-size:11px;font-weight:500;color:#6B7280;cursor:pointer;transition:all 0.15s;letter-spacing:0.3px;}#fa-tweaks .fa-seg button:hover{color:#1A1F5C;}#fa-tweaks .fa-seg button.active{background:#1A1F5C;color:#fff;}</style>
|
||||
`;
|
||||
document.body.appendChild(panel);
|
||||
function apply() {
|
||||
if(window.__faSetCanvasMode)window.__faSetCanvasMode(state.canvasMode);
|
||||
const cap=document.getElementById('fa-cap-card'),stats=document.getElementById('fa-stat-stack'),visual=document.getElementById('fa-hero-visual'),hero=document.querySelector('.hero');
|
||||
hero.classList.remove('no-visual');
|
||||
if(state.visualMode==='stack'){cap.style.display='block';stats.style.display='none';visual.style.display='flex';visual.classList.remove('legacy');}
|
||||
else if(state.visualMode==='stats'){cap.style.display='none';stats.style.display='flex';visual.style.display='flex';visual.classList.add('legacy');}
|
||||
else{visual.style.display='none';hero.classList.add('no-visual');}
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
const h1=document.getElementById('fa-headline'),em=h1.querySelector('em');
|
||||
h1.classList.remove('flat');em.classList.remove('underline');
|
||||
if(state.emphasis==='underline')em.classList.add('underline');else if(state.emphasis==='flat')h1.classList.add('flat');
|
||||
panel.querySelectorAll('.fa-seg').forEach(seg=>{const key=seg.dataset.key;seg.querySelectorAll('button').forEach(b=>{b.classList.toggle('active',b.dataset.v===state[key]);});});
|
||||
}
|
||||
panel.querySelectorAll('.fa-seg button').forEach(btn=>{btn.addEventListener('click',()=>{const seg=btn.closest('.fa-seg');state[seg.dataset.key]=btn.dataset.v;apply();try{window.parent.postMessage({type:'__edit_mode_set_keys',edits:{[seg.dataset.key]:btn.dataset.v}},'*');}catch(e){}});});
|
||||
document.getElementById('fa-tweaks-close').addEventListener('click',()=>{panel.style.display='none';try{window.parent.postMessage({type:'__edit_mode_dismissed'},'*');}catch(e){}});
|
||||
(function(){const head=document.getElementById('fa-tweaks-head');let dragging=false,sx=0,sy=0,px=0,py=0;head.addEventListener('mousedown',e=>{if(e.target.id==='fa-tweaks-close')return;dragging=true;sx=e.clientX;sy=e.clientY;const r=panel.getBoundingClientRect();px=r.left;py=r.top;panel.style.right='auto';panel.style.bottom='auto';e.preventDefault();});window.addEventListener('mousemove',e=>{if(!dragging)return;panel.style.left=(px+e.clientX-sx)+'px';panel.style.top=(py+e.clientY-sy)+'px';});window.addEventListener('mouseup',()=>dragging=false);})();
|
||||
window.addEventListener('message',e=>{if(!e.data)return;if(e.data.type==='__activate_edit_mode')panel.style.display='block';if(e.data.type==='__deactivate_edit_mode')panel.style.display='none';});
|
||||
try{window.parent.postMessage({type:'__edit_mode_available'},'*');}catch(e){}
|
||||
apply();
|
||||
})();
|
||||
</script>
|
||||
124
src/components/KISection.astro
Normal file
124
src/components/KISection.astro
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
const caps = [
|
||||
{
|
||||
icon: '⬡',
|
||||
title: 'LLM-Agents & RAG',
|
||||
desc: 'Autonome Agenten und retrieval-gestützte Systeme für komplexe Unternehmensanwendungen.',
|
||||
},
|
||||
{
|
||||
icon: '◎',
|
||||
title: 'Computer Vision',
|
||||
desc: 'Bildanalyse und Objekterkennung – auch in regulierten Bereichen wie der Medizintechnik.',
|
||||
},
|
||||
{
|
||||
icon: '⟁',
|
||||
title: 'Predictive Analytics',
|
||||
desc: 'Forecasting-Modelle und Anomalieerkennung auf Basis realer Produktionsdaten.',
|
||||
},
|
||||
{
|
||||
icon: '⊞',
|
||||
title: 'MLOps & Deployment',
|
||||
desc: 'Vom Notebook zur skalierbaren Produktionsinfrastruktur – zuverlässig und wartbar.',
|
||||
},
|
||||
];
|
||||
---
|
||||
<section class="ki-section" id="ki">
|
||||
<div class="ki-content">
|
||||
<div class="ki-tag" data-reveal>KI-Expertise</div>
|
||||
<h2 data-reveal style="--reveal-delay:80ms">Intelligente Systeme,<br>die <em>echte Arbeit</em> leisten.</h2>
|
||||
<p data-reveal style="--reveal-delay:160ms">Wir entwickeln keine Demos – wir bauen KI-Systeme, die in produktiven Umgebungen funktionieren. Von autonomen Agenten bis zur regulierten medizinischen Bildanalyse.</p>
|
||||
<a href="#kontakt" class="btn-primary" data-reveal style="--reveal-delay:240ms">Beratungstermin vereinbaren</a>
|
||||
</div>
|
||||
<div class="ki-caps" data-reveal style="--reveal-delay:200ms">
|
||||
{caps.map((c) => (
|
||||
<div class="ki-cap">
|
||||
<div class="ki-cap-icon">{c.icon}</div>
|
||||
<h4>{c.title}</h4>
|
||||
<p>{c.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.ki-section {
|
||||
padding: 7rem var(--content-pad);
|
||||
background: var(--fa-navy);
|
||||
display: grid; grid-template-columns: 1fr 1fr;
|
||||
gap: 5rem; align-items: center;
|
||||
position: relative; overflow: hidden;
|
||||
}
|
||||
.ki-section::before {
|
||||
content: 'AI';
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 22rem; font-weight: 800;
|
||||
color: rgba(107,143,212,0.04);
|
||||
position: absolute; right: -2rem; bottom: -4rem;
|
||||
line-height: 1; pointer-events: none; letter-spacing: -10px;
|
||||
}
|
||||
.ki-content { position: relative; z-index: 1; }
|
||||
.ki-tag {
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 3px; text-transform: uppercase;
|
||||
color: var(--fa-blue-light); margin-bottom: 1rem;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
}
|
||||
.ki-tag::before { content: ''; display: block; width: 24px; height: 1px; background: var(--fa-blue-light); }
|
||||
.ki-content h2 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: clamp(1.8rem,2.8vw,2.6rem); font-weight: 700;
|
||||
letter-spacing: -1px; line-height: 1.1; color: #fff; margin-bottom: 1.5rem;
|
||||
}
|
||||
.ki-content h2 em { font-style: normal; color: var(--fa-blue-light); }
|
||||
.ki-content p {
|
||||
font-size: 15px; font-weight: 300; line-height: 1.8;
|
||||
color: rgba(255,255,255,0.72); margin-bottom: 2.5rem; max-width: 440px;
|
||||
}
|
||||
.btn-primary {
|
||||
font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 500;
|
||||
color: var(--fa-navy); background: #fff; border: none;
|
||||
padding: 0.9rem 2.25rem; cursor: pointer; transition: background 0.2s;
|
||||
text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-primary:hover { background: var(--fa-blue-light); }
|
||||
.ki-caps {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
|
||||
background: rgba(107,143,212,0.15);
|
||||
border: 0.5px solid rgba(107,143,212,0.2);
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
.ki-cap {
|
||||
background: rgba(26,31,92,0.6);
|
||||
padding: 1.75rem; transition: background 0.2s;
|
||||
}
|
||||
.ki-cap:hover { background: rgba(107,143,212,0.12); }
|
||||
.ki-cap-icon {
|
||||
width: 32px; height: 32px;
|
||||
background: rgba(107,143,212,0.15);
|
||||
border: 0.5px solid rgba(107,143,212,0.3);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: 0.85rem;
|
||||
font-size: 14px; color: var(--fa-blue-light);
|
||||
}
|
||||
.ki-cap h4 {
|
||||
font-family: 'Syne', sans-serif; font-size: 14px; font-weight: 700;
|
||||
color: #fff; margin-bottom: 0.4rem; letter-spacing: -0.2px;
|
||||
}
|
||||
.ki-cap p {
|
||||
font-size: 13px; font-weight: 300; line-height: 1.6;
|
||||
color: rgba(255,255,255,0.65); margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.ki-section { padding-top: 5.5rem; padding-bottom: 5.5rem; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.ki-section { grid-template-columns: 1fr; padding: 4.5rem var(--content-pad); gap: 3rem; }
|
||||
.ki-caps { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.ki-section { padding: 4rem var(--content-pad); gap: 2rem; }
|
||||
.ki-section::before { font-size: 12rem; right: -1rem; bottom: -2rem; }
|
||||
.ki-content h2 { font-size: clamp(1.5rem, 6.5vw, 2rem); }
|
||||
}
|
||||
</style>
|
||||
96
src/components/Kontorion.astro
Normal file
96
src/components/Kontorion.astro
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
---
|
||||
<section class="showcase-section" id="kontorion">
|
||||
<div>
|
||||
<div class="showcase-label" data-reveal>Eigenes Produkt · Referenz</div>
|
||||
<h2 data-reveal style="--reveal-delay:80ms">Kontorion – unsere Billing-Plattform für den EU-Markt.</h2>
|
||||
<p data-reveal style="--reveal-delay:160ms">Kontorion ist der Beweis unserer Leistungsfähigkeit: eine vollständig intern entwickelte SaaS-Plattform für Subscription-Billing, EU-Steuern, SEPA und Revenue Recognition – gebaut für die regulatorische Realität von 27 Mitgliedsstaaten.</p>
|
||||
<a href="https://kontorion.eu" target="_blank" rel="noopener" class="btn-primary" data-reveal style="--reveal-delay:240ms">Mehr über Kontorion ↗</a>
|
||||
</div>
|
||||
<div class="kontorion-card" data-reveal style="--reveal-delay:120ms">
|
||||
<div class="k-label">Produkt · kontorion.eu</div>
|
||||
<div class="k-title">Kontorion</div>
|
||||
<p class="k-desc">Subscription-Billing, EU-VAT/OSS, XRechnung, SEPA und IFRS 15 in einer Plattform. Nativ für Europa gebaut, nicht nachgerüstet.</p>
|
||||
<div class="k-pills">
|
||||
<span class="k-pill">EU-VAT / OSS</span>
|
||||
<span class="k-pill">SEPA</span>
|
||||
<span class="k-pill">XRechnung</span>
|
||||
<span class="k-pill">IFRS 15</span>
|
||||
</div>
|
||||
<a class="k-link" href="https://kontorion.eu" target="_blank" rel="noopener">kontorion.eu besuchen →</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.showcase-section {
|
||||
padding: 7rem var(--content-pad); background: var(--fa-off-white);
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 5rem; align-items: center;
|
||||
}
|
||||
.showcase-label {
|
||||
font-size: 10px; font-weight: 500; letter-spacing: 3px; text-transform: uppercase;
|
||||
color: var(--fa-mid); margin-bottom: 0.9rem;
|
||||
}
|
||||
.showcase-section h2 {
|
||||
font-family: 'Syne', sans-serif; font-size: clamp(1.7rem,2.5vw,2.2rem); font-weight: 700;
|
||||
letter-spacing: -0.8px; line-height: 1.15; color: var(--fa-navy); margin-bottom: 1.25rem;
|
||||
}
|
||||
.showcase-section > div > p {
|
||||
font-size: 15px; font-weight: 300; line-height: 1.8;
|
||||
color: var(--fa-mid); margin-bottom: 2.25rem; max-width: 430px;
|
||||
}
|
||||
.btn-primary {
|
||||
font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 500;
|
||||
color: #fff; background: var(--fa-navy); border: none;
|
||||
padding: 0.9rem 2.25rem; cursor: pointer; transition: background 0.2s;
|
||||
text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-primary:hover { background: var(--fa-blue); }
|
||||
|
||||
.kontorion-card {
|
||||
background: var(--fa-navy); padding: 3rem; color: #fff;
|
||||
position: relative; overflow: hidden;
|
||||
}
|
||||
.kontorion-card::before {
|
||||
content: 'K'; font-family: 'Syne', sans-serif; font-size: 16rem; font-weight: 800;
|
||||
color: rgba(107,143,212,0.06); position: absolute; right: -1.5rem; top: -2rem;
|
||||
line-height: 1; pointer-events: none;
|
||||
}
|
||||
.k-label {
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 3px; text-transform: uppercase;
|
||||
color: var(--fa-blue-light); margin-bottom: 1rem; opacity: 0.9;
|
||||
}
|
||||
.k-title {
|
||||
font-family: 'Syne', sans-serif; font-size: 1.8rem; font-weight: 700;
|
||||
letter-spacing: -0.5px; margin-bottom: 0.75rem; color: #fff;
|
||||
}
|
||||
.k-desc {
|
||||
font-size: 14px; font-weight: 300; line-height: 1.75;
|
||||
color: rgba(255,255,255,0.68); margin-bottom: 1.75rem; max-width: 310px;
|
||||
}
|
||||
.k-pills { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 2rem; }
|
||||
.k-pill {
|
||||
font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase;
|
||||
color: var(--fa-blue-light); border: 0.5px solid rgba(107,143,212,0.35); padding: 4px 10px;
|
||||
}
|
||||
.k-link {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
font-size: 13px; font-weight: 500; letter-spacing: 1px; text-transform: uppercase;
|
||||
color: #fff; text-decoration: none;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2); padding-bottom: 3px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.k-link:hover { border-color: rgba(255,255,255,0.6); }
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.showcase-section { padding-top: 5.5rem; padding-bottom: 5.5rem; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.showcase-section { grid-template-columns: 1fr; padding: 4.5rem var(--content-pad); gap: 3rem; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.showcase-section h2 { font-size: clamp(1.5rem, 6.5vw, 1.9rem); }
|
||||
.kontorion-card { padding: 2rem 1.75rem; }
|
||||
.kontorion-card::before { font-size: 11rem; }
|
||||
}
|
||||
</style>
|
||||
149
src/components/Nav.astro
Normal file
149
src/components/Nav.astro
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
---
|
||||
<nav
|
||||
id="fa-nav"
|
||||
class="flex items-center justify-between px-content border-b border-[rgba(26,31,92,0.1)] bg-white/[.97] sticky top-0 z-[200] backdrop-blur-[8px] gap-6"
|
||||
style="padding-top:1.1rem; padding-bottom:1.1rem;"
|
||||
>
|
||||
<a href="#top" class="flex items-center gap-3 flex-shrink-0 no-underline">
|
||||
<div class="font-syne font-extrabold text-2xl text-fa-blue tracking-[-1px] leading-none">FA</div>
|
||||
<div class="w-px h-[30px] bg-[rgba(26,31,92,0.1)]"></div>
|
||||
<div class="font-syne font-semibold text-[12px] text-fa-navy uppercase tracking-[2.5px] leading-snug">
|
||||
Frontier<br />Algorithmics
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<ul id="fa-nav-links" class="nav-links flex gap-2 list-none items-center">
|
||||
<li><a href="#services">Leistungen</a></li>
|
||||
<li><a href="#ki">KI & Daten</a></li>
|
||||
<li><a href="#kontorion">Kontorion</a></li>
|
||||
<li><a href="#why">Über uns</a></li>
|
||||
<li>
|
||||
<a href="#" class="lang" aria-disabled="true" title="English version coming soon">
|
||||
<span class="active">DE</span> / EN
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button
|
||||
id="fa-nav-menu"
|
||||
class="nav-menu hidden flex-col gap-1 bg-transparent border-0 cursor-pointer p-2"
|
||||
aria-label="Menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
|
||||
<a href="#kontakt" class="nav-cta">
|
||||
<span class="cta-dot"></span>
|
||||
Beratungstermin
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.nav-links a {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--fa-navy);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.3px;
|
||||
opacity: 0.65;
|
||||
transition: opacity 0.2s;
|
||||
white-space: nowrap;
|
||||
padding: 0.6rem 0.75rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
}
|
||||
.nav-links a:hover { opacity: 1; }
|
||||
.nav-links a.lang {
|
||||
opacity: 0.35;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
cursor: default;
|
||||
}
|
||||
.nav-links a.lang:hover { opacity: 0.5; }
|
||||
.nav-links a.lang .active { color: var(--fa-blue); opacity: 1; }
|
||||
|
||||
.nav-cta {
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: var(--fa-navy);
|
||||
border: none;
|
||||
padding: 0.65rem 1.4rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.nav-cta:hover { background: var(--fa-blue); }
|
||||
|
||||
.cta-dot {
|
||||
width: 6px; height: 6px;
|
||||
border-radius: 50%;
|
||||
background: #6FD49A;
|
||||
box-shadow: 0 0 0 2px rgba(111,212,154,0.25);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-menu span {
|
||||
display: block;
|
||||
width: 22px; height: 1.5px;
|
||||
background: var(--fa-navy);
|
||||
transition: transform 0.25s, opacity 0.25s;
|
||||
}
|
||||
|
||||
/* Mobile open state */
|
||||
.nav-links.menu-open {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
top: 56px; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(255,255,255,0.98);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 2rem var(--content-pad);
|
||||
gap: 1.5rem;
|
||||
align-items: flex-start;
|
||||
z-index: 199;
|
||||
}
|
||||
.nav-links.menu-open a { font-size: 18px; opacity: 0.85; }
|
||||
|
||||
nav.menu-open .nav-menu span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
|
||||
nav.menu-open .nav-menu span:nth-child(2) { opacity: 0; }
|
||||
nav.menu-open .nav-menu span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.nav-links { display: none; }
|
||||
.nav-menu { display: flex; }
|
||||
.nav-cta { padding: 0.55rem 1rem; font-size: 12px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const nav = document.getElementById('fa-nav');
|
||||
const btn = document.getElementById('fa-nav-menu');
|
||||
const links = document.getElementById('fa-nav-links');
|
||||
if (nav && btn && links) {
|
||||
btn.addEventListener('click', () => {
|
||||
const open = nav.classList.toggle('menu-open');
|
||||
links.classList.toggle('menu-open', open);
|
||||
btn.setAttribute('aria-expanded', String(open));
|
||||
});
|
||||
links.querySelectorAll('a').forEach((a) => {
|
||||
a.addEventListener('click', () => {
|
||||
nav.classList.remove('menu-open');
|
||||
links.classList.remove('menu-open');
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
133
src/components/Services.astro
Normal file
133
src/components/Services.astro
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
const services = [
|
||||
{
|
||||
num: '01',
|
||||
title: 'Data Science & KI',
|
||||
desc: 'LLM-Agents, RAG-Systeme, Computer Vision und Predictive Analytics – von der Datenpipeline bis zum produktiven KI-System.',
|
||||
tags: ['LLM / RAG', 'Agents', 'Computer Vision', 'Forecasting'],
|
||||
delay: '0ms',
|
||||
},
|
||||
{
|
||||
num: '02',
|
||||
title: 'Regulatorik',
|
||||
desc: 'Medizinproduktesoftware nach MDR, ISO 13485 und IEC 62304 – von der technischen Dokumentation bis zur erfolgreichen Zulassung.',
|
||||
tags: ['MDR', 'HL7 / FHIR', 'DICOM', 'IEC 62304'],
|
||||
delay: '80ms',
|
||||
},
|
||||
{
|
||||
num: '03',
|
||||
title: 'App-Entwicklung',
|
||||
desc: 'Native und Cross-Platform Apps, Backend-Systeme und Web-Frontends – präzise implementiert, skalierbar gebaut, KI-ready von Anfang an.',
|
||||
tags: ['Cross-Platform', 'Backend', 'Frontend', 'APIs'],
|
||||
delay: '160ms',
|
||||
},
|
||||
{
|
||||
num: '04',
|
||||
title: 'Software Engineering',
|
||||
desc: 'Systemarchitektur, Agile Methoden und technische Beratung für Projekte, bei denen Struktur über Erfolg oder Scheitern entscheidet.',
|
||||
tags: ['Architektur', 'Scrum / SAFe', 'Tech Consulting', 'Reviews'],
|
||||
delay: '240ms',
|
||||
},
|
||||
];
|
||||
---
|
||||
<section class="services-section" id="services">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<div class="section-tag" data-reveal>Was wir tun</div>
|
||||
<h2 data-reveal style="--reveal-delay:80ms">Vier Disziplinen.<br>Ein Partner.</h2>
|
||||
</div>
|
||||
<p class="section-desc" data-reveal style="--reveal-delay:160ms">KI ist kein Add-on – sie ist der Antrieb hinter jedem unserer Leistungsbereiche. Wir decken den gesamten Projektzyklus ab, von der ersten Idee bis zur regulierten Lösung.</p>
|
||||
</div>
|
||||
<div class="services-grid">
|
||||
{services.map((s) => (
|
||||
<div class="service-card" data-reveal style={`--reveal-delay:${s.delay}`}>
|
||||
<div class="sc-num">{s.num}</div>
|
||||
<div class="sc-title">{s.title}</div>
|
||||
<p class="sc-desc">{s.desc}</p>
|
||||
<div class="sc-tags">
|
||||
{s.tags.map((t) => <span>{t}</span>)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.services-section { padding: 7rem var(--content-pad); background: var(--fa-off-white); }
|
||||
|
||||
.section-header {
|
||||
display: grid; grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem; margin-bottom: 4.5rem; align-items: end;
|
||||
}
|
||||
.section-tag {
|
||||
font-size: 11px; font-weight: 500; letter-spacing: 3px; text-transform: uppercase;
|
||||
color: var(--fa-blue); margin-bottom: 1rem;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
}
|
||||
.section-tag::before { content: ''; display: block; width: 24px; height: 1px; background: var(--fa-blue); }
|
||||
.section-header h2 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: clamp(2rem,3vw,2.8rem); font-weight: 700;
|
||||
line-height: 1.1; letter-spacing: -1.2px; color: var(--fa-navy);
|
||||
}
|
||||
.section-desc {
|
||||
font-size: 15px; font-weight: 300; line-height: 1.8;
|
||||
color: var(--fa-mid); max-width: 420px; align-self: end;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
display: grid; grid-template-columns: repeat(4,1fr);
|
||||
gap: 1px; background: var(--fa-border); border: 0.5px solid var(--fa-border);
|
||||
}
|
||||
.service-card {
|
||||
background: #fff; padding: 2.25rem 2rem;
|
||||
cursor: pointer; transition: background 0.25s; position: relative;
|
||||
}
|
||||
.service-card:hover { background: var(--fa-navy); }
|
||||
.service-card:hover .sc-num { color: var(--fa-blue-light); }
|
||||
.service-card:hover .sc-title,
|
||||
.service-card:hover .sc-desc { color: rgba(255,255,255,0.85); }
|
||||
.service-card:hover .sc-tags span { color: rgba(255,255,255,0.6); border-color: rgba(255,255,255,0.15); }
|
||||
|
||||
.sc-num {
|
||||
font-family: 'Syne', sans-serif; font-size: 12px; font-weight: 700;
|
||||
letter-spacing: 2px; color: var(--fa-blue); margin-bottom: 1.25rem; transition: color 0.25s;
|
||||
}
|
||||
.sc-title {
|
||||
font-family: 'Syne', sans-serif; font-size: 16px; font-weight: 700;
|
||||
color: var(--fa-navy); margin-bottom: 0.75rem; line-height: 1.25; transition: color 0.25s;
|
||||
}
|
||||
.sc-desc {
|
||||
font-size: 14px; font-weight: 300; line-height: 1.7;
|
||||
color: var(--fa-mid); margin-bottom: 1.25rem; transition: color 0.25s;
|
||||
}
|
||||
.sc-tags { display: flex; flex-wrap: wrap; gap: 5px; }
|
||||
.sc-tags span {
|
||||
font-size: 11px; letter-spacing: 0.5px; color: var(--fa-navy);
|
||||
border: 0.5px solid var(--fa-border); padding: 3px 9px; opacity: 0.7;
|
||||
transition: color 0.25s, border-color 0.25s;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.services-section { padding-top: 5.5rem; padding-bottom: 5.5rem; }
|
||||
.services-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.section-header { gap: 1.5rem; margin-bottom: 3.5rem; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.services-section { padding: 4.5rem var(--content-pad); }
|
||||
.section-header { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.services-grid { grid-template-columns: 1fr; }
|
||||
.section-header h2 { font-size: clamp(1.6rem, 7vw, 2rem); }
|
||||
.service-card { padding: 1.75rem 1.5rem; }
|
||||
}
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.service-card:hover { background: #fff; }
|
||||
.service-card:hover .sc-num { color: var(--fa-blue); }
|
||||
.service-card:hover .sc-title,
|
||||
.service-card:hover .sc-desc { color: var(--fa-navy); }
|
||||
.service-card:hover .sc-tags span { color: var(--fa-navy); border-color: var(--fa-border); }
|
||||
.service-card:hover .sc-desc { color: var(--fa-mid); }
|
||||
}
|
||||
</style>
|
||||
55
src/components/Ticker.astro
Normal file
55
src/components/Ticker.astro
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
const items = [
|
||||
'KI-Strategie','LLM-Agents','Computer Vision','Predictive Analytics',
|
||||
'RAG-Systeme','App-Entwicklung','Regulatory Affairs','HL7 / FHIR',
|
||||
'DICOM','Software-Architektur','Agile Methoden','Data Science',
|
||||
'MLOps','MDR / IEC 62304','Backend & APIs','Technologieberatung',
|
||||
];
|
||||
---
|
||||
<div class="ticker-wrap" aria-hidden="true">
|
||||
<div class="ticker-inner">
|
||||
{[...items, ...items].map((item) => (
|
||||
<span class="ticker-item">{item}<div class="ticker-dot"></div></span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.ticker-wrap {
|
||||
background: var(--fa-navy);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.ticker-inner {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
animation: ticker 75s linear infinite;
|
||||
padding: 1.6rem 0;
|
||||
}
|
||||
.ticker-wrap:hover .ticker-inner { animation-play-state: paused; }
|
||||
.ticker-item {
|
||||
display: flex; align-items: center; gap: 1.8rem;
|
||||
padding: 0 2.2rem;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 16px; font-weight: 600;
|
||||
letter-spacing: 3px; text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.65);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ticker-dot {
|
||||
width: 5px; height: 5px; border-radius: 50%;
|
||||
background: var(--fa-blue); flex-shrink: 0;
|
||||
}
|
||||
.ticker-wrap::before,
|
||||
.ticker-wrap::after {
|
||||
content: '';
|
||||
position: absolute; top: 0; bottom: 0; width: 120px; z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
.ticker-wrap::before { left: 0; background: linear-gradient(to right, var(--fa-navy), transparent); }
|
||||
.ticker-wrap::after { right: 0; background: linear-gradient(to left, var(--fa-navy), transparent); }
|
||||
@media (max-width: 720px) {
|
||||
.ticker-inner { animation-duration: 45s; padding: 1.1rem 0; }
|
||||
.ticker-item { font-size: 11px; letter-spacing: 2px; padding: 0 1.5rem; gap: 1.25rem; }
|
||||
}
|
||||
</style>
|
||||
140
src/components/Why.astro
Normal file
140
src/components/Why.astro
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
const points = [
|
||||
{
|
||||
num: '01',
|
||||
title: 'KI mit Substanz, nicht mit Buzzwords',
|
||||
desc: 'Wir implementieren LLM-Agents, RAG-Systeme und Computer-Vision-Lösungen, die in der Praxis funktionieren – nicht nur in der Demo.',
|
||||
delay: '200ms',
|
||||
},
|
||||
{
|
||||
num: '02',
|
||||
title: 'Regulatorik trifft Technologie',
|
||||
desc: 'Wir sind eine der wenigen Firmen, die KI-Expertise und regulatorisches Know-how (MDR, IEC 62304) unter einem Dach vereinen.',
|
||||
delay: '280ms',
|
||||
},
|
||||
{
|
||||
num: '03',
|
||||
title: 'Beweis statt Versprechen: Kontorion',
|
||||
desc: 'Wir haben ein eigenes Produkt entwickelt und am Markt platziert. Wir denken wie Unternehmer, nicht wie Dienstleister.',
|
||||
delay: '360ms',
|
||||
},
|
||||
];
|
||||
---
|
||||
<section class="why-section" id="why">
|
||||
<div class="why-visual" data-reveal>
|
||||
<img
|
||||
src="/assets/regensburg.jpg"
|
||||
alt="Regensburg — Steinerne Brücke und Dom St. Peter"
|
||||
class="why-img"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="accent-bar">
|
||||
<div class="city">RGN</div>
|
||||
<div class="tagline">Bayern · DE</div>
|
||||
</div>
|
||||
<div class="why-block">
|
||||
<div class="why-block-quote">„Wir bauen nicht nur Software – wir tragen <em>Verantwortung</em> für das System dahinter."</div>
|
||||
<div class="why-block-sub">Frontier Algorithmics verbindet Ingenieursdenken mit unternehmerischer Verantwortung. Regensburg ist unser Zuhause – Europa und darüber hinaus unser Markt.</div>
|
||||
</div>
|
||||
<a class="photo-credit" href="https://unsplash.com/de/@lecreusois" target="_blank" rel="noopener">Foto · L. Niederwimmer / Unsplash</a>
|
||||
</div>
|
||||
<div class="why-content">
|
||||
<h2 data-reveal style="--reveal-delay:120ms">Warum Frontier<br>Algorithmics?</h2>
|
||||
<div class="points">
|
||||
{points.map((p) => (
|
||||
<div class="point" data-reveal style={`--reveal-delay:${p.delay}`}>
|
||||
<div class="point-num">{p.num}</div>
|
||||
<div class="point-text">
|
||||
<strong>{p.title}</strong>
|
||||
<p>{p.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.why-section {
|
||||
padding: 7rem var(--content-pad);
|
||||
display: grid; grid-template-columns: 1fr 1fr;
|
||||
gap: 5rem; align-items: center; background: #fff;
|
||||
}
|
||||
.why-visual { position: relative; aspect-ratio: 2 / 3; overflow: hidden; }
|
||||
.why-img {
|
||||
position: absolute; inset: 0;
|
||||
width: 100%; height: 100%;
|
||||
object-fit: cover; object-position: center; display: block;
|
||||
}
|
||||
.why-visual::after {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(15,19,64,0.0) 30%,
|
||||
rgba(15,19,64,0.18) 65%,
|
||||
rgba(15,19,64,0.55) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.why-block {
|
||||
position: absolute; left: 1.5rem; right: 1.5rem; bottom: 1.5rem;
|
||||
background: var(--fa-navy); padding: 2rem 2.25rem; color: #fff; z-index: 2;
|
||||
box-shadow: 0 20px 50px -20px rgba(15,19,64,0.45);
|
||||
}
|
||||
.why-block-quote {
|
||||
font-family: 'Syne', sans-serif; font-size: 1.25rem; font-weight: 600;
|
||||
line-height: 1.35; letter-spacing: -0.3px; color: #fff; margin-bottom: 1rem;
|
||||
}
|
||||
.why-block-quote em { font-style: normal; color: var(--fa-blue-light); }
|
||||
.why-block-sub {
|
||||
font-size: 13px; font-weight: 300; line-height: 1.7; color: rgba(255,255,255,0.72);
|
||||
}
|
||||
.accent-bar {
|
||||
position: absolute; top: 1.5rem; right: 1.5rem;
|
||||
width: 110px; height: 110px;
|
||||
background: rgba(255,255,255,0.92); backdrop-filter: blur(6px);
|
||||
border: 0.5px solid rgba(26,31,92,0.08);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-direction: column; gap: 3px; z-index: 2;
|
||||
}
|
||||
.city {
|
||||
font-family: 'Syne', sans-serif; font-size: 13px; font-weight: 800;
|
||||
color: var(--fa-navy); letter-spacing: 2.5px; text-transform: uppercase;
|
||||
}
|
||||
.tagline { font-size: 9px; color: var(--fa-mid); letter-spacing: 1.5px; text-transform: uppercase; }
|
||||
.photo-credit {
|
||||
position: absolute; bottom: 6px; right: 10px; z-index: 2;
|
||||
font-family: 'DM Sans', sans-serif; font-size: 9px; letter-spacing: 1px;
|
||||
color: rgba(255,255,255,0.7); text-shadow: 0 1px 4px rgba(0,0,0,0.6); text-decoration: none;
|
||||
}
|
||||
.photo-credit:hover { color: #fff; }
|
||||
|
||||
.why-content h2 {
|
||||
font-family: 'Syne', sans-serif; font-size: clamp(1.8rem,2.8vw,2.4rem);
|
||||
font-weight: 700; letter-spacing: -0.8px; line-height: 1.15;
|
||||
color: var(--fa-navy); margin-bottom: 2.75rem;
|
||||
}
|
||||
.points { display: flex; flex-direction: column; gap: 1.75rem; }
|
||||
.point { display: grid; grid-template-columns: 2.5rem 1fr; gap: 1rem; align-items: start; }
|
||||
.point-num {
|
||||
font-family: 'Syne', sans-serif; font-size: 12px; font-weight: 700;
|
||||
color: var(--fa-blue); letter-spacing: 1px; padding-top: 3px;
|
||||
}
|
||||
.point-text strong { display: block; font-size: 15px; font-weight: 500; color: var(--fa-navy); margin-bottom: 5px; }
|
||||
.point-text p { font-size: 14px; font-weight: 300; line-height: 1.7; color: var(--fa-mid); }
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.why-section { padding-top: 5.5rem; padding-bottom: 5.5rem; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.why-section { grid-template-columns: 1fr; padding: 4.5rem var(--content-pad); gap: 3rem; }
|
||||
.why-visual { aspect-ratio: 4 / 5; max-height: 70vh; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.why-block { left: 0.75rem; right: 0.75rem; bottom: 0.75rem; padding: 1.5rem; }
|
||||
.why-block-quote { font-size: 1.05rem; }
|
||||
.why-block-sub { font-size: 11px; }
|
||||
.accent-bar { top: 0.75rem; right: 0.75rem; width: 88px; height: 88px; }
|
||||
.city { font-size: 11px; letter-spacing: 2px; }
|
||||
.tagline { font-size: 8px; }
|
||||
.why-content h2 { font-size: clamp(1.6rem, 7vw, 2rem); }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user