Simplify bridge onboarding pages

This commit is contained in:
Remi
2025-09-24 08:26:02 +02:00
parent df9a5dd2f0
commit e886a2ddb0
2 changed files with 410 additions and 406 deletions

View File

@@ -1,10 +1,10 @@
<template>
<div class="relative min-h-screen bg-[#0B1020] text-white">
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top,_rgba(22,187,215,0.12),transparent_55%)]"/>
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top,_rgba(22,187,215,0.14),transparent_55%)]"/>
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_bottom,_rgba(79,70,229,0.12),transparent_60%)]"/>
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col gap-10 px-6 py-12 lg:px-12">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<main class="relative mx-auto w-full max-w-3xl px-5 py-12 sm:px-6 lg:px-8">
<nav class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<NuxtLink
to="/"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl border border-white/10 bg-white/5 px-4 py-2 text-sm font-medium text-white transition hover:border-white/20 hover:bg-white/10 sm:w-auto"
@@ -18,270 +18,223 @@
>
Bridge landing
</NuxtLink>
</div>
</nav>
<header class="space-y-4 text-center">
<header class="mt-10 space-y-4 text-center sm:text-left">
<p class="text-xs font-semibold uppercase tracking-[0.45em] text-[#16BBD7]">OpenSquawk Bridge</p>
<h1 class="text-3xl font-semibold sm:text-4xl">Connect your Bridge token</h1>
<p class="mx-auto max-w-2xl text-sm text-white/70">
Sign in, confirm the token from the desktop Bridge app, and then jump straight back into the simulator.
</p>
<h1 class="text-3xl font-semibold sm:text-4xl">Link your Bridge in seconds</h1>
<p class="mx-auto max-w-2xl text-sm text-white/70 sm:mx-0">Follow the prompt from the desktop Bridge app and you&rsquo;ll be ready to fly.</p>
</header>
<div
v-if="!hasToken"
class="rounded-3xl border border-red-500/30 bg-red-500/10 px-8 py-12 text-center shadow-[0_25px_80px_rgba(64,18,18,0.35)]"
class="mt-10 rounded-3xl border border-red-500/30 bg-red-500/10 px-6 py-10 text-center shadow-[0_25px_80px_rgba(64,18,18,0.35)] sm:px-8"
>
<h2 class="text-2xl font-semibold">Token missing</h2>
<p class="mt-4 text-sm text-red-100/80">Open this page from the Bridge app so the token can be passed through automatically.</p>
<p class="mt-6 text-xs uppercase tracking-[0.4em] text-red-200/60">Waiting for desktop link</p>
<h2 class="text-2xl font-semibold">Open from the Bridge app</h2>
<p class="mt-4 text-sm text-red-100/80">Launch the desktop Bridge and tap the &ldquo;Link account&rdquo; button so we can pass the code over automatically.</p>
<p class="mt-6 text-xs uppercase tracking-[0.38em] text-red-200/60">Waiting for desktop link</p>
</div>
<div v-else class="grid gap-6 lg:grid-cols-[1.35fr_1fr]">
<div class="space-y-6">
<section class="rounded-3xl border border-white/10 bg-[#111832]/85 p-8 shadow-[0_30px_80px_rgba(5,10,30,0.55)] backdrop-blur">
<div class="flex flex-wrap items-center justify-between gap-4">
<div class="flex items-center gap-3">
<span class="flex h-11 w-11 items-center justify-center rounded-2xl bg-[#16BBD7]/10 text-[#16BBD7]">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-5 w-5"
>
<path d="M7 9V7a5 5 0 1110 0v2" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="5" y="9" width="14" height="11" rx="3" ry="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 14v3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<h2 class="text-xl font-semibold">Step 1 · Sign in</h2>
</div>
<span class="rounded-full bg-[#292D3B] px-3 py-1 text-[11px] uppercase tracking-[0.32em] text-white/70">Login</span>
<template v-else>
<section class="mt-10 rounded-3xl border border-white/10 bg-[#111832]/85 p-6 shadow-[0_24px_70px_rgba(5,10,30,0.55)] sm:p-8">
<div class="flex items-center gap-3">
<span class="flex h-10 w-10 items-center justify-center rounded-2xl bg-[#16BBD7]/15 text-sm font-semibold text-[#16BBD7]">1</span>
<div>
<p class="text-xs uppercase tracking-[0.32em] text-white/55">Step 1</p>
<h2 class="text-lg font-semibold">Sign in</h2>
</div>
<p class="mt-3 text-sm text-white/70">Use your OpenSquawk credentials to unlock the token linking step.</p>
</div>
<p class="mt-4 text-sm text-white/70">Use your OpenSquawk login so we can link the Bridge to the right account.</p>
<div v-if="isAuthenticated" class="mt-6 rounded-2xl border border-[#16BBD7]/40 bg-[#16BBD7]/10 px-5 py-4 text-sm">
<p class="font-medium text-[#16BBD7]">You&rsquo;re signed in</p>
<p class="mt-1 text-white/70">Logged in as <span class="font-semibold text-white">{{ authDisplayName }}</span>.</p>
</div>
<form v-else class="mt-6 space-y-4" @submit.prevent="submitLogin">
<div class="space-y-2">
<label class="block text-sm font-medium text-white/70">Email</label>
<input
v-model.trim="loginForm.email"
type="email"
required
autocomplete="email"
class="w-full rounded-2xl border border-white/10 bg-[#0B132A]/90 px-4 py-3 text-sm text-white placeholder-white/40 outline-none transition focus:border-[#16BBD7]/60 focus:ring-2 focus:ring-[#16BBD7]/60"
/>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between">
<label class="block text-sm font-medium text-white/70">Password</label>
<NuxtLink
to="/forgot-password"
class="text-xs font-semibold uppercase tracking-[0.28em] text-[#16BBD7] transition hover:text-[#72d9ea]"
>
Forgot?
</NuxtLink>
</div>
<input
v-model="loginForm.password"
type="password"
required
autocomplete="current-password"
class="w-full rounded-2xl border border-white/10 bg-[#0B132A]/90 px-4 py-3 text-sm text-white placeholder-white/40 outline-none transition focus:border-[#16BBD7]/60 focus:ring-2 focus:ring-[#16BBD7]/60"
/>
</div>
<button
type="submit"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-[#16BBD7] px-5 py-3 text-sm font-semibold text-[#0B1020] transition hover:bg-[#13a7c4] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#72d9ea] disabled:cursor-not-allowed disabled:bg-[#16BBD7]/60"
:disabled="loginLoading"
>
<span v-if="loginLoading" class="flex items-center gap-2">
<span class="h-4 w-4 animate-spin rounded-full border-2 border-[#0B1020]/30 border-t-[#0B1020]"/>
Signing in
</span>
<span v-else>Sign in</span>
</button>
<p v-if="loginError" class="text-sm text-red-300">{{ loginError }}</p>
</form>
</section>
<section class="mt-6 rounded-3xl border border-white/10 bg-[#111832]/85 p-6 shadow-[0_24px_70px_rgba(5,10,30,0.55)] sm:p-8">
<div class="flex items-center gap-3">
<span class="flex h-10 w-10 items-center justify-center rounded-2xl bg-[#16BBD7]/15 text-sm font-semibold text-[#16BBD7]">2</span>
<div>
<p class="text-xs uppercase tracking-[0.32em] text-white/55">Step 2</p>
<h2 class="text-lg font-semibold">Confirm the code</h2>
</div>
</div>
<p class="mt-4 text-sm text-white/70">This secure code comes straight from the desktop Bridge.</p>
<div class="mt-4 flex flex-wrap items-center gap-3">
<code class="rounded-2xl bg-[#0B132A]/80 px-4 py-2 font-mono text-sm text-[#72d9ea] shadow-inner">{{ token }}</code>
<button
type="button"
class="inline-flex items-center gap-2 rounded-2xl border border-white/15 bg-white/5 px-4 py-2 text-xs font-medium text-white/70 transition hover:border-white/30 hover:text-white"
@click="copyToken"
>
<span aria-hidden="true"></span>
<span>{{ copiedToken ? 'Copied' : 'Copy code' }}</span>
</button>
</div>
<div class="mt-6 space-y-4">
<button
type="button"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-[#16BBD7] px-5 py-3 text-sm font-semibold text-[#0B1020] transition hover:bg-[#13a7c4] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#72d9ea] disabled:cursor-not-allowed disabled:bg-[#16BBD7]/60"
:disabled="!isAuthenticated || connectLoading"
@click="connectBridge"
>
<span v-if="connectLoading" class="flex items-center gap-2">
<span class="h-4 w-4 animate-spin rounded-full border-2 border-[#0B1020]/30 border-t-[#0B1020]"/>
Linking
</span>
<span v-else>Link Bridge</span>
</button>
<p v-if="!isAuthenticated" class="text-sm text-white/60">Sign in above to enable the link button.</p>
<p v-if="connectError" class="text-sm text-red-300">{{ connectError }}</p>
<div
v-if="isAuthenticated"
class="mt-6 rounded-2xl border border-[#16BBD7]/40 bg-[#16BBD7]/10 px-5 py-4 text-sm"
v-if="successBannerVisible"
class="rounded-2xl border border-emerald-400/40 bg-emerald-400/10 px-5 py-4 text-sm text-emerald-100 shadow-[0_0_35px_rgba(16,185,129,0.25)]"
>
<p class="font-medium text-[#16BBD7]">Signed in</p>
<p class="mt-1 text-white/70">
You are currently logged in as
<span class="font-semibold text-white">{{ authDisplayName }}</span>.
<p class="text-base font-semibold text-emerald-200">Bridge linked</p>
<p class="mt-1 text-emerald-100/80">You can close this page and head back to the Bridge app.</p>
</div>
</div>
</section>
<section class="mt-6 rounded-3xl border border-white/10 bg-[#111832]/85 p-6 shadow-[0_24px_70px_rgba(5,10,30,0.55)] sm:p-8">
<div class="flex items-center gap-3">
<span class="flex h-10 w-10 items-center justify-center rounded-2xl bg-[#16BBD7]/15 text-sm font-semibold text-[#16BBD7]">3</span>
<div>
<p class="text-xs uppercase tracking-[0.32em] text-white/55">Step 3</p>
<h2 class="text-lg font-semibold">Watch the Bridge</h2>
</div>
</div>
<p class="mt-4 text-sm text-white/70">The desktop app pings us every few seconds while it&rsquo;s running.</p>
<div class="mt-5 space-y-4">
<div
class="rounded-2xl border px-4 py-4"
:class="connectionStatus?.connected
? 'border-emerald-400/45 bg-emerald-400/10 shadow-[0_0_35px_rgba(16,185,129,0.25)]'
: 'border-white/10 bg-[#0B132A]/75'"
>
<div class="flex flex-wrap items-center justify-between gap-2">
<p class="text-sm font-semibold">Bridge status</p>
<span
class="rounded-full px-3 py-1 text-[11px] uppercase tracking-[0.32em]"
:class="connectionStatus?.connected ? 'bg-emerald-400/20 text-emerald-200' : 'bg-white/10 text-white/60'"
>
{{ connectionStatus?.connected ? 'Linked' : 'Waiting' }}
</span>
</div>
<p
class="mt-3 text-base font-medium"
:class="connectionStatus?.connected ? 'text-emerald-200' : 'text-white/60'"
>
{{ connectionLabel }}
</p>
<p v-if="connectionSubLabel" class="text-xs text-white/45">{{ connectionSubLabel }}</p>
</div>
<form v-else class="mt-6 space-y-5" @submit.prevent="submitLogin">
<div>
<label class="block text-sm font-medium text-white/70">Email</label>
<input
v-model.trim="loginForm.email"
type="email"
required
autocomplete="email"
class="mt-2 w-full rounded-2xl border border-white/10 bg-[#0B132A]/90 px-4 py-3 text-sm text-white placeholder-white/40 outline-none transition focus:border-[#16BBD7]/60 focus:ring-2 focus:ring-[#16BBD7]/60"
/>
</div>
<div>
<div class="flex items-center justify-between">
<label class="block text-sm font-medium text-white/70">Password</label>
<NuxtLink
to="/forgot-password"
class="text-xs font-semibold uppercase tracking-[0.28em] text-[#16BBD7] transition hover:text-[#72d9ea]"
>
Forgot?
</NuxtLink>
</div>
<input
v-model="loginForm.password"
type="password"
required
autocomplete="current-password"
class="mt-2 w-full rounded-2xl border border-white/10 bg-[#0B132A]/90 px-4 py-3 text-sm text-white placeholder-white/40 outline-none transition focus:border-[#16BBD7]/60 focus:ring-2 focus:ring-[#16BBD7]/60"
/>
</div>
<button
type="submit"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-[#16BBD7] px-5 py-3 text-sm font-semibold text-[#0B1020] transition hover:bg-[#13a7c4] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#72d9ea] disabled:cursor-not-allowed disabled:bg-[#16BBD7]/60"
:disabled="loginLoading"
>
<span v-if="loginLoading" class="flex items-center gap-2">
<span class="h-4 w-4 animate-spin rounded-full border-2 border-[#0B1020]/30 border-t-[#0B1020]"/>
Signing in
</span>
<span v-else>Sign in</span>
</button>
<p v-if="loginError" class="text-sm text-red-300">{{ loginError }}</p>
</form>
</section>
<section class="rounded-3xl border border-white/10 bg-[#111832]/85 p-8 shadow-[0_30px_80px_rgba(5,10,30,0.55)] backdrop-blur">
<div class="flex flex-wrap items-center justify-between gap-4">
<div class="flex items-center gap-3">
<span class="flex h-11 w-11 items-center justify-center rounded-2xl bg-[#16BBD7]/10 text-[#16BBD7]">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-5 w-5"
>
<path d="M8 12l-3 3 3 3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 12l3 3-3 3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 15h6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.5 9A4.5 4.5 0 0112 4.5h0A4.5 4.5 0 0116.5 9" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<h2 class="text-xl font-semibold">Step 2 · Link the token</h2>
</div>
<span class="rounded-full bg-[#16BBD7]/10 px-3 py-1 text-[11px] uppercase tracking-[0.32em] text-[#16BBD7]">Active</span>
</div>
<p class="mt-3 text-sm text-white/70">Confirm the token that the desktop Bridge generated for you.</p>
<div class="mt-6 flex flex-wrap items-center gap-3">
<code class="rounded-2xl bg-[#0B132A]/80 px-4 py-2 font-mono text-sm text-[#72d9ea] shadow-inner">{{ token }}</code>
<button
type="button"
class="inline-flex items-center gap-2 rounded-2xl border border-white/15 bg-white/5 px-4 py-2 text-xs font-medium text-white/70 transition hover:border-white/30 hover:text-white"
@click="copyToken"
>
<span aria-hidden="true"></span>
<span>{{ copiedToken ? 'Copied' : 'Copy token' }}</span>
</button>
</div>
<div class="mt-8 space-y-4">
<button
type="button"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-[#16BBD7] px-5 py-3 text-sm font-semibold text-[#0B1020] transition hover:bg-[#13a7c4] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#72d9ea] disabled:cursor-not-allowed disabled:bg-[#16BBD7]/60"
:disabled="!isAuthenticated || connectLoading"
@click="connectBridge"
>
<span v-if="connectLoading" class="flex items-center gap-2">
<span class="h-4 w-4 animate-spin rounded-full border-2 border-[#0B1020]/30 border-t-[#0B1020]"/>
Linking
</span>
<span v-else>Link token</span>
</button>
<p v-if="!isAuthenticated" class="text-sm text-white/60">Sign in first to enable the link button.</p>
<p v-if="connectError" class="text-sm text-red-300">{{ connectError }}</p>
<div class="grid gap-3 sm:grid-cols-2">
<div
v-if="successBannerVisible"
class="rounded-2xl border border-emerald-400/40 bg-emerald-400/10 px-5 py-4 text-sm text-emerald-100 shadow-[0_0_35px_rgba(16,185,129,0.25)]"
class="flex items-center gap-3 rounded-2xl border px-4 py-3"
:class="connectionStatus?.simConnected
? 'border-emerald-400/45 bg-emerald-400/10 shadow-[0_0_30px_rgba(16,185,129,0.25)]'
: 'border-white/10 bg-[#0B132A]/75'"
>
<p class="font-semibold">Linked successfully!</p>
<p class="mt-1 text-emerald-100/80">You can close this tab and return to the Bridge app.</p>
</div>
</div>
</section>
</div>
<aside>
<section
class="h-full rounded-3xl border border-white/10 bg-[#111832]/80 p-8 shadow-[0_30px_80px_rgba(4,8,24,0.55)] backdrop-blur"
:class="{ 'opacity-80': statusLoading }"
>
<div class="flex items-center justify-between gap-3">
<div class="flex items-center gap-3">
<span class="flex h-11 w-11 items-center justify-center rounded-2xl bg-white/5 text-white/70">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-5 w-5"
>
<path d="M4 17l4-4 3 3 5-5 4 4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 7h16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12h10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<div>
<h3 class="text-lg font-semibold">Live status</h3>
<p class="text-xs uppercase tracking-[0.32em] text-white/50">Refreshes every 5s</p>
</div>
</div>
</div>
<div class="mt-6 space-y-4">
<div class="rounded-2xl border border-white/10 bg-[#0B132A]/80 px-5 py-4 shadow-inner">
<p class="text-sm font-semibold text-white/70">Connection</p>
<p
class="mt-1 text-base font-medium"
:class="connectionStatus?.connected ? 'text-emerald-300' : 'text-white/50'"
>
{{ connectionLabel }}
</p>
<p v-if="connectionSubLabel" class="text-xs text-white/40">{{ connectionSubLabel }}</p>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<div
class="flex items-center gap-3 rounded-2xl border px-4 py-3 transition"
<span
class="flex h-3 w-3 items-center justify-center rounded-full"
:class="connectionStatus?.simConnected
? 'border-emerald-400/60 bg-emerald-400/10 shadow-[0_0_30px_rgba(16,185,129,0.35)]'
: 'border-white/10 bg-[#0B132A]/70'"
>
<span
class="flex h-3 w-3 items-center justify-center rounded-full"
:class="connectionStatus?.simConnected
? 'bg-emerald-400 shadow-[0_0_18px_rgba(16,185,129,0.85)]'
: 'bg-white/30'"
/>
<div>
<p class="text-sm font-semibold">Simulator</p>
<p class="text-xs text-white/60">
{{ connectionStatus?.simConnected ? 'Simulator detected' : 'Waiting for simulator' }}
</p>
</div>
</div>
<div
class="flex items-center gap-3 rounded-2xl border px-4 py-3 transition"
:class="connectionStatus?.flightActive
? 'border-emerald-400/60 bg-emerald-400/10 shadow-[0_0_30px_rgba(16,185,129,0.35)]'
: 'border-white/10 bg-[#0B132A]/70'"
>
<span
class="flex h-3 w-3 items-center justify-center rounded-full"
:class="connectionStatus?.flightActive
? 'bg-emerald-400 shadow-[0_0_18px_rgba(16,185,129,0.85)]'
: 'bg-white/30'"
/>
<div>
<p class="text-sm font-semibold">Flight</p>
<p class="text-xs text-white/60">
{{ connectionStatus?.flightActive ? 'Flight in progress' : 'No flight detected' }}
</p>
</div>
? 'bg-emerald-400 shadow-[0_0_18px_rgba(16,185,129,0.85)]'
: 'bg-white/30'"
/>
<div>
<p class="text-sm font-semibold">Simulator</p>
<p class="text-xs text-white/60">{{ connectionStatus?.simConnected ? 'Simulator detected' : 'Waiting for simulator' }}</p>
</div>
</div>
<div class="rounded-2xl border border-white/10 bg-[#0B132A]/80 px-5 py-4 text-sm text-white/65">
<div class="flex items-center justify-between">
<span>Connected since</span>
<span class="font-medium text-white">{{ formattedConnectedAt }}</span>
</div>
<div class="mt-2 flex items-center justify-between text-white/60">
<span>Last update</span>
<span class="font-medium text-white">{{ formattedLastStatusAt }}</span>
<div
class="flex items-center gap-3 rounded-2xl border px-4 py-3"
:class="connectionStatus?.flightActive
? 'border-emerald-400/45 bg-emerald-400/10 shadow-[0_0_30px_rgba(16,185,129,0.25)]'
: 'border-white/10 bg-[#0B132A]/75'"
>
<span
class="flex h-3 w-3 items-center justify-center rounded-full"
:class="connectionStatus?.flightActive
? 'bg-emerald-400 shadow-[0_0_18px_rgba(16,185,129,0.85)]'
: 'bg-white/30'"
/>
<div>
<p class="text-sm font-semibold">Flight</p>
<p class="text-xs text-white/60">{{ connectionStatus?.flightActive ? 'Flight in progress' : 'No flight detected' }}</p>
</div>
</div>
</div>
<p v-if="statusLoading" class="mt-6 text-xs uppercase tracking-[0.4em] text-white/45">Refreshing </p>
<p v-if="statusError" class="mt-2 text-sm text-red-300">{{ statusError }}</p>
</section>
</aside>
</div>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<div class="rounded-2xl border border-white/10 bg-[#0B132A]/75 px-4 py-3 text-sm text-white/65">
<p class="text-xs uppercase tracking-[0.28em] text-white/45">Connected since</p>
<p class="mt-1 font-medium text-white">{{ formattedConnectedAt }}</p>
</div>
<div class="rounded-2xl border border-white/10 bg-[#0B132A]/75 px-4 py-3 text-sm text-white/65">
<p class="text-xs uppercase tracking-[0.28em] text-white/45">Last update</p>
<p class="mt-1 font-medium text-white">{{ formattedLastStatusAt }}</p>
</div>
</div>
</div>
<p v-if="statusLoading" class="mt-6 text-xs uppercase tracking-[0.38em] text-white/45">Refreshing </p>
<p v-if="statusError" class="mt-2 text-sm text-red-300">{{ statusError }}</p>
</section>
</template>
</main>
</div>
</template>

View File

@@ -1,10 +1,10 @@
<template>
<div class="relative min-h-screen bg-[#0B1020] text-white">
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top,_rgba(22,187,215,0.12),transparent_55%)]"/>
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_bottom,_rgba(14,165,233,0.18),transparent_65%)]"/>
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top,_rgba(22,187,215,0.15),transparent_55%)]"/>
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_bottom,_rgba(41,45,59,0.4),transparent_65%)]"/>
<div class="relative mx-auto w-full max-w-6xl px-6 py-12 lg:px-12">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<main class="relative mx-auto w-full max-w-4xl px-5 py-12 sm:px-7 lg:px-10">
<nav class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<NuxtLink
to="/"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl border border-white/10 bg-white/5 px-4 py-2 text-sm font-medium text-white transition hover:border-white/20 hover:bg-white/10 sm:w-auto"
@@ -16,167 +16,215 @@
to="/bridge/connect"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-[#292D3B] px-4 py-2 text-sm font-semibold text-white transition hover:bg-[#353a4c] sm:w-auto"
>
Already have a token?
Link a token
</NuxtLink>
</div>
</nav>
<header class="mt-10 space-y-4 text-center sm:text-left">
<span class="inline-flex items-center gap-2 rounded-full bg-[#16BBD7]/10 px-4 py-2 text-xs font-semibold uppercase tracking-[0.45em] text-[#16BBD7]">Bridge</span>
<h1 class="text-3xl font-semibold sm:text-4xl">Download the OpenSquawk Bridge</h1>
<p class="max-w-3xl text-sm text-white/70">
Pick the build for your simulator and let the desktop Bridge handle the rest. Tokens, login, and live status are all baked in.
<header class="mt-10 space-y-5 text-center sm:text-left">
<p class="text-xs font-semibold uppercase tracking-[0.45em] text-[#16BBD7]">OpenSquawk Bridge</p>
<h1 class="text-3xl font-semibold sm:text-4xl">Your simulator, connected in under a minute.</h1>
<p class="mx-auto max-w-2xl text-sm text-white/70 sm:mx-0">
Download the Bridge for your sim, sign in once, and let the desktop app keep your flights in sync.
</p>
<div class="flex flex-col gap-3 sm:flex-row sm:items-center">
<a
href="https://github.com/itsrubberduck/OpenSquawk-MSFS-Bridge/"
target="_blank"
rel="noopener"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-[#16BBD7] px-5 py-3 text-sm font-semibold text-[#0B1020] transition hover:bg-[#13a7c4] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#72d9ea] sm:w-auto"
>
<span aria-hidden="true"></span>
Download for MSFS 2020
</a>
<NuxtLink
to="/bridge/connect"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl border border-white/15 bg-white/5 px-5 py-3 text-sm font-medium text-white transition hover:border-white/30 hover:bg-white/10 sm:w-auto"
>
Already have a code? Link it
</NuxtLink>
</div>
</header>
<div class="mt-12 grid gap-6 lg:grid-cols-[1.2fr_0.8fr]">
<section class="space-y-6">
<div class="rounded-3xl border border-white/10 bg-[#111832]/80 p-8 shadow-[0_30px_80px_rgba(5,10,30,0.55)] backdrop-blur">
<h2 class="text-xl font-semibold">Downloads</h2>
<p class="mt-2 text-sm text-white/70">Short and sweet. Click, install, fly.</p>
<div class="mt-6 grid gap-5 md:grid-cols-2">
<article
v-for="item in downloads"
:key="item.id"
class="flex h-full flex-col justify-between rounded-3xl border border-white/10 bg-[#0B132A]/80 p-6 transition hover:border-white/20 hover:shadow-[0_25px_60px_rgba(12,18,40,0.5)]"
>
<div class="space-y-4">
<span class="flex h-12 w-12 items-center justify-center rounded-2xl bg-white/5 text-[#16BBD7]">
<svg
v-if="item.icon === 'plane'"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-6 w-6"
>
<path d="M2 13l10-3 10 3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 10V3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 14v7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 21l5-7 5 7" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg
v-else-if="item.icon === 'future'"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-6 w-6"
>
<path d="M12 3l3 6 6 1-4.5 4.3L18 21l-6-3-6 3 1.5-6.7L3 10l6-1z" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-6 w-6"
>
<path d="M4 17l8-11 8 11" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 7h16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12h16" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<div class="space-y-2">
<span
class="inline-flex items-center gap-2 rounded-full px-3 py-1 text-[11px] uppercase tracking-[0.35em]"
:class="item.available ? 'bg-[#16BBD7]/20 text-[#16BBD7]' : 'bg-white/10 text-white/60'"
>
{{ item.badge }}
</span>
<h3 class="text-lg font-semibold">{{ item.title }}</h3>
<p class="text-sm text-white/65">{{ item.description }}</p>
</div>
</div>
<div class="mt-6">
<a
v-if="item.available"
:href="item.href"
target="_blank"
rel="noopener"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-[#16BBD7] px-4 py-3 text-sm font-semibold text-[#0B1020] transition hover:bg-[#13a7c4] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#72d9ea]"
<section class="mt-14 space-y-4">
<div class="space-y-2 text-center sm:text-left">
<h2 class="text-2xl font-semibold">Choose your simulator</h2>
<p class="text-sm text-white/65">Every build walks you through the same quick link flow.</p>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<article
v-for="item in downloads"
:key="item.id"
class="flex h-full flex-col justify-between rounded-3xl border border-white/10 bg-[#111832]/80 p-6 shadow-[0_20px_60px_rgba(4,8,24,0.45)] transition hover:border-white/20 hover:shadow-[0_28px_75px_rgba(4,8,24,0.55)]"
>
<div class="space-y-4">
<div class="flex items-center gap-3">
<span class="flex h-11 w-11 items-center justify-center rounded-2xl bg-white/5 text-[#16BBD7]">
<svg
v-if="item.icon === 'plane'"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-6 w-6"
>
<span aria-hidden="true"></span>
Download
</a>
<button
<path d="M2 13l10-3 10 3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 10V3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 14v7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 21l5-7 5 7" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg
v-else-if="item.icon === 'future'"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-6 w-6"
>
<path d="M12 3l3 6 6 1-4.5 4.3L18 21l-6-3-6 3 1.5-6.7L3 10l6-1z" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg
v-else
type="button"
disabled
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-[#292D3B] px-4 py-3 text-sm font-semibold text-white/70 opacity-80"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-6 w-6"
>
<span aria-hidden="true"></span>
Coming soon
</button>
</div>
</article>
</div>
</div>
<div class="rounded-3xl border border-white/10 bg-[#111832]/70 p-8 shadow-[0_20px_60px_rgba(4,8,24,0.45)] backdrop-blur">
<h2 class="text-xl font-semibold">How it works</h2>
<ol class="mt-6 space-y-4 text-sm text-white/70">
<li v-for="step in steps" :key="step.id" class="flex items-start gap-4">
<span class="mt-1 flex h-8 w-8 items-center justify-center rounded-full bg-[#16BBD7]/20 text-[#16BBD7]">{{ step.id }}</span>
<path d="M4 17l8-11 8 11" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 7h16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12h16" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<div>
<p class="font-medium text-white">{{ step.title }}</p>
<p class="text-white/70">{{ step.description }}</p>
<span
class="inline-flex items-center gap-2 rounded-full px-3 py-1 text-[11px] uppercase tracking-[0.32em]"
:class="item.available ? 'bg-[#16BBD7]/15 text-[#16BBD7]' : 'bg-white/10 text-white/55'"
>
{{ item.status }}
</span>
<h3 class="mt-2 text-lg font-semibold">{{ item.title }}</h3>
</div>
</li>
</ol>
<div class="mt-6 rounded-2xl border border-white/10 bg-[#0B132A]/70 px-5 py-4 text-sm text-white/70">
<p class="font-semibold text-white">Status at a glance</p>
<p class="mt-1 text-white/65">The desktop Bridge mirrors your login, shows simulator + flight status, and updates OpenSquawk every few seconds.</p>
</div>
<p class="text-sm text-white/70">{{ item.description }}</p>
</div>
</div>
</section>
<aside class="space-y-6">
<div class="rounded-3xl border border-white/10 bg-[#111832]/80 p-8 shadow-[0_30px_80px_rgba(4,8,24,0.55)] backdrop-blur">
<h2 class="text-xl font-semibold">Requirements</h2>
<ul class="mt-4 space-y-3 text-sm text-white/70">
<li class="flex items-center gap-3">
<span class="h-2 w-2 rounded-full bg-[#16BBD7] shadow-[0_0_12px_rgba(22,187,215,0.8)]"/>
Windows 10/11 (64-bit)
</li>
<li class="flex items-center gap-3">
<span class="h-2 w-2 rounded-full bg-[#16BBD7] shadow-[0_0_12px_rgba(22,187,215,0.8)]"/>
.NET 8 Desktop Runtime
</li>
<li class="flex items-center gap-3">
<span class="h-2 w-2 rounded-full bg-[#16BBD7] shadow-[0_0_12px_rgba(22,187,215,0.8)]"/>
Active OpenSquawk membership
</li>
<li class="flex items-center gap-3">
<span class="h-2 w-2 rounded-full bg-[#16BBD7] shadow-[0_0_12px_rgba(22,187,215,0.8)]"/>
Reliable internet connection
</li>
</ul>
</div>
<div class="rounded-3xl border border-white/10 bg-[#111832]/70 p-8 shadow-[0_20px_60px_rgba(4,8,24,0.45)] backdrop-blur">
<h2 class="text-xl font-semibold">Need help?</h2>
<p class="mt-3 text-sm text-white/70">
Ping us at
<a href="mailto:support@opensquawk.de" class="text-[#16BBD7] underline decoration-dotted underline-offset-4">support@opensquawk.de</a>
and we'll get you flying again.
</p>
<div class="mt-6 rounded-2xl border border-[#16BBD7]/30 bg-[#16BBD7]/10 px-5 py-4 text-sm text-white/75">
<p class="font-semibold text-[#16BBD7]">Tip</p>
<p class="mt-1">Leave the Bridge running in the background. It wakes up when the simulator launches and keeps the data fresh.</p>
<div class="mt-6">
<a
v-if="item.available"
:href="item.href"
target="_blank"
rel="noopener"
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-[#16BBD7] px-4 py-3 text-sm font-semibold text-[#0B1020] transition hover:bg-[#13a7c4] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#72d9ea]"
>
<span aria-hidden="true"></span>
Download
</a>
<button
v-else
type="button"
disabled
class="inline-flex w-full items-center justify-center gap-2 rounded-2xl border border-white/15 bg-white/5 px-4 py-3 text-sm font-semibold text-white/65 opacity-80"
>
<span aria-hidden="true"></span>
Coming soon
</button>
</div>
</article>
</div>
</section>
<section class="mt-16 space-y-6">
<div class="space-y-2 text-center sm:text-left">
<h2 class="text-2xl font-semibold">How linking works</h2>
<p class="text-sm text-white/65">The Bridge walks you through these steps automatically.</p>
</div>
<div class="grid gap-4 sm:grid-cols-3">
<article
v-for="step in steps"
:key="step.id"
class="rounded-3xl border border-white/10 bg-[#111832]/80 p-6 text-left shadow-[0_20px_60px_rgba(4,8,24,0.45)]"
>
<span class="flex h-11 w-11 items-center justify-center rounded-2xl bg-white/5 text-[#16BBD7]">
<svg
v-if="step.icon === 'spark'"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-6 w-6"
>
<path d="M12 3l1.8 5.4H19l-4.6 3.3L16.2 17 12 13.8 7.8 17l1-5.3L4 8.4h5.2L12 3z" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg
v-else-if="step.icon === 'lock'"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-6 w-6"
>
<rect x="5" y="10" width="14" height="10" rx="3" ry="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 10V7a5 5 0 0110 0v3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="h-6 w-6"
>
<path d="M4 17l4-4 3 3 5-5 4 4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 7h16" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<p class="mt-4 text-base font-semibold">{{ step.title }}</p>
<p class="mt-2 text-sm text-white/70">{{ step.description }}</p>
</article>
</div>
</section>
<section class="mt-16 grid gap-4 sm:grid-cols-2">
<div class="rounded-3xl border border-white/10 bg-[#111832]/80 p-6 shadow-[0_18px_55px_rgba(4,8,24,0.45)]">
<h2 class="text-xl font-semibold">Requirements</h2>
<ul class="mt-4 space-y-3 text-sm text-white/70">
<li class="flex items-center gap-3">
<span class="h-2 w-2 rounded-full bg-[#16BBD7] shadow-[0_0_12px_rgba(22,187,215,0.85)]"/>
Windows 10/11 (64-bit)
</li>
<li class="flex items-center gap-3">
<span class="h-2 w-2 rounded-full bg-[#16BBD7] shadow-[0_0_12px_rgba(22,187,215,0.85)]"/>
.NET 8 Desktop Runtime
</li>
<li class="flex items-center gap-3">
<span class="h-2 w-2 rounded-full bg-[#16BBD7] shadow-[0_0_12px_rgba(22,187,215,0.85)]"/>
Active OpenSquawk membership
</li>
<li class="flex items-center gap-3">
<span class="h-2 w-2 rounded-full bg-[#16BBD7] shadow-[0_0_12px_rgba(22,187,215,0.85)]"/>
Reliable internet connection
</li>
</ul>
</div>
<div class="rounded-3xl border border-white/10 bg-[#111832]/80 p-6 shadow-[0_18px_55px_rgba(4,8,24,0.45)]">
<h2 class="text-xl font-semibold">Need help?</h2>
<p class="mt-3 text-sm text-white/70">
Email
<a href="mailto:support@opensquawk.de" class="font-medium text-[#16BBD7] underline decoration-dotted underline-offset-4">support@opensquawk.de</a>
and we&rsquo;ll get you connected.
</p>
<div class="mt-6 rounded-2xl border border-[#16BBD7]/30 bg-[#16BBD7]/10 px-5 py-4 text-sm text-white/75">
<p class="font-semibold text-[#16BBD7]">Tip</p>
<p class="mt-1">Leave the Bridge running. It wakes up with your sim and keeps sending status updates automatically.</p>
</div>
</aside>
</div>
</div>
</div>
</section>
</main>
</div>
</template>
@@ -189,8 +237,8 @@ const downloads = [
{
id: 'msfs2020',
title: 'Microsoft Flight Simulator 2020',
description: 'Stable build with automatic token linking and live updates.',
badge: 'Available now',
description: 'One-click installer for the current simulator with live status built in.',
status: 'Ready now',
available: true,
href: 'https://github.com/itsrubberduck/OpenSquawk-MSFS-Bridge/',
icon: 'plane',
@@ -198,8 +246,8 @@ const downloads = [
{
id: 'msfs2024',
title: 'Microsoft Flight Simulator 2024',
description: 'Integration in progress. The release build is almost ready.',
badge: 'In progress',
description: 'Were updating the Bridge for the new sim launch. Watch this space.',
status: 'In development',
available: false,
href: '#',
icon: 'future',
@@ -207,8 +255,8 @@ const downloads = [
{
id: 'xplane',
title: 'X-Plane 12',
description: 'Shared Bridge architecture. Support arrives later this year.',
badge: 'Planned',
description: 'The same guided setup is coming to X-Plane later this year.',
status: 'Planned',
available: false,
href: '#',
icon: 'xplane',
@@ -218,18 +266,21 @@ const downloads = [
const steps = [
{
id: 1,
title: 'Install the Bridge',
description: 'Download the build, run the installer, and launch the desktop app.',
title: 'Start the Bridge app',
description: 'Launch the desktop Bridge. It drops a secure code and opens this page for you.',
icon: 'spark',
},
{
id: 2,
title: 'Open the connect page',
description: 'Follow the in-app link to opensquawk.de/bridge/connect. The token comes along automatically.',
title: 'Sign in & confirm',
description: 'Log in with your OpenSquawk account and link the code in a single tap.',
icon: 'lock',
},
{
id: 3,
title: 'Link and fly',
description: 'Sign in, link the token, and head back to the simulator. The Bridge keeps sending updates in the background.',
title: 'Fly like normal',
description: 'Head back to the sim. The Bridge keeps your flights and status in sync automatically.',
icon: 'status',
},
]
</script>