- Run record
- loading
Loading this Run…
Loading the Revisions of this Run…
A published source file of Revision 0 of Fjora — Taste the North 1.2.0, shown as text in the browser.
Source is browser-viewable only. There is no download or archive endpoint, and no additional license is granted beyond inspection and research.
import { useEffect, useId, useState } from "react";
import { brandInfo, faq, products, story, tastingNotes } from "./content";
const NAV = [
{ href: "#flavors", label: "Flavors" },
{ href: "#story", label: "Story" },
{ href: "#faq", label: "Questions" },
];
function scrollToId(id: string) {
const el = document.getElementById(id);
if (!el) return;
el.scrollIntoView({ behavior: "smooth", block: "start" });
}
/* ---------- Wordmark (designed in-workspace, inline SVG) ---------- */
function Wordmark({ compact = false }: { compact?: boolean }) {
return (
<svg
className={compact ? "wordmark wordmark--compact" : "wordmark"}
viewBox="0 0 220 56"
role="img"
aria-label="Fjora"
>
<title>Fjora</title>
{/* Mark: two peaks over a still waterline */}
<g fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinejoin="round" strokeLinecap="round">
<path d="M4 38 L18 12 L27 26 L34 16 L50 38" />
<path d="M4 46 H50" opacity="0.9" />
<path d="M14 46 L18 40 L22 46" opacity="0.5" />
<path d="M30 46 L34 40 L38 46" opacity="0.5" />
</g>
{/* Wordmark */}
<text
x="66"
y="41"
fill="currentColor"
fontFamily="'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', Georgia, 'Times New Roman', serif"
fontSize="36"
fontWeight="400"
letterSpacing="7"
>
FJORA
</text>
</svg>
);
}
/* ---------- Header ---------- */
function Header() {
const [open, setOpen] = useState(false);
const menuId = useId();
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open]);
const go = (e: React.MouseEvent<HTMLAnchorElement>, href: string) => {
e.preventDefault();
setOpen(false);
scrollToId(href.slice(1));
history.replaceState(null, "", href);
};
return (
<header className="site-header">
<div className="container site-header__inner">
<a
className="site-header__brand"
href="#top"
onClick={(e) => go(e, "#top")}
aria-label="Fjora, back to top"
>
<Wordmark compact />
</a>
<button
type="button"
className="nav-toggle"
aria-expanded={open}
aria-controls={menuId}
onClick={() => setOpen((v) => !v)}
>
<span className="nav-toggle__label">{open ? "Close" : "Menu"}</span>
<span className="nav-toggle__icon" aria-hidden="true">
<span />
<span />
</span>
</button>
<nav
id={menuId}
className={open ? "site-nav site-nav--open" : "site-nav"}
aria-label="Sections"
>
<ul>
{NAV.map((item) => (
<li key={item.href}>
<a href={item.href} onClick={(e) => go(e, item.href)}>
{item.label}
</a>
</li>
))}
</ul>
</nav>
</div>
</header>
);
}
/* ---------- Hero ---------- */
function Hero() {
return (
<section className="hero" id="top" aria-labelledby="hero-title">
<img
className="hero__image"
src="/assets/hero-fjord.webp"
alt="A still fjord at dawn, snow-covered peaks reflected in the water."
width={1672}
height={941}
fetchPriority="high"
/>
<div className="hero__veil" aria-hidden="true" />
<div className="container hero__content">
<div className="hero__brand">
<Wordmark />
</div>
<p className="hero__eyebrow">Seasonal line. Bottled once.</p>
<h1 className="hero__title" id="hero-title">
Taste the North
</h1>
<p className="hero__lead">
Glacial meltwater. Cold-steeped botanicals. Three flavors for the
long light of winter.
</p>
<a
className="button button--primary"
href="#flavors"
onClick={(e) => {
e.preventDefault();
scrollToId("flavors");
history.replaceState(null, "", "#flavors");
}}
>
Choose a flavor
<svg viewBox="0 0 20 20" width="18" height="18" aria-hidden="true">
<path
d="M10 3v13M4.5 10.5 10 16l5.5-5.5"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</a>
</div>
</section>
);
}
/* ---------- Flavor selector ---------- */
function Flavors() {
const [selectedId, setSelectedId] = useState(products[0].id);
const selected = products.find((p) => p.id === selectedId) ?? products[0];
const note = tastingNotes[selected.id];
const index = products.findIndex((p) => p.id === selected.id);
return (
<section className="section flavors" id="flavors" aria-labelledby="flavors-title">
<div className="container">
<header className="section__head">
<p className="eyebrow">The seasonal line</p>
<h2 className="section__title" id="flavors-title">
Three flavors. One winter.
</h2>
<p className="section__lead">
Each one is built from three botanicals, steeped cold. Pick one.
</p>
</header>
<div className="flavors__layout">
<div
className="flavor-options"
role="group"
aria-label="Choose a flavor"
>
{products.map((p, i) => {
const isSelected = p.id === selected.id;
return (
<button
key={p.id}
type="button"
className={isSelected ? "flavor-option is-selected" : "flavor-option"}
aria-pressed={isSelected}
onClick={() => setSelectedId(p.id)}
data-flavor={p.id}
>
<span className="flavor-option__index" aria-hidden="true">
{String(i + 1).padStart(2, "0")}
</span>
<img
src={p.image}
alt=""
width={1122}
height={1402}
className="flavor-option__image"
/>
<span className="flavor-option__name">{p.name}</span>
<span className="flavor-option__botanicals">
{p.botanicals.join(" · ")}
</span>
</button>
);
})}
</div>
<div className="flavor-detail" aria-live="polite" data-selected={selected.id}>
<div className="flavor-detail__media">
<img
key={selected.id}
src={selected.image}
alt={`A bottle of Fjora ${selected.name}.`}
width={1122}
height={1402}
className="flavor-detail__image"
/>
</div>
<div className="flavor-detail__body">
<p className="eyebrow">
Flavor {String(index + 1).padStart(2, "0")} of {products.length}
</p>
<h3 className="flavor-detail__name">{selected.name}</h3>
<p className="flavor-detail__lead">{note.lead}</p>
<h4 className="flavor-detail__label">Botanicals</h4>
<ul className="botanical-list">
{selected.botanicals.map((b) => (
<li key={b}>{b}</li>
))}
</ul>
<h4 className="flavor-detail__label">Tasting note</h4>
<p className="flavor-detail__note">{note.note}</p>
<ul className="flavor-detail__facts" aria-label="Product facts">
<li>Unsweetened</li>
<li>Caffeine-free</li>
<li>Glass bottle</li>
</ul>
</div>
</div>
</div>
</div>
</section>
);
}
/* ---------- Story ---------- */
function Story() {
return (
<section className="section story" id="story" aria-labelledby="story-title">
<div className="container story__layout">
<figure className="story__media">
<img
src="/assets/story-botanicals.webp"
alt="Cloudberries, juniper berries, lime peel and a frosted spruce sprig on wet rock by the sea."
width={1122}
height={1402}
/>
<figcaption>Cloudberry, juniper, lime peel, spruce. Picked cold.</figcaption>
</figure>
<div className="story__body">
<p className="eyebrow">{story.eyebrow}</p>
<h2 className="section__title" id="story-title">
{story.title}
</h2>
{story.paragraphs.map((p) => (
<p key={p} className="story__text">
{p}
</p>
))}
<dl className="story__facts">
<div>
<dt>Water</dt>
<dd>Glacial meltwater</dd>
</div>
<div>
<dt>Method</dt>
<dd>Cold-steeped</dd>
</div>
<div>
<dt>Season</dt>
<dd>Winter, bottled once</dd>
</div>
</dl>
</div>
</div>
</section>
);
}
/* ---------- FAQ ---------- */
function FaqItem({ q, a, i }: { q: string; a: string; i: number }) {
const [open, setOpen] = useState(false);
const panelId = useId();
const buttonId = useId();
return (
<li className={open ? "faq-item is-open" : "faq-item"}>
<h3 className="faq-item__heading">
<button
type="button"
id={buttonId}
className="faq-item__trigger"
aria-expanded={open}
aria-controls={panelId}
onClick={() => setOpen((v) => !v)}
>
<span className="faq-item__index" aria-hidden="true">
{String(i + 1).padStart(2, "0")}
</span>
<span className="faq-item__question">{q}</span>
<span className="faq-item__icon" aria-hidden="true">
<span />
<span />
</span>
</button>
</h3>
<div
id={panelId}
role="region"
aria-labelledby={buttonId}
className="faq-item__panel"
hidden={!open}
>
<p>{a}</p>
</div>
</li>
);
}
function Faq() {
return (
<section className="section faq" id="faq" aria-labelledby="faq-title">
<div className="container faq__layout">
<header className="faq__head">
<p className="eyebrow">Questions</p>
<h2 className="section__title" id="faq-title">
Plain answers.
</h2>
<p className="section__lead">Short, like the days up here.</p>
</header>
<ul className="faq-list">
{faq.map((item, i) => (
<FaqItem key={item.q} q={item.q} a={item.a} i={i} />
))}
</ul>
</div>
</section>
);
}
/* ---------- Footer ---------- */
function Footer() {
return (
<footer className="site-footer">
<div className="container site-footer__inner">
<div className="site-footer__brand">
<Wordmark compact />
<p className="site-footer__tag">Taste the North. Seasonal, while stock lasts.</p>
</div>
<address className="site-footer__contact">{brandInfo.footer_contact}</address>
<p className="site-footer__notice">{brandInfo.fictional_notice}</p>
</div>
</footer>
);
}
export default function App() {
return (
<>
<a className="skip-link" href="#flavors">
Skip to flavors
</a>
<Header />
<main>
<Hero />
<Flavors />
<Story />
<Faq />
</main>
<Footer />
</>
);
}