Something went wrong. Try again.
A Takuzu game native to the AT Protocol. atkuzu.tomplanche.com
binairo atproto game takuzu
Something went wrong. Try again.
5.0 kB · 220 lines
Svelte
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221<script lang="ts"> import { enhance } from "$app/forms"; import { resolve } from "$app/paths"; import Button from "$lib/components/Button.svelte"; import { loginModal } from "$lib/state/login-modal.svelte"; import { rulesModal } from "$lib/state/rules-modal.svelte"; import { themeModal } from "$lib/state/theme-modal.svelte";
type Props = { session: App.Session | null; };
let { session }: Props = $props();
// The Sign Up link below keeps href="/login" as a no-JS fallback; this handler // just intercepts the click to open the modal instead when JS is available. const openLoginModal = (event: MouseEvent) => { event.preventDefault(); loginModal.show(); };</script>
{#snippet logoutIcon()} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="m16 17 5-5-5-5" /> <path d="M21 12H9" /> <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /> </svg>{/snippet}
{#snippet themeIcon()} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <circle cx="9" cy="12" r="7" /> <circle cx="15" cy="12" r="7" /> </svg>{/snippet}
{#snippet signUpIcon()} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /> <circle cx="9" cy="7" r="4" /> <line x1="19" x2="19" y1="8" y2="14" /> <line x1="22" x2="16" y1="11" y2="11" /> </svg>{/snippet}
<header class="site-header"> <div class="container site-header__inner"> <div class="site-header__left"> <a class="brand" href={resolve("/")}>atkuzu</a> <a class="nav-link" href={resolve("/daily")}>Daily</a> <button aria-label="How to play" class="nav-link nav-link--btn" type="button" onclick={() => rulesModal.show()} > <span class="nav-link__short" aria-hidden="true">?</span> <span class="nav-link__full">How to play</span> </button> <button class="theme-btn" type="button" aria-label="Board theme" onclick={() => themeModal.show()} > {@render themeIcon()} </button> </div> {#if session} <nav class="navbar"> <form method="POST" action="/logout" use:enhance> <Button variant="secondary" icon={logoutIcon} type="submit">Log out</Button> </form> </nav> {:else} <nav class="navbar"> <Button href={resolve("/login")} icon={signUpIcon} letter="S" onclick={openLoginModal}> Sign Up </Button> </nav> {/if} </div></header>
<style lang="scss"> .site-header { position: sticky; top: 0; z-index: 10; border-bottom: 1px solid var(--border); background: color-mix(in oklab, var(--bg) 75%, transparent); backdrop-filter: blur(8px);
&__inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; row-gap: var(--space-2); column-gap: var(--space-4); padding-block: var(--space-3);
@media (min-width: 640px) { padding-block: var(--space-4); } } }
.site-header__left { display: flex; align-items: center; gap: var(--space-3);
@media (min-width: 640px) { gap: var(--space-6); } }
.brand { font-weight: 700; font-size: 1.1rem; letter-spacing: -0.02em; white-space: nowrap; color: var(--fg);
&:hover { color: var(--fg); } }
.nav-link { font-size: 0.9rem; white-space: nowrap; color: var(--muted);
&:hover { color: var(--fg); } }
.nav-link--btn { border: none; background: none; padding: 0; font-family: inherit; cursor: pointer; }
.nav-link__short { display: inline;
@media (min-width: 640px) { display: none; } }
.nav-link__full { display: none;
@media (min-width: 640px) { display: inline; } }
.theme-btn { display: inline-flex; align-items: center; justify-content: center; width: 1.5rem; height: 1.5rem; border: none; background: none; padding: 0; color: var(--muted); cursor: pointer;
:global(svg) { width: 100%; height: 100%; }
&:hover { color: var(--fg); } }
.navbar { display: flex; align-items: center; gap: var(--space-4); // Keeps this pinned to the row's right edge even when it wraps onto its own line // (space-between only has an effect when there's another item left to space against). margin-left: auto; }</style>