diff --git a/.gitignore b/.gitignore
index 57b1a04..56b578b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,9 @@
node_modules/
dist/
+tmp/
+test-results/
+playwright-report/
+e2e/site/playwright-report/
.env
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,windows
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,windows
@@ -85,4 +89,4 @@ $RECYCLE.BIN/
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,windows
*.tgz
-colbrush-*.tgz
\ No newline at end of file
+colbrush-*.tgz
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..081f4b4
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,12 @@
+# Exclude development and test assets from published packages
+e2e/
+scripts/
+test-results/
+tmp/
+
+# Ignore repository-specific artifacts
+*.tgz
+*.log
+.DS_Store
+.vscode/
+node_modules/
diff --git a/e2e/cli/package.json b/e2e/cli/package.json
new file mode 100644
index 0000000..8032aae
--- /dev/null
+++ b/e2e/cli/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "colbrush-cli-e2e",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "test": "node --test tests/*.test.mjs"
+ }
+}
diff --git a/e2e/cli/tests/cli.test.mjs b/e2e/cli/tests/cli.test.mjs
new file mode 100644
index 0000000..bb21b2a
--- /dev/null
+++ b/e2e/cli/tests/cli.test.mjs
@@ -0,0 +1,152 @@
+import { before, after, test } from 'node:test';
+import assert from 'node:assert/strict';
+import path from 'node:path';
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { spawn } from 'node:child_process';
+import { mkdtemp, readFile, cp, rm } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const repoRoot = path.resolve(__dirname, '../../..');
+const cliEntry = path.join(repoRoot, 'dist/cli.cjs');
+const fixtureCss = path.join(repoRoot, 'e2e/site/src/index.css');
+const packageVersion = JSON.parse(
+ readFileSync(path.join(repoRoot, 'package.json'), 'utf8')
+).version;
+
+const sandboxes = [];
+
+const runProcess = (command, args, options = {}) =>
+ new Promise((resolve, reject) => {
+ const child = spawn(command, args, {
+ cwd: options.cwd ?? repoRoot,
+ env: { ...process.env, ...options.env },
+ stdio: options.inheritStdio ? 'inherit' : 'pipe',
+ });
+
+ if (options.inheritStdio) {
+ child.on('exit', (code) => {
+ resolve({ code, stdout: '', stderr: '' });
+ });
+ child.on('error', reject);
+ return;
+ }
+
+ let stdout = '';
+ let stderr = '';
+
+ child.stdout?.on('data', (chunk) => {
+ stdout += chunk.toString();
+ });
+ child.stderr?.on('data', (chunk) => {
+ stderr += chunk.toString();
+ });
+
+ child.on('exit', (code) => {
+ resolve({ code, stdout, stderr });
+ });
+ child.on('error', reject);
+ });
+
+before(async () => {
+ console.log('๐ ๏ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋น๋๋ฅผ ์ค๋น ์ค์
๋๋ค...\n');
+ const buildResult = await runProcess('pnpm', ['build'], {
+ inheritStdio: true,
+ });
+ if (buildResult.code !== 0) {
+ throw new Error('Failed to build library before CLI tests');
+ }
+ console.log('\nโ
๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋น๋ ์๋ฃ\n');
+});
+
+after(async () => {
+ await Promise.all(
+ sandboxes.map((dir) => rm(dir, { recursive: true, force: true }))
+ );
+});
+
+async function createSandbox() {
+ const sandboxRoot = await mkdtemp(path.join(tmpdir(), 'colbrush-cli-'));
+ const targetCssPath = path.join(sandboxRoot, 'input.css');
+ await cp(fixtureCss, targetCssPath);
+ sandboxes.push(sandboxRoot);
+ return { sandboxRoot, cssPath: targetCssPath };
+}
+
+async function runCli(args, options) {
+ return runProcess(process.execPath, [cliEntry, ...args], options);
+}
+
+test('prints version information with --version flag', async () => {
+ const result = await runCli(['--version']);
+
+ assert.equal(result.code, 0);
+ assert.ok(
+ result.stdout.includes(`Colbrush v${packageVersion}`),
+ `CLI stdout should contain version banner, got: ${result.stdout}`
+ );
+});
+
+test('displays usage instructions with --help', async () => {
+ const result = await runCli(['--help']);
+
+ assert.equal(result.code, 0);
+ const normalized = result.stdout.replace(/\u001B\[[0-9;]*m/g, '');
+ assert.ok(
+ normalized.includes('USAGE'),
+ '๋์๋ง์๋ "USAGE" ์น์
์ด ํฌํจ๋์ด์ผ ํฉ๋๋ค'
+ );
+ assert.ok(
+ normalized.includes('Generate color-blind accessible themes'),
+ '๋์๋ง์๋ generate ๋ช
๋ น ์ค๋ช
์ด ํฌํจ๋์ด์ผ ํฉ๋๋ค'
+ );
+});
+
+test('generates themed CSS and report for a valid input file', async () => {
+ const { sandboxRoot, cssPath } = await createSandbox();
+ const reportPath = path.join(sandboxRoot, 'report.json');
+
+ const result = await runCli(
+ ['generate', `--css=${cssPath}`, `--json=${reportPath}`],
+ { cwd: sandboxRoot }
+ );
+
+ assert.equal(
+ result.code,
+ 0,
+ `CLI exited with non-zero code: ${result.stderr}`
+ );
+ assert.ok(
+ result.stdout.includes('All themes generated successfully'),
+ 'CLI๋ ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋์๋ค๋ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํด์ผ ํฉ๋๋ค'
+ );
+
+ const cssOutput = await readFile(cssPath, 'utf8');
+ assert.match(
+ cssOutput,
+ /\[data-theme='protanopia']\s*{[\s\S]+--color-primary-500:/,
+ 'CSS file should contain the protanopia theme block'
+ );
+ assert.match(
+ cssOutput,
+ /\[data-theme='deuteranopia']\s*{[\s\S]+--color-primary-500:/,
+ 'CSS file should contain the deuteranopia theme block'
+ );
+ assert.match(
+ cssOutput,
+ /\[data-theme='tritanopia']\s*{[\s\S]+--color-primary-500:/,
+ 'CSS file should contain the tritanopia theme block'
+ );
+
+ const jsonReportRaw = await readFile(reportPath, 'utf8');
+ const jsonReport = JSON.parse(jsonReportRaw);
+
+ assert.equal(jsonReport.input, cssPath);
+ assert.equal(jsonReport.exitCode, 0);
+ assert.equal(jsonReport.themes.length, 3);
+ assert.ok(
+ jsonReport.variables.processed >= 4,
+ `Expected at least 4 processed variables, got ${jsonReport.variables.processed}`
+ );
+});
diff --git a/e2e/site/index.html b/e2e/site/index.html
new file mode 100644
index 0000000..ef858ff
--- /dev/null
+++ b/e2e/site/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Colbrush E2E Playground
+
+
+
+
+
+
diff --git a/e2e/site/package.json b/e2e/site/package.json
new file mode 100644
index 0000000..ce6fbe1
--- /dev/null
+++ b/e2e/site/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "colbrush-e2e-site",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "test:e2e": "node ../../scripts/run-site-e2e.mjs"
+ },
+ "dependencies": {
+ "@tailwindcss/vite": "^4.1.16",
+ "colbrush": "link:../../",
+ "playwright": "^1.56.1",
+ "playwright-core": "^1.56.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tailwindcss": "^4.1.16"
+ },
+ "devDependencies": {
+ "@playwright/test": "1.56.1",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "^5.1.0",
+ "typescript": "^5.8.0",
+ "vite": "^6.0.0"
+ }
+}
diff --git a/e2e/site/playwright.config.ts b/e2e/site/playwright.config.ts
new file mode 100644
index 0000000..26626c0
--- /dev/null
+++ b/e2e/site/playwright.config.ts
@@ -0,0 +1,31 @@
+import { defineConfig, devices } from '@playwright/test';
+
+const useExternalServer =
+ process.env.PLAYWRIGHT_EXTERNAL_SERVER === '1' ||
+ process.env.PLAYWRIGHT_SKIP_WEB_SERVER === '1';
+
+export default defineConfig({
+ testDir: './tests',
+ fullyParallel: false,
+ retries: process.env.CI ? 2 : 0,
+ reporter: [['list'], ['html', { outputFolder: 'playwright-report', open: 'never' }]],
+ use: {
+ baseURL: 'http://127.0.0.1:4173',
+ trace: 'retain-on-failure',
+ },
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ ],
+ webServer: useExternalServer
+ ? undefined
+ : {
+ command: 'pnpm dev -- --host 127.0.0.1 --port 4173',
+ url: 'http://127.0.0.1:4173',
+ reuseExistingServer: !process.env.CI,
+ stdout: 'pipe',
+ stderr: 'pipe',
+ },
+});
diff --git a/e2e/site/pnpm-lock.yaml b/e2e/site/pnpm-lock.yaml
new file mode 100644
index 0000000..c34671c
--- /dev/null
+++ b/e2e/site/pnpm-lock.yaml
@@ -0,0 +1,1468 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@tailwindcss/vite':
+ specifier: ^4.1.16
+ version: 4.1.16(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))
+ colbrush:
+ specifier: link:../../
+ version: link:../..
+ playwright:
+ specifier: ^1.56.1
+ version: 1.56.1
+ playwright-core:
+ specifier: ^1.56.1
+ version: 1.56.1
+ react:
+ specifier: ^19.0.0
+ version: 19.2.0
+ react-dom:
+ specifier: ^19.0.0
+ version: 19.2.0(react@19.2.0)
+ tailwindcss:
+ specifier: ^4.1.16
+ version: 4.1.16
+ devDependencies:
+ '@playwright/test':
+ specifier: 1.56.1
+ version: 1.56.1
+ '@types/react':
+ specifier: ^19.0.0
+ version: 19.2.2
+ '@types/react-dom':
+ specifier: ^19.0.0
+ version: 19.2.2(@types/react@19.2.2)
+ '@vitejs/plugin-react':
+ specifier: ^5.1.0
+ version: 5.1.0(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))
+ typescript:
+ specifier: ^5.8.0
+ version: 5.9.3
+ vite:
+ specifier: ^6.0.0
+ version: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2)
+
+packages:
+
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.5':
+ resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.5':
+ resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.5':
+ resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.27.2':
+ resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.27.1':
+ resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.3':
+ resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.4':
+ resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/template@7.27.2':
+ resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.5':
+ resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+ engines: {node: '>=6.9.0'}
+
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@playwright/test@1.56.1':
+ resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ '@rolldown/pluginutils@1.0.0-beta.43':
+ resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==}
+
+ '@rollup/rollup-android-arm-eabi@4.52.5':
+ resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.52.5':
+ resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.52.5':
+ resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.52.5':
+ resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.52.5':
+ resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.52.5':
+ resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
+ resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.52.5':
+ resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.52.5':
+ resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.52.5':
+ resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.52.5':
+ resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.52.5':
+ resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.52.5':
+ resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.52.5':
+ resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.52.5':
+ resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.52.5':
+ resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.52.5':
+ resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openharmony-arm64@4.52.5':
+ resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.52.5':
+ resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.52.5':
+ resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.52.5':
+ resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.52.5':
+ resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/node@4.1.16':
+ resolution: {integrity: sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.16':
+ resolution: {integrity: sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.16':
+ resolution: {integrity: sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.16':
+ resolution: {integrity: sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.16':
+ resolution: {integrity: sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16':
+ resolution: {integrity: sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.16':
+ resolution: {integrity: sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.16':
+ resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.16':
+ resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.16':
+ resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.16':
+ resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.16':
+ resolution: {integrity: sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.16':
+ resolution: {integrity: sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.16':
+ resolution: {integrity: sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/vite@4.1.16':
+ resolution: {integrity: sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6 || ^7
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/react-dom@19.2.2':
+ resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.2':
+ resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
+
+ '@vitejs/plugin-react@5.1.0':
+ resolution: {integrity: sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ baseline-browser-mapping@2.8.23:
+ resolution: {integrity: sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==}
+ hasBin: true
+
+ browserslist@4.27.0:
+ resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ caniuse-lite@1.0.30001753:
+ resolution: {integrity: sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ electron-to-chromium@1.5.244:
+ resolution: {integrity: sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==}
+
+ enhanced-resolve@5.18.3:
+ resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
+ engines: {node: '>=10.13.0'}
+
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ lightningcss-android-arm64@1.30.2:
+ resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.30.2:
+ resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.2:
+ resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.2:
+ resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.2:
+ resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.2:
+ resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
+ engines: {node: '>= 12.0.0'}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ playwright-core@1.56.1:
+ resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ playwright@1.56.1:
+ resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ react-dom@19.2.0:
+ resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
+ peerDependencies:
+ react: ^19.2.0
+
+ react-refresh@0.18.0:
+ resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
+ engines: {node: '>=0.10.0'}
+
+ react@19.2.0:
+ resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
+ engines: {node: '>=0.10.0'}
+
+ rollup@4.52.5:
+ resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ tailwindcss@4.1.16:
+ resolution: {integrity: sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ update-browserslist-db@1.1.4:
+ resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ vite@6.4.1:
+ resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+snapshots:
+
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.5': {}
+
+ '@babel/core@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5)
+ '@babel/helpers': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.28.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.27.2':
+ dependencies:
+ '@babel/compat-data': 7.28.5
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.27.0
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.4':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+
+ '@babel/parser@7.28.5':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/template@7.27.2':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+
+ '@babel/traverse@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.28.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@playwright/test@1.56.1':
+ dependencies:
+ playwright: 1.56.1
+
+ '@rolldown/pluginutils@1.0.0-beta.43': {}
+
+ '@rollup/rollup-android-arm-eabi@4.52.5':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.52.5':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.52.5':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.52.5':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.52.5':
+ optional: true
+
+ '@tailwindcss/node@4.1.16':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.16
+
+ '@tailwindcss/oxide-android-arm64@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.16':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.16
+ '@tailwindcss/oxide-darwin-arm64': 4.1.16
+ '@tailwindcss/oxide-darwin-x64': 4.1.16
+ '@tailwindcss/oxide-freebsd-x64': 4.1.16
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.16
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.16
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.16
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.16
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.16
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.16
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.16
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.16
+
+ '@tailwindcss/vite@4.1.16(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))':
+ dependencies:
+ '@tailwindcss/node': 4.1.16
+ '@tailwindcss/oxide': 4.1.16
+ tailwindcss: 4.1.16
+ vite: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2)
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@types/estree@1.0.8': {}
+
+ '@types/react-dom@19.2.2(@types/react@19.2.2)':
+ dependencies:
+ '@types/react': 19.2.2
+
+ '@types/react@19.2.2':
+ dependencies:
+ csstype: 3.1.3
+
+ '@vitejs/plugin-react@5.1.0(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5)
+ '@rolldown/pluginutils': 1.0.0-beta.43
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.18.0
+ vite: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2)
+ transitivePeerDependencies:
+ - supports-color
+
+ baseline-browser-mapping@2.8.23: {}
+
+ browserslist@4.27.0:
+ dependencies:
+ baseline-browser-mapping: 2.8.23
+ caniuse-lite: 1.0.30001753
+ electron-to-chromium: 1.5.244
+ node-releases: 2.0.27
+ update-browserslist-db: 1.1.4(browserslist@4.27.0)
+
+ caniuse-lite@1.0.30001753: {}
+
+ convert-source-map@2.0.0: {}
+
+ csstype@3.1.3: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ detect-libc@2.1.2: {}
+
+ electron-to-chromium@1.5.244: {}
+
+ enhanced-resolve@5.18.3:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
+ escalade@3.2.0: {}
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ fsevents@2.3.2:
+ optional: true
+
+ fsevents@2.3.3:
+ optional: true
+
+ gensync@1.0.0-beta.2: {}
+
+ graceful-fs@4.2.11: {}
+
+ jiti@2.6.1: {}
+
+ js-tokens@4.0.0: {}
+
+ jsesc@3.1.0: {}
+
+ json5@2.2.3: {}
+
+ lightningcss-android-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ optional: true
+
+ lightningcss@1.30.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.30.2
+ lightningcss-darwin-arm64: 1.30.2
+ lightningcss-darwin-x64: 1.30.2
+ lightningcss-freebsd-x64: 1.30.2
+ lightningcss-linux-arm-gnueabihf: 1.30.2
+ lightningcss-linux-arm64-gnu: 1.30.2
+ lightningcss-linux-arm64-musl: 1.30.2
+ lightningcss-linux-x64-gnu: 1.30.2
+ lightningcss-linux-x64-musl: 1.30.2
+ lightningcss-win32-arm64-msvc: 1.30.2
+ lightningcss-win32-x64-msvc: 1.30.2
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ node-releases@2.0.27: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.3: {}
+
+ playwright-core@1.56.1: {}
+
+ playwright@1.56.1:
+ dependencies:
+ playwright-core: 1.56.1
+ optionalDependencies:
+ fsevents: 2.3.2
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ react-dom@19.2.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ scheduler: 0.27.0
+
+ react-refresh@0.18.0: {}
+
+ react@19.2.0: {}
+
+ rollup@4.52.5:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.52.5
+ '@rollup/rollup-android-arm64': 4.52.5
+ '@rollup/rollup-darwin-arm64': 4.52.5
+ '@rollup/rollup-darwin-x64': 4.52.5
+ '@rollup/rollup-freebsd-arm64': 4.52.5
+ '@rollup/rollup-freebsd-x64': 4.52.5
+ '@rollup/rollup-linux-arm-gnueabihf': 4.52.5
+ '@rollup/rollup-linux-arm-musleabihf': 4.52.5
+ '@rollup/rollup-linux-arm64-gnu': 4.52.5
+ '@rollup/rollup-linux-arm64-musl': 4.52.5
+ '@rollup/rollup-linux-loong64-gnu': 4.52.5
+ '@rollup/rollup-linux-ppc64-gnu': 4.52.5
+ '@rollup/rollup-linux-riscv64-gnu': 4.52.5
+ '@rollup/rollup-linux-riscv64-musl': 4.52.5
+ '@rollup/rollup-linux-s390x-gnu': 4.52.5
+ '@rollup/rollup-linux-x64-gnu': 4.52.5
+ '@rollup/rollup-linux-x64-musl': 4.52.5
+ '@rollup/rollup-openharmony-arm64': 4.52.5
+ '@rollup/rollup-win32-arm64-msvc': 4.52.5
+ '@rollup/rollup-win32-ia32-msvc': 4.52.5
+ '@rollup/rollup-win32-x64-gnu': 4.52.5
+ '@rollup/rollup-win32-x64-msvc': 4.52.5
+ fsevents: 2.3.3
+
+ scheduler@0.27.0: {}
+
+ semver@6.3.1: {}
+
+ source-map-js@1.2.1: {}
+
+ tailwindcss@4.1.16: {}
+
+ tapable@2.3.0: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ typescript@5.9.3: {}
+
+ update-browserslist-db@1.1.4(browserslist@4.27.0):
+ dependencies:
+ browserslist: 4.27.0
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.52.5
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ fsevents: 2.3.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+
+ yallist@3.1.1: {}
diff --git a/e2e/site/pnpm-workspace.yaml b/e2e/site/pnpm-workspace.yaml
new file mode 100644
index 0000000..efc037a
--- /dev/null
+++ b/e2e/site/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+onlyBuiltDependencies:
+ - esbuild
diff --git a/e2e/site/src/App.tsx b/e2e/site/src/App.tsx
new file mode 100644
index 0000000..e2c9c64
--- /dev/null
+++ b/e2e/site/src/App.tsx
@@ -0,0 +1,83 @@
+import { ThemeProvider, ThemeSwitcher } from 'colbrush/client';
+import React from 'react';
+import { SimulationFilter } from 'colbrush/devtools';
+import { DeuteranopiaTest } from './components/DeuteranopiaTest';
+import { ProtanopiaTest } from './components/ProtanopiaTest';
+import { TritanopiaTest } from './components/TritanopiaTest';
+import ThemeStatus from './components/ThemeStatus';
+import TestWrapper from './components/TestWrapper';
+
+export default function App() {
+ const tests = [
+ {
+ key: 'protanopia',
+ title: '์ ์๋งน ํ
์คํธ',
+ badge: 'Protanopia',
+ description:
+ '๋ถ์ ๊ณ์ด์ด ์ฝํด์ง ์๋ ฅ์ ๊ฐ์ ํ๊ณ UI ๋๋น์ ์ํ ํจํด์ ์ ๊ฒํฉ๋๋ค.',
+ render: () => ,
+ },
+ {
+ key: 'deuteranopia',
+ title: '๋
น์๋งน ํ
์คํธ',
+ badge: 'Deuteranopia',
+ description:
+ '๋
น์ ์ธ์์ด ์ ํ๋ ์ฌ์ฉ์๊ฐ ๋ณผ ๋ ์์ ์ธต์๊ฐ ์ ์ง๋๋์ง ํ์ธํ์ธ์.',
+ render: () => ,
+ },
+ {
+ key: 'tritanopia',
+ title: '์ฒญ์๋งน ํ
์คํธ',
+ badge: 'Tritanopia',
+ description:
+ '์ฒญ์๊ณผ ๋
ธ๋์์ ๊ตฌ๋ถํ๊ธฐ ์ด๋ ค์ด ํ๊ฒฝ์์ ์ฃผ์ ์ ๋ณด๊ฐ ์ ์ง๋๋์ง ์ดํด๋ด
๋๋ค.',
+ render: () => ,
+ },
+ ] as const;
+ const usageTips = [
+ {
+ title: 'ํ
๋ง์ ์ธ์ด ํ์ธ',
+ detail: '์ค๋ฅธ์ชฝ ํ๋จ Theme Switcher๋ก ํ
๋ง ยท ์ธ์ด๋ฅผ ํ ๊ธํ๋ฉด ์ปจํ
์คํธ๊ฐ ์ฆ์ ๊ฐฑ์ ๋ฉ๋๋ค.',
+ },
+ {
+ title: '์๋ฎฌ๋ ์ด์
ํํฐ ์ ์ฉ',
+ detail: 'Vision Simulation ํด๋ฐ์์ ๋ชจ๋๋ฅผ ์ ํํ๋ฉด ์๋์ผ๋ก SVG ํ
์คํธ ์นด๋์ ํํฐ๊ฐ ์ ์ฉ๋ฉ๋๋ค.',
+ },
+ {
+ title: 'ํ์ฌ ์ํ ์ถ์ ',
+ detail: '์ข์ธก ์ํ ํจ๋์์ theme, language, simulation filter ๊ฐ์ด ๊ธฐ๋์ ์ผ์นํ๋์ง ํ์ธํ์ธ์.',
+ },
+ {
+ title: 'E2E ์ค๋
์ท',
+ detail: '๊ฐ ์นด๋ ํ๋จ ์ค๋ช
์ ์ฐธ๊ณ ํด ์คํฌ๋ฆฐ์ท ๋๋ ์ค๋
์ท ํ
์คํธ ํฌ์ธํธ๋ฅผ ๋น ๋ฅด๊ฒ ํ์
ํ ์ ์์ต๋๋ค.',
+ },
+ ] as const;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {tests.map((test) => (
+
+ {test.render()}
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/e2e/site/src/components/Chip.tsx b/e2e/site/src/components/Chip.tsx
new file mode 100644
index 0000000..9b6f3ac
--- /dev/null
+++ b/e2e/site/src/components/Chip.tsx
@@ -0,0 +1,17 @@
+function Chip({
+ children,
+ testId,
+}: {
+ children: React.ReactNode;
+ testId?: string;
+}) {
+ return (
+
+ {children}
+
+ );
+}
+export default Chip;
diff --git a/e2e/site/src/components/DeuteranopiaTest.tsx b/e2e/site/src/components/DeuteranopiaTest.tsx
new file mode 100644
index 0000000..6f57c8d
--- /dev/null
+++ b/e2e/site/src/components/DeuteranopiaTest.tsx
@@ -0,0 +1,12866 @@
+import { memo } from "react";
+import type { SVGProps } from "react";
+
+interface DeuteranopiaTestProps extends SVGProps {
+ width?: number;
+ height?: number;
+}
+
+export const DeuteranopiaTest = memo(
+ ({ width = "100%", height = "100%", className, ...props }) => {
+ return (
+
+ );
+ },
+);
+
+DeuteranopiaTest.displayName = "deuteranopiaTest";
diff --git a/e2e/site/src/components/ProtanopiaTest.tsx b/e2e/site/src/components/ProtanopiaTest.tsx
new file mode 100644
index 0000000..cb4d1eb
--- /dev/null
+++ b/e2e/site/src/components/ProtanopiaTest.tsx
@@ -0,0 +1,12716 @@
+import { memo } from 'react';
+import type { SVGProps } from 'react';
+
+interface ProtanopiaTestProps extends SVGProps {
+ width?: number;
+ height?: number;
+}
+
+export const ProtanopiaTest = memo(
+ ({ width = '100%', height = '100%', className, ...props }) => {
+ return (
+
+ );
+ }
+);
+
+ProtanopiaTest.displayName = 'protanopiaTest';
diff --git a/e2e/site/src/components/StatusRow.tsx b/e2e/site/src/components/StatusRow.tsx
new file mode 100644
index 0000000..7947295
--- /dev/null
+++ b/e2e/site/src/components/StatusRow.tsx
@@ -0,0 +1,23 @@
+import Chip from './Chip';
+
+function StatusRow({
+ label,
+ value,
+ testId,
+}: {
+ label: string;
+ value: React.ReactNode;
+ testId?: string;
+}) {
+ return (
+
+
+ {label}
+
+
+ {value}
+
+
+ );
+}
+export default StatusRow;
diff --git a/e2e/site/src/components/TestWrapper.tsx b/e2e/site/src/components/TestWrapper.tsx
new file mode 100644
index 0000000..6c776a2
--- /dev/null
+++ b/e2e/site/src/components/TestWrapper.tsx
@@ -0,0 +1,41 @@
+export default function TestWrapper({
+ children,
+ title,
+ description,
+ badge = 'Vision Simulation',
+}: {
+ children: React.ReactNode;
+ title?: string;
+ description?: string;
+ badge?: string;
+}) {
+ return (
+
+
+
+ {badge}
+
+ {title && (
+
+ {title}
+
+ )}
+
+
+
+
+ {children}
+
+
+
+ {description && (
+
+ {description}
+
+ )}
+
+ );
+}
diff --git a/e2e/site/src/components/ThemeStatus.tsx b/e2e/site/src/components/ThemeStatus.tsx
new file mode 100644
index 0000000..2672211
--- /dev/null
+++ b/e2e/site/src/components/ThemeStatus.tsx
@@ -0,0 +1,60 @@
+import { useTheme } from 'colbrush/client';
+import StatusRow from './StatusRow';
+
+function ThemeStatus() {
+ const { theme, language, simulationFilter } = useTheme();
+
+ return (
+
+
+
+
+ Context
+
+
+
+ Colbrush E2E Playground
+
+
+ ThemeProvider๊ฐ ๋
ธ์ถํ๋ ํ์ฌ ์ปจํ
์คํธ ๊ฐ์
+ ์ค์๊ฐ์ผ๋ก ํ์ธํ์ธ์.
+
+
+
+
+ Live
+
+
+
+
+
+
+
+
+
+
+
+ Theme Switcher ๋๋ Simulation Filter์์ ์ต์
์ ๋ณ๊ฒฝํ ๋๋ง๋ค
+ ๋ณธ ์นด๋์ ๊ฐ์ด ์ฆ์ ๊ฐฑ์ ๋์ด E2E ํ
์คํธ์ ๋จ์ผ ๊ธฐ์ค์ ์ญํ ์
+ ํฉ๋๋ค. ๊ฐ์ด ์์๊ณผ ๋ค๋ฅด๋ฉด ์ ์ญ ์ํ ๋๋ ํฌํธ ์์น๋ฅผ ๋จผ์
+ ์ ๊ฒํ์ธ์.
+
+
+ );
+}
+export default ThemeStatus;
diff --git a/e2e/site/src/components/TritanopiaTest.tsx b/e2e/site/src/components/TritanopiaTest.tsx
new file mode 100644
index 0000000..5ef3c67
--- /dev/null
+++ b/e2e/site/src/components/TritanopiaTest.tsx
@@ -0,0 +1,12716 @@
+import { memo } from 'react';
+import type { SVGProps } from 'react';
+
+interface TritanopiaTestProps extends SVGProps {
+ width?: number;
+ height?: number;
+}
+
+export const TritanopiaTest = memo(
+ ({ width = '100%', height = '100%', className, ...props }) => {
+ return (
+
+ );
+ }
+);
+
+TritanopiaTest.displayName = 'tritanopiaTest';
diff --git a/e2e/site/src/index.css b/e2e/site/src/index.css
new file mode 100644
index 0000000..1072d58
--- /dev/null
+++ b/e2e/site/src/index.css
@@ -0,0 +1,111 @@
+@import "tailwindcss";
+
+@import "colbrush/styles.css";
+
+@theme {
+ --color-primary-500: #64d2ff;
+ --color-primary-700: #287cff;
+ --color-accent-500: #ff9f7a;
+ --color-accent-700: #ff6d51;
+ --color-surface-500: #0f1f3a;
+ --color-surface-700: #08132a;
+ --color-foreground-500: #f7faff;
+ --color-foreground-muted: rgba(230, 236, 255, 0.76);
+
+ --color-protanopia-a: #a34245;
+ --color-protanopia-b: #7e464a;
+ --color-protanopia-sub-a: #275841;
+ --color-protanopia-sub-b: #455c4e;
+ --color-protanopia-sub-c: #525856;
+
+ --color-deuteranopia-a: #11724d;
+ --color-deuteranopia-b: #5a7d6e;
+ --color-deuteranopia-sub-a: #b84f47;
+ --color-deuteranopia-sub-b: #a04d4d;
+ --color-deuteranopia-sub-c: #6b6363;
+
+ --color-tritanopia-a: #7287e4;
+ --color-tritanopia-b: #627cac;
+ --color-tritanopia-sub-a: #5ba192;
+ --color-tritanopia-sub-b: #4da08b;
+ --color-tritanopia-sub-c: #4c7e71;
+}
+
+[data-theme='deuteranopia']{
+ --color-primary-500: #8677c2;
+ --color-primary-700: #5b4c8f;
+ --color-accent-500: #ff6b00;
+ --color-accent-700: #c53e00;
+ --color-surface-500: #121222;
+ --color-surface-700: #000003;
+ --color-foreground-500: #f2faff;
+ --color-foreground-muted: rgba(230, 236, 255, 0.76);
+ --color-protanopia-a: #aa4000;
+ --color-protanopia-b: #864405;
+ --color-protanopia-sub-a: #005a77;
+ --color-protanopia-sub-b: #2d5d6b;
+ --color-protanopia-sub-c: #4e585d;
+ --color-deuteranopia-a: #0076a7;
+ --color-deuteranopia-b: #327f96;
+ --color-deuteranopia-sub-a: #bf4d00;
+ --color-deuteranopia-sub-b: #a84b00;
+ --color-deuteranopia-sub-c: #6e635b;
+ --color-tritanopia-a: #9884b5;
+ --color-tritanopia-b: #6a7ba3;
+ --color-tritanopia-sub-a: #00a4d6;
+ --color-tritanopia-sub-b: #00a4dc;
+ --color-tritanopia-sub-c: #0080a4;
+}
+
+[data-theme='protanopia']{
+ --color-primary-500: #8677c2;
+ --color-primary-700: #5b4c8f;
+ --color-accent-500: #ff6b00;
+ --color-accent-700: #c53e00;
+ --color-surface-500: #121222;
+ --color-surface-700: #000003;
+ --color-foreground-500: #f2faff;
+ --color-foreground-muted: rgba(230, 236, 255, 0.76);
+ --color-protanopia-a: #aa4000;
+ --color-protanopia-b: #864405;
+ --color-protanopia-sub-a: #005a77;
+ --color-protanopia-sub-b: #2d5d6b;
+ --color-protanopia-sub-c: #4e585d;
+ --color-deuteranopia-a: #0076a7;
+ --color-deuteranopia-b: #327f96;
+ --color-deuteranopia-sub-a: #bf4d00;
+ --color-deuteranopia-sub-b: #a84b00;
+ --color-deuteranopia-sub-c: #6e635b;
+ --color-tritanopia-a: #9884b5;
+ --color-tritanopia-b: #6a7ba3;
+ --color-tritanopia-sub-a: #00a4d6;
+ --color-tritanopia-sub-b: #00a4dc;
+ --color-tritanopia-sub-c: #0080a4;
+}
+
+[data-theme='tritanopia']{
+ --color-primary-500: #be41ff;
+ --color-primary-700: #8c00c5;
+ --color-accent-500: #d38b4e;
+ --color-accent-700: #9f5f24;
+ --color-surface-500: #1e0b2a;
+ --color-surface-700: #020006;
+ --color-foreground-500: #fcf7ff;
+ --color-foreground-muted: rgba(230, 236, 255, 0.76);
+ --color-protanopia-a: #914f44;
+ --color-protanopia-b: #764b4a;
+ --color-protanopia-sub-a: #0d5a41;
+ --color-protanopia-sub-b: #3d5e4e;
+ --color-protanopia-sub-c: #525856;
+ --color-deuteranopia-a: #00754d;
+ --color-deuteranopia-b: #537e6e;
+ --color-deuteranopia-sub-a: #9f6046;
+ --color-deuteranopia-sub-b: #90574c;
+ --color-deuteranopia-sub-c: #6a6363;
+ --color-tritanopia-a: #bf67e5;
+ --color-tritanopia-b: #8e6eac;
+ --color-tritanopia-sub-a: #59a192;
+ --color-tritanopia-sub-b: #44a18b;
+ --color-tritanopia-sub-c: #487f71;
+}
+
diff --git a/e2e/site/src/main.tsx b/e2e/site/src/main.tsx
new file mode 100644
index 0000000..b21722f
--- /dev/null
+++ b/e2e/site/src/main.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+import './index.css';
+
+const rootElement = document.getElementById('root');
+
+if (!rootElement) {
+ throw new Error('Failed to find the root element for the sample app.');
+}
+
+ReactDOM.createRoot(rootElement).render(
+
+
+
+);
diff --git a/e2e/site/tests/theme-switcher.spec.ts b/e2e/site/tests/theme-switcher.spec.ts
new file mode 100644
index 0000000..4e63533
--- /dev/null
+++ b/e2e/site/tests/theme-switcher.spec.ts
@@ -0,0 +1,114 @@
+import { expect, test } from '@playwright/test';
+
+const toggleButtonName = /theme switcher menu|ํ
๋ง ์ ํ ๋ฉ๋ด/;
+const simulateButtonName = /simulation options|์๋ฎฌ๋ ์ด์
๋๊ตฌ/;
+
+test.beforeEach(async ({ page }) => {
+ await page.goto('/');
+ // Ensure a clean slate for tests while staying on the correct origin.
+ await page.evaluate(() => localStorage.clear());
+ await page.reload();
+});
+
+test('์๋ก์ด ํ
๋ง๋ฅผ ์ ํํ ๋ HTML์ data-theme ์์ฑ์ ์ค์ ํฉ๋๋ค.', async ({
+ page,
+}) => {
+ await page.getByRole('button', { name: toggleButtonName }).click();
+ await page.getByRole('menu', { name: 'Select theme' }).isVisible();
+
+ await page.getByRole('menuitemradio', { name: 'protanopia' }).click();
+
+ await expect(page.locator('html')).toHaveAttribute(
+ 'data-theme',
+ 'protanopia'
+ );
+ await expect(page.getByTestId('active-theme')).toHaveText('protanopia');
+});
+
+test('์ธ์ด ์ ํ ๊ธฐ๋ฅ์ ํตํด ์ธ์ด๋ฅผ ํ๊ตญ์ด๋ก ์ ํํฉ๋๋ค.', async ({ page }) => {
+ await page.getByRole('button', { name: toggleButtonName }).click();
+ await page.getByRole('button', { name: 'English' }).click();
+
+ await expect(page.getByTestId('active-language')).toHaveText('Korean');
+ await expect(
+ page.getByRole('menuitemradio', { name: '๊ธฐ๋ณธ' })
+ ).toBeVisible();
+ await expect(
+ page.getByRole('button', { name: toggleButtonName })
+ ).toHaveAttribute('aria-label', 'ํ
๋ง ์ ํ ๋ฉ๋ด ๋ซ๊ธฐ');
+});
+
+test('์๋ก๊ณ ์นจ์ ํ์ฌ๋ ๊ธฐ์กด์ ์ ํํ ํ
๋ง๊ฐ ์ ์งํฉ๋๋ค.', async ({ page }) => {
+ await page.getByRole('button', { name: toggleButtonName }).click();
+ await page.getByRole('menuitemradio', { name: 'deuteranopia' }).click();
+
+ await expect(page.locator('html')).toHaveAttribute(
+ 'data-theme',
+ 'deuteranopia'
+ );
+
+ await page.reload();
+
+ await expect(page.locator('html')).toHaveAttribute(
+ 'data-theme',
+ 'deuteranopia'
+ );
+ await expect(page.getByTestId('active-theme')).toHaveText('deuteranopia');
+});
+
+test('์๋ฎฌ๋ ์ดํฐ ํ
๋ง๋ฅผ ์ ์ฉํ๋ฉด ๋ฃจํธ ์์์ ํํฐ๊ฐ ์ค์ ๋ฉ๋๋ค.', async ({
+ page,
+}) => {
+ const rootFilter = () =>
+ page.evaluate(
+ () => document.getElementById('root')?.style.filter ?? ''
+ );
+
+ await page.getByRole('button', { name: simulateButtonName }).click();
+ await page
+ .getByRole('group', {
+ name: /vision simulation modes|์๊ฐ ์๋ฎฌ๋ ์ด์
๋ชจ๋/,
+ })
+ .isVisible();
+
+ await page.getByRole('button', { name: 'protanopia' }).click();
+
+ await expect.poll(rootFilter).toContain('cb-vision-filter');
+ await expect(
+ page.getByRole('button', { name: 'protanopia' })
+ ).toHaveAttribute('aria-pressed', 'true');
+
+ await page.getByRole('button', { name: 'default' }).click();
+
+ await expect.poll(rootFilter).toBe('');
+ await expect(
+ page.getByRole('button', { name: 'protanopia' })
+ ).toHaveAttribute('aria-pressed', 'false');
+ await expect(page.getByRole('button', { name: 'default' })).toHaveAttribute(
+ 'aria-pressed',
+ 'true'
+ );
+});
+
+test('์ ํํ ์๋ฎฌ๋ ์ดํฐ ํ
๋ง๋ ์๋ก๊ณ ์นจ ํ์๋ ์ ์ง๋ฉ๋๋ค.', async ({
+ page,
+}) => {
+ const rootFilter = () =>
+ page.evaluate(
+ () => document.getElementById('root')?.style.filter ?? ''
+ );
+
+ await page.getByRole('button', { name: simulateButtonName }).click();
+ await page.getByRole('button', { name: 'deuteranopia' }).click();
+
+ await expect.poll(rootFilter).toContain('cb-vision-filter');
+
+ await page.reload();
+
+ await expect.poll(rootFilter).toContain('cb-vision-filter');
+
+ await page.getByRole('button', { name: simulateButtonName }).click();
+ await expect(
+ page.getByRole('button', { name: 'deuteranopia' })
+ ).toHaveAttribute('aria-pressed', 'true');
+});
diff --git a/e2e/site/tsconfig.json b/e2e/site/tsconfig.json
new file mode 100644
index 0000000..abe4f40
--- /dev/null
+++ b/e2e/site/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "jsx": "react-jsx",
+ "allowImportingTsExtensions": true,
+ "allowSyntheticDefaultImports": true,
+ "declaration": false,
+ "declarationMap": false,
+ "noEmit": true,
+ "types": ["vite/client"]
+ },
+ "include": [
+ "src",
+ "vite.config.ts",
+ "playwright.config.ts",
+ "tests"
+ ]
+}
diff --git a/e2e/site/tsconfig.node.json b/e2e/site/tsconfig.node.json
new file mode 100644
index 0000000..3ecbd34
--- /dev/null
+++ b/e2e/site/tsconfig.node.json
@@ -0,0 +1,13 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "noEmit": true
+ },
+ "include": [
+ "vite.config.ts",
+ "playwright.config.ts"
+ ]
+}
diff --git a/e2e/site/vite.config.ts b/e2e/site/vite.config.ts
new file mode 100644
index 0000000..cbc91b7
--- /dev/null
+++ b/e2e/site/vite.config.ts
@@ -0,0 +1,33 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import tailwindcss from '@tailwindcss/vite';
+import react from '@vitejs/plugin-react';
+import { defineConfig, type PluginOption } from 'vite';
+
+const configDir = path.dirname(fileURLToPath(import.meta.url));
+const libraryRoot = path.resolve(configDir, '../../src');
+const distStyles = path.resolve(configDir, '../../dist/styles.css');
+const stylesAlias = fs.existsSync(distStyles)
+ ? distStyles
+ : path.join(libraryRoot, 'styles.css');
+
+const plugins: PluginOption[] = [
+ react(),
+ tailwindcss() as PluginOption,
+];
+
+export default defineConfig({
+ plugins,
+ server: {
+ host: '127.0.0.1',
+ port: 4173,
+ },
+ resolve: {
+ alias: {
+ 'colbrush/client': path.join(libraryRoot, 'client.ts'),
+ 'colbrush/styles.css': stylesAlias,
+ 'colbrush/devtools': path.join(libraryRoot, 'devtools/index.ts'),
+ },
+ },
+});
diff --git a/package.json b/package.json
index f3d9d61..a78d7be 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,11 @@
"build": "pnpm svgr && tsup --config tsup.config.ts && pnpm build:css",
"dev": "tsup --config tsup.config.ts --watch",
"prepublishOnly": "pnpm build",
- "lint": "eslint ."
+ "lint": "eslint .",
+ "test:e2e:site": "node ./scripts/run-site-e2e.mjs",
+ "test:e2e:cli:smoke": "node ./scripts/run-cli-smoke.mjs",
+ "test:e2e:cli": "pnpm --dir e2e/cli test",
+ "test": "pnpm --silent test:e2e:cli && pnpm --silent test:e2e:cli:smoke && pnpm --silent test:e2e:site"
},
"keywords": [],
"author": "TEAM ColorBrush",
diff --git a/scripts/run-cli-smoke.mjs b/scripts/run-cli-smoke.mjs
new file mode 100644
index 0000000..b75e6d2
--- /dev/null
+++ b/scripts/run-cli-smoke.mjs
@@ -0,0 +1,71 @@
+#!/usr/bin/env node
+import { spawn } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
+import { mkdir, readFile } from 'node:fs/promises';
+import path from 'node:path';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const repoRoot = path.resolve(__dirname, '..');
+const siteCss = path.join(repoRoot, 'e2e/site/src/index.css');
+const cliEntry = path.join(repoRoot, 'dist/cli.cjs');
+const reportPath = path.join(repoRoot, 'test-results/cli-report.json');
+
+function run(command, args, { cwd = repoRoot, env = {} } = {}) {
+ return new Promise((resolve, reject) => {
+ const child = spawn(command, args, {
+ cwd,
+ stdio: 'inherit',
+ env: { ...process.env, ...env },
+ });
+ child.on('exit', (code) => {
+ if (code === 0) resolve();
+ else
+ reject(
+ new Error(
+ `${command} ${args.join(' ')} exited with code ${code}`
+ )
+ );
+ });
+ child.on('error', reject);
+ });
+}
+
+function runColbrush(args, options = {}) {
+ return run(process.execPath, [cliEntry, ...args], options);
+}
+
+async function runGenerateOnSiteCss() {
+ await mkdir(path.dirname(reportPath), { recursive: true });
+
+ await runColbrush(['generate', `--css=${siteCss}`, `--json=${reportPath}`]);
+
+ const generatedCss = await readFile(siteCss, 'utf8');
+ if (!generatedCss.includes("[data-theme='protanopia']")) {
+ throw new Error(
+ '์์ฑ๋ CSS์์ protanopia ํ
๋ง ๋ธ๋ก์ ์ฐพ์ ์ ์์ต๋๋ค.'
+ );
+ }
+ const report = JSON.parse(await readFile(reportPath, 'utf8'));
+ if (report.exitCode !== 0) {
+ throw new Error('CLI JSON ๋ฆฌํฌํธ๊ฐ ์ฑ๊ณต ์ํ๋ฅผ ๋ฐํํ์ง ์์์ต๋๋ค.');
+ }
+}
+
+async function main() {
+ console.log('๐ ๏ธ CLI ํ
์คํธ๋ฅผ ์ค๋น ์ค์
๋๋ค...\n');
+
+ await runColbrush(['--version']);
+ await runColbrush(['--help']);
+ await runColbrush(['--doctor']);
+
+ console.log(
+ '\n๐ง ์ค์ e2e ์ฌ์ดํธ CSS์ ๋ํด colbrush generate ๋ช
๋ น์ ๊ฒ์ฆํฉ๋๋ค...\n'
+ );
+ await runGenerateOnSiteCss();
+ console.log('\nโ
colbrush CLI ๊ธฐ๋ณธ ๋ช
๋ น๋ค์ด ์ ์์ ์ผ๋ก ๋์ํ์ต๋๋ค.');
+}
+
+main().catch((error) => {
+ console.error('โ CLI ํ
์คํธ๊ฐ ์คํจํ์ต๋๋ค.', error);
+ process.exit(1);
+});
diff --git a/scripts/run-site-e2e.mjs b/scripts/run-site-e2e.mjs
new file mode 100644
index 0000000..d5719b6
--- /dev/null
+++ b/scripts/run-site-e2e.mjs
@@ -0,0 +1,186 @@
+#!/usr/bin/env node
+import { spawn } from 'node:child_process';
+import net from 'node:net';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { setTimeout as sleep } from 'node:timers/promises';
+import fs from 'node:fs';
+import { readFileSync, writeFileSync } from 'node:fs';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const siteDir = path.resolve(__dirname, '../e2e/site');
+const host = '127.0.0.1';
+const port = 4173;
+const pidFile = path.resolve(siteDir, '../test-results/vite-dev.pid');
+
+function checkServer() {
+ return new Promise((resolve) => {
+ const socket = net.createConnection({ host, port }, () => {
+ socket.end();
+ resolve(true);
+ });
+ socket.on('error', () => {
+ socket.destroy();
+ resolve(false);
+ });
+ socket.setTimeout(500, () => {
+ socket.destroy();
+ resolve(false);
+ });
+ });
+}
+
+async function waitForServer(timeoutMs = 15000) {
+ const start = Date.now();
+ while (Date.now() - start < timeoutMs) {
+ if (await checkServer()) {
+ return;
+ }
+ await sleep(300);
+ }
+ throw new Error('๊ฐ๋ฐ ์๋ฒ๊ฐ ์์ ์๊ฐ ๋ด์ ์ค๋น๋์ง ์์์ต๋๋ค.');
+}
+
+function existingPid() {
+ if (!fs.existsSync(pidFile)) return null;
+ try {
+ const pid = Number(readFileSync(pidFile, 'utf8').trim());
+ if (!Number.isFinite(pid)) return null;
+ process.kill(pid, 0);
+ return pid;
+ } catch {
+ return null;
+ }
+}
+
+async function stopExistingServer() {
+ const pid = existingPid();
+
+ if (!pid) {
+ if (fs.existsSync(pidFile)) {
+ fs.rmSync(pidFile, { force: true });
+ }
+
+ if (await checkServer()) {
+ throw new Error(
+ `ํฌํธ ${port}์์ ์คํ ์ค์ธ ํ๋ก์ธ์ค๋ฅผ ์ข
๋ฃํ์ง ๋ชปํ์ต๋๋ค. ์๋์ผ๋ก ์ข
๋ฃ ํ ๋ค์ ์คํํด์ฃผ์ธ์.`
+ );
+ }
+ return;
+ }
+
+ console.log(`๐ ๊ธฐ์กด E2E ์ฌ์ดํธ ์๋ฒ๋ฅผ ์ข
๋ฃํฉ๋๋ค. (PID ${pid})`);
+
+ try {
+ process.kill(pid, 'SIGTERM');
+ } catch (error) {
+ if (error?.code === 'ESRCH') {
+ fs.rmSync(pidFile, { force: true });
+ return;
+ }
+ throw error;
+ }
+
+ const gracefulDeadline = Date.now() + 5000;
+ while (await checkServer()) {
+ if (Date.now() > gracefulDeadline) {
+ console.log('โ ๏ธ ์ ์ ์ข
๋ฃ๋์ง ์์ ๊ฐ์ ์ข
๋ฃํฉ๋๋ค.');
+ try {
+ process.kill(pid, 'SIGKILL');
+ } catch (error) {
+ if (error?.code !== 'ESRCH') {
+ throw error;
+ }
+ }
+ break;
+ }
+ await sleep(200);
+ }
+
+ const serverStillRunning = await checkServer();
+ fs.rmSync(pidFile, { force: true });
+
+ if (serverStillRunning) {
+ throw new Error(
+ `๊ธฐ์กด E2E ์ฌ์ดํธ ์๋ฒ(PID ${pid})๋ฅผ ์ข
๋ฃํ์ง ๋ชปํ์ต๋๋ค.`
+ );
+ }
+
+ console.log('โ
๊ธฐ์กด E2E ์ฌ์ดํธ ์๋ฒ๋ฅผ ์ข
๋ฃํ์ต๋๋ค.');
+}
+
+async function ensureServer() {
+ if (await checkServer()) {
+ await stopExistingServer();
+ }
+
+ const viteBin = path.resolve(siteDir, 'node_modules/vite/bin/vite.js');
+ if (!fs.existsSync(viteBin)) {
+ console.log('๐ฆ e2e ์ฌ์ดํธ ์์กด์ฑ์ ์ค์นํฉ๋๋ค...');
+ await new Promise((resolve, reject) => {
+ const child = spawn('pnpm', ['install'], {
+ cwd: siteDir,
+ stdio: 'inherit',
+ });
+ child.on('exit', (code) => {
+ if (code === 0) resolve();
+ else reject(new Error('pnpm install ์คํจ'));
+ });
+ child.on('error', reject);
+ });
+ }
+
+ console.log('๐ E2E ์ฌ์ดํธ ์๋ฒ๋ฅผ ์์ํฉ๋๋ค...');
+ const devProcess = spawn(
+ process.execPath,
+ [viteBin, 'dev', '--host', host, '--port', String(port)],
+ {
+ cwd: siteDir,
+ stdio: 'ignore',
+ detached: true,
+ }
+ );
+ devProcess.unref();
+ fs.mkdirSync(path.dirname(pidFile), { recursive: true });
+ writeFileSync(pidFile, String(devProcess.pid));
+
+ await waitForServer();
+ console.log(
+ `๐ ${`http://${host}:${port}`} ์๋ฒ๊ฐ ์ค๋น๋์์ต๋๋ค. (PID ${devProcess.pid})`
+ );
+ return true;
+}
+
+async function runPlaywright() {
+ return new Promise((resolve, reject) => {
+ const child = spawn('pnpm', ['exec', 'playwright', 'test'], {
+ cwd: siteDir,
+ stdio: 'inherit',
+ env: {
+ ...process.env,
+ PLAYWRIGHT_EXTERNAL_SERVER: '1',
+ },
+ });
+
+ child.on('exit', (code) => resolve(code ?? 1));
+ child.on('error', reject);
+ });
+}
+
+async function main() {
+ const startedServer = await ensureServer();
+ const exitCode = await runPlaywright();
+
+ if (exitCode !== 0) {
+ process.exit(exitCode);
+ }
+
+ if (startedServer) {
+ console.log('โน๏ธ ํ
์คํธ๊ฐ ๋๋ฌ์ต๋๋ค. ์๋ฒ๋ ๊ณ์ ์คํ๋ฉ๋๋ค.');
+ }
+}
+
+main().catch((error) => {
+ console.error('โ ํ
์คํธ ์คํ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.', error);
+ process.exit(1);
+});
diff --git a/src/styles.css b/src/styles.css
index 6ba3306..a19bac6 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1 +1 @@
-@source "./src/**/*.{js,ts,jsx,tsx}";
\ No newline at end of file
+@source "./**/*.{js,ts,jsx,tsx}";