diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index 6a00429..b6caa88 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -20,7 +20,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
- node-version: '18'
+ node-version: '24'
cache: 'npm'
- name: Install dependencies
diff --git a/README.md b/README.md
index 9d692fe..b4de073 100644
--- a/README.md
+++ b/README.md
@@ -110,3 +110,5 @@ http://localhost:3000 에서 확인
## License
MIT License
+
+단, 카드에 임베드되는 JetBrains Mono 폰트(`src/utils/fonts/`)는 [SIL Open Font License 1.1](src/utils/fonts/OFL.txt)을 따릅니다.
diff --git a/package.json b/package.json
index 807654b..4d73cf8 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,9 @@
"name": "dreamhack-readme-stats",
"version": "1.0.0",
"private": true,
+ "engines": {
+ "node": "24.x"
+ },
"scripts": {
"dev": "next dev",
"build": "next build",
diff --git a/scripts/generate-font-subset.sh b/scripts/generate-font-subset.sh
new file mode 100755
index 0000000..8ccc193
--- /dev/null
+++ b/scripts/generate-font-subset.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+# SVG 카드에 임베드할 JetBrains Mono 서브셋 폰트(src/utils/fonts/jetbrainsMono.ts)를 생성합니다.
+#
+# GitHub README처럼 SVG가 로 렌더링되면 브라우저가 외부 리소스(@import 등) 로드를 차단해
+# Google Fonts를 쓸 수 없고 OS 기본 monospace 폰트로 대체됩니다.
+# 그래서 카드에 쓰는 글자(ASCII)와 굵기(400~700)만 남긴 가변 폰트를 base64로 SVG에 직접 넣습니다.
+#
+# 필요: curl, python3 + fonttools, brotli
+# pip install fonttools==4.65.0 brotli==1.2.1 (버전이 다르면 생성 결과가 달라질 수 있음)
+# 사용: ./scripts/generate-font-subset.sh (다른 python 사용 시 PYTHON=/path/to/python 지정)
+
+set -euo pipefail
+
+VERSION="v2.304"
+BASE_URL="https://raw.githubusercontent.com/JetBrains/JetBrainsMono/$VERSION"
+FONT_SHA256="662a196d58f1183bf2d77428b6d5283fe3f45161ab021bea4036bc98e5cac016"
+OFL_SHA256="30f0c136e3c88e422d0791acd97238870f9054a9729bc34cf2ff0d4ed8cac4ad"
+UNICODES="U+0020-007E"
+WEIGHT_MIN=400
+WEIGHT_MAX=700
+PYTHON="${PYTHON:-python3}"
+
+OUT_DIR="$(cd "$(dirname "$0")/.." && pwd)/src/utils/fonts"
+TMP_DIR="$(mktemp -d)"
+trap 'rm -rf "$TMP_DIR"' EXIT
+
+# 실행할 때마다 같은 결과가 나오도록 폰트 타임스탬프 고정
+export SOURCE_DATE_EPOCH=0
+
+curl -sSfL -o "$TMP_DIR/font.ttf" "$BASE_URL/fonts/variable/JetBrainsMono%5Bwght%5D.ttf"
+curl -sSfL -o "$TMP_DIR/OFL.txt" "$BASE_URL/OFL.txt"
+echo "$FONT_SHA256 $TMP_DIR/font.ttf" | shasum -a 256 -c -
+echo "$OFL_SHA256 $TMP_DIR/OFL.txt" | shasum -a 256 -c -
+
+"$PYTHON" -m fontTools.varLib.instancer "$TMP_DIR/font.ttf" "wght=$WEIGHT_MIN:$WEIGHT_MAX" \
+ -o "$TMP_DIR/instanced.ttf" -q
+
+# 리거처 등 레이아웃 기능은 제외하고, 라이선스 정보(name ID 13, 14)는 유지
+"$PYTHON" -m fontTools.subset "$TMP_DIR/instanced.ttf" \
+ --unicodes="$UNICODES" \
+ --layout-features='' \
+ --name-IDs=0,1,2,3,4,5,6,13,14 \
+ --flavor=woff2 \
+ --output-file="$TMP_DIR/subset.woff2"
+
+FONT_BASE64="$(base64 < "$TMP_DIR/subset.woff2" | tr -d '\n')"
+
+mkdir -p "$OUT_DIR"
+cp "$TMP_DIR/OFL.txt" "$OUT_DIR/OFL.txt"
+cat > "$OUT_DIR/jetbrainsMono.ts" < {
expect(result).toContain('fill="#21262d"'); // cardBackground
expect(result).toContain('stroke="#30363d"'); // border
});
+
+ it('폰트를 외부에서 불러오지 않고 SVG에 임베드해야 함', () => {
+ const mockStats: TCategoryStats = {
+ nickname: 'weakness',
+ total_score: 5000,
+ categories: [
+ { name: 'web', score: 2000, rank: 50, color: '#ff6b6b' }
+ ]
+ };
+
+ const result = generateCategorySvg(mockStats);
+
+ // 로 렌더링되는 SVG는 외부 리소스(@import)를 불러올 수 없음
+ expect(result).not.toContain('@import');
+ expect(result).toContain("font-family: 'JetBrains Mono'");
+ expect(result).toContain('url(data:font/woff2;base64,');
+ });
});
\ No newline at end of file
diff --git a/src/__tests__/utils/generateStatsSvg.test.ts b/src/__tests__/utils/generateStatsSvg.test.ts
index 8b3b76e..15687fd 100644
--- a/src/__tests__/utils/generateStatsSvg.test.ts
+++ b/src/__tests__/utils/generateStatsSvg.test.ts
@@ -73,4 +73,13 @@ describe('generateStatsSvg 유틸리티 함수 테스트', () => {
expect(result).toContain('fill="#21262d"'); // cardBackground
expect(result).toContain('stroke="#30363d"'); // border
});
+
+ it('폰트를 외부에서 불러오지 않고 SVG에 임베드해야 함', () => {
+ const result = generateStatsSvg(mockStats);
+
+ // 로 렌더링되는 SVG는 외부 리소스(@import)를 불러올 수 없음
+ expect(result).not.toContain('@import');
+ expect(result).toContain("font-family: 'JetBrains Mono'");
+ expect(result).toContain('url(data:font/woff2;base64,');
+ });
});
\ No newline at end of file
diff --git a/src/utils/fonts/OFL.txt b/src/utils/fonts/OFL.txt
new file mode 100644
index 0000000..8bee414
--- /dev/null
+++ b/src/utils/fonts/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/src/utils/fonts/jetbrainsMono.ts b/src/utils/fonts/jetbrainsMono.ts
new file mode 100644
index 0000000..1749106
--- /dev/null
+++ b/src/utils/fonts/jetbrainsMono.ts
@@ -0,0 +1,5 @@
+// 이 파일은 scripts/generate-font-subset.sh 로 생성되었습니다. 직접 수정하지 마세요.
+// JetBrains Mono v2.304 가변 폰트 (wght 400~700, U+0020-007E 서브셋)
+// SIL Open Font License 1.1 (같은 디렉터리의 OFL.txt 참고)
+export const jetBrainsMonoFontFace =
+ "@font-face { font-family: 'JetBrains Mono'; font-weight: 400 700; src: url(data:font/woff2;base64,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) format('woff2'); }";
diff --git a/src/utils/generateCategorySvg.ts b/src/utils/generateCategorySvg.ts
index dfe4ed4..2892701 100644
--- a/src/utils/generateCategorySvg.ts
+++ b/src/utils/generateCategorySvg.ts
@@ -1,4 +1,5 @@
import { TCategoryStats, Theme, ThemeColors } from '../types';
+import { jetBrainsMonoFontFace } from './fonts/jetbrainsMono';
const themes: Record = {
light: {
@@ -123,7 +124,7 @@ export function generateCategorySvg(stats: TCategoryStats, theme: Theme = 'light
return `