Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176<script lang="ts"> import { resolve } from "$app/paths"; import XIcon from "$icon/x"; import Logo from "#lib/components/ui/Logo.svelte"; import SiteLink from "#lib/components/landing/shell/SiteLink.svelte"; import { LOGIN, NAV, SIGNUP } from "#lib/components/landing/shell/links.ts"; import Button from "#lib/components/ui/Button.svelte";
/** * The mobile menu: a full-screen sheet over a blurred, faintly lit * ground — the links set large on the left, log in and join side by side at the foot. A * modal <dialog>, so focus stays in it and Escape closes it; the page under it holds * still. Following a link closes it. */ interface Props { id: string; open: boolean; }
let { id, open = $bindable() }: Props = $props();
let dialog: HTMLDialogElement | undefined = $state(); let closeButton: HTMLAnchorElement | HTMLButtonElement | undefined = $state();
$effect(() => { if (!dialog) return; if (open && !dialog.open) { dialog.showModal(); // showModal() focuses the first focusable element, the logo; start on close instead. closeButton?.focus(); document.documentElement.style.overflow = "hidden"; } else if (!open && dialog.open) dialog.close(); });
function onclose() { open = false; document.documentElement.style.overflow = ""; }
// Past the breakpoint the menu has nothing to show. function onresize() { if (open && matchMedia("(width >= 64rem)").matches) open = false; }</script>
<svelte:window {onresize} />
<dialog {id} bind:this={dialog} class="menu" aria-label="Menu" {onclose}> <div class="sheet"> <div class="head"> <a class="brand" href={resolve("/")} onclick={() => (open = false)}> <Logo wordmark class="h-8" /> </a> <Button size="large" icon={XIcon} aria-label="Close menu" bind:ref={closeButton} onclick={() => (open = false)} /> </div>
<nav aria-label="Main"> <ul> {#each NAV as link, i (link.label)} <li style:--i={i}> <SiteLink {link} onclick={() => (open = false)} /> </li> {/each} </ul> </nav>
<div class="actions"> <Button size="large" href={resolve(LOGIN)}>Sign in</Button> <Button variant="primary" size="large" href={resolve(SIGNUP)}>Join now</Button> </div> </div></dialog>
<style> .menu { position: fixed; inset: 0; width: 100%; max-width: none; height: 100%; max-height: none; margin: 0; padding: 0; border: 0; color: var(--text); overscroll-behavior: contain; background: var(--bg); } .menu::backdrop { background: transparent; } .menu[open] { animation: in 0.28s var(--ease-out) both; }
.sheet { display: flex; flex-direction: column; min-height: 100%; box-sizing: border-box; padding: calc(env(safe-area-inset-top, 0px) + 16px) 20px calc(env(safe-area-inset-bottom, 0px) + 24px); }
.head { display: flex; align-items: center; justify-content: space-between; height: 34px; } .brand { display: flex; align-items: center; translate: 0 1.25px; } nav { flex: 1; display: flex; align-items: center; } ul { display: grid; gap: 14px; padding: 0 2px; list-style: none; } /* Tablet: the sheet is wide, so the links stand in its middle; phones keep them flush left. */ @media (width >= 48rem) { nav { justify-content: center; } ul { justify-items: center; text-align: center; } } li { animation: rise 0.42s var(--ease-out) both; animation-delay: calc(80ms + var(--i) * 45ms); } li :global(a) { font-family: var(--font-display); font-feature-settings: normal; font-size: var(--text-menu); font-weight: 420; line-height: 1.3; letter-spacing: -0.02em; color: var(--text); }
.actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; animation: rise 0.42s var(--ease-out) 0.25s both; }
@keyframes in { from { opacity: 0; } } @keyframes rise { from { opacity: 0; translate: 0 10px; } }</style>