mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-10-03 10:13:47 +10:00
fix(ui): unify fonts and prevent footer text clipping
This commit is contained in:
@@ -32,8 +32,6 @@
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fontsource-variable/anybody": "^5.3.0",
|
||||
"@fontsource-variable/martian-mono": "^5.3.0",
|
||||
"@lezer/highlight": "^1.2.5",
|
||||
"@lingui/core": "^6.8.0",
|
||||
"@lingui/react": "^6.8.0",
|
||||
|
||||
@@ -199,7 +199,7 @@ export function ApplicationInsights({ applications }: { applications: Applicatio
|
||||
const FLOW_COLORS = ["#a5b4fc", "#818cf8", "#22d3ee", "#fbbf24", "#34d399"];
|
||||
const FLOW_BG = "#0a0a0f";
|
||||
const FLOW_REJECTED = "#fb7185";
|
||||
const FLOW_FONT = '"Hanken Grotesk Variable", "Hanken Grotesk", ui-sans-serif, sans-serif';
|
||||
const FLOW_FONT = "var(--font-ui)";
|
||||
// RxR mark ~18px wide in the 256-unit icon viewBox (the mark's glyphs span y ≈ 36–220).
|
||||
const ICON_SCALE = 18 / 256;
|
||||
|
||||
@@ -211,10 +211,16 @@ function toBase64(buffer: ArrayBuffer): string {
|
||||
}
|
||||
|
||||
// A rasterized SVG (loaded as an <img>) can't reach the page's webfonts, so the exported PNG falls
|
||||
// back to a system font unless the font is inlined. Find the Hanken Grotesk woff2 the app already
|
||||
// back to a system font unless the font is inlined. Find the UI woff2 the app already
|
||||
// loaded (basic-latin subset covers the chart's English labels), base64 it, and return an
|
||||
// @font-face the export SVG can embed. Returns null on any failure so export still proceeds.
|
||||
async function uiFontFace(): Promise<string | null> {
|
||||
const uiFamily = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue("--font-ui")
|
||||
.split(",")[0]
|
||||
?.trim()
|
||||
.replace(/["']/g, "");
|
||||
if (!uiFamily) return null;
|
||||
for (const sheet of Array.from(document.styleSheets)) {
|
||||
let rules: CSSRuleList | undefined;
|
||||
try {
|
||||
@@ -225,7 +231,7 @@ async function uiFontFace(): Promise<string | null> {
|
||||
for (const rule of Array.from(rules ?? [])) {
|
||||
if (!(rule instanceof CSSFontFaceRule)) continue;
|
||||
const family = rule.style.getPropertyValue("font-family").replace(/["']/g, "");
|
||||
if (!family.includes("Hanken Grotesk")) continue;
|
||||
if (family !== uiFamily) continue;
|
||||
if ((rule.style.getPropertyValue("font-style") || "normal") !== "normal") continue;
|
||||
// Keep only the basic-latin subset (covers the chart's English labels). CSSOM normalizes
|
||||
// its range to "U+0-FF" — i.e. "U+" then all-zero start — so match that, not "U+0000".
|
||||
@@ -237,7 +243,7 @@ async function uiFontFace(): Promise<string | null> {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) return null;
|
||||
const buffer = await res.arrayBuffer();
|
||||
return `@font-face{font-family:"Hanken Grotesk Variable";font-style:normal;font-weight:100 900;src:url(data:font/woff2;base64,${toBase64(buffer)}) format("woff2");}`;
|
||||
return `@font-face{font-family:"${family}";font-style:normal;font-weight:${rule.style.getPropertyValue("font-weight")};src:url(data:font/woff2;base64,${toBase64(buffer)}) format("woff2");}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -273,8 +279,9 @@ function PipelineFlow({ insights }: { insights: ReturnType<typeof computeInsight
|
||||
if (!svg) return;
|
||||
// Reveal the export-only watermark on a clone so the on-screen chart stays clean.
|
||||
const clone = svg.cloneNode(true) as SVGSVGElement;
|
||||
clone.style.fontFamily = getComputedStyle(svg).fontFamily;
|
||||
for (const el of clone.querySelectorAll<SVGElement>("[data-export-only]")) el.style.display = "";
|
||||
// Inline the UI font so the rasterized PNG renders in Hanken Grotesk, not a system fallback.
|
||||
// Inline the UI font so the rasterized PNG uses the same type as the app.
|
||||
const fontFace = await uiFontFace();
|
||||
if (fontFace) {
|
||||
const styleEl = document.createElementNS("http://www.w3.org/2000/svg", "style");
|
||||
|
||||
@@ -126,8 +126,8 @@ export function LandingFooter() {
|
||||
|
||||
{/* It fades in by sliding up out of the mask's faded bottom; an opacity fade would fail contrast checks. */}
|
||||
<div aria-hidden="true" className="@container mx-auto mt-14 max-w-[1440px] px-(--gutter)">
|
||||
<div className="h-[23cqw] overflow-hidden [mask-image:linear-gradient(to_bottom,#000_35%,transparent_100%)]">
|
||||
<div className="select-none whitespace-nowrap pb-[.06em] font-anybody font-extrabold font-stretch-[78%] text-[17.5cqw] text-ink leading-[.84] tracking-[-.035em] [transform:translateY(calc((1-clamp(0,(var(--p)-.1)/.8,1))*60%))] [transition:transform_.35s_var(--ease)]">
|
||||
<div className="h-[27cqw] overflow-hidden [mask-image:linear-gradient(to_bottom,#000_35%,transparent_100%)]">
|
||||
<div className="select-none whitespace-nowrap pt-[.12em] pb-[.06em] font-anybody font-extrabold font-stretch-[78%] text-[17.5cqw] text-ink leading-[.84] tracking-[-.035em] [transform:translateY(calc((1-clamp(0,(var(--p)-.1)/.8,1))*60%))] [transition:transform_.35s_var(--ease)]">
|
||||
<span className="block">Reactive</span>
|
||||
<span className="block text-accent-text">Resume</span>
|
||||
</div>
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
* section's progress as --p (0 to 1) and every motion below is derived from it with calc(), so React only re-renders
|
||||
* on coarse steps. Imported by index.css, not the route, so a server-prerendered page is styled at first paint.
|
||||
*/
|
||||
@import "@fontsource-variable/anybody/wdth.css";
|
||||
@import "@fontsource-variable/martian-mono/wdth.css";
|
||||
|
||||
/* Short viewports hide the secondary captions of the pinned scenes so nothing overlaps. */
|
||||
@custom-variant short (@media (height < 720px));
|
||||
|
||||
@theme {
|
||||
--font-anybody: "Anybody Variable", "Noto Sans", system-ui, sans-serif;
|
||||
--font-martian: "Martian Mono Variable", ui-monospace, monospace;
|
||||
/* Existing landing classes share the app's display and UI families. */
|
||||
--font-anybody: var(--font-display);
|
||||
--font-martian: var(--font-ui);
|
||||
|
||||
--animate-breathe: landing-breathe 16s ease-in-out infinite alternate;
|
||||
--animate-drift: landing-drift 90s ease-in-out infinite alternate;
|
||||
|
||||
@@ -24,7 +24,7 @@ export function Homepage() {
|
||||
useScrollScenes(root);
|
||||
|
||||
return (
|
||||
<div ref={root} className="landing relative isolate overflow-x-clip bg-bg font-display text-ink">
|
||||
<div ref={root} className="landing relative isolate overflow-x-clip bg-bg font-ui text-ink">
|
||||
<LandingBackground />
|
||||
<a
|
||||
href="#main"
|
||||
|
||||
@@ -13,10 +13,10 @@ import { Icon } from "@reactive-resume/ui/components/icon";
|
||||
*/
|
||||
|
||||
const fonts = {
|
||||
sans: "'Hanken Grotesk Variable', system-ui, sans-serif",
|
||||
serif: "'Newsreader Variable', Georgia, serif",
|
||||
mono: "'Martian Mono Variable', ui-monospace, monospace",
|
||||
display: "'Anybody Variable', system-ui, sans-serif",
|
||||
sans: "var(--font-ui)",
|
||||
serif: "var(--font-display)",
|
||||
mono: "var(--font-mono)",
|
||||
display: "var(--font-display)",
|
||||
};
|
||||
|
||||
export type SheetTemplate = {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Link } from "@tanstack/react-router";
|
||||
import { Icon } from "@reactive-resume/ui/components/icon";
|
||||
import { cn } from "@reactive-resume/utils/style";
|
||||
|
||||
/** A Martian Mono label: section kickers, scene numbers, small caps metadata. */
|
||||
/** A UI label: section kickers, scene numbers, small caps metadata. */
|
||||
export const labelClass =
|
||||
"font-martian font-medium text-[11px] uppercase leading-[1.4] tracking-[.08em] font-stretch-[87.5%]";
|
||||
|
||||
@@ -39,7 +39,7 @@ type SceneCaptionProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/** A pinned scene's caption: "01 / Write" over a sentence or two in Newsreader. */
|
||||
/** A pinned scene's caption: "01 / Write" over a sentence or two in the display family. */
|
||||
export function SceneCaption({ number, title, children, action, className }: SceneCaptionProps) {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-3", className)}>
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.8.0",
|
||||
"@fontsource-variable/hanken-grotesk": "^5.3.0",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.3.0",
|
||||
"@fontsource-variable/newsreader": "^5.3.0",
|
||||
"@phosphor-icons/web": "^2.1.2",
|
||||
"@reactive-resume/utils": "workspace:*",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "@fontsource-variable/hanken-grotesk";
|
||||
@import "@fontsource-variable/hanken-grotesk/wght-italic.css";
|
||||
@import "@fontsource-variable/newsreader/opsz.css";
|
||||
@import "@fontsource-variable/newsreader/opsz-italic.css";
|
||||
@import "@fontsource-variable/hanken-grotesk";
|
||||
@import "@fontsource-variable/jetbrains-mono";
|
||||
/* Phosphor stays for icons inside resumes (their names are stored in resume data). */
|
||||
@import "@phosphor-icons/web/regular/style.css";
|
||||
|
||||
@@ -134,8 +134,8 @@
|
||||
@theme {
|
||||
--font-display: "Newsreader Variable", Georgia, serif;
|
||||
--font-ui: "Hanken Grotesk Variable", system-ui, sans-serif;
|
||||
--font-sans: "Hanken Grotesk Variable", system-ui, sans-serif;
|
||||
--font-mono: "JetBrains Mono Variable", ui-monospace, monospace;
|
||||
--font-sans: var(--font-ui);
|
||||
--font-mono: ui-monospace, monospace;
|
||||
|
||||
/* 6 chips and small buttons · 8 controls · 10 list items · 12 cards and menus · 16 dialogs · 18 mobile sheets */
|
||||
--radius-sm: 6px;
|
||||
@@ -217,6 +217,10 @@
|
||||
@apply bg-bg text-ink text-sm proportional-nums antialiased selection:bg-accent-soft selection:text-ink;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply font-display;
|
||||
}
|
||||
|
||||
/* A 2px accent ring with a 2px gap on every interactive element; never removed. */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
|
||||
Generated
-24
@@ -528,12 +528,6 @@ importers:
|
||||
'@dnd-kit/utilities':
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2(react@19.3.0)
|
||||
'@fontsource-variable/anybody':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@fontsource-variable/martian-mono':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@lezer/highlight':
|
||||
specifier: ^1.2.5
|
||||
version: 1.2.5
|
||||
@@ -1366,9 +1360,6 @@ importers:
|
||||
'@fontsource-variable/hanken-grotesk':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@fontsource-variable/jetbrains-mono':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@fontsource-variable/newsreader':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
@@ -2752,18 +2743,9 @@ packages:
|
||||
'@floating-ui/utils@0.2.12':
|
||||
resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
|
||||
|
||||
'@fontsource-variable/anybody@5.3.0':
|
||||
resolution: {integrity: sha512-3u/FSV3mGv9xW3x+CdK9lAZYT1Y3NaRA1bexeoIuk9s5QE8sD8OOTcofvpPx0Z6M0Fq1WAJ06ZAw8hivdRQWHQ==}
|
||||
|
||||
'@fontsource-variable/hanken-grotesk@5.3.0':
|
||||
resolution: {integrity: sha512-DOd0FqL+3cH2dfNy0JtSq9tUfxBz5mLmHMjTU0DOr3E9Zo6wxuatCl53DIPNrSPgCsnuKyM739BTIG5glCLirw==}
|
||||
|
||||
'@fontsource-variable/jetbrains-mono@5.3.0':
|
||||
resolution: {integrity: sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==}
|
||||
|
||||
'@fontsource-variable/martian-mono@5.3.0':
|
||||
resolution: {integrity: sha512-0wVODx9ILwNnaYC4XCuSaFRYtj1Prd89MRVaSuv2ng6zLiQkS4SepaWEPefs7vqCZS8sbEOA8jb/4F5KNMnGdg==}
|
||||
|
||||
'@fontsource-variable/newsreader@5.3.0':
|
||||
resolution: {integrity: sha512-rrzYi43qMpbzwuFtf9OkWH8sxAPVPcQQQEwXpPtwaKYeJ8yVg5aLs5kawmo1f2Q1t1M38TLmEKCkGVDsYwgdFw==}
|
||||
|
||||
@@ -10129,14 +10111,8 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.12': {}
|
||||
|
||||
'@fontsource-variable/anybody@5.3.0': {}
|
||||
|
||||
'@fontsource-variable/hanken-grotesk@5.3.0': {}
|
||||
|
||||
'@fontsource-variable/jetbrains-mono@5.3.0': {}
|
||||
|
||||
'@fontsource-variable/martian-mono@5.3.0': {}
|
||||
|
||||
'@fontsource-variable/newsreader@5.3.0': {}
|
||||
|
||||
'@formepdf/core@0.25.0(react@19.3.0)':
|
||||
|
||||
Reference in New Issue
Block a user