Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/atomic-family-font-opt-in.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@devslab/site-kit": patch
---

Add an opt-in atomic Korean UI font stylesheet, additive pure preload exports, and reproducible font-only overlays while preserving legacy subset CSS and asset paths.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"check:consumers": "node scripts/check-consumers.mjs",
"verify": "pnpm run build && pnpm run check",
"verify:source:stage1-2": "node --test packages/compatibility-canary/tests/contracts.test.mjs packages/compatibility-canary/tests/application-contracts.test.mjs tests/foundation-contracts.test.mjs tests/react-packages-contracts.test.mjs",
"verify:source:stage3-4": "node --test tests/dds-solid-contracts.test.mjs tests/site-kit-core.test.mjs tests/site-kit-consent.test.mjs tests/site-kit-publisher.test.mjs tests/site-kit-contracts.test.mjs tests/site-kit-worker.test.mjs",
"verify:source:stage3-4": "node --test tests/dds-solid-contracts.test.mjs tests/site-kit-core.test.mjs tests/site-kit-consent.test.mjs tests/site-kit-publisher.test.mjs tests/site-kit-contracts.test.mjs tests/site-kit-worker.test.mjs tests/font-overlay.test.mjs",
"storybook": "pnpm run build && storybook dev -p 6006",
"build-storybook": "pnpm run build && storybook build",
"verify:canary:dependencies": "pnpm --filter @devslab/compatibility-canary run verify:dependencies",
Expand Down
13 changes: 13 additions & 0 deletions packages/site-kit/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,3 +511,16 @@ import하는 소비자는 kit 전체로 클라이언트 JS ~150 KB가 아니라
`pnpm check`가 최소 소비자(`fixtures/bundle-probe`)를 빌드해 국기 본문이 메인
청크로 되돌아오면 실패시킨다. 본문은 서버 렌더 HTML에는 여전히 실린다 —
가져오는 파일로 옮기는 것은 별도 결정이다.


### Atomic UI font opt-in

Keep legacy fonts.css for consumers that require its subset-and-swap behavior. Products that need stable first-paint UI text can replace that import with @devslab/site-kit/fonts-atomic.css. Import one stylesheet, not both. The opt-in keeps the same DDS family names and fallback token stack. It uses one complete unmodified Pretendard 1.3.9 WOFF2 without unicode-range/local(), and optional display for Geist, Geist Mono and Pretendard. A slow first visit may retain system fallback for that page; it does not promise that every visit uses the custom font.

Use the pure ESM @devslab/site-kit/fonts subpath and fontPreloadLinks([geistLatinUrl, fullKoreanUrl]) in the initial HTML/head. Vite application imports use @devslab/site-kit/fonts/geist/geist-latin-wght-normal.woff2?url and @devslab/site-kit/fonts/pretendard/PretendardVariable.woff2?url, so preload and CSS refer to identical content-hashed files. FAMILY_FONT_PRELOAD_FILE is the existing Latin path; FAMILY_FONT_ATOMIC_FILE is the complete Korean path; FAMILY_FONT_MONO_FILE is available when the initial screen uses mono text. Plain HTML/Vite consumers may generate head links from final emitted asset URLs in their HTML build step. Late client-side JS is not an early preload.

fontPreloadLinks still accepts only same-origin absolute WOFF2 URLs and supplies type font/woff2 plus anonymous CORS mode. The CSS keeps external same-origin URLs; no inline/data font or CSP expansion is required. Content-hashed public WOFF2 caching belongs to the consuming application asset response. Do not change HTML, API or account cache policy as part of a font cache rule.

Both stylesheets and the complete font are generated/verified from fonts/manifest.json. The full font is 2,057,688 bytes, SHA256 9599f12fd42fc0bce1cd50b47a0c022e108d7aa64dd0d1bb0ed44f3282d900b4, from pretendard@1.3.9 dist/web/variable/woff2/PretendardVariable.woff2. It shares the packaged OFL-1.1 license with the preserved upstream subsets. This opt-in does not copy application-specific inline critical fonts or calibrated local font metrics; consumers must verify real first-paint glyphs/layout under cold, warm and delayed font loading.

The atomic stylesheet also overrides only the sans token’s Korean entry with the CSS alias `DDS Pretendard Atomic`. Keep this stylesheet after the token stylesheet. The original font bytes, all other fallback entries, and the legacy `fonts.css` remain unchanged; an installed `Pretendard` face cannot enter the optional fallback later.
13 changes: 13 additions & 0 deletions packages/site-kit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,3 +490,16 @@ does not exist.
`LocaleMenu` renders a native `<select>` by default. `variant="flag"` renders a `<details>` disclosure whose trigger is the current locale's flag and whose rows are flag + native-name links — it works without JavaScript; Solid adds Escape-to-close and the `onLocaleChange(locale, href)` callback. `SiteHeader` forwards `localeVariant`. Flag data (`FLAG_COUNTRY`, `LOCALE_FLAGS`, `flagFor`, `flagCountryFor`) is exported from `@devslab/site-kit/flags`, a dedicated subpath — not the runtime-neutral `.` entry — because the vendored artwork is ~110 KB of SVG and most consumers never render a flag menu.

The flag menu keeps that artwork out of the browser bundle too. Each menu renders one `<symbol>` sprite (one body per country) and every flag is an `<svg><use href="#…">` of it, so a locale change on the client only swaps an `href`. The sprite's markup is written by the server build; hydration adopts it, and the browser build reaches the bodies only through a dynamic `import()` — its own chunk, `dist/flag-bodies.js` — taken when a flag menu renders with no server HTML (a client-only app, a jsdom test). A consumer that imports `SiteHeader` therefore ships ~40 KB of client JS for the whole kit rather than ~150 KB; `pnpm check` builds a minimal consumer (`fixtures/bundle-probe`) and fails if a flag body ever returns to the main chunk. The bodies still sit in the server-rendered HTML — moving them to fetched files is a separate decision. The artwork is vendored from flag-icons (MIT, `flags/LICENSE-flag-icons.txt`). Flags are site-kit data, not `dds-icons` entries, because the icon set's contract requires single-colour `currentColor` strokes.


### Atomic UI font opt-in

Keep legacy fonts.css for consumers that require its subset-and-swap behavior. Products that need stable first-paint UI text can replace that import with @devslab/site-kit/fonts-atomic.css. Import one stylesheet, not both. The opt-in keeps the same DDS family names and fallback token stack. It uses one complete unmodified Pretendard 1.3.9 WOFF2 without unicode-range/local(), and optional display for Geist, Geist Mono and Pretendard. A slow first visit may retain system fallback for that page; it does not promise that every visit uses the custom font.

Use the pure ESM @devslab/site-kit/fonts subpath and fontPreloadLinks([geistLatinUrl, fullKoreanUrl]) in the initial HTML/head. Vite application imports use @devslab/site-kit/fonts/geist/geist-latin-wght-normal.woff2?url and @devslab/site-kit/fonts/pretendard/PretendardVariable.woff2?url, so preload and CSS refer to identical content-hashed files. FAMILY_FONT_PRELOAD_FILE is the existing Latin path; FAMILY_FONT_ATOMIC_FILE is the complete Korean path; FAMILY_FONT_MONO_FILE is available when the initial screen uses mono text. Plain HTML/Vite consumers may generate head links from final emitted asset URLs in their HTML build step. Late client-side JS is not an early preload.

fontPreloadLinks still accepts only same-origin absolute WOFF2 URLs and supplies type font/woff2 plus anonymous CORS mode. The CSS keeps external same-origin URLs; no inline/data font or CSP expansion is required. Content-hashed public WOFF2 caching belongs to the consuming application asset response. Do not change HTML, API or account cache policy as part of a font cache rule.

Both stylesheets and the complete font are generated/verified from fonts/manifest.json. The full font is 2,057,688 bytes, SHA256 9599f12fd42fc0bce1cd50b47a0c022e108d7aa64dd0d1bb0ed44f3282d900b4, from pretendard@1.3.9 dist/web/variable/woff2/PretendardVariable.woff2. It shares the packaged OFL-1.1 license with the preserved upstream subsets. This opt-in does not copy application-specific inline critical fonts or calibrated local font metrics; consumers must verify real first-paint glyphs/layout under cold, warm and delayed font loading.

The atomic stylesheet also overrides only the sans token’s Korean entry with the CSS alias `DDS Pretendard Atomic`. Keep this stylesheet after the token stylesheet. The original font bytes, all other fallback entries, and the legacy `fonts.css` remain unchanged; an installed `Pretendard` face cannot enter the optional fallback later.
116 changes: 116 additions & 0 deletions packages/site-kit/fonts-atomic.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/* Generated atomic opt-in by scripts/build-fonts.mjs. Legacy fonts.css is unchanged.
* Full Pretendard uses a distinct CSS alias and one optional loading decision.
* Only the opt-in sans token alias changes; installed Pretendard cannot become a late fallback.
* Same-origin relative WOFF2 URLs preserve font-src 'self'; no local() or inline data fonts.
*/

:root {
--dds-font-family-sans: Geist, "DDS Pretendard Atomic", -apple-system, BlinkMacSystemFont, system-ui, Roboto, 'Helvetica Neue', 'Segoe UI', 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
}

@font-face {
font-family: "Geist";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist/geist-cyrillic-ext-wght-normal.woff2") format("woff2");
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

@font-face {
font-family: "Geist";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist/geist-cyrillic-wght-normal.woff2") format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
font-family: "Geist";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist/geist-vietnamese-wght-normal.woff2") format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

@font-face {
font-family: "Geist";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist/geist-latin-ext-wght-normal.woff2") format("woff2");
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
font-family: "Geist";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist/geist-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;
}

@font-face {
font-family: "Geist Mono";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist-mono/geist-mono-cyrillic-ext-wght-normal.woff2") format("woff2");
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

@font-face {
font-family: "Geist Mono";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist-mono/geist-mono-cyrillic-wght-normal.woff2") format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
font-family: "Geist Mono";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist-mono/geist-mono-symbols2-wght-normal.woff2") format("woff2");
unicode-range: U+2000-2001, U+2004-2008, U+200A, U+23B8-23BD, U+2500-259F;
}

@font-face {
font-family: "Geist Mono";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist-mono/geist-mono-vietnamese-wght-normal.woff2") format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

@font-face {
font-family: "Geist Mono";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist-mono/geist-mono-latin-ext-wght-normal.woff2") format("woff2");
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
font-family: "Geist Mono";
font-style: normal;
font-weight: 100 900;
font-display: optional;
src: url("./fonts/geist-mono/geist-mono-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;
}

@font-face {
font-family: "DDS Pretendard Atomic";
font-style: normal;
font-weight: 45 920;
font-display: optional;
src: url("./fonts/pretendard/PretendardVariable.woff2") format("woff2");
}
8 changes: 7 additions & 1 deletion packages/site-kit/fonts/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,13 @@
"sha256": "fc9fcd1bea9c8f43f97136a1e89bfa6e34db85f820f86538288647b4c24c5b06",
"unicodeRange": "U+20-22, U+27-2a, U+2c-39, U+3a-3b, U+3f, U+41-4e, U+4f-5d, U+61-7b, U+7d, U+a0-a1, U+ab, U+ad-ae, U+b7, U+bb, U+bf, U+2013-2014, U+201c-201d, U+2122, U+ac00, U+ace0, U+ae30, U+b2e4, U+b85c, U+b9ac, U+c0ac, U+c2a4, U+c2dc, U+c774, U+c778, U+c9c0, U+d558"
}
]
],
"atomicFace": {
"file": "PretendardVariable.woff2",
"bytes": 2057688,
"sha256": "9599f12fd42fc0bce1cd50b47a0c022e108d7aa64dd0d1bb0ed44f3282d900b4",
"sourcePath": "dist/web/variable/woff2/PretendardVariable.woff2"
}
}
]
}
Binary file not shown.
13 changes: 10 additions & 3 deletions packages/site-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"sideEffects": [
"./styles.css",
"./site-sections.css",
"./fonts.css"
"./fonts.css",
"./fonts-atomic.css"
],
"repository": {
"type": "git",
Expand All @@ -30,7 +31,8 @@
"site-sections.css",
"fonts.css",
"README.md",
"README.ko.md"
"README.ko.md",
"fonts-atomic.css"
],
"main": "./src/core/index.mjs",
"types": "./src/core/index.d.mts",
Expand Down Expand Up @@ -62,7 +64,12 @@
"./styles.css": "./styles.css",
"./site-sections.css": "./site-sections.css",
"./fonts.css": "./fonts.css",
"./fonts/*": "./fonts/*"
"./fonts/*": "./fonts/*",
"./fonts-atomic.css": "./fonts-atomic.css",
"./fonts": {
"types": "./src/core/fonts.d.mts",
"import": "./src/core/fonts.mjs"
}
},
"scripts": {
"build": "vite build && vite build --config vite.server.config.ts && tsc -p tsconfig.build.json --emitDeclarationOnly",
Expand Down
Loading
Loading