style: Update default theme

This commit is contained in:
2026-05-25 16:51:44 +02:00
parent 155fe20862
commit 1259645706
23 changed files with 1206 additions and 630 deletions

View File

@@ -1,538 +1,184 @@
<!-- Hero -->
<section class="relative min-h-screen flex items-center justify-center overflow-hidden">
<!-- Gradient orbs -->
<div
[appParallax]="0.15"
class="absolute top-1/4 -left-32 w-96 h-96 bg-purple-600/20 rounded-full blur-[128px] animate-float"
></div>
<div
[appParallax]="0.25"
class="absolute bottom-1/4 -right-32 w-80 h-80 bg-violet-500/15 rounded-full blur-[100px] animate-float"
style="animation-delay: -3s"
></div>
<section class="home-shell">
<div class="hero-grid">
<div class="hero-copy section-fade">
<p class="eyebrow">{{ 'pages.home.hero.badge' | translate }}</p>
<h1>
<span>{{ 'pages.home.hero.titleLine1' | translate }}</span>
<span>{{ 'pages.home.hero.titleLine2' | translate }}</span>
</h1>
<p class="hero-description">{{ 'pages.home.hero.description' | translate }}</p>
<div class="relative z-10 container mx-auto px-6 text-center pt-32 pb-20">
<div
class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-purple-500/30 bg-purple-500/10 text-purple-400 text-sm font-medium mb-8 animate-fade-in"
>
<span class="w-2 h-2 rounded-full bg-purple-400 animate-pulse"></span>
{{ 'pages.home.hero.badge' | translate }}
</div>
<div class="hero-actions">
@if (downloadUrl()) {
<a
[href]="downloadUrl()"
class="button-primary"
>
{{ 'common.actions.downloadFor' | translate:{ os: getDetectedOsLabel() } }}
<span aria-hidden="true">{{ detectedOS().icon }}</span>
</a>
} @else {
<a
routerLink="/downloads"
class="button-primary"
>
{{ 'common.actions.downloadBrand' | translate }}
</a>
}
<h1 class="text-5xl md:text-7xl lg:text-8xl font-extrabold tracking-tight mb-6 animate-fade-in-up">
<span class="text-foreground">{{ 'pages.home.hero.titleLine1' | translate }}</span><br />
<span class="gradient-text">{{ 'pages.home.hero.titleLine2' | translate }}</span>
</h1>
<p
class="text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto mb-10 leading-relaxed animate-fade-in-up"
style="animation-delay: 0.2s"
>
{{ 'pages.home.hero.description' | translate }}
</p>
<!-- CTA Buttons -->
<div
class="flex flex-col sm:flex-row items-center justify-center gap-4 mb-6 animate-fade-in-up"
style="animation-delay: 0.4s"
>
@if (downloadUrl()) {
<a
[href]="downloadUrl()"
class="group inline-flex items-center gap-3 px-8 py-4 rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 text-white font-semibold text-lg hover:from-purple-500 hover:to-violet-500 transition-all shadow-2xl shadow-purple-500/25 hover:shadow-purple-500/40 animate-glow-pulse"
>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
{{ 'common.actions.downloadFor' | translate:{ os: getDetectedOsLabel() } }}
<span class="text-sm opacity-75">{{ detectedOS().icon }}</span>
</a>
} @else {
<a
routerLink="/downloads"
class="group inline-flex items-center gap-3 px-8 py-4 rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 text-white font-semibold text-lg hover:from-purple-500 hover:to-violet-500 transition-all shadow-2xl shadow-purple-500/25 hover:shadow-purple-500/40"
>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
{{ 'common.actions.downloadBrand' | translate }}
</a>
}
<a
href="https://web.toju.app/"
target="_blank"
rel="noopener"
class="inline-flex items-center gap-2 px-8 py-4 rounded-xl border border-border/50 bg-card/50 text-foreground font-medium text-lg hover:bg-card hover:border-purple-500/30 transition-all backdrop-blur-sm"
>
{{ 'common.actions.openInBrowser' | translate }}
<svg
class="w-5 h-5 opacity-60"
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>
</div>
@if (latestVersion()) {
<p
class="text-xs text-muted-foreground/60 animate-fade-in"
style="animation-delay: 0.6s"
>
{{ 'pages.home.hero.version' | translate:{ version: latestVersion() } }} ·
<a
routerLink="/downloads"
class="underline hover:text-muted-foreground transition-colors"
>{{ 'pages.home.hero.allPlatforms' | translate }}</a
>
</p>
}
<!-- Scroll indicator -->
<div class="absolute bottom-8 left-1/2 -translate-x-1/2 animate-bounce">
<svg
class="w-6 h-6 text-muted-foreground/40"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 14l-7 7m0 0l-7-7m7 7V3"
/>
</svg>
</div>
</div>
</section>
<app-ad-slot />
<!-- Features Grid -->
<section class="relative py-32">
<div class="container mx-auto px-6">
<div class="text-center mb-20 section-fade">
<h2 class="text-3xl md:text-5xl font-bold text-foreground mb-4">
{{ 'pages.home.features.titleLine1' | translate }}<br />
<span class="gradient-text">{{ 'pages.home.features.titleLine2' | translate }}</span>
</h2>
<p class="text-muted-foreground text-lg max-w-xl mx-auto">{{ 'pages.home.features.description' | translate }}</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Voice Calls -->
<div
class="section-fade group relative rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-8 hover:border-purple-500/30 hover:bg-card/50 transition-all duration-300"
>
<div class="w-12 h-12 rounded-xl bg-purple-500/10 flex items-center justify-center mb-6 group-hover:bg-purple-500/20 transition-colors">
<svg
class="w-6 h-6 text-purple-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"
/>
</svg>
</div>
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.voiceCalls.title' | translate }}</h3>
<p class="text-muted-foreground leading-relaxed">
{{ 'pages.home.features.items.voiceCalls.description' | translate }}
</p>
</div>
<!-- Screen Sharing -->
<div
class="section-fade group relative rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-8 hover:border-purple-500/30 hover:bg-card/50 transition-all duration-300"
style="transition-delay: 0.1s"
>
<div class="w-12 h-12 rounded-xl bg-violet-500/10 flex items-center justify-center mb-6 group-hover:bg-violet-500/20 transition-colors">
<svg
class="w-6 h-6 text-violet-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
</div>
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.screenSharing.title' | translate }}</h3>
<p class="text-muted-foreground leading-relaxed">
{{ 'pages.home.features.items.screenSharing.description' | translate }}
</p>
</div>
<!-- File Sharing -->
<div
class="section-fade group relative rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-8 hover:border-purple-500/30 hover:bg-card/50 transition-all duration-300"
style="transition-delay: 0.2s"
>
<div class="w-12 h-12 rounded-xl bg-pink-500/10 flex items-center justify-center mb-6 group-hover:bg-pink-500/20 transition-colors">
<svg
class="w-6 h-6 text-pink-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/>
</svg>
</div>
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.fileSharing.title' | translate }}</h3>
<p class="text-muted-foreground leading-relaxed">
{{ 'pages.home.features.items.fileSharing.description' | translate }}
</p>
</div>
<!-- Privacy -->
<div
class="section-fade group relative rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-8 hover:border-purple-500/30 hover:bg-card/50 transition-all duration-300"
>
<div class="w-12 h-12 rounded-xl bg-emerald-500/10 flex items-center justify-center mb-6 group-hover:bg-emerald-500/20 transition-colors">
<svg
class="w-6 h-6 text-emerald-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
</div>
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.privacy.title' | translate }}</h3>
<p class="text-muted-foreground leading-relaxed">
{{ 'pages.home.features.items.privacy.description' | translate }}
</p>
</div>
<!-- Open Source -->
<div
class="section-fade group relative rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-8 hover:border-purple-500/30 hover:bg-card/50 transition-all duration-300"
style="transition-delay: 0.1s"
>
<div class="w-12 h-12 rounded-xl bg-blue-500/10 flex items-center justify-center mb-6 group-hover:bg-blue-500/20 transition-colors">
<svg
class="w-6 h-6 text-blue-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
/>
</svg>
</div>
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.openSource.title' | translate }}</h3>
<p class="text-muted-foreground leading-relaxed">
{{ 'pages.home.features.items.openSource.description' | translate }}
</p>
</div>
<!-- Free -->
<div
class="section-fade group relative rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-8 hover:border-purple-500/30 hover:bg-card/50 transition-all duration-300"
style="transition-delay: 0.2s"
>
<div class="w-12 h-12 rounded-xl bg-amber-500/10 flex items-center justify-center mb-6 group-hover:bg-amber-500/20 transition-colors">
<svg
class="w-6 h-6 text-amber-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="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"
/>
</svg>
</div>
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.free.title' | translate }}</h3>
<p class="text-muted-foreground leading-relaxed">
{{ 'pages.home.features.items.free.description' | translate }}
</p>
</div>
</div>
</div>
</section>
<app-ad-slot />
<!-- Gaming Section -->
<section class="relative py-32">
<div class="absolute inset-0 bg-gradient-to-b from-transparent via-purple-950/10 to-transparent"></div>
<div class="relative container mx-auto px-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
<div class="section-fade">
<div
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20 text-purple-400 text-sm font-medium mb-6"
>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{{ 'pages.home.gaming.badge' | translate }}
</div>
<h2 class="text-3xl md:text-5xl font-bold text-foreground mb-6">
{{ 'pages.home.gaming.titleLine1' | translate }}<br />
<span class="gradient-text">{{ 'pages.home.gaming.titleLine2' | translate }}</span>
</h2>
<p class="text-lg text-muted-foreground leading-relaxed mb-8">
{{ 'pages.home.gaming.description' | translate }}
</p>
<ul class="space-y-4">
<li class="flex items-center gap-3 text-muted-foreground">
<svg
class="w-5 h-5 text-purple-400 flex-shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
/>
</svg>
<span>{{ 'pages.home.gaming.bullets.lowLatency' | translate }}</span>
</li>
<li class="flex items-center gap-3 text-muted-foreground">
<svg
class="w-5 h-5 text-purple-400 flex-shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
/>
</svg>
<span>{{ 'pages.home.gaming.bullets.noiseSuppression' | translate }}</span>
</li>
<li class="flex items-center gap-3 text-muted-foreground">
<svg
class="w-5 h-5 text-purple-400 flex-shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
/>
</svg>
<span>{{ 'pages.home.gaming.bullets.screenShare' | translate }}</span>
</li>
<li class="flex items-center gap-3 text-muted-foreground">
<svg
class="w-5 h-5 text-purple-400 flex-shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
/>
</svg>
<span>{{ 'pages.home.gaming.bullets.fileTransfers' | translate }}</span>
</li>
</ul>
</div>
<div class="section-fade relative">
<div class="relative rounded-2xl overflow-hidden border border-border/30 bg-card/30 backdrop-blur-sm aspect-video">
<img
ngSrc="/images/screenshots/screenshare_gaming.png"
fill
priority
[attr.alt]="'pages.home.gaming.imageAlt' | translate"
class="object-cover"
/>
<div class="absolute inset-0 bg-gradient-to-t from-background/80 via-transparent to-transparent"></div>
<div class="absolute bottom-4 left-4 text-sm text-muted-foreground/60">{{ 'pages.home.gaming.caption' | translate }}</div>
</div>
<!-- Glow effect -->
<div class="absolute -inset-4 bg-purple-600/5 rounded-3xl blur-2xl -z-10"></div>
</div>
</div>
</div>
</section>
<app-ad-slot />
<!-- Self-hostable Section -->
<section class="relative py-32">
<div class="container mx-auto px-6">
<div class="section-fade max-w-3xl mx-auto text-center">
<div
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-sm font-medium mb-6"
>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"
/>
</svg>
{{ 'pages.home.selfHostable.badge' | translate }}
</div>
<h2 class="text-3xl md:text-5xl font-bold text-foreground mb-6">
{{ 'pages.home.selfHostable.titleLine1' | translate }}<br />
<span class="gradient-text">{{ 'pages.home.selfHostable.titleLine2' | translate }}</span>
</h2>
<p class="text-lg text-muted-foreground leading-relaxed mb-8">
{{ 'pages.home.selfHostable.description' | translate }}
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
<a
routerLink="/what-is-toju"
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl border border-border/50 bg-card/50 text-foreground font-medium hover:border-purple-500/30 transition-all"
>
{{ 'common.actions.learnHowItWorks' | translate }}
<svg
class="w-4 h-4"
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>
</a>
<a
href="https://git.azaaxin.com/myxelium/Toju"
href="https://web.toju.app/"
target="_blank"
rel="noopener"
class="inline-flex items-center gap-2 px-6 py-3 text-muted-foreground hover:text-foreground transition-colors text-sm"
class="button-secondary"
>
<img
src="/images/gitea.png"
alt=""
width="16"
height="16"
class="w-4 h-4 object-contain"
/>
{{ 'common.actions.viewSourceCode' | translate }}
{{ 'common.actions.openInBrowser' | translate }}
</a>
</div>
@if (latestVersion()) {
<p class="release-note">
{{ 'pages.home.hero.version' | translate:{ version: latestVersion() } }}
<a routerLink="/downloads">{{ 'pages.home.hero.allPlatforms' | translate }}</a>
</p>
}
</div>
<div class="hero-product section-fade">
<img
ngSrc="/images/screenshots/screenshot_main.png"
width="1320"
height="860"
priority
[attr.alt]="'pages.gallery.featured.imageAlt' | translate"
/>
<div class="product-note">
<strong>{{ 'pages.home.features.items.privacy.title' | translate }}</strong>
<span>{{ 'pages.home.features.items.privacy.description' | translate }}</span>
</div>
</div>
</div>
</section>
<!-- CTA Banner -->
<section class="relative py-24">
<div class="absolute inset-0 bg-gradient-to-r from-purple-950/20 via-violet-950/30 to-purple-950/20"></div>
<div class="relative container mx-auto px-6 text-center section-fade">
<h2 class="text-3xl md:text-4xl font-bold text-foreground mb-4">{{ 'pages.home.cta.title' | translate }}</h2>
<p class="text-muted-foreground text-lg mb-8 max-w-lg mx-auto">{{ 'pages.home.cta.description' | translate }}</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
@if (downloadUrl()) {
<a
[href]="downloadUrl()"
class="inline-flex items-center gap-2 px-8 py-4 rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 text-white font-semibold hover:from-purple-500 hover:to-violet-500 transition-all shadow-2xl shadow-purple-500/25"
>
{{ 'common.actions.downloadFor' | translate:{ os: getDetectedOsLabel() } }}
</a>
} @else {
<a
routerLink="/downloads"
class="inline-flex items-center gap-2 px-8 py-4 rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 text-white font-semibold hover:from-purple-500 hover:to-violet-500 transition-all shadow-2xl shadow-purple-500/25"
>
{{ 'common.actions.downloadBrand' | translate }}
</a>
}
<a
href="https://web.toju.app/"
target="_blank"
rel="noopener"
class="inline-flex items-center gap-2 px-8 py-4 rounded-xl border border-border/50 bg-card/50 text-foreground font-medium hover:border-purple-500/30 transition-all"
>
{{ 'common.actions.tryInBrowser' | translate }}
</a>
</div>
<app-ad-slot />
<section class="feature-editorial">
<div class="section-heading section-fade">
<p class="eyebrow">{{ 'pages.home.features.titleLine1' | translate }}</p>
<h2>{{ 'pages.home.features.titleLine2' | translate }}</h2>
<p>{{ 'pages.home.features.description' | translate }}</p>
</div>
<div class="feature-layout">
<article class="feature-panel feature-panel-large section-fade">
<span>01</span>
<h3>{{ 'pages.home.features.items.voiceCalls.title' | translate }}</h3>
<p>{{ 'pages.home.features.items.voiceCalls.description' | translate }}</p>
</article>
<article class="feature-panel section-fade">
<span>02</span>
<h3>{{ 'pages.home.features.items.screenSharing.title' | translate }}</h3>
<p>{{ 'pages.home.features.items.screenSharing.description' | translate }}</p>
</article>
<article class="feature-panel section-fade">
<span>03</span>
<h3>{{ 'pages.home.features.items.fileSharing.title' | translate }}</h3>
<p>{{ 'pages.home.features.items.fileSharing.description' | translate }}</p>
</article>
<article class="feature-panel feature-panel-wide section-fade">
<span>04</span>
<h3>{{ 'pages.home.features.items.openSource.title' | translate }}</h3>
<p>{{ 'pages.home.features.items.openSource.description' | translate }}</p>
</article>
<article class="feature-panel feature-panel-quiet section-fade">
<span>05</span>
<h3>{{ 'pages.home.features.items.free.title' | translate }}</h3>
<p>{{ 'pages.home.features.items.free.description' | translate }}</p>
</article>
</div>
</section>
<app-ad-slot />
<section class="proof-section">
<div class="proof-image section-fade">
<img
ngSrc="/images/screenshots/screenshare_gaming.png"
width="1280"
height="720"
[attr.alt]="'pages.home.gaming.imageAlt' | translate"
/>
</div>
<div class="proof-copy section-fade">
<p class="eyebrow">{{ 'pages.home.gaming.badge' | translate }}</p>
<h2>
{{ 'pages.home.gaming.titleLine1' | translate }}
{{ 'pages.home.gaming.titleLine2' | translate }}
</h2>
<p>{{ 'pages.home.gaming.description' | translate }}</p>
<ul>
<li>{{ 'pages.home.gaming.bullets.lowLatency' | translate }}</li>
<li>{{ 'pages.home.gaming.bullets.noiseSuppression' | translate }}</li>
<li>{{ 'pages.home.gaming.bullets.screenShare' | translate }}</li>
<li>{{ 'pages.home.gaming.bullets.fileTransfers' | translate }}</li>
</ul>
</div>
</section>
<app-ad-slot />
<section class="host-section section-fade">
<div>
<p class="eyebrow">{{ 'pages.home.selfHostable.badge' | translate }}</p>
<h2>
{{ 'pages.home.selfHostable.titleLine1' | translate }}
{{ 'pages.home.selfHostable.titleLine2' | translate }}
</h2>
</div>
<div class="host-section-copy">
<p>{{ 'pages.home.selfHostable.description' | translate }}</p>
<a
routerLink="/what-is-toju"
class="text-link"
>
{{ 'common.actions.learnHowItWorks' | translate }}
</a>
</div>
</section>
<section class="closing-cta section-fade">
<p class="eyebrow">Toju</p>
<h2>{{ 'pages.home.cta.title' | translate }}</h2>
<p>{{ 'pages.home.cta.description' | translate }}</p>
<div class="hero-actions">
@if (downloadUrl()) {
<a
[href]="downloadUrl()"
class="button-primary"
>
{{ 'common.actions.downloadFor' | translate:{ os: getDetectedOsLabel() } }}
</a>
} @else {
<a
routerLink="/downloads"
class="button-primary"
>
{{ 'common.actions.downloadBrand' | translate }}
</a>
}
<a
href="https://web.toju.app/"
target="_blank"
rel="noopener"
class="button-secondary"
>
{{ 'common.actions.tryInBrowser' | translate }}
</a>
</div>
</section>

View File

@@ -0,0 +1,341 @@
:host {
display: block;
}
.home-shell,
.feature-editorial,
.proof-section,
.host-section,
.closing-cta {
width: min(100% - 2rem, 1240px);
margin: 0 auto;
}
.home-shell {
min-height: 100dvh;
padding: 9rem 0 5rem;
}
.hero-grid {
display: grid;
grid-template-columns: minmax(0, 0.78fr) minmax(22rem, 1fr);
gap: clamp(2rem, 5vw, 5.5rem);
align-items: center;
}
.hero-copy h1,
.section-heading h2,
.proof-copy h2,
.host-section h2,
.closing-cta h2 {
margin: 0;
color: hsl(var(--foreground));
font-size: clamp(3.5rem, 8vw, 7.5rem);
line-height: 0.92;
font-weight: 800;
letter-spacing: 0;
text-wrap: balance;
}
.hero-copy h1 span {
display: block;
}
.eyebrow {
margin: 0 0 1.25rem;
color: hsl(var(--primary));
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hero-description,
.section-heading p,
.proof-copy p,
.host-section p,
.closing-cta p,
.feature-panel p {
color: hsl(var(--muted-foreground));
line-height: 1.75;
text-wrap: pretty;
}
.hero-description {
max-width: 35rem;
margin: 2rem 0 0;
font-size: clamp(1.05rem, 2vw, 1.35rem);
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 0.85rem;
margin-top: 2rem;
}
.button-primary,
.button-secondary,
.text-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 3rem;
border-radius: 0.45rem;
font-weight: 700;
transition: transform 180ms ease, border-color 180ms ease, background 180ms ease, color 180ms ease;
}
.button-primary {
padding: 0.85rem 1.2rem;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
box-shadow: 0 1rem 2.5rem hsl(var(--primary) / 0.16);
}
.button-secondary {
padding: 0.85rem 1.1rem;
border: 1px solid hsl(var(--border));
color: hsl(var(--foreground));
background: hsl(var(--card) / 0.74);
}
.button-primary:hover,
.button-secondary:hover,
.text-link:hover {
transform: translateY(-2px);
}
.button-primary:active,
.button-secondary:active,
.text-link:active {
transform: translateY(1px);
}
.release-note {
margin-top: 1.25rem;
color: hsl(var(--muted-foreground));
font-size: 0.84rem;
}
.release-note a,
.text-link {
margin-left: 0.75rem;
color: hsl(var(--primary));
}
.hero-product {
position: relative;
overflow: hidden;
border: 1px solid hsl(var(--border));
border-radius: 0.85rem;
background: linear-gradient(180deg, hsl(var(--card)), hsl(var(--background)));
box-shadow: 0 2rem 5rem hsl(0 0% 0% / 0.28);
}
.hero-product img,
.proof-image img {
display: block;
width: 100%;
height: auto;
}
.product-note {
position: absolute;
right: 1rem;
bottom: 1rem;
width: min(22rem, calc(100% - 2rem));
padding: 1rem;
border: 1px solid hsl(var(--border));
border-radius: 0.65rem;
background: hsl(var(--background) / 0.84);
backdrop-filter: blur(18px);
}
.product-note strong,
.product-note span {
display: block;
}
.product-note span {
margin-top: 0.35rem;
color: hsl(var(--muted-foreground));
font-size: 0.86rem;
line-height: 1.5;
}
.feature-editorial,
.proof-section,
.host-section,
.closing-cta {
padding: 6rem 0;
}
.section-heading {
max-width: 54rem;
margin-bottom: 2.75rem;
}
.section-heading h2,
.proof-copy h2,
.host-section h2,
.closing-cta h2 {
font-size: clamp(2.4rem, 5vw, 5.25rem);
line-height: 1;
}
.section-heading p {
max-width: 36rem;
margin: 1rem 0 0;
font-size: 1.08rem;
}
.feature-layout {
display: grid;
grid-template-columns: 1.15fr 0.85fr 0.85fr;
gap: 1rem;
}
.feature-panel {
min-height: 18rem;
padding: clamp(1.25rem, 3vw, 2rem);
border: 1px solid hsl(var(--border));
border-radius: 0.7rem;
background: hsl(var(--card));
}
.feature-panel-large {
grid-row: span 2;
min-height: 37rem;
background: hsl(var(--primary) / 0.12);
}
.feature-panel-wide {
grid-column: span 2;
}
.feature-panel-quiet {
background: transparent;
}
.feature-panel span {
color: hsl(var(--primary));
font-family: var(--font-mono);
font-size: 0.82rem;
font-variant-numeric: tabular-nums;
}
.feature-panel h3 {
margin: 3rem 0 0.8rem;
color: hsl(var(--foreground));
font-size: clamp(1.4rem, 3vw, 2.6rem);
line-height: 1.06;
text-wrap: balance;
}
.proof-section {
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(20rem, 0.7fr);
gap: clamp(2rem, 5vw, 5rem);
align-items: center;
}
.proof-image {
overflow: hidden;
border: 1px solid hsl(var(--border));
border-radius: 0.8rem;
background: hsl(var(--card));
}
.proof-copy ul {
display: grid;
gap: 0.75rem;
padding: 0;
margin: 1.5rem 0 0;
list-style: none;
}
.proof-copy li {
padding-left: 1rem;
border-left: 2px solid hsl(var(--primary));
color: hsl(var(--muted-foreground));
line-height: 1.55;
}
.host-section {
display: grid;
grid-template-columns: minmax(0, 0.82fr) minmax(0, 1fr);
gap: clamp(2rem, 6vw, 5rem);
align-items: start;
border-top: 1px solid hsl(var(--border));
border-bottom: 1px solid hsl(var(--border));
}
.host-section p {
margin: 0;
}
.host-section-copy {
display: grid;
gap: 1.2rem;
max-width: 38rem;
padding-top: 0.25rem;
}
.host-section-copy .text-link {
justify-self: start;
margin-left: 0;
}
.closing-cta {
text-align: left;
}
.closing-cta p:not(.eyebrow) {
max-width: 34rem;
}
@media (max-width: 980px) {
.hero-grid,
.proof-section,
.host-section {
grid-template-columns: 1fr;
}
.feature-layout {
grid-template-columns: 1fr 1fr;
}
.feature-panel-large,
.feature-panel-wide {
grid-column: span 2;
min-height: 20rem;
}
}
@media (max-width: 640px) {
.home-shell {
padding-top: 7rem;
}
.feature-layout {
grid-template-columns: 1fr;
}
.feature-panel-large,
.feature-panel-wide {
grid-column: auto;
}
.product-note {
position: static;
width: auto;
border-width: 1px 0 0;
border-radius: 0;
}
.button-primary,
.button-secondary {
width: 100%;
}
}

View File

@@ -14,7 +14,6 @@ import { ReleaseService, DetectedOS } from '../../services/release.service';
import { SeoService } from '../../services/seo.service';
import { ScrollAnimationService } from '../../services/scroll-animation.service';
import { AdSlotComponent } from '../../components/ad-slot/ad-slot.component';
import { ParallaxDirective } from '../../directives/parallax.directive';
@Component({
selector: 'app-home',
@@ -23,10 +22,10 @@ import { ParallaxDirective } from '../../directives/parallax.directive';
NgOptimizedImage,
TranslateModule,
RouterLink,
AdSlotComponent,
ParallaxDirective
AdSlotComponent
],
templateUrl: './home.component.html'
templateUrl: './home.component.html',
styleUrl: './home.component.scss'
})
export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
readonly detectedOS = signal<DetectedOS>({