- 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 { StrictMode, useEffect, useState, type CSSProperties } from "react";
import { createRoot } from "react-dom/client";
import brand from "../public/data/brand.json";
import productData from "../public/data/products.json";
import { ArrowDown, Contours, Mark, Wordmark } from "./Brand";
import { faqs, flavorCopy } from "./copy";
import "./styles.css";
type Product = (typeof productData.products)[number];
const products: Product[] = productData.products;
const navItems = [
{ href: "#flavors", label: "Flavors" },
{ href: "#story", label: "Story" },
{ href: "#faq", label: "FAQ" },
];
function Header() {
const [solid, setSolid] = useState(false);
useEffect(() => {
const onScroll = () => setSolid(window.scrollY > 40);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
return (
<header className={`site-header${solid ? " is-solid" : ""}`}>
<div className="site-header__inner">
<a className="brand-link" href="#top" aria-label="Fjora, back to top">
<Mark className="brand-link__mark" />
<Wordmark className="brand-link__word" />
</a>
<nav aria-label="Sections">
<ul className="nav-list">
{navItems.map((item) => (
<li key={item.href}>
<a href={item.href}>{item.label}</a>
</li>
))}
</ul>
</nav>
</div>
</header>
);
}
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 first light, snow on the peaks" />
<div className="hero__shade" aria-hidden="true" />
<div className="hero__content">
<Wordmark className="hero__wordmark" title="Fjora" />
<p className="eyebrow eyebrow--light">The winter line</p>
<h1 id="hero-title" className="hero__title">
Taste the North
</h1>
<p className="hero__lede">
Glacial meltwater. Cold-steeped botanicals. Three flavors, bottled once per season.
</p>
<a className="button" href="#flavors">
Choose your flavor
<ArrowDown className="button__icon" />
</a>
</div>
<dl className="hero__facts">
<div>
<dt>Flavors</dt>
<dd>3</dd>
</div>
<div>
<dt>Sugar</dt>
<dd>0</dd>
</div>
<div>
<dt>Bottled</dt>
<dd>Once a season</dd>
</div>
</dl>
</section>
);
}
function FlavorSelector() {
const [selectedId, setSelectedId] = useState(products[0].id);
const selected = products.find((p) => p.id === selectedId) ?? products[0];
const copy = flavorCopy[selected.id];
const index = products.indexOf(selected);
const style = { "--accent": copy.accent, "--tint": copy.tint } as CSSProperties;
return (
<section className="flavors" id="flavors" aria-labelledby="flavors-title" style={style}>
<div className="container">
<div className="section-head">
<p className="eyebrow">Taste the North · Winter</p>
<h2 id="flavors-title" className="section-title">
Three flavors. One season.
</h2>
<p className="section-lede">Choose a bottle to read its botanicals and tasting note.</p>
</div>
<fieldset className="flavor-options">
<legend className="visually-hidden">Choose a flavor</legend>
{products.map((product, i) => {
const c = flavorCopy[product.id];
const checked = product.id === selectedId;
return (
<label
key={product.id}
className={`flavor-option${checked ? " is-selected" : ""}`}
style={{ "--accent": c.accent, "--tint": c.tint } as CSSProperties}
>
<input
type="radio"
name="flavor"
value={product.id}
checked={checked}
onChange={() => setSelectedId(product.id)}
className="visually-hidden"
/>
<span className="flavor-option__photo">
<img src={product.image} alt="" />
</span>
<span className="flavor-option__text">
<span className="flavor-option__num">0{i + 1}</span>
<span className="flavor-option__name">{product.name}</span>
<span className="flavor-option__tag">{c.tagline}</span>
</span>
</label>
);
})}
</fieldset>
<article className="flavor-detail" aria-live="polite" aria-labelledby="flavor-detail-name" data-flavor={selected.id}>
<figure className="flavor-detail__figure" key={`img-${selected.id}`}>
<img src={selected.image} alt={`A bottle of Fjora ${selected.name}`} />
</figure>
<div className="flavor-detail__body" key={`body-${selected.id}`}>
<p className="eyebrow">
Flavor 0{index + 1} of 0{products.length} · {copy.tagline}
</p>
<h3 id="flavor-detail-name" className="flavor-detail__name">
{selected.name}
</h3>
<h4 className="detail-label">Tasting note</h4>
<p className="flavor-detail__note">{copy.note}</p>
<h4 className="detail-label">Botanicals</h4>
<ul className="botanicals">
{selected.botanicals.map((b) => (
<li key={b}>{b}</li>
))}
</ul>
<p className="flavor-detail__meta">Unsweetened · Caffeine-free · Glass bottle, aluminium cap</p>
</div>
</article>
</div>
</section>
);
}
function Story() {
return (
<section className="story" id="story" aria-labelledby="story-title">
<Contours className="story__contours" />
<div className="container story__grid">
<figure className="story__figure">
<img
src="/assets/story-botanicals.webp"
alt="Cloudberries, lime peel, juniper and spruce on a frosted shoreline rock"
loading="lazy"
/>
</figure>
<div className="story__body">
<p className="eyebrow eyebrow--light">Our story</p>
<h2 id="story-title" className="section-title">
Made from cold things.
</h2>
<p className="story__lead">Fjora begins with water from the north. Glacial meltwater, clear and still.</p>
<p>
We add only botanicals. They steep cold, so each one keeps its own voice. Peel, berry, needle and leaf. Nothing
to cover them.
</p>
<p>
Taste the North is our winter line. Three flavors, bottled once per season. When the season ends, so does the
bottling.
</p>
<ol className="story__steps">
<li>
<span className="story__step-num">01</span>
<span className="story__step-title">Glacial meltwater</span>
<span className="story__step-text">The base of every bottle.</span>
</li>
<li>
<span className="story__step-num">02</span>
<span className="story__step-title">Cold-steeped botanicals</span>
<span className="story__step-text">Three in each flavor.</span>
</li>
<li>
<span className="story__step-num">03</span>
<span className="story__step-title">Once per season</span>
<span className="story__step-text">One winter, one bottling.</span>
</li>
</ol>
</div>
</div>
</section>
);
}
function Faq() {
return (
<section className="faq" id="faq" aria-labelledby="faq-title">
<div className="container faq__grid">
<div className="faq__head">
<p className="eyebrow">Questions</p>
<h2 id="faq-title" className="section-title">
Plainly answered.
</h2>
<p className="section-lede">What is in the bottle, and what is not.</p>
</div>
<div className="faq__list">
{faqs.map((item) => (
<details key={item.q} className="faq-item">
<summary>
<span>{item.q}</span>
<span className="faq-item__icon" aria-hidden="true" />
</summary>
<p>{item.a}</p>
</details>
))}
</div>
</div>
</section>
);
}
function Footer() {
return (
<footer className="site-footer">
<div className="container site-footer__inner">
<div className="site-footer__brand">
<Wordmark className="site-footer__word" />
<p>Taste the North. The winter line.</p>
</div>
<nav aria-label="Footer">
<ul className="nav-list nav-list--footer">
{navItems.map((item) => (
<li key={item.href}>
<a href={item.href}>{item.label}</a>
</li>
))}
</ul>
</nav>
<p className="site-footer__contact">{brand.footer_contact}</p>
<p className="site-footer__notice">Fjora is a fictional brand created for UIBenchmark.</p>
</div>
</footer>
);
}
function App() {
return (
<>
<a className="skip-link" href="#flavors">
Skip to flavors
</a>
<Header />
<main>
<Hero />
<FlavorSelector />
<Story />
<Faq />
</main>
<Footer />
</>
);
}
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);