extremely claude-assisted go game based on atproto! working on cleaning up and giving a more unique design, still has a bit of a slop vibe to it.
Something went wrong. Try again.
5.2 kB · 209 lines
Svelte
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210<script lang="ts"> import { onMount } from 'svelte'; import { page } from '$app/stores'; import { fetchUserProfile, type UserProfile } from '$lib/atproto-client'; import ProfileDropdown from './ProfileDropdown.svelte'; import { Card, Modal, Input, Button } from '$lib/components/ui'; import { spectating } from '$lib/stores/spectating';
type Session = { did: string } | null;
let { session }: { session: Session } = $props();
let userProfile: UserProfile | null = $state(null); let showLoginModal = $state(false); let handle = $state(''); let isLoggingIn = $state(false);
// Show login button when: not on homepage, OR on homepage but spectating let isHomepage = $derived($page.url.pathname === '/'); let showLoginButton = $derived(!isHomepage || $spectating);
onMount(async () => { if (session) { userProfile = await fetchUserProfile(session.did); } });
async function login() { if (!handle.trim()) return;
isLoggingIn = true; try { const response = await fetch('/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ handle: handle.trim() }), });
if (!response.ok) { throw new Error(`Login failed: ${response.statusText}`); }
const result = await response.json(); if (result.authorizationUrl) { window.location.href = result.authorizationUrl; } } catch (err) { console.error('Login failed:', err); alert('Login failed. Please try again.'); isLoggingIn = false; } }</script>
<div class="header-wrapper"> <Card variant="large" class="header"> <div class="header-content"> <a href="/" class="logo">☁️ Cloud Go ☁️</a>
<div class="header-right"> {#if session && userProfile} <ProfileDropdown avatar={userProfile.avatar || null} handle={userProfile.handle} did={session.did} /> {:else if session} <!-- Loading profile --> <div class="avatar-placeholder"></div> {:else if showLoginButton} <button class="login-link" onclick={() => showLoginModal = true}>Login</button> {/if} </div> </div> </Card>
<Modal isOpen={showLoginModal} onClose={() => showLoginModal = false}> <div class="login-modal-content"> <h2>Login with @proto</h2> <form onsubmit={(e) => { e.preventDefault(); login(); }}> <actor-typeahead> <Input value={handle} oninput={(e) => handle = e.currentTarget.value} onchange={(e) => handle = e.currentTarget.value} placeholder="your-handle.bsky.social" disabled={isLoggingIn} class="login-input" /> </actor-typeahead> <Button type="submit" disabled={isLoggingIn} variant="primary" class="login-button"> {isLoggingIn ? 'Logging in...' : 'Login'} </Button> </form> </div> </Modal></div>
<style> .header-wrapper { max-width: 1200px; margin: clamp(1rem, 3vw, 2rem) auto clamp(1.5rem, 4vw, 3rem); padding: 0 clamp(1rem, 3vw, 2rem); position: relative; z-index: 100; }
.header { position: relative; }
.header-content { display: flex; align-items: center; justify-content: space-between; padding: clamp(1rem, 2vw, 1.5rem) clamp(1.5rem, 3vw, 2.5rem); max-width: 1200px; margin: 0 auto; }
.logo { font-size: clamp(1.5rem, 4vw, 2.75rem); font-weight: 700; color: var(--sky-slate-dark); text-decoration: none; letter-spacing: -0.02em; transition: color 0.6s ease, transform 0.6s ease; }
.logo:hover { color: var(--sky-apricot-dark); filter: drop-shadow(0 0 8px rgba(229, 168, 120, 0.6)); }
.header-right { display: flex; align-items: center; }
.login-link { color: var(--sky-slate); background: transparent; border: none; cursor: pointer; text-decoration: none; font-weight: 500; font-size: clamp(1rem, 2vw, 1.125rem); padding: 0.75rem 1.25rem; border-radius: 0.5rem; transition: all 0.6s ease; }
.login-link:hover { background: var(--sky-apricot-light); color: var(--sky-apricot-dark); box-shadow: 0 0 12px rgba(229, 168, 120, 0.5); }
.login-modal-content { padding: 2rem; }
.login-modal-content h2 { margin: 0 0 1.5rem 0; color: var(--sky-slate-dark); font-size: 1.75rem; text-align: center; }
.login-modal-content form { display: flex; flex-direction: column; gap: 1rem; }
.login-modal-content :global(.login-input) { width: 100%; }
.login-modal-content :global(.login-button) { width: 100%; }
.avatar-placeholder { width: 80px; height: 80px; border-radius: 50%; background: var(--sky-cloud); animation: pulse 1.5s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }
@media (max-width: 768px) { .avatar-placeholder { width: 60px; height: 60px; } }</style>