Homepage V1

This commit is contained in:
Dionis
2026-03-01 02:35:01 +01:00
parent 319581eb18
commit 08cb097d09
35 changed files with 3414 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

14
about.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/src/assets/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Über uns - Euastra GmbH</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/about/main.tsx"></script>
</body>
</html>

14
beratung.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/src/assets/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>IT-Beratung - Euastra GmbH</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/beratung/main.tsx"></script>
</body>
</html>

14
boss4j.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/src/assets/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Boss4J - Euastra GmbH</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/boss4j/main.tsx"></script>
</body>
</html>

14
contact.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/src/assets/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Kontakt - Euastra GmbH</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/contact/main.tsx"></script>
</body>
</html>

14
datenschutz.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/src/assets/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Datenschutz - Euastra GmbH</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/datenschutz/main.tsx"></script>
</body>
</html>

14
impressum.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/src/assets/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Impressum - Euastra GmbH</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/impressum/main.tsx"></script>
</body>
</html>

14
index.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/src/assets/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Euastra GmbH</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/home/main.tsx"></script>
</body>
</html>

1817
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"@vitejs/plugin-react": "^5.1.4",
"typescript": "~5.9.3",
"vite": "^7.3.1"
},
"dependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"react": "^19.2.4",
"react-dom": "^19.2.4"
}
}

14
products.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/src/assets/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Produkte - Euastra GmbH</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/products/main.tsx"></script>
</body>
</html>

BIN
src/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
src/assets/test_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

59
src/components/About.tsx Normal file
View File

@@ -0,0 +1,59 @@
import { getLanguage, translations } from '../i18n';
function About() {
const lang = getLanguage();
const t = translations[lang].about;
return (
<section id="about" className="section">
<div className="container" style={{ textAlign: 'center' }}>
<h2>{t.title}</h2>
<p style={{ fontSize: '1.2rem', maxWidth: '900px', margin: '2rem auto', color: 'var(--text-secondary)' }}>
{t.description}
</p>
<div className="about-grid">
<div>
<h3 style={{ marginBottom: '1rem' }}>{t.expertise.title}</h3>
<p>{t.expertise.text}</p>
</div>
<div>
<h3 style={{ marginBottom: '1rem' }}>{t.security.title}</h3>
<p>{t.security.text}</p>
</div>
<div>
<h3 style={{ marginBottom: '1rem' }}>{t.sovereignty.title}</h3>
<p>{t.sovereignty.text}</p>
</div>
</div>
<div style={{ marginTop: '8rem' }}>
<h2 style={{ marginBottom: '4rem' }}>{t.team.title}</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '4rem' }}>
{[t.team.member1, t.team.member2, t.team.member3].map((member, i) => (
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{
width: '180px',
height: '180px',
borderRadius: '50%',
backgroundColor: 'var(--bg-light)',
marginBottom: '1.5rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid var(--border-color)'
}}>
<span className="material-icons" style={{ fontSize: '4rem', opacity: 0.2 }}>person</span>
</div>
<h3 style={{ marginBottom: '0.5rem', fontSize: '1.3rem' }}>{member.name}</h3>
<p style={{ color: 'var(--primary-color)', fontWeight: 'bold', marginBottom: '1rem', fontSize: '0.9rem', textTransform: 'uppercase' }}>{member.role}</p>
<p style={{ fontSize: '0.95rem', maxWidth: '300px' }}>{member.bio}</p>
</div>
))}
</div>
</div>
</div>
</section>
);
}
export default About;

View File

@@ -0,0 +1,35 @@
import { getLanguage, translations } from '../i18n';
function Contact() {
const lang = getLanguage();
const t = translations[lang].contact;
return (
<section id="contact" className="section">
<div className="container">
<h2 style={{ textAlign: 'center' }}>{t.title}</h2>
<p style={{ textAlign: 'center', marginBottom: '3rem', maxWidth: '600px', margin: '1rem auto 3rem' }}>
{t.subtitle}
</p>
<form className="contact-form">
<div className="form-group">
<input type="text" placeholder={t.name} className="form-input" required />
</div>
<div className="form-group">
<input type="email" placeholder={t.email} className="form-input" required />
</div>
<div className="form-group">
<input type="text" placeholder={t.company} className="form-input" />
</div>
<div className="form-group">
<textarea placeholder={t.message} rows={5} className="form-input" required></textarea>
</div>
<button type="submit" className="submit-btn">{t.submit}</button>
</form>
</div>
</section>
);
}
export default Contact;

25
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,25 @@
import { getLanguage, translations } from '../i18n';
function Footer() {
const lang = getLanguage();
const t = translations[lang].footer;
return (
<footer className="footer">
<div className="container">
<p>&copy; {new Date().getFullYear()} Euastra GmbH. {t.rights}</p>
<p style={{ marginTop: '0.5rem', opacity: 0.8, fontSize: '0.9rem' }}>
{t.tagline}
</p>
<div className="footer-links">
<a href="/impressum.html" className="footer-link">{t.imprint}</a>
<a href="/datenschutz.html" className="footer-link">{t.privacy}</a>
<a href="/about.html" className="footer-link">{translations[lang].nav.about}</a>
<a href="/contact.html" className="footer-link">{translations[lang].nav.contact}</a>
</div>
</div>
</footer>
);
}
export default Footer;

52
src/components/Header.tsx Normal file
View File

@@ -0,0 +1,52 @@
import logo from '../assets/logo.png';
import { getLanguage, setLanguage, translations } from '../i18n';
function Header() {
const lang = getLanguage();
const t = translations[lang];
return (
<header className="header">
<a href="/" className="logo-link">
<img src={logo} alt="Euastra Logo" className="logo-img" />
</a>
<nav>
<ul className="nav-list">
<li><a href="/about.html" className="nav-link">{t.nav.about}</a></li>
<li className="nav-item-dropdown">
<a href="/products.html" className="nav-link nav-link-with-icon">
{t.nav.products} <span className="material-icons" style={{ fontSize: '1.2rem' }}>expand_more</span>
</a>
<div className="dropdown-menu">
<a href="/boss4j.html" className="dropdown-link">
<span className="material-icons">layers</span>
{t.products.boss4j.title}
</a>
<a href="/zertifikatsverwaltung.html" className="dropdown-link">
<span className="material-icons">verified_user</span>
{t.products.cert.title}
</a>
<a href="/beratung.html" className="dropdown-link">
<span className="material-icons">engineering</span>
{t.products.consulting.title}
</a>
</div>
</li>
<li><a href="/contact.html" className="nav-link">{t.nav.contact}</a></li>
<li className="lang-switch">
<button
className={`lang-btn ${lang === 'de' ? 'active' : ''}`}
onClick={() => setLanguage('de')}
>DE</button>
<button
className={`lang-btn ${lang === 'en' ? 'active' : ''}`}
onClick={() => setLanguage('en')}
>EN</button>
</li>
</ul>
</nav>
</header>
);
}
export default Header;

21
src/components/Hero.tsx Normal file
View File

@@ -0,0 +1,21 @@
import { getLanguage, translations } from '../i18n';
function Hero() {
const lang = getLanguage();
const t = translations[lang];
return (
<section className="hero">
<div className="container">
<h1>{t.hero.title}</h1>
<p>{t.hero.subtitle}</p>
<a href="/products.html" className="hero-btn">{t.hero.cta}</a>
<div className="vienna-signet">
{t.hero.viennaGimmick}
</div>
</div>
</section>
);
}
export default Hero;

View File

@@ -0,0 +1,79 @@
import Header from './Header';
import Footer from './Footer';
import { getLanguage, translations } from '../i18n';
interface ProductPageProps {
title: string;
description: string;
icon: string;
trademark?: string;
children?: React.ReactNode;
}
function ProductPage({ title, description, icon, trademark, children }: ProductPageProps) {
const lang = getLanguage();
const t = translations[lang].common;
return (
<>
<Header />
<main>
<div className="hero" style={{ padding: '6rem 0' }}>
<div className="container">
<span className="material-icons" style={{ fontSize: '4rem', marginBottom: '1.5rem', color: 'var(--accent-color)' }}>{icon}</span>
<h1>{title}</h1>
<p style={{ maxWidth: '800px', margin: '0 auto' }}>{description}</p>
</div>
</div>
<section className="section">
<div className="container">
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '4rem', alignItems: 'center' }}>
<div>
<h2>{t.detailsTitle}</h2>
<p>{t.detailsText}</p>
<ul style={{ listStyle: 'none', marginTop: '2rem' }}>
<li style={{ display: 'flex', alignItems: 'center', gap: '1rem', marginBottom: '1rem' }}>
<span className="material-icons" style={{ color: 'var(--accent-color)' }}>check_circle</span>
{t.featureSecurity}
</li>
<li style={{ display: 'flex', alignItems: 'center', gap: '1rem', marginBottom: '1rem' }}>
<span className="material-icons" style={{ color: 'var(--accent-color)' }}>check_circle</span>
{t.featureIntegration}
</li>
<li style={{ display: 'flex', alignItems: 'center', gap: '1rem', marginBottom: '1rem' }}>
<span className="material-icons" style={{ color: 'var(--accent-color)' }}>check_circle</span>
{t.featureSupport}
</li>
</ul>
{trademark && (
<p style={{ fontSize: '0.8rem', fontStyle: 'italic', marginTop: '2rem', opacity: 0.7 }}>
{trademark}
</p>
)}
</div>
<div style={{
backgroundColor: 'var(--bg-light)',
height: '400px',
borderRadius: '8px',
border: '1px solid var(--border-color)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
color: 'var(--text-secondary)'
}}>
<span className="material-icons" style={{ fontSize: '5rem', marginBottom: '1rem', opacity: 0.3 }}>image</span>
<p>{t.screenshotPlaceholder}</p>
</div>
</div>
{children}
</div>
</section>
</main>
<Footer />
</>
);
}
export default ProductPage;

View File

@@ -0,0 +1,57 @@
import { getLanguage, translations } from '../i18n';
function Products() {
const lang = getLanguage();
const t = translations[lang].products;
return (
<section id="products" className="section section-alt">
<div className="container">
<h2 style={{ textAlign: 'center' }}>{t.title}</h2>
<p style={{ textAlign: 'center', maxWidth: '800px', margin: '1rem auto 4rem', fontSize: '1.1rem' }}>
{t.subtitle}
</p>
<div className="products-grid">
<div className="product-card">
<div className="product-icon">
<span className="material-icons" style={{ fontSize: '2rem' }}>layers</span>
</div>
<h3>{t.boss4j.title}</h3>
<p>{t.boss4j.text}</p>
<p style={{ fontSize: '0.8rem', fontStyle: 'italic', marginTop: '1rem', opacity: 0.8 }}>
{t.boss4j.trademark}
</p>
<a href="/boss4j.html" style={{ marginTop: 'auto', fontWeight: 'bold', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
{translations[lang].common.moreInfo} <span className="material-icons">arrow_forward</span>
</a>
</div>
<div className="product-card">
<div className="product-icon">
<span className="material-icons" style={{ fontSize: '2rem' }}>verified_user</span>
</div>
<h3>{t.cert.title}</h3>
<p>{t.cert.text}</p>
<a href="/zertifikatsverwaltung.html" style={{ marginTop: 'auto', fontWeight: 'bold', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
{translations[lang].common.moreInfo} <span className="material-icons">arrow_forward</span>
</a>
</div>
<div className="product-card">
<div className="product-icon">
<span className="material-icons" style={{ fontSize: '2rem' }}>engineering</span>
</div>
<h3>{t.consulting.title}</h3>
<p>{t.consulting.text}</p>
<a href="/beratung.html" style={{ marginTop: 'auto', fontWeight: 'bold', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
{translations[lang].common.moreInfo} <span className="material-icons">arrow_forward</span>
</a>
</div>
</div>
</div>
</section>
);
}
export default Products;

288
src/i18n.ts Normal file
View File

@@ -0,0 +1,288 @@
type Language = 'de' | 'en';
export const translations = {
de: {
nav: {
about: 'Über uns',
products: 'Produkte',
contact: 'Kontakt',
},
hero: {
title: 'Enterprise Software. Digitale Souveränität.',
subtitle: 'Wir liefern die Werkzeuge für die IT-Infrastruktur von morgen. Sicher, leistungsstark und zu 100% unter Ihrer Kontrolle.',
cta: 'Lösungen entdecken',
viennaGimmick: 'Enterprise Technologie & Lösungen\naus Wien',
},
about: {
title: 'Über Euastra',
description: 'Bei der Euastra GmbH widmen wir uns der Bereitstellung leistungsstarker, sicherer und datensouveräner Softwarelösungen für das moderne europäische Unternehmen. In einer Zeit der digitalen Transformation ist Vertrauen in die eigene IT-Infrastruktur unser höchstes Gut.',
expertise: {
title: 'Expertise',
text: 'Tiefes technisches Verständnis für Middleware und komplexe Enterprise-Umgebungen.',
},
security: {
title: 'Sicherheit',
text: 'Sicherung digitaler Identitäten und Zertifikate nach höchsten Standards.',
},
sovereignty: {
title: 'Souveränität',
text: 'Softwarelösungen, die Ihnen die volle Kontrolle über Ihre Daten und Infrastruktur lassen.',
},
team: {
title: 'Ihre Ansprechpartner bei Euastra',
member1: {
name: 'Ing. Dionis Ramadani, MSc',
role: 'Geschäftsführung & Strategie',
bio: 'Experte für Enterprise-Softwareentwicklung und komplexe IT-Architekturen.',
},
member2: {
name: 'Mohammed Laktaoui, BSc',
role: 'Geschäftsführung & Software-Engineering',
bio: 'Spezialist für Java-Middleware und die Entwicklung hochverfügbarer Cloud-Infrastrukturen.',
},
member3: {
name: 'Felix Oskar Lanz, MSc',
role: 'IT-Consulting & Automatisierung',
bio: 'Experte für DevOps-Prozesse und die Optimierung von Enterprise-Workflows.',
},
},
},
products: {
title: 'Unsere Produkte',
subtitle: 'Wir entwickeln spezialisierte Software für höchste Ansprüche an Sicherheit, Zuverlässigkeit und Effizienz in Enterprise-Umgebungen.',
boss4j: {
title: 'Boss4J',
text: 'Die erstklassige Orchestrierungs-Suite für JBoss® und WildFly® Anwendungsserver. Vereinfachen Sie die Verwaltung komplexer Cluster und automatisieren Sie Deployments.',
trademark: 'JBoss® und WildFly® sind eingetragene Marken von Red Hat, Inc.',
},
cert: {
title: 'Zertifikatsverwaltung',
text: 'Automatisierte Suite für das Lebenszyklusmanagement digitaler Zertifikate. Behalten Sie die Kontrolle über Ihre Java-Keystores und SSL/TLS-Zertifikate.',
},
consulting: {
title: 'IT-Beratung',
text: 'Ganzheitliche Beratung zur Optimierung Ihrer IT-Prozesse. Unser Fokus liegt auf Individualsoftware-Entwicklung, Infrastruktur-Automatisierung sowie dem Customizing bestehender Systeme für maximale Effizienz.',
},
},
common: {
detailsTitle: 'Details & Features',
detailsText: 'Hier finden Sie detaillierte Informationen zu den Funktionen und Vorteilen unserer Lösung. Wir unterstützen Sie bei der Implementierung und dem Betrieb in Ihrer spezifischen Enterprise-Umgebung.',
featureSecurity: 'Höchste Sicherheitsstandards',
featureIntegration: 'Nahtlose Integration',
featureSupport: 'Enterprise Support',
screenshotPlaceholder: 'Screenshot Platzhalter',
moreInfo: 'Mehr erfahren',
},
contact: {
title: 'Kontaktieren Sie uns',
subtitle: 'Interessieren Sie sich für unsere Lösungen? Wir freuen uns auf Ihre Nachricht.',
name: 'Vollständiger Name',
email: 'Geschäftliche E-Mail',
company: 'Unternehmen',
message: 'Wie können wir Ihnen helfen?',
submit: 'Anfrage senden',
},
impressum: {
title: 'Impressum',
disclosure: 'Offenlegungspflicht gemäß § 25 Mediengesetz und Informationspflichten gemäß § 5 E-Commerce-Gesetz (ECG) und § 14 Unternehmensgesetzbuch (UGB):',
address: {
street: 'Seitenstettengasse 5/37',
city: '1010 Wien, Österreich',
},
purpose: {
label: 'Unternehmensgegenstand:',
value: 'Die Entwicklung, die Lizenzierung und der Vertrieb von Software sowie die Einbringung von IT-Beratungsdienstleistungen.',
},
registry: {
label: 'Firmenbuchnummer:',
value: 'FN [noch offen]',
},
court: {
label: 'Firmenbuchgericht:',
value: 'Handelsgericht Wien',
},
uid: {
label: 'UID-Nummer:',
value: '[beantragt]',
},
contact: {
label: 'Kontakt:',
email: 'info@euastra.at',
phone: '+43 1 2345678',
},
managingDirector: {
label: 'Geschäftsführer:',
name: 'Ing. Dionis Ramadani, MSc; Mohammed Laktaoui, BSc',
}
},
privacy: {
title: 'Datenschutzerklärung',
intro: 'Der Schutz Ihrer persönlichen Daten ist uns ein besonderes Anliegen. Wir verarbeiten Ihre Daten daher ausschließlich auf Grundlage der gesetzlichen Bestimmungen (DSGVO, TKG 2003).',
contact: {
title: 'Kontakt mit uns',
text: 'Wenn Sie per Formular auf der Website oder per E-Mail Kontakt mit uns aufnehmen, werden Ihre angegebenen Daten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen sechs Monate bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.',
},
rights: {
title: 'Ihre Rechte',
text: 'Ihnen stehen grundsätzlich die Rechte auf Auskunft, Berichtigung, Löschung, Einschränkung, Datenübertragbarkeit, Widerruf und Widerspruch zu. Wenn Sie glauben, dass die Verarbeitung Ihrer Daten gegen das Datenschutzrecht verstößt oder Ihre datenschutzrechtlichen Ansprüche sonst in einer Weise verletzt worden sind, können Sie sich bei der Aufsichtsbehörde beschweren. In Österreich ist dies die Datenschutzbehörde.',
},
controller: {
title: 'Verantwortliche Stelle',
}
},
footer: {
rights: 'Alle Rechte vorbehalten.',
tagline: 'Professionelle Softwarelösungen für komplexe IT-Infrastrukturen.',
imprint: 'Impressum',
privacy: 'Datenschutz',
}
},
en: {
nav: {
about: 'About Us',
products: 'Products',
contact: 'Contact',
},
hero: {
title: "Enterprise Software. Digital Sovereignty.",
subtitle: "Delivering the tools for tomorrow's IT infrastructure. Secure, powerful, and 100% under your control.",
cta: "Explore Solutions",
viennaGimmick: 'Enterprise Technology & Solutions\nfrom Vienna',
},
about: {
title: 'About Euastra',
description: 'At Euastra GmbH, we are dedicated to providing high-performance, secure, and data-sovereign software solutions for the modern European enterprise. In an age of digital transformation, trust in your own IT infrastructure is our greatest asset.',
expertise: {
title: 'Expertise',
text: 'Deep technical understanding of middleware and complex enterprise environments.',
},
security: {
title: 'Security',
text: 'Securing digital identities and certificates to the highest standards.',
},
sovereignty: {
title: 'Sovereignty',
text: 'Software solutions that give you full control over your data and infrastructure.',
},
team: {
title: 'Your Contacts at Euastra',
member1: {
name: 'Ing. Dionis Ramadani, MSc',
role: 'CEO & Strategy',
bio: 'Expert for enterprise software development and complex IT architectures.',
},
member2: {
name: 'Mohammed Laktaoui, BSc',
role: 'CEO & Software Engineering',
bio: 'Specialist for Java middleware and the development of high-availability cloud infrastructures.',
},
member3: {
name: 'Felix Oskar Lanz, MSc',
role: 'IT Consulting & Automation',
bio: 'Expert for DevOps processes and optimization of enterprise workflows.',
},
},
},
products: {
title: 'Our Products',
subtitle: 'We develop specialized software for the highest demands on security, reliability, and efficiency in enterprise environments.',
boss4j: {
title: 'Boss4J',
text: 'The premier orchestration suite for JBoss® and WildFly® application servers. Simplify management of complex clusters and automate deployments.',
trademark: 'JBoss® and WildFly® are registered trademarks of Red Hat, Inc.',
},
cert: {
title: 'Certificate Management',
text: 'Automated suite for digital certificate lifecycle management. Stay in control of your Java keystores and SSL/TLS certificates.',
},
consulting: {
title: 'IT Consulting',
text: 'Holistic consulting to optimize your IT processes. Our focus is on individual software development, infrastructure automation, and customizing existing systems for maximum efficiency.',
},
},
common: {
detailsTitle: 'Details & Features',
detailsText: 'Here you will find detailed information about the functions and benefits of our solution. We support you in the implementation and operation in your specific enterprise environment.',
featureSecurity: 'Highest Security Standards',
featureIntegration: 'Seamless Integration',
featureSupport: 'Enterprise Support',
screenshotPlaceholder: 'Screenshot Placeholder',
moreInfo: 'Learn more',
},
contact: {
title: 'Contact Us',
subtitle: 'Interested in our solutions? We look forward to hearing from you.',
name: 'Full Name',
email: 'Business Email',
company: 'Company',
message: 'How can we help you?',
submit: 'Send Inquiry',
},
impressum: {
title: 'Legal Notice / Imprint',
disclosure: 'Disclosure obligation according to § 25 Media Act and information obligations according to § 5 E-Commerce Act (ECG) and § 14 Commercial Code (UGB):',
address: {
street: 'Seitenstettengasse 5/37',
city: '1010 Vienna, Austria',
},
purpose: {
label: 'Business Purpose:',
value: 'The development, licensing and distribution of software as well as the provision of IT consulting services.',
},
registry: {
label: 'Commercial Register Number:',
value: 'FN [pending]',
},
court: {
label: 'Register Court:',
value: 'Commercial Court Vienna',
},
uid: {
label: 'VAT Number:',
value: '[applied for]',
},
contact: {
label: 'Contact:',
email: 'info@euastra.at',
phone: '+43 1 2345678',
},
managingDirector: {
label: 'Managing Director:',
name: 'Ing. Dionis Ramadani, MSc; Mohammed Laktaoui, BSc',
}
},
privacy: {
title: 'Privacy Policy',
intro: 'The protection of your personal data is of particular concern to us. We therefore process your data exclusively on the basis of the legal provisions (GDPR, TKG 2003).',
contact: {
title: 'Contacting Us',
text: 'If you contact us via the form on the website or by email, the data you provide will be stored by us for six months for the purpose of processing the inquiry and in case of follow-up questions. We do not pass on this data without your consent.',
},
rights: {
title: 'Your Rights',
text: 'In principle, you have the right to information, correction, deletion, restriction, data portability, revocation, and objection. If you believe that the processing of your data violates data protection law or your data protection claims have otherwise been violated in any way, you can complain to the supervisory authority. In Austria, this is the Data Protection Authority (Datenschutzbehörde).',
},
controller: {
title: 'Data Controller',
}
},
footer: {
rights: 'All rights reserved.',
tagline: 'Professional software solutions for complex IT infrastructures.',
imprint: 'Imprint',
privacy: 'Privacy Policy',
}
}
};
export const getLanguage = (): Language => {
const saved = localStorage.getItem('lang');
if (saved === 'de' || saved === 'en') return saved;
return navigator.language.startsWith('de') ? 'de' : 'en';
};
export const setLanguage = (lang: Language) => {
localStorage.setItem('lang', lang);
window.location.reload();
};

16
src/pages/about/main.tsx Normal file
View File

@@ -0,0 +1,16 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Header from '../../components/Header'
import About from '../../components/About'
import Footer from '../../components/Footer'
import '../../style.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Header />
<main>
<About />
</main>
<Footer />
</React.StrictMode>,
)

View File

@@ -0,0 +1,24 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import ProductPage from '../../components/ProductPage'
import '../../style.css'
import { getLanguage, translations } from '../../i18n'
const Page = () => {
const lang = getLanguage();
const t = translations[lang].products.consulting;
return (
<ProductPage
title={t.title}
description={t.text}
icon="engineering"
/>
);
};
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Page />
</React.StrictMode>,
)

25
src/pages/boss4j/main.tsx Normal file
View File

@@ -0,0 +1,25 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import ProductPage from '../../components/ProductPage'
import '../../style.css'
import { getLanguage, translations } from '../../i18n'
const Page = () => {
const lang = getLanguage();
const t = translations[lang].products.boss4j;
return (
<ProductPage
title={t.title}
description={t.text}
icon="layers"
trademark={t.trademark}
/>
);
};
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Page />
</React.StrictMode>,
)

View File

@@ -0,0 +1,16 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Header from '../../components/Header'
import Contact from '../../components/Contact'
import Footer from '../../components/Footer'
import '../../style.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Header />
<main>
<Contact />
</main>
<Footer />
</React.StrictMode>,
)

View File

@@ -0,0 +1,40 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Header from '../../components/Header'
import Footer from '../../components/Footer'
import '../../style.css'
import { getLanguage, translations } from '../../i18n'
const Datenschutz = () => {
const lang = getLanguage();
const t = translations[lang].privacy;
const imp = translations[lang].impressum;
return (
<main className="section container legal-page">
<h2>{t.title}</h2>
<p>{t.intro}</p>
<h3>{t.contact.title}</h3>
<p>{t.contact.text}</p>
<h3>{t.rights.title}</h3>
<p>{t.rights.text}</p>
<h3>{t.controller.title}</h3>
<p>
Euastra GmbH<br />
{imp.address.street}<br />
{imp.address.city}
</p>
</main>
);
};
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Header />
<Datenschutz />
<Footer />
</React.StrictMode>,
)

16
src/pages/home/main.tsx Normal file
View File

@@ -0,0 +1,16 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Header from '../../components/Header'
import Hero from '../../components/Hero'
import Footer from '../../components/Footer'
import '../../style.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Header />
<main>
<Hero />
</main>
<Footer />
</React.StrictMode>,
)

View File

@@ -0,0 +1,45 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Header from '../../components/Header'
import Footer from '../../components/Footer'
import '../../style.css'
import { getLanguage, translations } from '../../i18n'
const Impressum = () => {
const lang = getLanguage();
const t = translations[lang].impressum;
return (
<main className="section container legal-page">
<h2>{t.title}</h2>
<p>{t.disclosure}</p>
<p>
<strong>Euastra GmbH</strong><br />
{t.address.street}<br />
{t.address.city}
</p>
<p>
<strong>{t.purpose.label}</strong> <br></br>{t.purpose.value}<br />
<strong>{t.registry.label}</strong> {t.registry.value}<br />
<strong>{t.court.label}</strong> {t.court.value}<br />
<strong>{t.uid.label}</strong> {t.uid.value}
</p>
<p>
<strong>{t.contact.label}</strong><br />
E-Mail: {t.contact.email}<br />
Telefon: {t.contact.phone}
</p>
<p>
<strong>{t.managingDirector.label}</strong> {t.managingDirector.name}
</p>
</main>
);
};
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Header />
<Impressum />
<Footer />
</React.StrictMode>,
)

View File

@@ -0,0 +1,16 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Header from '../../components/Header'
import Products from '../../components/Products'
import Footer from '../../components/Footer'
import '../../style.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Header />
<main>
<Products />
</main>
<Footer />
</React.StrictMode>,
)

View File

@@ -0,0 +1,24 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import ProductPage from '../../components/ProductPage'
import '../../style.css'
import { getLanguage, translations } from '../../i18n'
const Page = () => {
const lang = getLanguage();
const t = translations[lang].products.cert;
return (
<ProductPage
title={t.title}
description={t.text}
icon="verified_user"
/>
);
};
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Page />
</React.StrictMode>,
)

537
src/style.css Normal file
View File

@@ -0,0 +1,537 @@
:root {
--primary-color: #20446C; /* Blue from 'ASTRA' */
--primary-dark: #0D2B4D; /* Dark Navy from 'EU' */
--accent-color: #deaa0c; /* Gold from the Star */
--text-primary: #1a1a1a;
--text-secondary: #4a4a4a;
--bg-white: #ffffff;
--bg-light: #f4f7f9;
--border-color: #e0e0e0;
--max-width: 1200px;
--transition: all 0.3s ease;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 1.6;
color: var(--text-primary);
background-color: var(--bg-white);
overflow-x: hidden;
}
h1, h2, h3, h4 {
font-family: 'Inter', sans-serif;
font-weight: 700;
color: var(--primary-color);
line-height: 1.2;
}
h2 {
font-size: 2.5rem;
margin-bottom: 2rem;
position: relative;
display: inline-block;
}
h2::after {
content: '';
position: absolute;
bottom: -10px;
left: 0;
width: 60px;
height: 4px;
background-color: var(--accent-color);
}
p {
margin-bottom: 1rem;
color: var(--text-secondary);
text-align: center;
}
a {
text-decoration: none;
color: var(--primary-color);
transition: var(--transition);
}
a:hover {
color: var(--accent-color);
}
.container {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 2rem;
width: 100%;
}
.section {
padding: 6rem 0;
}
.section-alt {
background-color: var(--bg-light);
}
/* Header Styles */
.header {
height: 100px;
display: flex;
justify-content: space-between; /* Logo left, Nav right */
align-items: center;
padding: 0 4rem;
background-color: #ffffff;
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
z-index: 1000;
}
.logo-link {
display: flex;
align-items: center;
height: 100px;
}
.logo-img {
height: 90px; /* Prominent size on the left */
width: auto;
display: block;
mix-blend-mode: multiply;
}
.nav-list {
display: flex;
gap: 3rem;
list-style: none;
align-items: center;
}
.nav-link {
font-weight: 600;
font-size: 1.1rem;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 1rem 0;
}
/* Dropdown Menu */
.nav-item-dropdown {
position: relative;
display: flex;
align-items: center;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
background-color: var(--bg-white);
border: 1px solid var(--border-color);
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
border-radius: 4px;
min-width: 280px;
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
z-index: 1100;
padding: 1rem 0;
}
.nav-item-dropdown:hover .dropdown-menu {
opacity: 1;
visibility: visible;
top: calc(100% - 10px);
}
.dropdown-link {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.8rem 1.5rem;
color: var(--text-primary);
font-weight: 500;
transition: var(--transition);
}
.dropdown-link:hover {
background-color: var(--bg-light);
color: var(--primary-color);
}
.dropdown-link .material-icons {
font-size: 1.2rem;
color: var(--accent-color);
}
.material-icons {
color: var(--accent-color);
}
.nav-link-with-icon {
display: flex;
align-items: center;
gap: 0.3rem;
}
.lang-switch {
display: flex;
gap: 0.5rem;
margin-left: 1rem;
border-left: 1px solid var(--border-color);
padding-left: 1rem;
}
.lang-btn {
background: none;
border: none;
font-weight: 600;
color: var(--text-secondary);
cursor: pointer;
padding: 0.2rem 0.5rem;
border-radius: 4px;
transition: var(--transition);
}
.lang-btn.active {
color: var(--primary-color);
background-color: var(--bg-light);
}
/* Hero Section */
.hero {
padding: 10rem 0 8rem;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
color: white;
text-align: center;
}
.hero h1 {
font-size: 3.5rem;
color: white;
margin-bottom: 1.5rem;
}
.hero p {
font-size: 1.25rem;
color: rgba(255, 255, 255, 0.9);
max-width: 800px;
margin: 0 auto 2.5rem;
}
.hero-btn {
display: inline-block;
padding: 1rem 2.5rem;
background-color: #ffc107f1;
color: var(--primary-color);
font-weight: 700;
border-radius: 4px;
text-transform: uppercase;
letter-spacing: 1px;
}
.hero-btn:hover {
background-color: #ffc107;
color: var(--primary-color);
transform: translateY(-2px);
}
/* About Grid */
.about-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 3rem;
margin-top: 4rem;
}
/* Products Section */
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 2.5rem;
margin-top: 3rem;
}
.product-card {
background: white;
padding: 2.5rem;
border-radius: 8px;
border: 1px solid var(--border-color);
box-shadow: 0 4px 6px rgba(0,0,0,0.02);
transition: var(--transition);
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 20px rgba(0,0,0,0.08);
border-color: var(--accent-color);
}
.product-card h3 {
margin-bottom: 1rem;
}
.product-icon {
width: 60px;
height: 60px;
background-color: var(--bg-light);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.5rem;
color: var(--accent-color);
font-size: 1.2rem;
font-weight: bold;
}
.legal-page {
text-align: left !important;
padding-left: 5% !important;
padding-right: 5% !important;
}
.legal-page p, .legal-page h2, .legal-page h3 {
text-align: left !important;
}
/* Contact Form */
.contact-form {
max-width: 700px;
margin: 0 auto;
background: white;
padding: 3rem;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-input {
width: 100%;
padding: 1rem;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 1rem;
transition: var(--transition);
}
.form-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(0, 51, 102, 0.1);
}
.submit-btn {
width: 100%;
padding: 1rem;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
font-weight: 700;
font-size: 1.1rem;
cursor: pointer;
transition: var(--transition);
}
.submit-btn:hover {
background-color: #004080;
}
.vienna-signet {
margin-top: 5rem;
font-size: 0.75rem;
opacity: 0.5;
letter-spacing: 2px;
text-transform: uppercase;
font-weight: 400;
color: white;
white-space: pre-line; /* Enables the linebreak from i18n */
line-height: 1.8;
text-align: center;
}
/* Footer */
.footer {
background-color: var(--primary-color);
color: white;
padding: 4rem 0 2rem;
text-align: center;
}
.footer p {
color: rgba(255, 255, 255, 0.7);
}
.footer-links {
margin-top: 2rem;
display: flex;
justify-content: center;
gap: 2rem;
}
.footer-link {
color: rgba(255, 255, 255, 0.8);
font-size: 0.9rem;
}
.footer-link:hover {
color: var(--accent-color);
}
html {
scroll-behavior: smooth;
}
@media (max-width: 1024px) {
.header {
padding: 0 2rem;
}
}
@media (max-width: 768px) {
.container {
padding: 0 1.5rem;
}
.section {
padding: 4rem 0;
}
.header {
padding: 1rem;
height: auto;
flex-direction: column;
gap: 1.5rem;
position: relative; /* Stickiness can be annoying on very small headers if they are too tall */
}
.logo-img {
height: 70px;
}
.nav-list {
gap: 1.5rem;
flex-wrap: wrap;
justify-content: center;
}
.lang-switch {
margin-left: 0;
border-left: none;
padding-left: 0;
width: 100%;
justify-content: center;
margin-top: 0.5rem;
border-top: 1px solid var(--border-color);
padding-top: 1rem;
}
.hero {
padding: 6rem 0 4rem;
}
.hero h1 {
font-size: 2.2rem;
padding: 0 1rem;
}
.hero p {
font-size: 1.1rem;
padding: 0 1.5rem;
}
h2 {
font-size: 2rem;
}
.about-grid, .products-grid {
gap: 2rem;
text-align: center;
}
/* Target the third child specifically to center it when stacking */
.about-grid > div:nth-child(3), .products-grid > div:nth-child(3) {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
align-items: center;
}
.about-grid > div, .product-card {
display: flex;
flex-direction: column;
align-items: center;
max-width: 100%;
}
.product-icon {
margin-left: auto;
margin-right: auto;
}
.contact-form {
padding: 2rem 1.5rem;
}
.footer-links {
flex-direction: column;
gap: 1rem;
}
}
@media (max-width: 480px) {
.hero h1 {
font-size: 1.8rem;
}
.header {
padding: 1rem 0.5rem;
}
.nav-list {
gap: 0.8rem;
}
.nav-item-dropdown:hover .dropdown-menu {
opacity: 1;
visibility: visible;
}
.dropdown-menu {
position: static;
transform: none;
opacity: 1;
visibility: visible;
box-shadow: none;
border: none;
background-color: var(--bg-light);
width: 100%;
margin-top: 0.5rem;
padding: 0.5rem 0;
}
.dropdown-link {
justify-content: center;
padding: 0.5rem 1rem;
}
.nav-link {
font-size: 0.9rem;
}
}

27
tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

23
vite.config.ts Normal file
View File

@@ -0,0 +1,23 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
about: resolve(__dirname, 'about.html'),
products: resolve(__dirname, 'products.html'),
contact: resolve(__dirname, 'contact.html'),
impressum: resolve(__dirname, 'impressum.html'),
datenschutz: resolve(__dirname, 'datenschutz.html'),
boss4j: resolve(__dirname, 'boss4j.html'),
zertifikatsverwaltung: resolve(__dirname, 'zertifikatsverwaltung.html'),
beratung: resolve(__dirname, 'beratung.html'),
},
},
},
});

View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/src/assets/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Zertifikatsverwaltung - Euastra GmbH</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/zertifikatsverwaltung/main.tsx"></script>
</body>
</html>