mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-15 06:12:18 +00:00
hero: the wordmark's weight interpolates into the heading cut
The wordmark was Archivo Black — a separate single-weight cut — so it stayed poster-bold after settling into the top stack while every other heading sat at the display 620, and the mismatch read as off. It now renders variable Archivo at the poster extreme (wght 900 / wdth 125%, visually the Black cut), and the hero flip interpolates each letter's weight and width down to the headings' 620 / 118% along its flight — the change lands invisibly inside the motion, and the settled heading matches its neighbors exactly. Reversible with scroll like everything else, and the Archivo Black font download is gone entirely. Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
This commit is contained in:
1 parent
03f4314ae4
commit
356353f8eb
3 files changed
+35
-19
No files matched your search
@@ -1,7 +1,6 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import {
|
||||
Archivo,
|
||||
Archivo_Black,
|
||||
Instrument_Serif,
|
||||
Inter,
|
||||
Geist_Mono,
|
||||
@@ -15,8 +14,12 @@ import "./globals.css";
|
||||
|
||||
const SITE_URL = "https://getartcraft.com";
|
||||
|
||||
// Display face: Archivo with its width axis loaded, so headings can run
|
||||
// slightly expanded (font-stretch) for the industrial-grotesque look.
|
||||
// Display face: variable Archivo with its width axis loaded — headings run
|
||||
// slightly expanded (font-stretch) for the industrial-grotesque look, and
|
||||
// the hero wordmark renders the same family at its poster extreme
|
||||
// (wght 900 / wdth 125%, the Archivo Black look) so the ruler's hero flip
|
||||
// can interpolate weight and width down to the heading setting. A separate
|
||||
// Archivo Black cut could never interpolate.
|
||||
const archivo = Archivo({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-archivo",
|
||||
@@ -24,15 +27,6 @@ const archivo = Archivo({
|
||||
axes: ["wdth"],
|
||||
});
|
||||
|
||||
// Wordmark face: Archivo Black, the display family's poster-weight cut
|
||||
// (a single-weight family of its own, not Archivo at 900).
|
||||
const archivoBlack = Archivo_Black({
|
||||
subsets: ["latin"],
|
||||
weight: "400",
|
||||
variable: "--font-archivo-black",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const instrumentSerif = Instrument_Serif({
|
||||
subsets: ["latin"],
|
||||
weight: "400",
|
||||
@@ -110,7 +104,7 @@ export default function RootLayout({
|
||||
<html
|
||||
lang="en"
|
||||
suppressHydrationWarning
|
||||
className={`${archivo.variable} ${archivoBlack.variable} ${instrumentSerif.variable} ${inter.variable} ${geistMono.variable}`}
|
||||
className={`${archivo.variable} ${instrumentSerif.variable} ${inter.variable} ${geistMono.variable}`}
|
||||
>
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: THEME_SCRIPT }} />
|
||||
|
||||
@@ -4,8 +4,13 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { heroWordmark } from "@/components/ruler/ruler-shared";
|
||||
|
||||
const WORDMARK_TEXT = "ARTCRAFT";
|
||||
const WORDMARK_FONT =
|
||||
"var(--font-archivo-black), var(--font-archivo), system-ui, sans-serif";
|
||||
// Variable Archivo at its poster extreme — the Archivo Black look, but on
|
||||
// the same variable family as every heading, so HeadingFlow can
|
||||
// interpolate weight/width down to the display setting (620 / 118%)
|
||||
// during the hero flip.
|
||||
const WORDMARK_FONT = "var(--font-archivo), system-ui, sans-serif";
|
||||
const WORDMARK_WEIGHT = 900;
|
||||
const WORDMARK_STRETCH = "125%";
|
||||
|
||||
// The poster masthead: the wordmark justified flush across the hero rails in
|
||||
// the site's display type — per-letter spans instead of one text node,
|
||||
@@ -37,6 +42,8 @@ export default function HeroMasthead() {
|
||||
probe.style.cssText =
|
||||
"position:absolute;left:-9999px;top:0;visibility:hidden;white-space:pre;font-size:100px;line-height:1;";
|
||||
probe.style.fontFamily = WORDMARK_FONT;
|
||||
probe.style.fontWeight = String(WORDMARK_WEIGHT);
|
||||
probe.style.fontStretch = WORDMARK_STRETCH;
|
||||
probe.textContent = WORDMARK_TEXT;
|
||||
document.body.appendChild(probe);
|
||||
const w100 = probe.getBoundingClientRect().width;
|
||||
@@ -67,7 +74,13 @@ export default function HeroMasthead() {
|
||||
if (els.length !== WORDMARK_TEXT.length) return;
|
||||
|
||||
const measure = () => {
|
||||
for (const el of els) el.style.transform = "";
|
||||
for (const el of els) {
|
||||
// Reset transforms AND the variable-font morph to the resting cut
|
||||
// before measuring, so a mid-flip resize re-baselines cleanly.
|
||||
el.style.transform = "";
|
||||
el.style.fontWeight = "";
|
||||
el.style.fontStretch = "";
|
||||
}
|
||||
const rects = els.map((el) => el.getBoundingClientRect());
|
||||
heroWordmark.els = els;
|
||||
heroWordmark.baseX = rects.map((r) => r.left + r.width / 2);
|
||||
@@ -119,6 +132,8 @@ export default function HeroMasthead() {
|
||||
className="relative whitespace-pre text-ink-strong"
|
||||
style={{
|
||||
fontFamily: WORDMARK_FONT,
|
||||
fontWeight: WORDMARK_WEIGHT,
|
||||
fontStretch: WORDMARK_STRETCH,
|
||||
fontSize: fontPx || "13vw",
|
||||
lineHeight: 1,
|
||||
}}
|
||||
|
||||
@@ -48,9 +48,10 @@ import {
|
||||
// the top current-heading slot as the wordmark is about to duck under the
|
||||
// nav (title-condenses-into-header), no queue home and no rail ride. From
|
||||
// the stack onward it behaves like any section (demotes when FEATURES
|
||||
// flips in). The hero letters keep their Archivo Black wordmark face
|
||||
// through the whole lifecycle — brand identity, and font families can't
|
||||
// interpolate.
|
||||
// flips in). The hero letters render variable Archivo at the poster
|
||||
// extreme (wght 900 / wdth 125% — the Archivo Black look) and morph to
|
||||
// the headings' display setting (620 / 118%) during the flip: one
|
||||
// variable family, so weight and width genuinely interpolate.
|
||||
|
||||
// A letter's pose on screen. x/y are the letter center in viewport px.
|
||||
type Pose = {
|
||||
@@ -442,6 +443,12 @@ export default function HeadingFlow({
|
||||
}px, ${
|
||||
y - (heroWordmark.baseDocY[i] - scrollY)
|
||||
}px, 0) rotate(${rot}deg) scale(${scale * scaleFix})`;
|
||||
// Variable-font morph: the wordmark's poster cut (wght 900,
|
||||
// wdth 125%) eases into the headings' display setting
|
||||
// (620, 118%) letter by letter with the flight — the weight
|
||||
// difference lands unnoticed inside the motion.
|
||||
el.style.fontWeight = String(Math.round(900 - 280 * e));
|
||||
el.style.fontStretch = `${(125 - 7 * e).toFixed(1)}%`;
|
||||
} else {
|
||||
el.style.transform = `translate3d(${x}px, ${y}px, 0) translate(-50%, -50%) rotate(${rot}deg) scale(${scale})`;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user