Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -967,3 +967,258 @@ a.underline-brutal:hover .arrow {
border-top: 1px solid color-mix(in srgb, var(--ink) 15%, transparent);
margin-block: 2.4em;
}

/* --- mobile menu ------------------------------------------------------
Phone-only (<md) slide-out drawer. See MobileMenu.tsx for the
portal/focus model. */

.mm-toggle {
position: fixed;
top: 24px;
right: var(--frame-gutter);
z-index: 65;
width: 22px;
Comment thread
yigitdot marked this conversation as resolved.
height: 22px;
color: var(--ink);
background: transparent;
border: 0;
padding: 0;
cursor: pointer;
transition: color 400ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.mm-toggle[data-tone="ink"] {
color: var(--paper);
}

.mm-toggle::before,
.mm-toggle::after {
content: "";
position: absolute;
left: 2px;
right: 2px;
height: 1px;
background: currentColor;
transition:
transform 460ms cubic-bezier(0.16, 1, 0.3, 1),
top 460ms cubic-bezier(0.16, 1, 0.3, 1);
}

.mm-toggle::before {
top: 8px;
}
.mm-toggle::after {
top: 14px;
}

.mm-toggle[data-open="true"]::before {
top: 11px;
transform: rotate(45deg);
}
.mm-toggle[data-open="true"]::after {
top: 11px;
transform: rotate(-45deg);
}

.mm-toggle:focus-visible {
outline: 1px dashed currentColor;
outline-offset: 4px;
border-radius: 1px;
}

.mm-overlay {
position: fixed;
inset: 0;
z-index: 55;
background: color-mix(in srgb, var(--ink) 55%, transparent);
backdrop-filter: blur(6px) saturate(1.05);
-webkit-backdrop-filter: blur(6px) saturate(1.05);
opacity: 0;
pointer-events: none;
transition: opacity 480ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.mm-overlay[data-open="true"] {
opacity: 1;
pointer-events: auto;
}

.mm-panel {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: min(88vw, 380px);
z-index: 60;
background: var(--paper);
color: var(--ink);
Comment thread
yigitdot marked this conversation as resolved.
border-left: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
box-shadow: -12px 0 28px -16px color-mix(in srgb, var(--ink) 32%, transparent);
transform: translate3d(100%, 0, 0);
transition: transform 720ms cubic-bezier(0.16, 1, 0.3, 1);
padding: clamp(4.25rem, 16vw, 5.5rem) clamp(1.5rem, 6vw, 2rem)
clamp(1.5rem, 6vw, 2rem);
overflow-y: auto;
/* React sets `inert` on this element when closed — that's what
removes descendants from the tab order and blocks pointer
interaction. aria-hidden is also set as the AT signal. */
}
.mm-panel[data-open="true"] {
transform: translate3d(0, 0, 0);
}

.mm-head {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 2rem;
opacity: 0;
transform: translateY(8px);
}
.mm-panel[data-open="true"] .mm-head {
animation: mm-rise 760ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
animation-delay: 140ms;
}

.mm-head-link {
color: inherit;
text-decoration: none;
transition: color 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.mm-head-link:hover,
.mm-head-link:active {
color: var(--whisper);
}
.mm-head-link:focus-visible {
outline: 1px dashed currentColor;
outline-offset: 4px;
border-radius: 1px;
}

.mm-list {
list-style: none;
padding: 0;
margin: 0;
}

.mm-row {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 14px;
padding: 18px 0;
border-bottom: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
color: inherit;
text-decoration: none;
min-height: 56px;
opacity: 0;
transform: translateY(8px);
}

.mm-panel[data-open="true"] .mm-row {
animation: mm-rise 760ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
animation-delay: calc(var(--mm-i, 0) * 80ms + 220ms);
}

@keyframes mm-rise {
to {
opacity: 1;
transform: translateY(0);
}
}

.mm-label {
font-size: clamp(1.5rem, 6.5vw, 1.875rem);
letter-spacing: -0.025em;
line-height: 1.05;
font-weight: 500;
}

/* Mark renders at zero opacity on every row so the row height stays stable. */
.mm-mark {
width: 14px;
height: 3px;
background: var(--whisper);
opacity: 0;
transform: translateX(-6px);
transition:
opacity 540ms cubic-bezier(0.16, 1, 0.3, 1),
transform 540ms cubic-bezier(0.16, 1, 0.3, 1);
}
.mm-row[data-active="true"] .mm-mark {
opacity: 1;
transform: translateX(0);
}

.mm-row-external .mm-arrow {
font-family:
ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Monaco, "Cascadia Mono",
"Courier New", monospace;
font-size: 16px;
line-height: 1;
color: color-mix(in srgb, var(--ink) 55%, transparent);
transition:
transform 260ms cubic-bezier(0.16, 1, 0.3, 1),
color 260ms cubic-bezier(0.16, 1, 0.3, 1);
}
.mm-row-external:hover .mm-arrow,
.mm-row-external:active .mm-arrow {
transform: translateX(4px);
color: var(--whisper);
}

.mm-list-external {
margin-top: 2.5rem;
}

.mm-foot {
margin-top: 2rem;
color: color-mix(in srgb, var(--ink) 55%, transparent);
}

@media (prefers-reduced-motion: reduce) {
.mm-toggle,
.mm-toggle::before,
.mm-toggle::after {
transition: none;
}
/* Reduced-motion: cross-fade instead of slide. */
.mm-overlay {
transition: opacity 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.mm-panel {
transform: none;
opacity: 0;
transition: opacity 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.mm-panel[data-open="true"] {
transform: none;
opacity: 1;
}
Comment on lines +1184 to +1195
.mm-panel[data-open="true"] .mm-head,
.mm-panel[data-open="true"] .mm-row {
animation: none;
opacity: 1;
transform: none;
}
.mm-mark {
transition: none;
}
}

@supports not (
(backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))
) {
.mm-overlay {
background: color-mix(in srgb, var(--ink) 78%, transparent);
}
}

/* Phones only. Gated in CSS instead of Tailwind's md:hidden because
these custom rules are unlayered and would beat the utilities layer,
leaking the toggle onto desktop. */
@media (min-width: 768px) {
.mm-toggle,
.mm-overlay,
.mm-panel {
display: none;
}
}
26 changes: 22 additions & 4 deletions components/site/Chrome.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"use client";

import { useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import Link from "next/link";
import { links } from "@/lib/links";
import { MobileMenu } from "@/components/site/MobileMenu";

const SECTION_IDS = ["intro", "compare", "method", "faq", "contact"] as const;
// Hash anchors are written `/#section`. Required because this nav also
Expand All @@ -27,8 +28,14 @@ const DARK_SECTIONS: ReadonlySet<(typeof SECTION_IDS)[number]> = new Set([
export function Chrome() {
const [active, setActive] = useState<(typeof SECTION_IDS)[number]>("intro");
const [scrolled, setScrolled] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
const navRef = useRef<HTMLElement>(null);

const handleMobileOpenChange = useCallback(
(open: boolean) => setMobileOpen(open),
[],
);

// Active section = whichever one paints the strip directly under the
// navbar. We collapse the IO root into a 1px horizontal slice at y =
// navbar.offsetHeight via rootMargin — the section currently
Expand Down Expand Up @@ -110,6 +117,12 @@ export function Chrome() {
}, []);

const onDark = DARK_SECTIONS.has(active);
// The toggle is portalled to <body> (see MobileMenu.tsx), so it
// paints on top of the white panel while the drawer is open — force
// it to paper then. The rest of the nav stays at z-50 behind the
// drawer, visible only in the strip the panel doesn't cover, and
// keeps tracking `onDark`.
const toggleTone = mobileOpen ? "paper" : onDark ? "ink" : "paper";

return (
<nav
Expand Down Expand Up @@ -175,14 +188,14 @@ export function Chrome() {
<div className="col-start-3 flex items-center gap-5 justify-self-end">
<a
href={links.docs}
className="meta flex items-center gap-2 no-underline border-b border-current pb-[2px]"
className="meta hidden items-center gap-2 no-underline border-b border-current pb-[2px] md:flex"
>
<span>Docs</span>
<span aria-hidden>→</span>
</a>
<Link
href={links.blog}
className="meta flex items-center gap-2 no-underline border-b border-current pb-[2px]"
className="meta hidden items-center gap-2 no-underline border-b border-current pb-[2px] md:flex"
>
<span>Blog</span>
<span aria-hidden>→</span>
Expand All @@ -191,12 +204,17 @@ export function Chrome() {
href={links.litepaper}
target="_blank"
rel="noopener noreferrer"
className="meta flex items-center gap-2 no-underline"
className="meta hidden items-center gap-2 no-underline md:flex"
style={{ borderBottom: "1px solid currentColor", paddingBottom: 2 }}
>
<span>Litepaper</span>
<span aria-hidden>→</span>
</a>
<MobileMenu
activeSection={active}
tone={toggleTone}
onOpenChange={handleMobileOpenChange}
/>
</div>
</div>
</nav>
Expand Down
Loading