- 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, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import brand from "../public/data/brand.json";
import productData from "../public/data/products.json";
import "./styles.css";
const flavorNotes = [
{
character: "Bright. Crisp. Botanical.",
short: "Citrus & evergreen",
note: "Bright lime peel meets the evergreen depth of juniper. Arctic thyme brings a quiet herbal finish. A little citrus. A little wilderness.",
tone: "lime",
},
{
character: "Soft. Floral. A little wild.",
short: "Wild berry & blossom",
note: "The mellow berry notes of cloudberry unfold into delicate chamomile. Rosehip lends a gently tart edge. A softer side of the North.",
tone: "berry",
},
{
character: "Green. Woodsy. Unexpected.",
short: "Forest & green botanicals",
note: "Fresh spruce tip leads the way. Earthy angelica and green black currant leaf follow, for a quietly aromatic taste of the forest.",
tone: "spruce",
},
];
const questions = [
{ question: "Is Fjora sweetened?", answer: "No. Every flavor is unsweetened. No sugar, no sweeteners. Just room for the botanicals to express themselves." },
{ question: "Does Fjora contain caffeine?", answer: "All three flavors are caffeine-free. Glacier Lime, Cloudberry, and Spruce Tip each share that same simple approach." },
{ question: "Can I recycle the bottle?", answer: "Our bottles are single-material glass, finished with an aluminium cap. Both are widely recyclable." },
{ question: "Is the winter collection always available?", answer: "Fjora is sold seasonally, while stock lasts. A taste of the season, here for its own time." },
];
function Arrow({ direction = "right" }: { direction?: "right" | "down" | "up" }) {
return <svg className={`arrow arrow--${direction}`} viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 12h15M13 5l7 7-7 7" stroke="currentColor" strokeWidth="1.4" /></svg>;
}
function NorthStar({ className = "" }: { className?: string }) {
return <svg className={className} viewBox="0 0 32 32" fill="none" aria-hidden="true"><path d="M16 1v30M1 16h30M6 6l20 20M6 26 26 6" stroke="currentColor" strokeWidth="1" /><path d="m16 5 2.5 8.5L27 16l-8.5 2.5L16 27l-2.5-8.5L5 16l8.5-2.5L16 5Z" fill="currentColor" /></svg>;
}
function Wordmark({ className = "" }: { className?: string }) {
return (
<svg className={`wordmark ${className}`} viewBox="0 0 267 67" role="img" aria-label="Fjora">
<g fill="currentColor">
<path d="M2 7h38v12h-2L35 9H13v23h19v2H13v23h8v2H2v-2h5V9H2V7Z" />
<path d="M61 7h23v2h-8v35c0 13-6 20-17 20-7 0-12-4-12-9 0-3 2-5 4-5s4 2 4 4-1 3-2 4c1 2 4 4 7 4 7 0 10-6 10-17V9h-9V7Z" />
<path fillRule="evenodd" d="M121 6c16 0 27 11 27 27s-11 27-27 27-27-11-27-27 11-27 27-27Zm0 2c-13 0-20 10-20 25s7 25 20 25 20-10 20-25-7-25-20-25Z" />
<path fillRule="evenodd" d="M163 7h22c14 0 22 4 22 14 0 8-6 12-15 14l17 22h5v2h-12l-18-24h-10v22h7v2h-18v-2h5V9h-5V7Zm11 2v24h10c11 0 16-4 16-12 0-9-5-12-16-12h-10Z" />
<path fillRule="evenodd" d="m239 5 23 52h5v2h-21v-2h9l-7-17h-23l-7 17h7v2h-17v-2h7l23-52h1Zm-2 9-11 24h21l-10-24Z" />
</g>
</svg>
);
}
function LandscapeMark() {
return <svg viewBox="0 0 80 60" fill="none" aria-hidden="true"><path d="m9 30 16-19 12 12L50 5l22 25M21 16l5 6 5-4M43 15l7 6 6-6M8 40c11-8 21 8 32 0s21 8 32 0M8 49c11-8 21 8 32 0s21 8 32 0" stroke="currentColor" strokeWidth="1.2" /></svg>;
}
function NorthernSeal() {
return (
<div className="northern-seal" aria-hidden="true">
<svg viewBox="0 0 150 150" fill="none">
<defs><path id="seal-circle" d="M75,75m-58,0a58,58 0 1,1 116,0a58,58 0 1,1-116,0" /></defs>
<circle cx="75" cy="75" r="73" stroke="currentColor" strokeOpacity=".45" />
<text fill="currentColor" fontSize="9" letterSpacing="2.45"><textPath href="#seal-circle" textLength="364">GLACIAL WATER · NORDIC BOTANICALS · </textPath></text>
</svg>
<div className="seal-landscape"><LandscapeMark /></div>
</div>
);
}
function BotanicalSprig() {
return <svg className="botanical-sprig" viewBox="0 0 100 140" fill="none" aria-hidden="true"><g stroke="currentColor" strokeWidth="1"><path d="M28 135C39 95 60 71 72 6M43 91C20 89 15 71 14 64c16 0 27 9 29 27Zm9-18C33 68 27 53 28 45c17 4 23 13 24 28Zm8-21C47 44 44 33 45 24c14 5 18 14 15 28Zm8-27c-5-12-2-20 5-25 7 10 5 17-5 25ZM48 83c23 0 35-11 39-25-21 1-33 9-39 25Zm13-28c17-2 27-13 27-24-14 3-24 11-27 24ZM35 112c25 2 40-8 47-23-23-1-37 9-47 23Z" /></g></svg>;
}
function Header() {
const [menuOpen, setMenuOpen] = useState(false);
const menuButton = useRef<HTMLButtonElement>(null);
const header = useRef<HTMLElement>(null);
useEffect(() => {
if (!menuOpen) return;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") { setMenuOpen(false); menuButton.current?.focus(); }
};
const onPointerDown = (event: PointerEvent) => {
if (!header.current?.contains(event.target as Node)) setMenuOpen(false);
};
document.addEventListener("keydown", onKeyDown);
document.addEventListener("pointerdown", onPointerDown);
return () => { document.removeEventListener("keydown", onKeyDown); document.removeEventListener("pointerdown", onPointerDown); };
}, [menuOpen]);
return (
<header className="site-header" ref={header}>
<a className="brand-link" href="#top" aria-label="Fjora, back to top" onClick={() => setMenuOpen(false)}><Wordmark /></a>
<button ref={menuButton} className={`menu-toggle ${menuOpen ? "is-open" : ""}`} aria-expanded={menuOpen} aria-controls="main-navigation" onClick={() => setMenuOpen(!menuOpen)}>{menuOpen ? "Close" : "Menu"}<span className="menu-lines" aria-hidden="true"><span /><span /></span></button>
<nav id="main-navigation" className={`main-navigation ${menuOpen ? "is-open" : ""}`} aria-label="Main navigation">
<a href="#flavors" onClick={() => setMenuOpen(false)}>The flavors</a>
<a href="#story" onClick={() => setMenuOpen(false)}>Our story</a>
<a href="#questions" onClick={() => setMenuOpen(false)}>A few answers</a>
</nav>
<span className="header-note"><span className="status-dot" />Seasonal by nature</span>
</header>
);
}
function Hero() {
return (
<section className="hero" aria-labelledby="campaign-title">
<img className="hero-image" src="/assets/hero-fjord.webp" alt="Winter light falling across snow-covered mountains and the still waters of a Nordic fjord" fetchPriority="high" width="1672" height="941" />
<div className="hero-shade" />
<div className="hero-content">
<p className="eyebrow hero-eyebrow"><NorthStar />A northern kind of refreshment</p>
<h1 id="campaign-title" aria-label={brand.campaign}>Taste the<br /><em>North.</em></h1>
<p className="hero-description">Glacial water. Cold-steeped botanicals.<br />Three expressions of a northern winter.</p>
<a className="primary-link" href="#flavors">Explore the flavors<Arrow direction="down" /></a>
</div>
<div className="hero-bottom"><span>BOTANICAL SPARKLING WATER</span><span className="hero-bottom-rule" /><span>THE WINTER COLLECTION</span></div>
<NorthernSeal />
</section>
);
}
function FlavorSelector() {
const [selected, setSelected] = useState(0);
const product = productData.products[selected];
const notes = flavorNotes[selected];
return (
<section id="flavors" className="flavors content-width section-space" aria-labelledby="flavors-heading">
<div className="section-heading"><div><p className="eyebrow">01 / The winter collection</p><h2 id="flavors-heading">Find your northern <em>note.</em></h2></div><p className="section-intro">Three flavors. Three small escapes.<br />Follow your curiosity.</p></div>
<div className="flavor-options" role="group" aria-label="Choose a seasonal flavor">
{productData.products.map((item, index) => (
<button type="button" key={item.id} className={`flavor-option ${selected === index ? "is-selected" : ""}`} aria-label={item.name} aria-pressed={selected === index} aria-controls="flavor-detail" onClick={() => setSelected(index)}>
<img src={item.image} alt={`${item.name} bottle`} width="1122" height="1402" loading="lazy" />
<span className="option-copy"><span className="option-number">0{index + 1}</span><span className="option-name">{item.name}</span><span className="option-note">{flavorNotes[index].short}</span></span>
<span className="selection-dot" aria-hidden="true">{selected === index && <svg viewBox="0 0 16 16" fill="none"><path d="m4 8 3 3 5-6" stroke="currentColor" strokeWidth="1.4" /></svg>}</span>
</button>
))}
</div>
<div id="flavor-detail" className={`flavor-detail tone-${notes.tone}`} role="region" aria-label="Selected flavor" aria-live="polite" aria-atomic="true">
<div className="detail-photo"><img key={product.image} className="product-image" src={product.image} alt={`Fjora ${product.name} botanical sparkling water with its botanicals`} width="1122" height="1402" loading="lazy" /><span className="photo-index">THE WINTER COLLECTION <span>0{selected + 1} / 03</span></span></div>
<div className="detail-copy" key={product.id}>
<BotanicalSprig /><p className="eyebrow detail-eyebrow">A taste of the North / 0{selected + 1}</p><h3>{product.name}</h3><p className="flavor-character">{notes.character}</p>
<div className="botanicals"><p className="eyebrow">The botanicals</p><ul>{product.botanicals.map((botanical) => <li key={botanical}>{botanical}</li>)}</ul></div>
<div className="tasting-note"><p className="eyebrow">Tasting notes</p><p>{notes.note}</p></div>
<div className="detail-bottom"><NorthStar /><span>Bottled once per season.<br /><span>Here for the winter.</span></span></div>
</div>
</div>
<p className="collection-footnote">Nature sets the rhythm. Our collection follows.<span>Available seasonally, while stock lasts.</span></p>
</section>
);
}
function Story() {
return (
<section id="story" className="story" aria-labelledby="story-heading">
<div className="story-copy"><p className="eyebrow">02 / Our northern roots</p><h2 id="story-heading">From a <em>quieter</em><br />part of the world.</h2><div className="story-prose"><p>Our story begins in the North. With glacial meltwater, cold-steeped botanicals, and a taste for the unhurried.</p><p>With Taste the North, we follow winter’s lead. Three distinct flavors, bottled once per season. Each one an invitation to pause. To notice. To find a little wild in the everyday.</p></div><div className="story-signature"><LandscapeMark /><p>Rooted in the North.<br /><span>Bottled for the season.</span></p></div></div>
<figure className="story-figure"><img src="/assets/story-botanicals.webp" alt="Frost-dusted spruce, cloudberries, lime peel, and dark berries on northern stone" width="1122" height="1402" loading="lazy" /><figcaption>A little closer to nature.</figcaption></figure>
</section>
);
}
function Questions() {
return (
<section id="questions" className="questions content-width section-space" aria-labelledby="questions-heading">
<div className="questions-heading"><p className="eyebrow">03 / Good to know</p><h2 id="questions-heading">A little <em>clarity.</em></h2><p>The essentials, simply put.</p><NorthStar className="questions-star" /></div>
<div className="question-list">{questions.map((item, index) => <details className="question" key={item.question}><summary><span className="question-number">0{index + 1}</span><span>{item.question}</span><span className="expand-icon" aria-hidden="true" /></summary><p>{item.answer}</p></details>)}</div>
</section>
);
}
function Footer() {
return <footer className="site-footer"><div className="footer-top"><p>Northern by nature.</p><a href="#top">Back to the stillness<Arrow direction="up" /></a></div><div className="footer-brand"><Wordmark /><div className="footer-brand-note"><NorthStar /><p>BOTANICAL<br />SPARKLING WATER</p></div></div><div className="footer-bottom"><p>{brand.footer_contact}</p><p>{brand.fictional_notice}</p></div></footer>;
}
function App() {
return <><a className="skip-link" href="#main-content">Skip to content</a><div id="top" /><Header /><main id="main-content"><Hero /><div className="qualities content-width" aria-label="Fjora essentials"><span>Unsweetened</span><NorthStar /><span>Caffeine-free</span><NorthStar /><span>Cold-steeped botanicals</span></div><FlavorSelector /><Story /><Questions /></main><Footer /></>;
}
createRoot(document.getElementById("root")!).render(<App />);