mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 19:09:41 +02:00
✨ feat(shared-landing-ui): unify landing pages with shared components
Add new reusable components to shared-landing-ui package: - AppScrollerSection, TimelineSection, MasonryGridSection, PrinciplesSection - LegalPageTemplate for privacy/terms/cookies/imprint pages - Navigation component with mobile menu and language switcher - GradientText and LanguageSwitcher atoms - i18n system with getLangFromUrl, useTranslations, localizePath - Theme files for picture (indigo), chat (blue), zitare (teal) Add legal pages to ManaDeck and Chat landing pages: - privacy, terms, cookies, imprint pages using shared template - Updated footers with cookies link
This commit is contained in:
parent
6d86a08d63
commit
264149a913
25 changed files with 3589 additions and 1 deletions
|
|
@ -8,6 +8,7 @@ const footerLinks = {
|
|||
legal: [
|
||||
{ href: '/privacy', label: 'Datenschutz' },
|
||||
{ href: '/terms', label: 'AGB' },
|
||||
{ href: '/cookies', label: 'Cookies' },
|
||||
{ href: '/imprint', label: 'Impressum' },
|
||||
],
|
||||
};
|
||||
|
|
|
|||
67
apps/chat/apps/landing/src/pages/cookies.astro
Normal file
67
apps/chat/apps/landing/src/pages/cookies.astro
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import LegalPageTemplate from '@manacore/shared-landing-ui/templates/LegalPageTemplate.astro';
|
||||
---
|
||||
|
||||
<Layout title="Cookie-Richtlinie - ManaChat">
|
||||
<LegalPageTemplate
|
||||
title="Cookie-Richtlinie"
|
||||
backLink="/"
|
||||
backText="Zurück zur Startseite"
|
||||
lastUpdatedText="Zuletzt aktualisiert"
|
||||
>
|
||||
<h2>1. Was sind Cookies?</h2>
|
||||
<p>
|
||||
Cookies sind kleine Textdateien, die auf Ihrem Gerät gespeichert werden. Sie helfen uns, die
|
||||
Funktionalität unserer Website zu verbessern und Ihre Nutzererfahrung zu personalisieren.
|
||||
</p>
|
||||
|
||||
<h2>2. Welche Cookies verwenden wir?</h2>
|
||||
|
||||
<h3>Notwendige Cookies</h3>
|
||||
<p>Diese Cookies sind für den Betrieb der Website unerlässlich:</p>
|
||||
<ul>
|
||||
<li><strong>Session-Cookie:</strong> Hält Sie eingeloggt</li>
|
||||
<li><strong>Sprach-Cookie:</strong> Speichert Ihre Spracheinstellung</li>
|
||||
<li><strong>Auth-Token:</strong> Sichert Ihre Anmeldung</li>
|
||||
</ul>
|
||||
|
||||
<h3>Funktionale Cookies</h3>
|
||||
<p>Diese Cookies verbessern Ihre Nutzererfahrung:</p>
|
||||
<ul>
|
||||
<li><strong>Chat-Einstellungen:</strong> Speichern Ihre bevorzugten KI-Modelle</li>
|
||||
<li><strong>UI-Präferenzen:</strong> Dark Mode, Schriftgröße etc.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Analyse-Cookies</h3>
|
||||
<p>Wir verwenden Umami Analytics, eine datenschutzfreundliche Alternative:</p>
|
||||
<ul>
|
||||
<li>Anonymisierte Nutzungsstatistiken</li>
|
||||
<li>Keine personenbezogenen Daten</li>
|
||||
<li>Keine Tracking über Websites hinweg</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Cookies von Drittanbietern</h2>
|
||||
<p>
|
||||
Wir verwenden keine Tracking-Cookies von Drittanbietern wie Google Analytics oder Facebook.
|
||||
</p>
|
||||
|
||||
<h2>4. Ihre Cookie-Einstellungen</h2>
|
||||
<p>Sie können Cookies in Ihren Browser-Einstellungen verwalten:</p>
|
||||
<ul>
|
||||
<li>Alle Cookies blockieren</li>
|
||||
<li>Cookies beim Schließen des Browsers löschen</li>
|
||||
<li>Drittanbieter-Cookies separat verwalten</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Auswirkungen der Cookie-Deaktivierung</h2>
|
||||
<p>
|
||||
Wenn Sie notwendige Cookies deaktivieren, funktionieren einige Funktionen der App
|
||||
möglicherweise nicht mehr korrekt, z.B. das automatische Einloggen oder die Speicherung Ihrer
|
||||
Chat-Einstellungen.
|
||||
</p>
|
||||
|
||||
<h2>6. Kontakt</h2>
|
||||
<p>Bei Fragen zu unserer Cookie-Richtlinie kontaktieren Sie uns unter: privacy@mana.how</p>
|
||||
</LegalPageTemplate>
|
||||
</Layout>
|
||||
90
apps/chat/apps/landing/src/pages/imprint.astro
Normal file
90
apps/chat/apps/landing/src/pages/imprint.astro
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import LegalPageTemplate from '@manacore/shared-landing-ui/templates/LegalPageTemplate.astro';
|
||||
---
|
||||
|
||||
<Layout title="Impressum - ManaChat">
|
||||
<LegalPageTemplate
|
||||
title="Impressum"
|
||||
backLink="/"
|
||||
backText="Zurück zur Startseite"
|
||||
lastUpdatedText="Zuletzt aktualisiert"
|
||||
>
|
||||
<h2>Angaben gemäß § 5 TMG</h2>
|
||||
<p>
|
||||
Mana Technologies<br />
|
||||
Musterstraße 1<br />
|
||||
12345 Musterstadt<br />
|
||||
Deutschland
|
||||
</p>
|
||||
|
||||
<h2>Kontakt</h2>
|
||||
<p>
|
||||
E-Mail: contact@mana.how<br />
|
||||
Telefon: +49 (0) 123 456789
|
||||
</p>
|
||||
|
||||
<h2>Vertreten durch</h2>
|
||||
<p>Geschäftsführer: Max Mustermann</p>
|
||||
|
||||
<h2>Handelsregister</h2>
|
||||
<p>
|
||||
Registergericht: Amtsgericht Musterstadt<br />
|
||||
Registernummer: HRB 12345
|
||||
</p>
|
||||
|
||||
<h2>Umsatzsteuer-ID</h2>
|
||||
<p>
|
||||
Umsatzsteuer-Identifikationsnummer gemäß § 27a Umsatzsteuergesetz:<br />
|
||||
DE123456789
|
||||
</p>
|
||||
|
||||
<h2>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV</h2>
|
||||
<p>
|
||||
Max Mustermann<br />
|
||||
Musterstraße 1<br />
|
||||
12345 Musterstadt
|
||||
</p>
|
||||
|
||||
<h2>Streitschlichtung</h2>
|
||||
<p>
|
||||
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
|
||||
<a href="https://ec.europa.eu/consumers/odr" target="_blank" rel="noopener"
|
||||
>https://ec.europa.eu/consumers/odr</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer
|
||||
Verbraucherschlichtungsstelle teilzunehmen.
|
||||
</p>
|
||||
|
||||
<h2>Haftungsausschluss</h2>
|
||||
|
||||
<h3>Haftung für Inhalte</h3>
|
||||
<p>
|
||||
Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den
|
||||
allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch
|
||||
nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen.
|
||||
</p>
|
||||
|
||||
<h3>Haftung für KI-generierte Inhalte</h3>
|
||||
<p>
|
||||
Die von der KI generierten Antworten sind keine rechtsverbindlichen Aussagen. Die Nutzung
|
||||
erfolgt auf eigene Verantwortung.
|
||||
</p>
|
||||
|
||||
<h3>Haftung für Links</h3>
|
||||
<p>
|
||||
Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen
|
||||
Einfluss haben. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter
|
||||
verantwortlich.
|
||||
</p>
|
||||
|
||||
<h2>Urheberrecht</h2>
|
||||
<p>
|
||||
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem
|
||||
deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der
|
||||
Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung.
|
||||
</p>
|
||||
</LegalPageTemplate>
|
||||
</Layout>
|
||||
72
apps/chat/apps/landing/src/pages/privacy.astro
Normal file
72
apps/chat/apps/landing/src/pages/privacy.astro
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import LegalPageTemplate from '@manacore/shared-landing-ui/templates/LegalPageTemplate.astro';
|
||||
---
|
||||
|
||||
<Layout title="Datenschutzerklärung - ManaChat">
|
||||
<LegalPageTemplate
|
||||
title="Datenschutzerklärung"
|
||||
backLink="/"
|
||||
backText="Zurück zur Startseite"
|
||||
lastUpdatedText="Zuletzt aktualisiert"
|
||||
>
|
||||
<h2>1. Einleitung</h2>
|
||||
<p>
|
||||
Diese Datenschutzerklärung informiert Sie über die Art, den Umfang und den Zweck der
|
||||
Verarbeitung personenbezogener Daten innerhalb unserer ManaChat-Anwendung.
|
||||
</p>
|
||||
|
||||
<h2>2. Verantwortlicher</h2>
|
||||
<p>
|
||||
Verantwortlich für die Datenverarbeitung ist:<br />
|
||||
Mana Technologies<br />
|
||||
Musterstraße 1<br />
|
||||
12345 Musterstadt<br />
|
||||
Deutschland<br />
|
||||
E-Mail: privacy@mana.how
|
||||
</p>
|
||||
|
||||
<h2>3. Erhobene Daten</h2>
|
||||
<p>Wir erheben folgende Daten:</p>
|
||||
<ul>
|
||||
<li>E-Mail-Adresse bei Registrierung</li>
|
||||
<li>Chat-Verläufe und Konversationen</li>
|
||||
<li>Nutzungsstatistiken zur Verbesserung der App</li>
|
||||
<li>Geräteinformationen für die Synchronisierung</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Zweck der Datenverarbeitung</h2>
|
||||
<p>Ihre Daten werden verwendet für:</p>
|
||||
<ul>
|
||||
<li>Bereitstellung des KI-Chat-Dienstes</li>
|
||||
<li>Speicherung Ihrer Konversationen</li>
|
||||
<li>Synchronisierung über Geräte hinweg</li>
|
||||
<li>Kommunikation bezüglich Ihres Kontos</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. KI-Datenverarbeitung</h2>
|
||||
<p>
|
||||
Ihre Chat-Nachrichten werden an KI-Dienste (OpenRouter) zur Verarbeitung übermittelt. Diese
|
||||
Dienste nutzen Ihre Daten nicht für das Training ihrer Modelle.
|
||||
</p>
|
||||
|
||||
<h2>6. Datenspeicherung</h2>
|
||||
<p>
|
||||
Ihre Daten werden auf sicheren Servern in der Europäischen Union gespeichert. Wir verwenden
|
||||
Verschlüsselung für alle übertragenen und gespeicherten Daten.
|
||||
</p>
|
||||
|
||||
<h2>7. Ihre Rechte</h2>
|
||||
<p>Sie haben folgende Rechte:</p>
|
||||
<ul>
|
||||
<li>Auskunft über Ihre gespeicherten Daten</li>
|
||||
<li>Berichtigung unrichtiger Daten</li>
|
||||
<li>Löschung Ihrer Daten und Chat-Verläufe</li>
|
||||
<li>Export Ihrer Konversationen</li>
|
||||
<li>Widerruf der Einwilligung</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Kontakt</h2>
|
||||
<p>Bei Fragen zum Datenschutz kontaktieren Sie uns unter: privacy@mana.how</p>
|
||||
</LegalPageTemplate>
|
||||
</Layout>
|
||||
84
apps/chat/apps/landing/src/pages/terms.astro
Normal file
84
apps/chat/apps/landing/src/pages/terms.astro
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import LegalPageTemplate from '@manacore/shared-landing-ui/templates/LegalPageTemplate.astro';
|
||||
---
|
||||
|
||||
<Layout title="Nutzungsbedingungen - ManaChat">
|
||||
<LegalPageTemplate
|
||||
title="Nutzungsbedingungen"
|
||||
backLink="/"
|
||||
backText="Zurück zur Startseite"
|
||||
lastUpdatedText="Zuletzt aktualisiert"
|
||||
>
|
||||
<h2>1. Geltungsbereich</h2>
|
||||
<p>
|
||||
Diese Nutzungsbedingungen gelten für die Nutzung der ManaChat-Anwendung, einschließlich der
|
||||
mobilen Apps, der Web-Version und aller zugehörigen Dienste.
|
||||
</p>
|
||||
|
||||
<h2>2. Leistungsbeschreibung</h2>
|
||||
<p>ManaChat bietet:</p>
|
||||
<ul>
|
||||
<li>Zugang zu verschiedenen KI-Modellen (GPT-4o, GPT-4o-Mini, etc.)</li>
|
||||
<li>Speicherung von Chat-Verläufen</li>
|
||||
<li>Synchronisierung über mehrere Geräte</li>
|
||||
<li>Dokument-Modus für längere Texte</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Registrierung</h2>
|
||||
<p>
|
||||
Zur Nutzung ist eine Registrierung erforderlich. Sie sind für die Geheimhaltung Ihrer
|
||||
Zugangsdaten verantwortlich.
|
||||
</p>
|
||||
|
||||
<h2>4. Kostenfreie und kostenpflichtige Funktionen</h2>
|
||||
<p>ManaChat bietet sowohl kostenfreie als auch Premium-Funktionen:</p>
|
||||
<ul>
|
||||
<li><strong>Free:</strong> Begrenzte Nachrichten, GPT-4o-Mini</li>
|
||||
<li><strong>Pro:</strong> Unbegrenzte Nachrichten, alle KI-Modelle</li>
|
||||
<li><strong>Team:</strong> Zusätzliche Teamfunktionen und API-Zugang</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Nutzerverhalten</h2>
|
||||
<p>Sie verpflichten sich:</p>
|
||||
<ul>
|
||||
<li>Die KI nicht für illegale oder schädliche Zwecke zu nutzen</li>
|
||||
<li>Keine automatisierten Massenanfragen zu senden</li>
|
||||
<li>Keine versuche zu unternehmen, das System zu manipulieren</li>
|
||||
<li>Die Nutzungsbedingungen der KI-Modelle zu respektieren</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. KI-generierte Inhalte</h2>
|
||||
<p>
|
||||
KI-generierte Antworten können fehlerhaft sein. Sie sind für die Überprüfung und Nutzung der
|
||||
generierten Inhalte selbst verantwortlich.
|
||||
</p>
|
||||
|
||||
<h2>7. Verfügbarkeit</h2>
|
||||
<p>
|
||||
Wir bemühen uns um eine hohe Verfügbarkeit. Die Verfügbarkeit kann jedoch von Drittanbietern
|
||||
(KI-Dienste) abhängen und ist nicht garantiert.
|
||||
</p>
|
||||
|
||||
<h2>8. Kündigung</h2>
|
||||
<p>
|
||||
Sie können Ihr Konto jederzeit kündigen. Bei Premium-Abonnements gilt die Kündigung zum Ende
|
||||
des Abrechnungszeitraums.
|
||||
</p>
|
||||
|
||||
<h2>9. Haftung</h2>
|
||||
<p>
|
||||
Die Haftung für KI-generierte Inhalte ist ausgeschlossen. Wir haften nur für Vorsatz und grobe
|
||||
Fahrlässigkeit.
|
||||
</p>
|
||||
|
||||
<h2>10. Änderungen</h2>
|
||||
<p>
|
||||
Wir behalten uns vor, diese Nutzungsbedingungen zu ändern. Wesentliche Änderungen werden Ihnen
|
||||
rechtzeitig mitgeteilt.
|
||||
</p>
|
||||
|
||||
<h2>11. Anwendbares Recht</h2>
|
||||
<p>Es gilt deutsches Recht. Gerichtsstand ist, soweit zulässig, der Sitz des Anbieters.</p>
|
||||
</LegalPageTemplate>
|
||||
</Layout>
|
||||
|
|
@ -8,6 +8,7 @@ const footerLinks = {
|
|||
legal: [
|
||||
{ href: '/privacy', label: 'Datenschutz' },
|
||||
{ href: '/terms', label: 'AGB' },
|
||||
{ href: '/cookies', label: 'Cookies' },
|
||||
{ href: '/imprint', label: 'Impressum' },
|
||||
],
|
||||
};
|
||||
|
|
|
|||
67
apps/manadeck/apps/landing/src/pages/cookies.astro
Normal file
67
apps/manadeck/apps/landing/src/pages/cookies.astro
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import LegalPageTemplate from '@manacore/shared-landing-ui/templates/LegalPageTemplate.astro';
|
||||
---
|
||||
|
||||
<Layout title="Cookie-Richtlinie - ManaDeck">
|
||||
<LegalPageTemplate
|
||||
title="Cookie-Richtlinie"
|
||||
backLink="/"
|
||||
backText="Zurück zur Startseite"
|
||||
lastUpdatedText="Zuletzt aktualisiert"
|
||||
>
|
||||
<h2>1. Was sind Cookies?</h2>
|
||||
<p>
|
||||
Cookies sind kleine Textdateien, die auf Ihrem Gerät gespeichert werden. Sie helfen uns, die
|
||||
Funktionalität unserer Website zu verbessern und Ihre Nutzererfahrung zu personalisieren.
|
||||
</p>
|
||||
|
||||
<h2>2. Welche Cookies verwenden wir?</h2>
|
||||
|
||||
<h3>Notwendige Cookies</h3>
|
||||
<p>Diese Cookies sind für den Betrieb der Website unerlässlich:</p>
|
||||
<ul>
|
||||
<li><strong>Session-Cookie:</strong> Hält Sie eingeloggt</li>
|
||||
<li><strong>Sprach-Cookie:</strong> Speichert Ihre Spracheinstellung</li>
|
||||
</ul>
|
||||
|
||||
<h3>Funktionale Cookies</h3>
|
||||
<p>Diese Cookies verbessern Ihre Nutzererfahrung:</p>
|
||||
<ul>
|
||||
<li><strong>Präferenz-Cookies:</strong> Speichern Ihre Einstellungen</li>
|
||||
<li><strong>Lernfortschritts-Cookies:</strong> Ermöglichen Offline-Lernen</li>
|
||||
</ul>
|
||||
|
||||
<h3>Analyse-Cookies</h3>
|
||||
<p>
|
||||
Wir verwenden Umami Analytics, eine datenschutzfreundliche Alternative zu Google Analytics:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Anonymisierte Nutzungsstatistiken</li>
|
||||
<li>Keine personenbezogenen Daten</li>
|
||||
<li>Keine Tracking über Websites hinweg</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Cookies von Drittanbietern</h2>
|
||||
<p>
|
||||
Wir verwenden keine Tracking-Cookies von Drittanbietern wie Google Analytics oder Facebook.
|
||||
</p>
|
||||
|
||||
<h2>4. Ihre Cookie-Einstellungen</h2>
|
||||
<p>Sie können Cookies in Ihren Browser-Einstellungen verwalten:</p>
|
||||
<ul>
|
||||
<li>Alle Cookies blockieren</li>
|
||||
<li>Cookies beim Schließen des Browsers löschen</li>
|
||||
<li>Drittanbieter-Cookies separat verwalten</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Auswirkungen der Cookie-Deaktivierung</h2>
|
||||
<p>
|
||||
Wenn Sie notwendige Cookies deaktivieren, funktionieren einige Funktionen der App
|
||||
möglicherweise nicht mehr korrekt, z.B. das automatische Einloggen.
|
||||
</p>
|
||||
|
||||
<h2>6. Kontakt</h2>
|
||||
<p>Bei Fragen zu unserer Cookie-Richtlinie kontaktieren Sie uns unter: privacy@mana.how</p>
|
||||
</LegalPageTemplate>
|
||||
</Layout>
|
||||
84
apps/manadeck/apps/landing/src/pages/imprint.astro
Normal file
84
apps/manadeck/apps/landing/src/pages/imprint.astro
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import LegalPageTemplate from '@manacore/shared-landing-ui/templates/LegalPageTemplate.astro';
|
||||
---
|
||||
|
||||
<Layout title="Impressum - ManaDeck">
|
||||
<LegalPageTemplate
|
||||
title="Impressum"
|
||||
backLink="/"
|
||||
backText="Zurück zur Startseite"
|
||||
lastUpdatedText="Zuletzt aktualisiert"
|
||||
>
|
||||
<h2>Angaben gemäß § 5 TMG</h2>
|
||||
<p>
|
||||
Mana Technologies<br />
|
||||
Musterstraße 1<br />
|
||||
12345 Musterstadt<br />
|
||||
Deutschland
|
||||
</p>
|
||||
|
||||
<h2>Kontakt</h2>
|
||||
<p>
|
||||
E-Mail: contact@mana.how<br />
|
||||
Telefon: +49 (0) 123 456789
|
||||
</p>
|
||||
|
||||
<h2>Vertreten durch</h2>
|
||||
<p>Geschäftsführer: Max Mustermann</p>
|
||||
|
||||
<h2>Handelsregister</h2>
|
||||
<p>
|
||||
Registergericht: Amtsgericht Musterstadt<br />
|
||||
Registernummer: HRB 12345
|
||||
</p>
|
||||
|
||||
<h2>Umsatzsteuer-ID</h2>
|
||||
<p>
|
||||
Umsatzsteuer-Identifikationsnummer gemäß § 27a Umsatzsteuergesetz:<br />
|
||||
DE123456789
|
||||
</p>
|
||||
|
||||
<h2>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV</h2>
|
||||
<p>
|
||||
Max Mustermann<br />
|
||||
Musterstraße 1<br />
|
||||
12345 Musterstadt
|
||||
</p>
|
||||
|
||||
<h2>Streitschlichtung</h2>
|
||||
<p>
|
||||
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
|
||||
<a href="https://ec.europa.eu/consumers/odr" target="_blank" rel="noopener"
|
||||
>https://ec.europa.eu/consumers/odr</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer
|
||||
Verbraucherschlichtungsstelle teilzunehmen.
|
||||
</p>
|
||||
|
||||
<h2>Haftungsausschluss</h2>
|
||||
|
||||
<h3>Haftung für Inhalte</h3>
|
||||
<p>
|
||||
Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den
|
||||
allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch
|
||||
nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen.
|
||||
</p>
|
||||
|
||||
<h3>Haftung für Links</h3>
|
||||
<p>
|
||||
Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen
|
||||
Einfluss haben. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter
|
||||
verantwortlich.
|
||||
</p>
|
||||
|
||||
<h2>Urheberrecht</h2>
|
||||
<p>
|
||||
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem
|
||||
deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der
|
||||
Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung.
|
||||
</p>
|
||||
</LegalPageTemplate>
|
||||
</Layout>
|
||||
72
apps/manadeck/apps/landing/src/pages/privacy.astro
Normal file
72
apps/manadeck/apps/landing/src/pages/privacy.astro
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import LegalPageTemplate from '@manacore/shared-landing-ui/templates/LegalPageTemplate.astro';
|
||||
---
|
||||
|
||||
<Layout title="Datenschutzerklärung - ManaDeck">
|
||||
<LegalPageTemplate
|
||||
title="Datenschutzerklärung"
|
||||
backLink="/"
|
||||
backText="Zurück zur Startseite"
|
||||
lastUpdatedText="Zuletzt aktualisiert"
|
||||
>
|
||||
<h2>1. Einleitung</h2>
|
||||
<p>
|
||||
Diese Datenschutzerklärung informiert Sie über die Art, den Umfang und den Zweck der
|
||||
Verarbeitung personenbezogener Daten innerhalb unserer ManaDeck-Anwendung.
|
||||
</p>
|
||||
|
||||
<h2>2. Verantwortlicher</h2>
|
||||
<p>
|
||||
Verantwortlich für die Datenverarbeitung ist:<br />
|
||||
Mana Technologies<br />
|
||||
Musterstraße 1<br />
|
||||
12345 Musterstadt<br />
|
||||
Deutschland<br />
|
||||
E-Mail: privacy@mana.how
|
||||
</p>
|
||||
|
||||
<h2>3. Erhobene Daten</h2>
|
||||
<p>Wir erheben folgende Daten:</p>
|
||||
<ul>
|
||||
<li>E-Mail-Adresse bei Registrierung</li>
|
||||
<li>Karteikarten und Lernfortschritte</li>
|
||||
<li>Nutzungsstatistiken zur Verbesserung der App</li>
|
||||
<li>Geräteinformationen für die Synchronisierung</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Zweck der Datenverarbeitung</h2>
|
||||
<p>Ihre Daten werden verwendet für:</p>
|
||||
<ul>
|
||||
<li>Bereitstellung des Karteikarten-Dienstes</li>
|
||||
<li>Synchronisierung Ihrer Lernfortschritte</li>
|
||||
<li>Verbesserung der KI-generierten Karteikarten</li>
|
||||
<li>Kommunikation bezüglich Ihres Kontos</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Datenspeicherung</h2>
|
||||
<p>
|
||||
Ihre Daten werden auf sicheren Servern in der Europäischen Union gespeichert. Wir verwenden
|
||||
Verschlüsselung für alle übertragenen und gespeicherten Daten.
|
||||
</p>
|
||||
|
||||
<h2>6. Ihre Rechte</h2>
|
||||
<p>Sie haben folgende Rechte:</p>
|
||||
<ul>
|
||||
<li>Auskunft über Ihre gespeicherten Daten</li>
|
||||
<li>Berichtigung unrichtiger Daten</li>
|
||||
<li>Löschung Ihrer Daten</li>
|
||||
<li>Export Ihrer Daten</li>
|
||||
<li>Widerruf der Einwilligung</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. KI-Datenverarbeitung</h2>
|
||||
<p>
|
||||
Bei der KI-gestützten Karteikarten-Generierung werden Ihre Texte an KI-Dienste übermittelt.
|
||||
Diese Daten werden nur zur Verarbeitung verwendet und nicht dauerhaft gespeichert.
|
||||
</p>
|
||||
|
||||
<h2>8. Kontakt</h2>
|
||||
<p>Bei Fragen zum Datenschutz kontaktieren Sie uns unter: privacy@mana.how</p>
|
||||
</LegalPageTemplate>
|
||||
</Layout>
|
||||
83
apps/manadeck/apps/landing/src/pages/terms.astro
Normal file
83
apps/manadeck/apps/landing/src/pages/terms.astro
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import LegalPageTemplate from '@manacore/shared-landing-ui/templates/LegalPageTemplate.astro';
|
||||
---
|
||||
|
||||
<Layout title="Nutzungsbedingungen - ManaDeck">
|
||||
<LegalPageTemplate
|
||||
title="Nutzungsbedingungen"
|
||||
backLink="/"
|
||||
backText="Zurück zur Startseite"
|
||||
lastUpdatedText="Zuletzt aktualisiert"
|
||||
>
|
||||
<h2>1. Geltungsbereich</h2>
|
||||
<p>
|
||||
Diese Nutzungsbedingungen gelten für die Nutzung der ManaDeck-Anwendung, einschließlich der
|
||||
mobilen Apps und der Web-Version.
|
||||
</p>
|
||||
|
||||
<h2>2. Leistungsbeschreibung</h2>
|
||||
<p>ManaDeck bietet:</p>
|
||||
<ul>
|
||||
<li>KI-gestützte Erstellung von Karteikarten</li>
|
||||
<li>Spaced-Repetition-Lernsystem</li>
|
||||
<li>Synchronisierung über mehrere Geräte</li>
|
||||
<li>Import von Texten und Dokumenten</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Registrierung</h2>
|
||||
<p>
|
||||
Zur Nutzung ist eine Registrierung erforderlich. Sie sind für die Geheimhaltung Ihrer
|
||||
Zugangsdaten verantwortlich.
|
||||
</p>
|
||||
|
||||
<h2>4. Kostenfreie und kostenpflichtige Funktionen</h2>
|
||||
<p>ManaDeck bietet sowohl kostenfreie als auch Premium-Funktionen:</p>
|
||||
<ul>
|
||||
<li><strong>Free:</strong> Grundfunktionen mit begrenzten Karten</li>
|
||||
<li><strong>Pro:</strong> Unbegrenzte Karten und erweiterte KI-Funktionen</li>
|
||||
<li><strong>Team:</strong> Zusätzliche Teamfunktionen</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Nutzerverhalten</h2>
|
||||
<p>Sie verpflichten sich:</p>
|
||||
<ul>
|
||||
<li>Keine urheberrechtlich geschützten Inhalte ohne Berechtigung hochzuladen</li>
|
||||
<li>Den Dienst nicht missbräuchlich zu nutzen</li>
|
||||
<li>Keine automatisierten Zugriffe ohne Genehmigung durchzuführen</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Geistiges Eigentum</h2>
|
||||
<p>
|
||||
Die von Ihnen erstellten oder generierten Karteikarten gehören Ihnen. ManaDeck behält die
|
||||
Rechte an der Software und dem Design.
|
||||
</p>
|
||||
|
||||
<h2>7. Verfügbarkeit</h2>
|
||||
<p>
|
||||
Wir bemühen uns um eine hohe Verfügbarkeit, können jedoch keine 100%ige Verfügbarkeit
|
||||
garantieren. Wartungsarbeiten werden nach Möglichkeit vorab angekündigt.
|
||||
</p>
|
||||
|
||||
<h2>8. Kündigung</h2>
|
||||
<p>
|
||||
Sie können Ihr Konto jederzeit kündigen. Bei Premium-Abonnements gilt die Kündigung zum Ende
|
||||
des Abrechnungszeitraums.
|
||||
</p>
|
||||
|
||||
<h2>9. Haftung</h2>
|
||||
<p>
|
||||
Die Haftung ist auf Vorsatz und grobe Fahrlässigkeit beschränkt. Dies gilt nicht für Schäden
|
||||
aus der Verletzung von Leben, Körper oder Gesundheit.
|
||||
</p>
|
||||
|
||||
<h2>10. Änderungen</h2>
|
||||
<p>
|
||||
Wir behalten uns vor, diese Nutzungsbedingungen zu ändern. Wesentliche Änderungen werden Ihnen
|
||||
rechtzeitig mitgeteilt.
|
||||
</p>
|
||||
|
||||
<h2>11. Anwendbares Recht</h2>
|
||||
<p>Es gilt deutsches Recht. Gerichtsstand ist, soweit zulässig, der Sitz des Anbieters.</p>
|
||||
</LegalPageTemplate>
|
||||
</Layout>
|
||||
|
|
@ -9,12 +9,17 @@
|
|||
"./atoms/*": "./src/atoms/*",
|
||||
"./sections/*": "./src/sections/*",
|
||||
"./layouts/*": "./src/layouts/*",
|
||||
"./templates/*": "./src/templates/*",
|
||||
"./utils": "./src/utils/index.ts",
|
||||
"./i18n": "./src/i18n/index.ts",
|
||||
"./themes": "./src/themes/index.css",
|
||||
"./themes/memoro": "./src/themes/memoro.css",
|
||||
"./themes/manacore": "./src/themes/manacore.css",
|
||||
"./themes/maerchenzauber": "./src/themes/maerchenzauber.css",
|
||||
"./themes/manadeck": "./src/themes/manadeck.css"
|
||||
"./themes/manadeck": "./src/themes/manadeck.css",
|
||||
"./themes/picture": "./src/themes/picture.css",
|
||||
"./themes/chat": "./src/themes/chat.css",
|
||||
"./themes/zitare": "./src/themes/zitare.css"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
|
|
|
|||
82
packages/shared-landing-ui/src/atoms/GradientText.astro
Normal file
82
packages/shared-landing-ui/src/atoms/GradientText.astro
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
/**
|
||||
* GradientText - Text with gradient color effect
|
||||
*
|
||||
* Usage:
|
||||
* ```astro
|
||||
* <GradientText>Highlighted Text</GradientText>
|
||||
* <GradientText as="span" gradient="secondary">Custom Gradient</GradientText>
|
||||
* ```
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
as?: 'span' | 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div';
|
||||
gradient?: 'primary' | 'secondary' | 'accent' | 'rainbow';
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { as: Element = 'span', gradient = 'primary', class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<Element class:list={['gradient-text', `gradient-${gradient}`, className]}>
|
||||
<slot />
|
||||
</Element>
|
||||
|
||||
<style>
|
||||
.gradient-text {
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.gradient-primary {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-primary) 0%,
|
||||
var(--color-primary-hover) 50%,
|
||||
var(--color-primary) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.gradient-secondary {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-text-primary) 0%,
|
||||
var(--color-primary) 50%,
|
||||
var(--color-text-primary) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.gradient-accent {
|
||||
background: linear-gradient(135deg, #f97316 0%, #ec4899 50%, #8b5cf6 100%);
|
||||
}
|
||||
|
||||
.gradient-rainbow {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
#ef4444 0%,
|
||||
#f97316 15%,
|
||||
#eab308 30%,
|
||||
#22c55e 45%,
|
||||
#3b82f6 60%,
|
||||
#8b5cf6 75%,
|
||||
#ec4899 90%,
|
||||
#ef4444 100%
|
||||
);
|
||||
background-size: 200% 200%;
|
||||
animation: rainbow-shift 8s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes rainbow-shift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
178
packages/shared-landing-ui/src/atoms/LanguageSwitcher.astro
Normal file
178
packages/shared-landing-ui/src/atoms/LanguageSwitcher.astro
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
---
|
||||
/**
|
||||
* LanguageSwitcher - Dropdown for language selection
|
||||
*
|
||||
* Usage:
|
||||
* ```astro
|
||||
* <LanguageSwitcher
|
||||
* currentLang="en"
|
||||
* languages={{ de: 'Deutsch', en: 'English', fr: 'Français', it: 'Italiano', es: 'Español' }}
|
||||
* getLocalizedPath={(lang) => `/${lang}${currentPath}`}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
export interface LanguageOption {
|
||||
code: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
currentLang: string;
|
||||
languages: Record<string, string>;
|
||||
getLocalizedPath?: (lang: string) => string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
currentLang,
|
||||
languages,
|
||||
getLocalizedPath = (lang) => `/${lang}`,
|
||||
class: className = '',
|
||||
} = Astro.props;
|
||||
|
||||
const languageEntries = Object.entries(languages);
|
||||
---
|
||||
|
||||
<div class:list={['language-switcher', className]}>
|
||||
<button class="language-trigger" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="language-current">{languages[currentLang] || currentLang.toUpperCase()}</span>
|
||||
<svg class="language-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="language-dropdown" role="menu">
|
||||
{
|
||||
languageEntries.map(([code, label]) => (
|
||||
<a
|
||||
href={getLocalizedPath(code)}
|
||||
class:list={['language-option', { 'language-option-active': code === currentLang }]}
|
||||
role="menuitem"
|
||||
data-lang={code}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.language-switcher {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.language-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.language-trigger:hover {
|
||||
border-color: var(--color-border-hover);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.language-trigger:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px var(--color-primary-glow);
|
||||
}
|
||||
|
||||
.language-chevron {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.language-switcher.open .language-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 0;
|
||||
min-width: 140px;
|
||||
background: var(--color-background-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-8px);
|
||||
transition: all 0.2s ease;
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.language-switcher.open .language-dropdown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.language-option {
|
||||
display: block;
|
||||
padding: 10px 16px;
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.language-option:hover {
|
||||
background: var(--color-background-card-hover);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.language-option-active {
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-glow);
|
||||
}
|
||||
|
||||
.language-option-active:hover {
|
||||
background: var(--color-primary-glow);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Toggle dropdown on click
|
||||
document.querySelectorAll('.language-switcher').forEach((switcher) => {
|
||||
const trigger = switcher.querySelector('.language-trigger');
|
||||
|
||||
trigger?.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
switcher.classList.toggle('open');
|
||||
});
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', () => {
|
||||
document.querySelectorAll('.language-switcher.open').forEach((switcher) => {
|
||||
switcher.classList.remove('open');
|
||||
});
|
||||
});
|
||||
|
||||
// Close on escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
document.querySelectorAll('.language-switcher.open').forEach((switcher) => {
|
||||
switcher.classList.remove('open');
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
175
packages/shared-landing-ui/src/i18n/index.ts
Normal file
175
packages/shared-landing-ui/src/i18n/index.ts
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
/**
|
||||
* Shared i18n utilities for landing pages
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
|
||||
import {
|
||||
type Language,
|
||||
type CommonTranslations,
|
||||
type Translations,
|
||||
languages,
|
||||
defaultLang,
|
||||
defaultCommonTranslations,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Get the language from a URL pathname
|
||||
* Supports both /en/page and /page patterns
|
||||
*/
|
||||
export function getLangFromUrl(url: URL): Language {
|
||||
const [, lang] = url.pathname.split('/');
|
||||
if (lang && lang in languages) {
|
||||
return lang as Language;
|
||||
}
|
||||
return defaultLang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route without the language prefix
|
||||
*/
|
||||
export function getRouteFromUrl(url: URL): string {
|
||||
const pathname = url.pathname;
|
||||
const [, lang, ...rest] = pathname.split('/');
|
||||
|
||||
if (lang && lang in languages) {
|
||||
return '/' + rest.join('/') || '/';
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a localized path
|
||||
*/
|
||||
export function localizePath(path: string, lang: Language): string {
|
||||
// If the language is the default language, don't add a prefix
|
||||
if (lang === defaultLang) {
|
||||
return path;
|
||||
}
|
||||
// Ensure path starts with /
|
||||
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||||
return `/${lang}${normalizedPath}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a translation function for a specific language
|
||||
*/
|
||||
export function useTranslations<T extends CommonTranslations>(
|
||||
lang: Language,
|
||||
translations: Translations<T>
|
||||
): (key: keyof T | string) => string {
|
||||
return function t(key: keyof T | string): string {
|
||||
const langTranslations = translations[lang];
|
||||
if (langTranslations && key in langTranslations) {
|
||||
return langTranslations[key as keyof T] as string;
|
||||
}
|
||||
// Fallback to default language
|
||||
const defaultTranslations = translations[defaultLang];
|
||||
if (defaultTranslations && key in defaultTranslations) {
|
||||
return defaultTranslations[key as keyof T] as string;
|
||||
}
|
||||
// Return the key as fallback
|
||||
return String(key);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a translation function with merged common translations
|
||||
*/
|
||||
export function createTranslations<T extends CommonTranslations>(
|
||||
lang: Language,
|
||||
appTranslations: Translations<T>
|
||||
): (key: keyof T | string) => string {
|
||||
// Merge app translations with common translations
|
||||
const mergedTranslations = {} as Translations<T>;
|
||||
|
||||
for (const l of Object.keys(languages) as Language[]) {
|
||||
mergedTranslations[l] = {
|
||||
...defaultCommonTranslations[l],
|
||||
...appTranslations[l],
|
||||
} as T;
|
||||
}
|
||||
|
||||
return useTranslations(lang, mergedTranslations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get alternate language links for SEO (hreflang)
|
||||
*/
|
||||
export function getAlternateLinks(url: URL): Array<{ lang: Language; href: string }> {
|
||||
const route = getRouteFromUrl(url);
|
||||
const baseUrl = `${url.protocol}//${url.host}`;
|
||||
|
||||
return (Object.keys(languages) as Language[]).map((lang) => ({
|
||||
lang,
|
||||
href: `${baseUrl}${localizePath(route, lang)}`,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect browser language preference
|
||||
*/
|
||||
export function getBrowserLang(): Language {
|
||||
if (typeof navigator === 'undefined') {
|
||||
return defaultLang;
|
||||
}
|
||||
|
||||
const browserLang = navigator.language.split('-')[0];
|
||||
if (browserLang in languages) {
|
||||
return browserLang as Language;
|
||||
}
|
||||
|
||||
return defaultLang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date according to the locale
|
||||
*/
|
||||
export function formatDate(date: Date, lang: Language): string {
|
||||
const localeMap: Record<Language, string> = {
|
||||
de: 'de-DE',
|
||||
en: 'en-US',
|
||||
fr: 'fr-FR',
|
||||
it: 'it-IT',
|
||||
es: 'es-ES',
|
||||
};
|
||||
|
||||
return date.toLocaleDateString(localeMap[lang], {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a number according to the locale
|
||||
*/
|
||||
export function formatNumber(num: number, lang: Language): string {
|
||||
const localeMap: Record<Language, string> = {
|
||||
de: 'de-DE',
|
||||
en: 'en-US',
|
||||
fr: 'fr-FR',
|
||||
it: 'it-IT',
|
||||
es: 'es-ES',
|
||||
};
|
||||
|
||||
return num.toLocaleString(localeMap[lang]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format currency according to the locale
|
||||
*/
|
||||
export function formatCurrency(amount: number, lang: Language, currency = 'EUR'): string {
|
||||
const localeMap: Record<Language, string> = {
|
||||
de: 'de-DE',
|
||||
en: 'en-US',
|
||||
fr: 'fr-FR',
|
||||
it: 'it-IT',
|
||||
es: 'es-ES',
|
||||
};
|
||||
|
||||
return new Intl.NumberFormat(localeMap[lang], {
|
||||
style: 'currency',
|
||||
currency,
|
||||
}).format(amount);
|
||||
}
|
||||
241
packages/shared-landing-ui/src/i18n/types.ts
Normal file
241
packages/shared-landing-ui/src/i18n/types.ts
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
/**
|
||||
* Shared i18n types for landing pages
|
||||
*/
|
||||
|
||||
export type Language = 'de' | 'en' | 'fr' | 'it' | 'es';
|
||||
|
||||
export const languages: Record<Language, string> = {
|
||||
de: 'Deutsch',
|
||||
en: 'English',
|
||||
fr: 'Français',
|
||||
it: 'Italiano',
|
||||
es: 'Español',
|
||||
};
|
||||
|
||||
export const defaultLang: Language = 'de';
|
||||
|
||||
/**
|
||||
* Common translations interface that all apps should implement
|
||||
* Apps can extend this with their own specific translations
|
||||
*/
|
||||
export interface CommonTranslations {
|
||||
// Navigation
|
||||
'nav.home'?: string;
|
||||
'nav.features'?: string;
|
||||
'nav.pricing'?: string;
|
||||
'nav.about'?: string;
|
||||
'nav.contact'?: string;
|
||||
'nav.login'?: string;
|
||||
'nav.signup'?: string;
|
||||
|
||||
// Buttons
|
||||
'button.getStarted'?: string;
|
||||
'button.learnMore'?: string;
|
||||
'button.tryFree'?: string;
|
||||
'button.signUp'?: string;
|
||||
'button.login'?: string;
|
||||
'button.back'?: string;
|
||||
'button.submit'?: string;
|
||||
'button.cancel'?: string;
|
||||
|
||||
// Legal
|
||||
'legal.privacy'?: string;
|
||||
'legal.terms'?: string;
|
||||
'legal.cookies'?: string;
|
||||
'legal.imprint'?: string;
|
||||
'legal.backHome'?: string;
|
||||
'legal.lastUpdated'?: string;
|
||||
|
||||
// Footer
|
||||
'footer.product'?: string;
|
||||
'footer.company'?: string;
|
||||
'footer.resources'?: string;
|
||||
'footer.legal'?: string;
|
||||
'footer.copyright'?: string;
|
||||
|
||||
// Common
|
||||
'common.loading'?: string;
|
||||
'common.error'?: string;
|
||||
'common.success'?: string;
|
||||
'common.new'?: string;
|
||||
'common.comingSoon'?: string;
|
||||
|
||||
// Allow any other string keys
|
||||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for a translations object mapping languages to translations
|
||||
*/
|
||||
export type Translations<T extends CommonTranslations = CommonTranslations> = Record<Language, T>;
|
||||
|
||||
/**
|
||||
* Default common translations in all supported languages
|
||||
*/
|
||||
export const defaultCommonTranslations: Translations<CommonTranslations> = {
|
||||
de: {
|
||||
'nav.home': 'Startseite',
|
||||
'nav.features': 'Funktionen',
|
||||
'nav.pricing': 'Preise',
|
||||
'nav.about': 'Über uns',
|
||||
'nav.contact': 'Kontakt',
|
||||
'nav.login': 'Anmelden',
|
||||
'nav.signup': 'Registrieren',
|
||||
'button.getStarted': 'Jetzt starten',
|
||||
'button.learnMore': 'Mehr erfahren',
|
||||
'button.tryFree': 'Kostenlos testen',
|
||||
'button.signUp': 'Registrieren',
|
||||
'button.login': 'Anmelden',
|
||||
'button.back': 'Zurück',
|
||||
'button.submit': 'Absenden',
|
||||
'button.cancel': 'Abbrechen',
|
||||
'legal.privacy': 'Datenschutz',
|
||||
'legal.terms': 'AGB',
|
||||
'legal.cookies': 'Cookies',
|
||||
'legal.imprint': 'Impressum',
|
||||
'legal.backHome': 'Zurück zur Startseite',
|
||||
'legal.lastUpdated': 'Zuletzt aktualisiert',
|
||||
'footer.product': 'Produkt',
|
||||
'footer.company': 'Unternehmen',
|
||||
'footer.resources': 'Ressourcen',
|
||||
'footer.legal': 'Rechtliches',
|
||||
'footer.copyright': 'Alle Rechte vorbehalten.',
|
||||
'common.loading': 'Laden...',
|
||||
'common.error': 'Fehler',
|
||||
'common.success': 'Erfolg',
|
||||
'common.new': 'Neu',
|
||||
'common.comingSoon': 'Demnächst',
|
||||
},
|
||||
en: {
|
||||
'nav.home': 'Home',
|
||||
'nav.features': 'Features',
|
||||
'nav.pricing': 'Pricing',
|
||||
'nav.about': 'About',
|
||||
'nav.contact': 'Contact',
|
||||
'nav.login': 'Login',
|
||||
'nav.signup': 'Sign Up',
|
||||
'button.getStarted': 'Get Started',
|
||||
'button.learnMore': 'Learn More',
|
||||
'button.tryFree': 'Try for Free',
|
||||
'button.signUp': 'Sign Up',
|
||||
'button.login': 'Login',
|
||||
'button.back': 'Back',
|
||||
'button.submit': 'Submit',
|
||||
'button.cancel': 'Cancel',
|
||||
'legal.privacy': 'Privacy Policy',
|
||||
'legal.terms': 'Terms of Service',
|
||||
'legal.cookies': 'Cookie Policy',
|
||||
'legal.imprint': 'Imprint',
|
||||
'legal.backHome': 'Back to Home',
|
||||
'legal.lastUpdated': 'Last Updated',
|
||||
'footer.product': 'Product',
|
||||
'footer.company': 'Company',
|
||||
'footer.resources': 'Resources',
|
||||
'footer.legal': 'Legal',
|
||||
'footer.copyright': 'All rights reserved.',
|
||||
'common.loading': 'Loading...',
|
||||
'common.error': 'Error',
|
||||
'common.success': 'Success',
|
||||
'common.new': 'New',
|
||||
'common.comingSoon': 'Coming Soon',
|
||||
},
|
||||
fr: {
|
||||
'nav.home': 'Accueil',
|
||||
'nav.features': 'Fonctionnalités',
|
||||
'nav.pricing': 'Tarifs',
|
||||
'nav.about': 'À propos',
|
||||
'nav.contact': 'Contact',
|
||||
'nav.login': 'Connexion',
|
||||
'nav.signup': "S'inscrire",
|
||||
'button.getStarted': 'Commencer',
|
||||
'button.learnMore': 'En savoir plus',
|
||||
'button.tryFree': 'Essai gratuit',
|
||||
'button.signUp': "S'inscrire",
|
||||
'button.login': 'Connexion',
|
||||
'button.back': 'Retour',
|
||||
'button.submit': 'Envoyer',
|
||||
'button.cancel': 'Annuler',
|
||||
'legal.privacy': 'Confidentialité',
|
||||
'legal.terms': "Conditions d'utilisation",
|
||||
'legal.cookies': 'Politique de cookies',
|
||||
'legal.imprint': 'Mentions légales',
|
||||
'legal.backHome': "Retour à l'accueil",
|
||||
'legal.lastUpdated': 'Dernière mise à jour',
|
||||
'footer.product': 'Produit',
|
||||
'footer.company': 'Entreprise',
|
||||
'footer.resources': 'Ressources',
|
||||
'footer.legal': 'Mentions légales',
|
||||
'footer.copyright': 'Tous droits réservés.',
|
||||
'common.loading': 'Chargement...',
|
||||
'common.error': 'Erreur',
|
||||
'common.success': 'Succès',
|
||||
'common.new': 'Nouveau',
|
||||
'common.comingSoon': 'Bientôt disponible',
|
||||
},
|
||||
it: {
|
||||
'nav.home': 'Home',
|
||||
'nav.features': 'Funzionalità',
|
||||
'nav.pricing': 'Prezzi',
|
||||
'nav.about': 'Chi siamo',
|
||||
'nav.contact': 'Contatti',
|
||||
'nav.login': 'Accedi',
|
||||
'nav.signup': 'Registrati',
|
||||
'button.getStarted': 'Inizia',
|
||||
'button.learnMore': 'Scopri di più',
|
||||
'button.tryFree': 'Prova gratuita',
|
||||
'button.signUp': 'Registrati',
|
||||
'button.login': 'Accedi',
|
||||
'button.back': 'Indietro',
|
||||
'button.submit': 'Invia',
|
||||
'button.cancel': 'Annulla',
|
||||
'legal.privacy': 'Privacy',
|
||||
'legal.terms': 'Termini di servizio',
|
||||
'legal.cookies': 'Cookie Policy',
|
||||
'legal.imprint': 'Imprint',
|
||||
'legal.backHome': 'Torna alla home',
|
||||
'legal.lastUpdated': 'Ultimo aggiornamento',
|
||||
'footer.product': 'Prodotto',
|
||||
'footer.company': 'Azienda',
|
||||
'footer.resources': 'Risorse',
|
||||
'footer.legal': 'Legale',
|
||||
'footer.copyright': 'Tutti i diritti riservati.',
|
||||
'common.loading': 'Caricamento...',
|
||||
'common.error': 'Errore',
|
||||
'common.success': 'Successo',
|
||||
'common.new': 'Nuovo',
|
||||
'common.comingSoon': 'Prossimamente',
|
||||
},
|
||||
es: {
|
||||
'nav.home': 'Inicio',
|
||||
'nav.features': 'Características',
|
||||
'nav.pricing': 'Precios',
|
||||
'nav.about': 'Nosotros',
|
||||
'nav.contact': 'Contacto',
|
||||
'nav.login': 'Iniciar sesión',
|
||||
'nav.signup': 'Registrarse',
|
||||
'button.getStarted': 'Empezar',
|
||||
'button.learnMore': 'Saber más',
|
||||
'button.tryFree': 'Prueba gratis',
|
||||
'button.signUp': 'Registrarse',
|
||||
'button.login': 'Iniciar sesión',
|
||||
'button.back': 'Atrás',
|
||||
'button.submit': 'Enviar',
|
||||
'button.cancel': 'Cancelar',
|
||||
'legal.privacy': 'Privacidad',
|
||||
'legal.terms': 'Términos de servicio',
|
||||
'legal.cookies': 'Política de cookies',
|
||||
'legal.imprint': 'Aviso legal',
|
||||
'legal.backHome': 'Volver al inicio',
|
||||
'legal.lastUpdated': 'Última actualización',
|
||||
'footer.product': 'Producto',
|
||||
'footer.company': 'Empresa',
|
||||
'footer.resources': 'Recursos',
|
||||
'footer.legal': 'Legal',
|
||||
'footer.copyright': 'Todos los derechos reservados.',
|
||||
'common.loading': 'Cargando...',
|
||||
'common.error': 'Error',
|
||||
'common.success': 'Éxito',
|
||||
'common.new': 'Nuevo',
|
||||
'common.comingSoon': 'Próximamente',
|
||||
},
|
||||
};
|
||||
|
|
@ -8,8 +8,42 @@
|
|||
*
|
||||
* ```astro
|
||||
* ---
|
||||
* // Atoms
|
||||
* import Button from '@manacore/shared-landing-ui/atoms/Button.astro';
|
||||
* import Badge from '@manacore/shared-landing-ui/atoms/Badge.astro';
|
||||
* import Card from '@manacore/shared-landing-ui/atoms/Card.astro';
|
||||
* import Container from '@manacore/shared-landing-ui/atoms/Container.astro';
|
||||
* import SectionHeader from '@manacore/shared-landing-ui/atoms/SectionHeader.astro';
|
||||
* import GradientText from '@manacore/shared-landing-ui/atoms/GradientText.astro';
|
||||
* import LanguageSwitcher from '@manacore/shared-landing-ui/atoms/LanguageSwitcher.astro';
|
||||
*
|
||||
* // Sections
|
||||
* import HeroSection from '@manacore/shared-landing-ui/sections/HeroSection.astro';
|
||||
* import FeatureSection from '@manacore/shared-landing-ui/sections/FeatureSection.astro';
|
||||
* import PricingSection from '@manacore/shared-landing-ui/sections/PricingSection.astro';
|
||||
* import FAQSection from '@manacore/shared-landing-ui/sections/FAQSection.astro';
|
||||
* import CTASection from '@manacore/shared-landing-ui/sections/CTASection.astro';
|
||||
* import TestimonialSection from '@manacore/shared-landing-ui/sections/TestimonialSection.astro';
|
||||
* import StepsSection from '@manacore/shared-landing-ui/sections/StepsSection.astro';
|
||||
* import AppScrollerSection from '@manacore/shared-landing-ui/sections/AppScrollerSection.astro';
|
||||
* import TimelineSection from '@manacore/shared-landing-ui/sections/TimelineSection.astro';
|
||||
* import MasonryGridSection from '@manacore/shared-landing-ui/sections/MasonryGridSection.astro';
|
||||
* import PrinciplesSection from '@manacore/shared-landing-ui/sections/PrinciplesSection.astro';
|
||||
*
|
||||
* // Layouts
|
||||
* import Footer from '@manacore/shared-landing-ui/layouts/Footer.astro';
|
||||
* import Navigation from '@manacore/shared-landing-ui/layouts/Navigation.astro';
|
||||
*
|
||||
* // Templates
|
||||
* import LegalPageTemplate from '@manacore/shared-landing-ui/templates/LegalPageTemplate.astro';
|
||||
*
|
||||
* // i18n
|
||||
* import { getLangFromUrl, useTranslations, localizePath } from '@manacore/shared-landing-ui/i18n';
|
||||
*
|
||||
* // Themes (import as CSS)
|
||||
* import '@manacore/shared-landing-ui/themes';
|
||||
* import '@manacore/shared-landing-ui/themes/manacore';
|
||||
* import '@manacore/shared-landing-ui/themes/picture';
|
||||
* ---
|
||||
* ```
|
||||
*
|
||||
|
|
@ -18,3 +52,4 @@
|
|||
*/
|
||||
|
||||
export * from './utils/index';
|
||||
export * from './i18n/index';
|
||||
|
|
|
|||
510
packages/shared-landing-ui/src/layouts/Navigation.astro
Normal file
510
packages/shared-landing-ui/src/layouts/Navigation.astro
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
---
|
||||
/**
|
||||
* Navigation - Shared header navigation component
|
||||
*
|
||||
* Usage:
|
||||
* ```astro
|
||||
* <Navigation
|
||||
* brand={{ name: 'MyApp', logo: '/logo.svg', href: '/' }}
|
||||
* links={[
|
||||
* { label: 'Features', href: '#features' },
|
||||
* { label: 'Pricing', href: '/pricing' },
|
||||
* { label: 'Docs', href: 'https://docs.example.com', external: true }
|
||||
* ]}
|
||||
* ctaButton={{ text: 'Get Started', href: '/signup' }}
|
||||
* showLanguageSwitcher={true}
|
||||
* currentLang="en"
|
||||
* languages={{ de: 'Deutsch', en: 'English', fr: 'Français' }}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
export interface NavLink {
|
||||
label: string;
|
||||
href: string;
|
||||
external?: boolean;
|
||||
}
|
||||
|
||||
export interface Brand {
|
||||
name: string;
|
||||
logo?: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export interface CtaButton {
|
||||
text: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
brand: Brand;
|
||||
links?: NavLink[];
|
||||
ctaButton?: CtaButton;
|
||||
showLanguageSwitcher?: boolean;
|
||||
currentLang?: string;
|
||||
languages?: Record<string, string>;
|
||||
getLocalizedPath?: (lang: string) => string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
brand,
|
||||
links = [],
|
||||
ctaButton,
|
||||
showLanguageSwitcher = false,
|
||||
currentLang = 'en',
|
||||
languages = {},
|
||||
getLocalizedPath,
|
||||
class: className = '',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<header class:list={['nav-header', className]}>
|
||||
<div class="nav-container">
|
||||
<!-- Brand -->
|
||||
<a href={brand.href || '/'} class="nav-brand">
|
||||
{
|
||||
brand.logo ? (
|
||||
<img src={brand.logo} alt={brand.name} class="nav-logo" />
|
||||
) : (
|
||||
<span class="nav-brand-text">{brand.name}</span>
|
||||
)
|
||||
}
|
||||
</a>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<nav class="nav-links">
|
||||
{
|
||||
links.map((link) => (
|
||||
<a
|
||||
href={link.href}
|
||||
class="nav-link"
|
||||
target={link.external ? '_blank' : undefined}
|
||||
rel={link.external ? 'noopener noreferrer' : undefined}
|
||||
>
|
||||
{link.label}
|
||||
{link.external && (
|
||||
<svg class="nav-external-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
|
||||
<!-- Right side: Language switcher & CTA -->
|
||||
<div class="nav-right">
|
||||
{
|
||||
showLanguageSwitcher && Object.keys(languages).length > 0 && (
|
||||
<div class="nav-language">
|
||||
<button class="language-trigger" aria-haspopup="true" aria-expanded="false">
|
||||
<span>{languages[currentLang] || currentLang.toUpperCase()}</span>
|
||||
<svg class="language-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="language-dropdown" role="menu">
|
||||
{Object.entries(languages).map(([code, label]) => (
|
||||
<a
|
||||
href={getLocalizedPath ? getLocalizedPath(code) : `/${code}`}
|
||||
class:list={[
|
||||
'language-option',
|
||||
{ 'language-option-active': code === currentLang },
|
||||
]}
|
||||
role="menuitem"
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
ctaButton && (
|
||||
<a href={ctaButton.href} class="nav-cta">
|
||||
{ctaButton.text}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
<!-- Mobile Menu Toggle -->
|
||||
<button class="nav-mobile-toggle" aria-label="Toggle menu" aria-expanded="false">
|
||||
<svg class="nav-hamburger" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
<svg class="nav-close" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
<div class="nav-mobile-menu">
|
||||
<nav class="nav-mobile-links">
|
||||
{
|
||||
links.map((link) => (
|
||||
<a
|
||||
href={link.href}
|
||||
class="nav-mobile-link"
|
||||
target={link.external ? '_blank' : undefined}
|
||||
rel={link.external ? 'noopener noreferrer' : undefined}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
{
|
||||
ctaButton && (
|
||||
<a href={ctaButton.href} class="nav-mobile-cta">
|
||||
{ctaButton.text}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.nav-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: var(--color-background-page);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
backdrop-filter: blur(10px);
|
||||
background: color-mix(in srgb, var(--color-background-page) 80%, transparent);
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
height: 72px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
height: 36px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.nav-brand-text {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.nav-links {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 16px;
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-background-card);
|
||||
}
|
||||
|
||||
.nav-external-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-language {
|
||||
position: relative;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.nav-language {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.language-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.language-trigger:hover {
|
||||
border-color: var(--color-border-hover);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.language-chevron {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-language.open .language-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 0;
|
||||
min-width: 140px;
|
||||
background: var(--color-background-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-8px);
|
||||
transition: all 0.2s ease;
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-language.open .language-dropdown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.language-option {
|
||||
display: block;
|
||||
padding: 10px 16px;
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.language-option:hover {
|
||||
background: var(--color-background-card-hover);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.language-option-active {
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-glow);
|
||||
}
|
||||
|
||||
.nav-cta {
|
||||
display: none;
|
||||
padding: 10px 20px;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.nav-cta {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-cta:hover {
|
||||
background: var(--color-primary-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px var(--color-primary-glow);
|
||||
}
|
||||
|
||||
.nav-mobile-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.nav-mobile-toggle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-hamburger,
|
||||
.nav-close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.nav-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-header.mobile-open .nav-hamburger {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-header.mobile-open .nav-close {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-mobile-menu {
|
||||
display: none;
|
||||
padding: 16px 24px 24px;
|
||||
background: var(--color-background-page);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.nav-header.mobile-open .nav-mobile-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.nav-mobile-menu {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-mobile-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.nav-mobile-link {
|
||||
display: block;
|
||||
padding: 12px 16px;
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-mobile-link:hover {
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-background-card);
|
||||
}
|
||||
|
||||
.nav-mobile-cta {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
padding: 14px 24px;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-mobile-cta:hover {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Mobile menu toggle
|
||||
document.querySelectorAll('.nav-mobile-toggle').forEach((toggle) => {
|
||||
toggle.addEventListener('click', () => {
|
||||
const header = toggle.closest('.nav-header');
|
||||
header?.classList.toggle('mobile-open');
|
||||
});
|
||||
});
|
||||
|
||||
// Language dropdown toggle
|
||||
document.querySelectorAll('.nav-language .language-trigger').forEach((trigger) => {
|
||||
trigger.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const dropdown = trigger.closest('.nav-language');
|
||||
dropdown?.classList.toggle('open');
|
||||
});
|
||||
});
|
||||
|
||||
// Close dropdowns when clicking outside
|
||||
document.addEventListener('click', () => {
|
||||
document.querySelectorAll('.nav-language.open').forEach((dropdown) => {
|
||||
dropdown.classList.remove('open');
|
||||
});
|
||||
});
|
||||
|
||||
// Close mobile menu on escape
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
document.querySelectorAll('.nav-header.mobile-open').forEach((header) => {
|
||||
header.classList.remove('mobile-open');
|
||||
});
|
||||
document.querySelectorAll('.nav-language.open').forEach((dropdown) => {
|
||||
dropdown.classList.remove('open');
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
475
packages/shared-landing-ui/src/sections/AppScrollerSection.astro
Normal file
475
packages/shared-landing-ui/src/sections/AppScrollerSection.astro
Normal file
|
|
@ -0,0 +1,475 @@
|
|||
---
|
||||
/**
|
||||
* AppScrollerSection - Horizontal scrolling app showcase
|
||||
*
|
||||
* Usage:
|
||||
* ```astro
|
||||
* <AppScrollerSection
|
||||
* title="Discover our"
|
||||
* titleHighlight="AI Apps"
|
||||
* subtitle="Access to a growing ecosystem"
|
||||
* apps={[{ name: 'App', description: '...', logo: '/logo.png', category: 'Productivity', color: 'from-blue-500 to-cyan-500', href: '/app' }]}
|
||||
* scrollHint="Scroll for more"
|
||||
* learnMoreText="Learn more"
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
export interface App {
|
||||
name: string;
|
||||
description: string;
|
||||
logo: string; // URL starting with '/' or emoji string
|
||||
category: string;
|
||||
color: string; // Tailwind gradient classes e.g. 'from-blue-500 to-cyan-500'
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
titleHighlight?: string;
|
||||
subtitle?: string;
|
||||
apps: App[];
|
||||
scrollHint?: string;
|
||||
learnMoreText?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
titleHighlight,
|
||||
subtitle,
|
||||
apps,
|
||||
scrollHint,
|
||||
learnMoreText = 'Learn more',
|
||||
class: className = '',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<section class:list={['apps-section', className]}>
|
||||
<div class="apps-container">
|
||||
<div class="apps-header">
|
||||
<h2 class="apps-title">
|
||||
{title}
|
||||
{titleHighlight && <span class="gradient-text">{titleHighlight}</span>}
|
||||
</h2>
|
||||
{subtitle && <p class="apps-subtitle">{subtitle}</p>}
|
||||
</div>
|
||||
|
||||
<div class="apps-scroller">
|
||||
<div class="apps-track">
|
||||
{
|
||||
apps.map((app) => (
|
||||
<a href={app.href} class="app-card">
|
||||
<div class="app-card-inner">
|
||||
<div class="app-icon-wrapper">
|
||||
<div class={`app-icon-bg bg-gradient-to-br ${app.color}`} />
|
||||
{app.logo.startsWith('/') ? (
|
||||
<img src={app.logo} alt={app.name} class="app-logo" loading="lazy" />
|
||||
) : (
|
||||
<span class="app-emoji">{app.logo}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="app-content">
|
||||
<div class="app-category">{app.category}</div>
|
||||
<h3 class="app-name">{app.name}</h3>
|
||||
<p class="app-description">{app.description}</p>
|
||||
</div>
|
||||
|
||||
<div class="app-footer">
|
||||
<span class="app-button">
|
||||
{learnMoreText}
|
||||
<svg class="app-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
scrollHint && (
|
||||
<div class="scroll-hint">
|
||||
<span>{scrollHint}</span>
|
||||
<svg class="scroll-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 8l4 4m0 0l-4 4m4-4H3"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.apps-section {
|
||||
position: relative;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--color-background-page) 0%,
|
||||
var(--color-background-card) 100%
|
||||
);
|
||||
padding: 120px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.apps-container {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.apps-header {
|
||||
text-align: center;
|
||||
margin-bottom: 80px;
|
||||
padding: 0 80px;
|
||||
}
|
||||
|
||||
.apps-title {
|
||||
font-size: clamp(2rem, 4vw, 2.75rem);
|
||||
font-weight: 800;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 20px 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-primary) 0%,
|
||||
var(--color-primary-hover) 50%,
|
||||
var(--color-primary) 100%
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.apps-subtitle {
|
||||
font-size: clamp(1rem, 2vw, 1.25rem);
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
max-width: 700px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.apps-scroller {
|
||||
position: relative;
|
||||
padding: 20px 80px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-primary-glow) var(--color-background-card);
|
||||
}
|
||||
|
||||
.apps-scroller::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.apps-scroller::-webkit-scrollbar-track {
|
||||
background: var(--color-background-card);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.apps-scroller::-webkit-scrollbar-thumb {
|
||||
background: var(--color-primary-glow);
|
||||
border-radius: 4px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.apps-scroller::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.apps-track {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.apps-track::after {
|
||||
content: '';
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.app-card {
|
||||
flex: 0 0 auto;
|
||||
width: 320px;
|
||||
height: 380px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.app-card-inner {
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-background-card) 0%,
|
||||
var(--color-background-page) 100%
|
||||
);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
backdrop-filter: blur(10px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-card-inner::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, transparent 0%, var(--color-primary-glow) 100%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.app-card:hover .app-card-inner {
|
||||
transform: translateY(-8px);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow:
|
||||
0 20px 40px -15px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px var(--color-primary-glow);
|
||||
}
|
||||
|
||||
.app-card:hover .app-card-inner::before {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.app-icon-wrapper {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.app-icon-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 18px;
|
||||
opacity: 0.15;
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.app-card:hover .app-icon-bg {
|
||||
opacity: 0.25;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.app-logo {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 12px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.app-emoji {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 3rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.app-category {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.app-description {
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
padding-top: 10px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.app-button {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-muted);
|
||||
background: transparent;
|
||||
border: none;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.app-card:hover .app-button {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.app-arrow {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.app-card:hover .app-arrow {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.scroll-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-top: 60px;
|
||||
padding: 0 80px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.scroll-arrow {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.apps-section {
|
||||
padding: 100px 0;
|
||||
}
|
||||
.app-card {
|
||||
width: 300px;
|
||||
height: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.apps-section {
|
||||
padding: 80px 0;
|
||||
}
|
||||
.apps-header {
|
||||
margin-bottom: 60px;
|
||||
padding: 0 40px;
|
||||
}
|
||||
.app-card {
|
||||
width: 280px;
|
||||
height: 350px;
|
||||
}
|
||||
.apps-scroller {
|
||||
padding: 20px 40px;
|
||||
}
|
||||
.scroll-hint {
|
||||
padding: 0 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.apps-section {
|
||||
padding: 60px 0;
|
||||
}
|
||||
.apps-header {
|
||||
padding: 0 30px;
|
||||
}
|
||||
.app-card {
|
||||
width: 260px;
|
||||
height: 340px;
|
||||
}
|
||||
.app-card-inner {
|
||||
padding: 24px;
|
||||
}
|
||||
.apps-scroller {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
.scroll-hint {
|
||||
margin-top: 40px;
|
||||
padding: 0 30px;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
.app-card {
|
||||
width: 240px;
|
||||
height: 330px;
|
||||
}
|
||||
.app-icon-wrapper {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
||||
.app-emoji {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
.app-name {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.app-description {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
235
packages/shared-landing-ui/src/sections/MasonryGridSection.astro
Normal file
235
packages/shared-landing-ui/src/sections/MasonryGridSection.astro
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
---
|
||||
/**
|
||||
* MasonryGridSection - Grid layout with variable sized cards
|
||||
*
|
||||
* Usage:
|
||||
* ```astro
|
||||
* <MasonryGridSection
|
||||
* title="Why we do this"
|
||||
* subtitle="Our guiding principles"
|
||||
* items={[
|
||||
* { number: '01', title: 'Technology for all', text: '...', size: 'large' },
|
||||
* { number: '02', title: 'Transparent', text: '...', size: 'medium' }
|
||||
* ]}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
export interface MasonryItem {
|
||||
number?: string;
|
||||
title: string;
|
||||
text: string;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
items: MasonryItem[];
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { title, subtitle, items, class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<section class:list={['masonry-section', className]}>
|
||||
<div class="masonry-overlay"></div>
|
||||
|
||||
<div class="masonry-container">
|
||||
<div class="masonry-header">
|
||||
<h2 class="masonry-main-title">{title}</h2>
|
||||
{subtitle && <p class="masonry-main-subtitle">{subtitle}</p>}
|
||||
</div>
|
||||
|
||||
<div class="masonry-grid">
|
||||
{
|
||||
items.map((item) => (
|
||||
<div class={`masonry-item masonry-${item.size || 'medium'}`}>
|
||||
<div class="masonry-content">
|
||||
{item.number && <span class="masonry-number">{item.number}</span>}
|
||||
<h4 class="masonry-title">{item.title}</h4>
|
||||
<p class="masonry-text">{item.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.masonry-section {
|
||||
position: relative;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--color-background-page) 0%,
|
||||
var(--color-background-card) 100%
|
||||
);
|
||||
padding: 160px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.masonry-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 20% 30%, var(--color-primary-glow) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, var(--color-primary-glow) 0%, transparent 50%);
|
||||
opacity: 0.2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.masonry-container {
|
||||
position: relative;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 80px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.masonry-header {
|
||||
text-align: center;
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
.masonry-main-title {
|
||||
font-size: clamp(2.5rem, 5vw, 3.5rem);
|
||||
font-weight: 800;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 20px 0;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.masonry-main-subtitle {
|
||||
font-size: clamp(1rem, 2vw, 1.25rem);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.masonry-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
grid-auto-flow: dense;
|
||||
}
|
||||
|
||||
.masonry-item {
|
||||
position: relative;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.masonry-item:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.masonry-large {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.masonry-medium {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.masonry-small {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.masonry-content {
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-background-card) 0%,
|
||||
var(--color-background-page) 100%
|
||||
);
|
||||
border: 1px solid var(--color-primary-glow);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.masonry-item:hover .masonry-content {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 8px 32px var(--color-primary-glow);
|
||||
}
|
||||
|
||||
.masonry-number {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-hover) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
line-height: 1;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.masonry-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.masonry-text {
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.masonry-section {
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.masonry-container {
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.masonry-header {
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
.masonry-large,
|
||||
.masonry-medium,
|
||||
.masonry-small {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.masonry-content {
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.masonry-section {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.masonry-container {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.masonry-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.masonry-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.masonry-number {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.masonry-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
280
packages/shared-landing-ui/src/sections/PrinciplesSection.astro
Normal file
280
packages/shared-landing-ui/src/sections/PrinciplesSection.astro
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
---
|
||||
/**
|
||||
* PrinciplesSection - Horizontal cards with icons
|
||||
*
|
||||
* Usage:
|
||||
* ```astro
|
||||
* <PrinciplesSection
|
||||
* principles={[
|
||||
* { icon: 'lightning', title: 'Scalable with AI', description: '...' },
|
||||
* { icon: 'chat', title: 'Tight Feedback Loop', description: '...' }
|
||||
* ]}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
export interface Principle {
|
||||
icon:
|
||||
| 'lightning'
|
||||
| 'chat'
|
||||
| 'globe'
|
||||
| 'heart'
|
||||
| 'shield'
|
||||
| 'star'
|
||||
| 'code'
|
||||
| 'users'
|
||||
| 'rocket'
|
||||
| 'check';
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
principles: Principle[];
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { principles, class: className = '' } = Astro.props;
|
||||
|
||||
// SVG paths for icons
|
||||
const iconPaths: Record<string, string> = {
|
||||
lightning: 'M13 10V3L4 14h7v7l9-11h-7z',
|
||||
chat: 'M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z',
|
||||
globe:
|
||||
'M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
heart:
|
||||
'M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z',
|
||||
shield:
|
||||
'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z',
|
||||
star: 'M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z',
|
||||
code: 'M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4',
|
||||
users:
|
||||
'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z',
|
||||
rocket:
|
||||
'M15.59 14.37a6 6 0 01-5.84 7.38v-4.8m5.84-2.58a14.98 14.98 0 006.16-12.12A14.98 14.98 0 009.631 8.41m5.96 5.96a14.926 14.926 0 01-5.841 2.58m-.119-8.54a6 6 0 00-7.381 5.84h4.8m2.581-5.84a14.927 14.927 0 00-2.58 5.84m2.699 2.7c-.103.021-.207.041-.311.06a15.09 15.09 0 01-2.448-2.448 14.9 14.9 0 01.06-.312m-2.24 2.39a4.493 4.493 0 00-1.757 4.306 4.493 4.493 0 004.306-1.758M16.5 9a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0z',
|
||||
check: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
};
|
||||
---
|
||||
|
||||
<section class:list={['principles-section', className]}>
|
||||
<div class="principles-overlay"></div>
|
||||
|
||||
<div class="principles-container">
|
||||
<div class="principles-grid">
|
||||
{
|
||||
principles.map((principle) => (
|
||||
<div class="principle-card">
|
||||
<div class="principle-card-inner">
|
||||
<div class="principle-icon-wrapper">
|
||||
<svg class="principle-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={iconPaths[principle.icon] || iconPaths.star}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="principle-content">
|
||||
<h3 class="principle-title">{principle.title}</h3>
|
||||
<p class="principle-description">{principle.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.principles-section {
|
||||
position: relative;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--color-background-card) 0%,
|
||||
var(--color-background-page) 100%
|
||||
);
|
||||
padding: 160px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.principles-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 30% 50%, var(--color-primary-glow) 0%, transparent 50%),
|
||||
radial-gradient(circle at 70% 50%, var(--color-primary-glow) 0%, transparent 50%);
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.principles-container {
|
||||
position: relative;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 80px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.principles-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 30px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.principle-card {
|
||||
position: relative;
|
||||
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.principle-card:hover {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
|
||||
.principle-card-inner {
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-background-card) 0%,
|
||||
var(--color-background-page) 100%
|
||||
);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 24px;
|
||||
padding: 50px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
backdrop-filter: blur(10px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.principle-card-inner::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, transparent 0%, var(--color-primary-glow) 100%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.principle-card:hover .principle-card-inner {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow:
|
||||
0 20px 40px -15px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px var(--color-primary-glow);
|
||||
}
|
||||
|
||||
.principle-card:hover .principle-card-inner::before {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.principle-icon-wrapper {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-primary-glow) 0%,
|
||||
var(--color-primary-glow) 100%
|
||||
);
|
||||
border-radius: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.principle-card:hover .principle-icon-wrapper {
|
||||
background: linear-gradient(135deg, var(--color-primary-glow) 0%, var(--color-primary) 100%);
|
||||
transform: scale(1.1) rotate(-5deg);
|
||||
}
|
||||
|
||||
.principle-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: var(--color-primary);
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.principle-card:hover .principle-icon {
|
||||
color: var(--color-text-primary);
|
||||
filter: drop-shadow(0 0 8px var(--color-primary));
|
||||
}
|
||||
|
||||
.principle-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.principle-title {
|
||||
font-size: clamp(1.75rem, 3vw, 2.25rem);
|
||||
font-weight: 800;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.principle-description {
|
||||
font-size: clamp(1rem, 2vw, 1.125rem);
|
||||
line-height: 1.7;
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.principle-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.principles-section {
|
||||
padding: 120px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.principles-section {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.principles-container {
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.principle-card-inner {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.principle-icon-wrapper {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.principle-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.principle-content {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
286
packages/shared-landing-ui/src/sections/TimelineSection.astro
Normal file
286
packages/shared-landing-ui/src/sections/TimelineSection.astro
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
---
|
||||
/**
|
||||
* TimelineSection - Alternating timeline layout
|
||||
*
|
||||
* Usage:
|
||||
* ```astro
|
||||
* <TimelineSection
|
||||
* title="Our Journey"
|
||||
* subtitle="From vision to reality"
|
||||
* items={[
|
||||
* { badge: 'Mission', title: 'Democratizing AI', text: 'Breaking barriers...' },
|
||||
* { badge: 'Vision', title: 'A connected future', text: 'A world where...' }
|
||||
* ]}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
export interface TimelineItem {
|
||||
badge: string;
|
||||
title: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
items: TimelineItem[];
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { title, subtitle, items, class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<section class:list={['timeline-section', className]}>
|
||||
<div class="timeline-overlay"></div>
|
||||
|
||||
<div class="timeline-container">
|
||||
<div class="timeline-header">
|
||||
<h2 class="timeline-main-title">{title}</h2>
|
||||
{subtitle && <p class="timeline-main-subtitle">{subtitle}</p>}
|
||||
</div>
|
||||
|
||||
<div class="timeline-grid">
|
||||
{
|
||||
items.map((item, index) => (
|
||||
<div class={`timeline-item ${index % 2 === 0 ? 'timeline-left' : 'timeline-right'}`}>
|
||||
<div class="timeline-dot" />
|
||||
<div class="timeline-content">
|
||||
<span class="timeline-badge">{item.badge}</span>
|
||||
<h4 class="timeline-title">{item.title}</h4>
|
||||
<p class="timeline-text">{item.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.timeline-section {
|
||||
position: relative;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--color-background-card) 0%,
|
||||
var(--color-background-page) 100%
|
||||
);
|
||||
padding: 160px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeline-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 30% 50%, var(--color-primary-glow) 0%, transparent 50%),
|
||||
radial-gradient(circle at 70% 50%, var(--color-primary-glow) 0%, transparent 50%);
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.timeline-container {
|
||||
position: relative;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 80px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.timeline-header {
|
||||
text-align: center;
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
.timeline-main-title {
|
||||
font-size: clamp(2.5rem, 5vw, 3.5rem);
|
||||
font-weight: 800;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 20px 0;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.timeline-main-subtitle {
|
||||
font-size: clamp(1rem, 2vw, 1.25rem);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.timeline-grid {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 60px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.timeline-grid::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
transparent 0%,
|
||||
var(--color-primary) 10%,
|
||||
var(--color-primary) 90%,
|
||||
transparent 100%
|
||||
);
|
||||
opacity: 0.5;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.timeline-left .timeline-content {
|
||||
grid-column: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.timeline-right .timeline-content {
|
||||
grid-column: 3;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
grid-column: 2;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-hover) 100%);
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
0 0 0 4px var(--color-primary-glow),
|
||||
0 0 20px var(--color-primary-glow);
|
||||
z-index: 2;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.timeline-item:hover .timeline-dot {
|
||||
box-shadow:
|
||||
0 0 0 6px var(--color-primary-glow),
|
||||
0 0 30px var(--color-primary);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-background-card) 0%,
|
||||
var(--color-background-page) 100%
|
||||
);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeline-content::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, transparent 0%, var(--color-primary-glow) 100%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.timeline-item:hover .timeline-content {
|
||||
border-color: var(--color-primary);
|
||||
transform: scale(1.02);
|
||||
box-shadow:
|
||||
0 10px 30px -10px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px var(--color-primary-glow);
|
||||
}
|
||||
|
||||
.timeline-item:hover .timeline-content::before {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.timeline-badge {
|
||||
display: inline-block;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 10px 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.timeline-text {
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Mobile: Switch to left-aligned layout */
|
||||
@media (max-width: 768px) {
|
||||
.timeline-section {
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.timeline-container {
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.timeline-header {
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
.timeline-grid::before {
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.timeline-left .timeline-content,
|
||||
.timeline-right .timeline-content {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.timeline-section {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.timeline-container {
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
225
packages/shared-landing-ui/src/templates/LegalPageTemplate.astro
Normal file
225
packages/shared-landing-ui/src/templates/LegalPageTemplate.astro
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
---
|
||||
/**
|
||||
* LegalPageTemplate - Base template for legal pages (Privacy, Terms, Cookies, Imprint)
|
||||
*
|
||||
* Usage:
|
||||
* ```astro
|
||||
* <LegalPageTemplate
|
||||
* title="Privacy Policy"
|
||||
* backLink="/"
|
||||
* backText="Back to Home"
|
||||
* lastUpdatedText="Last Updated"
|
||||
* lastUpdated={new Date()}
|
||||
* >
|
||||
* <div set:html={content} />
|
||||
* </LegalPageTemplate>
|
||||
* ```
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
backLink?: string;
|
||||
backText?: string;
|
||||
lastUpdatedText?: string;
|
||||
lastUpdated?: Date;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
backLink = '/',
|
||||
backText = 'Back to Home',
|
||||
lastUpdatedText = 'Last Updated',
|
||||
lastUpdated = new Date(),
|
||||
class: className = '',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div class:list={['legal-page', className]}>
|
||||
<div class="legal-container">
|
||||
<!-- Back Button -->
|
||||
<a href={backLink} class="legal-back-link">
|
||||
<svg class="legal-back-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"
|
||||
></path>
|
||||
</svg>
|
||||
<span>{backText}</span>
|
||||
</a>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="legal-header">
|
||||
<h1 class="legal-title">{title}</h1>
|
||||
<p class="legal-date">{lastUpdatedText}: {lastUpdated.toLocaleDateString()}</p>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="legal-prose">
|
||||
<div class="legal-content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.legal-page {
|
||||
min-height: 100vh;
|
||||
background: var(--color-background-page);
|
||||
padding: 96px 0;
|
||||
}
|
||||
|
||||
.legal-container {
|
||||
max-width: 896px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.legal-back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 32px;
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.legal-back-link:hover {
|
||||
color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.legal-back-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.legal-header {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.legal-title {
|
||||
font-size: clamp(2rem, 5vw, 3rem);
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.legal-date {
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.legal-content {
|
||||
background: var(--color-background-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.legal-content {
|
||||
padding: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Prose styling for content */
|
||||
:global(.legal-prose) {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
:global(.legal-prose h2) {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:global(.legal-prose h2:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:global(.legal-prose h3) {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
margin-top: 24px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
:global(.legal-prose p) {
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.7;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
:global(.legal-prose ul),
|
||||
:global(.legal-prose ol) {
|
||||
margin-bottom: 16px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
:global(.legal-prose ul) {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
:global(.legal-prose ol) {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
:global(.legal-prose li) {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
:global(.legal-prose a) {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
:global(.legal-prose a:hover) {
|
||||
color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
:global(.legal-prose strong) {
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
:global(.legal-prose blockquote) {
|
||||
border-left: 4px solid var(--color-primary);
|
||||
padding-left: 16px;
|
||||
margin: 24px 0;
|
||||
font-style: italic;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
:global(.legal-prose code) {
|
||||
background: var(--color-background-page);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
:global(.legal-prose table) {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
:global(.legal-prose th),
|
||||
:global(.legal-prose td) {
|
||||
padding: 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:global(.legal-prose th) {
|
||||
background: var(--color-background-page);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
</style>
|
||||
80
packages/shared-landing-ui/src/themes/chat.css
Normal file
80
packages/shared-landing-ui/src/themes/chat.css
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Chat Theme - Blue
|
||||
* AI chat application
|
||||
*/
|
||||
:root {
|
||||
/* Primary colors - Vibrant Blue */
|
||||
--color-primary: #0ea5e9;
|
||||
--color-primary-hover: #0284c7;
|
||||
--color-primary-glow: rgba(14, 165, 233, 0.3);
|
||||
|
||||
/* Text colors */
|
||||
--color-text-primary: #f8fafc;
|
||||
--color-text-secondary: #cbd5e1;
|
||||
--color-text-muted: #64748b;
|
||||
|
||||
/* Background colors - Dark slate */
|
||||
--color-background-page: #0f172a;
|
||||
--color-background-card: #1e293b;
|
||||
--color-background-card-hover: #334155;
|
||||
|
||||
/* Border colors */
|
||||
--color-border: rgba(14, 165, 233, 0.2);
|
||||
--color-border-hover: rgba(14, 165, 233, 0.4);
|
||||
}
|
||||
|
||||
/* Light mode support */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-primary: #0284c7;
|
||||
--color-primary-hover: #0369a1;
|
||||
--color-primary-glow: rgba(2, 132, 199, 0.2);
|
||||
|
||||
--color-text-primary: #0c4a6e;
|
||||
--color-text-secondary: #0369a1;
|
||||
--color-text-muted: #0ea5e9;
|
||||
|
||||
--color-background-page: #f0f9ff;
|
||||
--color-background-card: #ffffff;
|
||||
--color-background-card-hover: #e0f2fe;
|
||||
|
||||
--color-border: #bae6fd;
|
||||
--color-border-hover: #7dd3fc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Force dark mode class */
|
||||
.dark {
|
||||
--color-primary: #0ea5e9;
|
||||
--color-primary-hover: #38bdf8;
|
||||
--color-primary-glow: rgba(14, 165, 233, 0.3);
|
||||
|
||||
--color-text-primary: #f8fafc;
|
||||
--color-text-secondary: #cbd5e1;
|
||||
--color-text-muted: #64748b;
|
||||
|
||||
--color-background-page: #0f172a;
|
||||
--color-background-card: #1e293b;
|
||||
--color-background-card-hover: #334155;
|
||||
|
||||
--color-border: rgba(14, 165, 233, 0.2);
|
||||
--color-border-hover: rgba(14, 165, 233, 0.4);
|
||||
}
|
||||
|
||||
/* Force light mode class */
|
||||
.light {
|
||||
--color-primary: #0284c7;
|
||||
--color-primary-hover: #0369a1;
|
||||
--color-primary-glow: rgba(2, 132, 199, 0.2);
|
||||
|
||||
--color-text-primary: #0c4a6e;
|
||||
--color-text-secondary: #0369a1;
|
||||
--color-text-muted: #0ea5e9;
|
||||
|
||||
--color-background-page: #f0f9ff;
|
||||
--color-background-card: #ffffff;
|
||||
--color-background-card-hover: #e0f2fe;
|
||||
|
||||
--color-border: #bae6fd;
|
||||
--color-border-hover: #7dd3fc;
|
||||
}
|
||||
80
packages/shared-landing-ui/src/themes/picture.css
Normal file
80
packages/shared-landing-ui/src/themes/picture.css
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Picture Theme - Dark Indigo
|
||||
* AI image generation app
|
||||
*/
|
||||
:root {
|
||||
/* Primary colors - Indigo/Purple */
|
||||
--color-primary: #6366f1;
|
||||
--color-primary-hover: #4f46e5;
|
||||
--color-primary-glow: rgba(99, 102, 241, 0.3);
|
||||
|
||||
/* Text colors */
|
||||
--color-text-primary: #f8fafc;
|
||||
--color-text-secondary: #cbd5e1;
|
||||
--color-text-muted: #64748b;
|
||||
|
||||
/* Background colors - Deep dark */
|
||||
--color-background-page: #0c0a1d;
|
||||
--color-background-card: #1a1833;
|
||||
--color-background-card-hover: #252345;
|
||||
|
||||
/* Border colors */
|
||||
--color-border: rgba(99, 102, 241, 0.2);
|
||||
--color-border-hover: rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
/* Light mode support */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-primary: #4f46e5;
|
||||
--color-primary-hover: #4338ca;
|
||||
--color-primary-glow: rgba(79, 70, 229, 0.2);
|
||||
|
||||
--color-text-primary: #1e1b4b;
|
||||
--color-text-secondary: #4338ca;
|
||||
--color-text-muted: #6366f1;
|
||||
|
||||
--color-background-page: #fafafa;
|
||||
--color-background-card: #ffffff;
|
||||
--color-background-card-hover: #f5f3ff;
|
||||
|
||||
--color-border: #e0e7ff;
|
||||
--color-border-hover: #c7d2fe;
|
||||
}
|
||||
}
|
||||
|
||||
/* Force dark mode class */
|
||||
.dark {
|
||||
--color-primary: #6366f1;
|
||||
--color-primary-hover: #818cf8;
|
||||
--color-primary-glow: rgba(99, 102, 241, 0.3);
|
||||
|
||||
--color-text-primary: #f8fafc;
|
||||
--color-text-secondary: #cbd5e1;
|
||||
--color-text-muted: #64748b;
|
||||
|
||||
--color-background-page: #0c0a1d;
|
||||
--color-background-card: #1a1833;
|
||||
--color-background-card-hover: #252345;
|
||||
|
||||
--color-border: rgba(99, 102, 241, 0.2);
|
||||
--color-border-hover: rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
/* Force light mode class */
|
||||
.light {
|
||||
--color-primary: #4f46e5;
|
||||
--color-primary-hover: #4338ca;
|
||||
--color-primary-glow: rgba(79, 70, 229, 0.2);
|
||||
|
||||
--color-text-primary: #1e1b4b;
|
||||
--color-text-secondary: #4338ca;
|
||||
--color-text-muted: #6366f1;
|
||||
|
||||
--color-background-page: #fafafa;
|
||||
--color-background-card: #ffffff;
|
||||
--color-background-card-hover: #f5f3ff;
|
||||
|
||||
--color-border: #e0e7ff;
|
||||
--color-border-hover: #c7d2fe;
|
||||
}
|
||||
80
packages/shared-landing-ui/src/themes/zitare.css
Normal file
80
packages/shared-landing-ui/src/themes/zitare.css
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Zitare Theme - Green/Teal
|
||||
* Daily inspiration quotes app
|
||||
*/
|
||||
:root {
|
||||
/* Primary colors - Teal/Emerald */
|
||||
--color-primary: #14b8a6;
|
||||
--color-primary-hover: #0d9488;
|
||||
--color-primary-glow: rgba(20, 184, 166, 0.3);
|
||||
|
||||
/* Text colors */
|
||||
--color-text-primary: #f8fafc;
|
||||
--color-text-secondary: #cbd5e1;
|
||||
--color-text-muted: #64748b;
|
||||
|
||||
/* Background colors - Dark with teal tint */
|
||||
--color-background-page: #0a1a18;
|
||||
--color-background-card: #132d2a;
|
||||
--color-background-card-hover: #1a3f3b;
|
||||
|
||||
/* Border colors */
|
||||
--color-border: rgba(20, 184, 166, 0.2);
|
||||
--color-border-hover: rgba(20, 184, 166, 0.4);
|
||||
}
|
||||
|
||||
/* Light mode support */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-primary: #0d9488;
|
||||
--color-primary-hover: #0f766e;
|
||||
--color-primary-glow: rgba(13, 148, 136, 0.2);
|
||||
|
||||
--color-text-primary: #134e4a;
|
||||
--color-text-secondary: #0f766e;
|
||||
--color-text-muted: #14b8a6;
|
||||
|
||||
--color-background-page: #f0fdfa;
|
||||
--color-background-card: #ffffff;
|
||||
--color-background-card-hover: #ccfbf1;
|
||||
|
||||
--color-border: #99f6e4;
|
||||
--color-border-hover: #5eead4;
|
||||
}
|
||||
}
|
||||
|
||||
/* Force dark mode class */
|
||||
.dark {
|
||||
--color-primary: #14b8a6;
|
||||
--color-primary-hover: #2dd4bf;
|
||||
--color-primary-glow: rgba(20, 184, 166, 0.3);
|
||||
|
||||
--color-text-primary: #f8fafc;
|
||||
--color-text-secondary: #cbd5e1;
|
||||
--color-text-muted: #64748b;
|
||||
|
||||
--color-background-page: #0a1a18;
|
||||
--color-background-card: #132d2a;
|
||||
--color-background-card-hover: #1a3f3b;
|
||||
|
||||
--color-border: rgba(20, 184, 166, 0.2);
|
||||
--color-border-hover: rgba(20, 184, 166, 0.4);
|
||||
}
|
||||
|
||||
/* Force light mode class */
|
||||
.light {
|
||||
--color-primary: #0d9488;
|
||||
--color-primary-hover: #0f766e;
|
||||
--color-primary-glow: rgba(13, 148, 136, 0.2);
|
||||
|
||||
--color-text-primary: #134e4a;
|
||||
--color-text-secondary: #0f766e;
|
||||
--color-text-muted: #14b8a6;
|
||||
|
||||
--color-background-page: #f0fdfa;
|
||||
--color-background-card: #ffffff;
|
||||
--color-background-card-hover: #ccfbf1;
|
||||
|
||||
--color-border: #99f6e4;
|
||||
--color-border-hover: #5eead4;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue