Compare commits

...

5 Commits

Author SHA1 Message Date
Ilia Mashkov dda8ef6368 docs(styles): strip decorative comment banners from app.css
Workflow / build (pull_request) Successful in 1m48s
Workflow / e2e (pull_request) Successful in 1m17s
Workflow / publish (pull_request) Has been skipped
Replace ASCII-art separators (====, box-drawing rules, ---- dashes) with
plain section labels and rewrite the casual one-liners as terse, factual
comments.
2026-06-01 10:11:42 +03:00
Ilia Mashkov d77b51736a fix(styles): default body font to Inter, drop unloaded Karla
The body font-family referenced "Karla", which was never loaded, so
body text silently fell back to system-ui. Point it at the existing
--font-secondary token (Inter + system fallbacks).
2026-06-01 10:06:18 +03:00
Ilia Mashkov 1e16330097 refactor(fonts): drop Google Fonts CDN links, preload self-hosted faces
Remove the googleapis stylesheet, both google preconnects, and the two
dead fontshare preconnects from the document head. Preload the two
render-critical faces (Inter, Space Grotesk) via Vite ?url imports.
Eliminates two third-party origins and the IP leak to Google.
2026-06-01 10:06:12 +03:00
Ilia Mashkov c41016ac5d feat(fonts): self-host interface fonts as vendored latin woff2
Replace Google Fonts CDN delivery of the four UI typefaces (Inter,
Space Grotesk, Space Mono, Syne) with latin-subset woff2 vendored into
app/assets/fonts and wired via a hand-authored @font-face stylesheet.
Variable faces keep wght (Inter also opsz). Vite content-hashes the
binaries for immutable caching.
2026-06-01 10:06:07 +03:00
Ilia Mashkov aa4189f6a8 chore(app): declare *.woff2?url module type for asset imports 2026-06-01 10:05:33 +03:00
12 changed files with 116 additions and 47 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16 -23
View File
@@ -1,5 +1,6 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tw-animate-css"; @import "tw-animate-css";
@import "./fonts.css";
@variant dark (&:where(.dark, .dark *)); @variant dark (&:where(.dark, .dark *));
@@ -216,9 +217,7 @@
/* Monospace label tracking — used in Loader and Footnote */ /* Monospace label tracking — used in Loader and Footnote */
--tracking-wider-mono: 0.2em; --tracking-wider-mono: 0.2em;
/* ============================================ /* Shadow tokens */
SHADOW TOKENS
============================================ */
/* Default resting shadow — equivalent to Tailwind's shadow-sm. Used on /* Default resting shadow — equivalent to Tailwind's shadow-sm. Used on
buttons, sliders, popover triggers in non-floating state. */ buttons, sliders, popover triggers in non-floating state. */
@@ -245,9 +244,7 @@
/* Drawer / overlay shadow — full-strength shadow-2xl. */ /* Drawer / overlay shadow — full-strength shadow-2xl. */
--shadow-overlay: 0 25px 50px -12px rgb(0 0 0 / 0.25); --shadow-overlay: 0 25px 50px -12px rgb(0 0 0 / 0.25);
/* ============================================ /* Motion tokens */
MOTION TOKENS
============================================ */
--duration-fast: 150ms; --duration-fast: 150ms;
--duration-normal: 200ms; --duration-normal: 200ms;
@@ -274,7 +271,7 @@
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-family: "Karla", system-ui, -apple-system, "Segoe UI", Inter, Roboto, Arial, sans-serif; font-family: var(--font-secondary);
font-optical-sizing: auto; font-optical-sizing: auto;
} }
@@ -325,9 +322,7 @@
} }
} }
/* ============================================ /* Design-system utilities.
DESIGN-SYSTEM UTILITIES
============================================
Defined via `@utility` (Tailwind v4) so they integrate with the variant Defined via `@utility` (Tailwind v4) so they integrate with the variant
system (`hover:`, `dark:`, breakpoints) and don't rely on `@apply` system (`hover:`, `dark:`, breakpoints) and don't rely on `@apply`
chains. Colors reference the mode-switching semantic vars defined in chains. Colors reference the mode-switching semantic vars defined in
@@ -362,7 +357,7 @@
} }
} }
/* ── Surface utilities ────────────────────────────────────────── */ /* Surface utilities */
@utility surface-canvas { @utility surface-canvas {
background-color: var(--color-surface); background-color: var(--color-surface);
@@ -391,7 +386,7 @@
border: 1px solid var(--color-border-subtle); border: 1px solid var(--color-border-subtle);
} }
/* ── Shape / layout ───────────────────────────────────────────── */ /* Shape / layout */
@utility flex-center { @utility flex-center {
display: flex; display: flex;
@@ -422,7 +417,7 @@
background-size: 10px 10px; background-size: 10px 10px;
} }
/* ── Typography ───────────────────────────────────────────────── */ /* Typography */
@utility text-label-mono { @utility text-label-mono {
font-family: var(--font-primary); font-family: var(--font-primary);
@@ -431,7 +426,7 @@
text-transform: uppercase; text-transform: uppercase;
} }
/* Global utility - useful across your app */ /* Honor prefers-reduced-motion: collapse animation and transition timing. */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
* { * {
animation-duration: 0.01ms !important; animation-duration: 0.01ms !important;
@@ -440,12 +435,12 @@
} }
} }
/* Performance optimization for collapsible elements */ /* Hint the upcoming height animation on open collapsibles. */
[data-state="open"] { [data-state="open"] {
will-change: height; will-change: height;
} }
/* Smooth focus transitions - good globally */ /* Transition siblings of a focus-visible peer. */
.peer:focus-visible ~ * { .peer:focus-visible ~ * {
transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1); transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
} }
@@ -472,11 +467,9 @@
animation: nudge 10s ease-in-out infinite; animation: nudge 10s ease-in-out infinite;
} }
/* ============================================ /* Scrollbar styling */
SCROLLBAR STYLES
============================================ */
/* ---- Modern API: color + width (Chrome 121+, FF 64+) ---- */ /* Standard API: color + width (Chrome 121+, Firefox 64+). */
@supports (scrollbar-width: auto) { @supports (scrollbar-width: auto) {
* { * {
scrollbar-width: thin; scrollbar-width: thin;
@@ -488,8 +481,8 @@
} }
} }
/* ---- Webkit layer: runs ON TOP in Chrome, standalone in old Safari ---- */ /* WebKit fallback: applies on top of the standard API in Chrome, standalone in
/* Handles things scrollbar-width can't: hiding buttons, exact sizing */ older Safari. Covers what scrollbar-width can't hiding buttons, exact sizing. */
@supports selector(::-webkit-scrollbar) { @supports selector(::-webkit-scrollbar) {
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;
@@ -497,7 +490,7 @@
} }
::-webkit-scrollbar-button { ::-webkit-scrollbar-button {
display: none; /* kills arrows */ display: none; /* hide scrollbar buttons */
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
+78
View File
@@ -0,0 +1,78 @@
/*
Self-hosted interface fonts (latin subset only).
Vendored from @fontsource — see docs/interface-font-selfhost-benchmark.md.
Variable faces (Inter, Space Grotesk) keep their wght axis; Inter also keeps opsz.
url()s are resolved + content-hashed by Vite at build → immutable long-cache.
*/
/* Inter — variable wght + opsz, the body/secondary UI font (--font-secondary) */
@font-face {
font-family: 'Inter';
font-style: normal;
font-display: swap;
font-weight: 100 900;
src: url('../assets/fonts/inter-latin-opsz-normal.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-display: swap;
font-weight: 100 900;
src: url('../assets/fonts/inter-latin-opsz-italic.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Space Grotesk — variable wght, the primary/display UI font (--font-primary) */
@font-face {
font-family: 'Space Grotesk';
font-style: normal;
font-display: swap;
font-weight: 300 700;
src: url('../assets/fonts/space-grotesk-latin-wght-normal.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Space Mono — static 400/700 × roman/italic (--font-mono) */
@font-face {
font-family: 'Space Mono';
font-style: normal;
font-display: swap;
font-weight: 400;
src: url('../assets/fonts/space-mono-latin-400-normal.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Space Mono';
font-style: italic;
font-display: swap;
font-weight: 400;
src: url('../assets/fonts/space-mono-latin-400-italic.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Space Mono';
font-style: normal;
font-display: swap;
font-weight: 700;
src: url('../assets/fonts/space-mono-latin-700-normal.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Space Mono';
font-style: italic;
font-display: swap;
font-weight: 700;
src: url('../assets/fonts/space-mono-latin-700-italic.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Syne — static 800, the logo font (--font-logo) */
@font-face {
font-family: 'Syne';
font-style: normal;
font-display: swap;
font-weight: 800;
src: url('../assets/fonts/syne-latin-800-normal.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
+5
View File
@@ -38,6 +38,11 @@ declare module '*.jpg' {
declare module '*.css'; declare module '*.css';
declare module '*.woff2?url' {
const content: string;
export default content;
}
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv { interface ImportMetaEnv {
+17 -24
View File
@@ -9,6 +9,14 @@ import { ResponsiveProvider } from '$shared/lib';
import { cn } from '$shared/lib'; import { cn } from '$shared/lib';
import { Footer } from '$widgets/Footer'; import { Footer } from '$widgets/Footer';
/*
Preload the two render-critical interface faces (primary + secondary).
`?url` resolves to the content-hashed path Vite emits, so the binary is
fetched immediately rather than waiting for CSS @font-face discovery.
*/
import interWoff2 from '../assets/fonts/inter-latin-opsz-normal.woff2?url';
import spaceGroteskWoff2 from '../assets/fonts/space-grotesk-latin-wght-normal.woff2?url';
import { import {
type Snippet, type Snippet,
onDestroy, onDestroy,
@@ -33,36 +41,21 @@ onDestroy(() => themeManager.destroy());
<svelte:head> <svelte:head>
<link rel="icon" href={G} type="image/svg+xml" /> <link rel="icon" href={G} type="image/svg+xml" />
<link rel="preconnect" href="https://api.fontshare.com" /> <!-- Self-hosted interface fonts (see src/app/styles/fonts/fonts.css). Preload the two critical faces. -->
<link <link
rel="preconnect" rel="preload"
href="https://cdn.fontshare.com" as="font"
crossorigin="anonymous" type="font/woff2"
/> href={interWoff2}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin="anonymous" crossorigin="anonymous"
/> />
<link <link
rel="preload" rel="preload"
as="style" as="font"
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Syne:wght@800&display=swap" type="font/woff2"
href={spaceGroteskWoff2}
crossorigin="anonymous"
/> />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Syne:wght@800&display=swap"
media="print"
onload={(e => ((e.currentTarget as HTMLLinkElement).media = 'all'))}
/>
<noscript>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Syne:wght@800&display=swap"
/>
</noscript>
<title>GlyphDiff | Typography & Typefaces</title> <title>GlyphDiff | Typography & Typefaces</title>
<meta <meta
name="description" name="description"