@ihyeon/flow-qa 0.0.0-stage → 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +73 -0
  3. package/README.md +72 -2
  4. package/THIRD_PARTY_NOTICES.md +9 -0
  5. package/dist/bin/flow-qa.mjs +3 -0
  6. package/dist/examples/demo/server.js +31 -0
  7. package/dist/src/cli.js +285 -0
  8. package/dist/src/compiler.js +62 -0
  9. package/dist/src/engine.js +246 -0
  10. package/dist/src/i18n.js +98 -0
  11. package/dist/src/planner.js +112 -0
  12. package/dist/src/report.js +315 -0
  13. package/dist/src/schema.js +71 -0
  14. package/dist/src/ui/entry.js +109 -0
  15. package/dist/src/ui/evidence.js +85 -0
  16. package/dist/src/ui/public/app.js +360 -0
  17. package/dist/src/ui/public/display.js +56 -0
  18. package/dist/src/ui/public/fonts/dm-sans.woff2 +0 -0
  19. package/dist/src/ui/public/fonts/noto-kr-subset.woff2 +0 -0
  20. package/dist/src/ui/public/i18n.js +290 -0
  21. package/dist/src/ui/public/index.html +117 -0
  22. package/dist/src/ui/public/style.css +200 -0
  23. package/dist/src/ui/server.js +785 -0
  24. package/dist/src/workspace.js +88 -0
  25. package/docs/architecture.md +38 -0
  26. package/docs/releases.md +54 -0
  27. package/docs/ui.md +71 -0
  28. package/licenses/DM-Sans-LICENSE.txt +93 -0
  29. package/licenses/Noto-Sans-KR-LICENSE.txt +93 -0
  30. package/node_modules/@hyeonse/design-system/.github/workflows/ci.yml +21 -0
  31. package/node_modules/@hyeonse/design-system/.npmignore +5 -0
  32. package/node_modules/@hyeonse/design-system/AGENTS.md +10 -0
  33. package/node_modules/@hyeonse/design-system/LICENSE +21 -0
  34. package/node_modules/@hyeonse/design-system/README.md +67 -0
  35. package/node_modules/@hyeonse/design-system/THIRD_PARTY_NOTICES.md +5 -0
  36. package/node_modules/@hyeonse/design-system/dist/components.css +105 -0
  37. package/node_modules/@hyeonse/design-system/dist/preferences.d.ts +21 -0
  38. package/node_modules/@hyeonse/design-system/dist/preferences.js +69 -0
  39. package/node_modules/@hyeonse/design-system/dist/react.d.ts +21 -0
  40. package/node_modules/@hyeonse/design-system/dist/react.js +100 -0
  41. package/node_modules/@hyeonse/design-system/dist/tokens.css +75 -0
  42. package/node_modules/@hyeonse/design-system/docs/api.md +33 -0
  43. package/node_modules/@hyeonse/design-system/docs/assets/fonts/dm-sans.woff2 +0 -0
  44. package/node_modules/@hyeonse/design-system/docs/assets/fonts/noto-kr-subset.woff2 +0 -0
  45. package/node_modules/@hyeonse/design-system/docs/audit.md +29 -0
  46. package/node_modules/@hyeonse/design-system/docs/foundations.md +30 -0
  47. package/node_modules/@hyeonse/design-system/docs/verification.md +13 -0
  48. package/node_modules/@hyeonse/design-system/index.html +51 -0
  49. package/node_modules/@hyeonse/design-system/licenses/DM-Sans-LICENSE.txt +93 -0
  50. package/node_modules/@hyeonse/design-system/licenses/Noto-Sans-KR-LICENSE.txt +93 -0
  51. package/node_modules/@hyeonse/design-system/licenses/arla6ka-MIT.txt +21 -0
  52. package/node_modules/@hyeonse/design-system/package.json +35 -0
  53. package/node_modules/@hyeonse/design-system/scripts/audit-tokens.mjs +48 -0
  54. package/node_modules/@hyeonse/design-system/scripts/build.mjs +12 -0
  55. package/node_modules/@hyeonse/design-system/showcase.css +75 -0
  56. package/node_modules/@hyeonse/design-system/showcase.mjs +63 -0
  57. package/node_modules/@hyeonse/design-system/skills/hyeonse-design-system/SKILL.md +18 -0
  58. package/node_modules/@hyeonse/design-system/skills/hyeonse-design-system/references/api.md +7 -0
  59. package/node_modules/@hyeonse/design-system/skills/hyeonse-design-system/references/build.md +7 -0
  60. package/node_modules/@hyeonse/design-system/skills/hyeonse-design-system/references/foundations.md +7 -0
  61. package/node_modules/@hyeonse/design-system/skills/hyeonse-design-system/references/mapping.md +7 -0
  62. package/node_modules/@hyeonse/design-system/skills/hyeonse-design-system/references/migration.md +5 -0
  63. package/node_modules/@hyeonse/design-system/skills/hyeonse-design-system/references/review-docs.md +5 -0
  64. package/node_modules/@hyeonse/design-system/src/components.css +105 -0
  65. package/node_modules/@hyeonse/design-system/src/preferences.ts +87 -0
  66. package/node_modules/@hyeonse/design-system/src/react.tsx +161 -0
  67. package/node_modules/@hyeonse/design-system/src/tokens.css +75 -0
  68. package/node_modules/@hyeonse/design-system/tests/audit.test.mjs +30 -0
  69. package/node_modules/@hyeonse/design-system/tests/preferences.test.mjs +86 -0
  70. package/node_modules/@hyeonse/design-system/tsconfig.json +16 -0
  71. package/package.json +73 -4
@@ -0,0 +1,93 @@
1
+ Copyright 2014 The DM Sans Project Authors (https://github.com/googlefonts/dm-fonts) DMSans-Italic[opsz,wght].ttf: Copyright 2014 The DM Sans Project Authors (https://github.com/googlefonts/dm-fonts)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Google Inc.
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arlan Marat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@hyeonse/design-system",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "repository": { "type": "git", "url": "https://github.com/hyeonse-swing/hyeonse-design-system.git" },
6
+ "type": "module",
7
+ "sideEffects": ["./dist/tokens.css", "./dist/components.css"],
8
+ "files": ["dist", "scripts/audit-tokens.mjs", "THIRD_PARTY_NOTICES.md", "licenses"],
9
+ "bin": { "hds-audit": "./scripts/audit-tokens.mjs" },
10
+ "exports": {
11
+ "./tokens.css": "./dist/tokens.css",
12
+ "./components.css": "./dist/components.css",
13
+ "./preferences": {
14
+ "types": "./dist/preferences.d.ts",
15
+ "default": "./dist/preferences.js"
16
+ },
17
+ "./react": {
18
+ "types": "./dist/react.d.ts",
19
+ "default": "./dist/react.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "node scripts/build.mjs",
24
+ "check": "tsc --noEmit",
25
+ "test": "node --test tests/*.test.mjs"
26
+ },
27
+ "peerDependencies": { "react": ">=19 <20" },
28
+ "peerDependenciesMeta": { "react": { "optional": true } },
29
+ "devDependencies": {
30
+ "@types/react": "^19.0.0",
31
+ "react": "19.3.0",
32
+ "react-dom": "19.3.0",
33
+ "typescript": "~5.9.3"
34
+ }
35
+ }
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, readdir } from 'node:fs/promises';
3
+ import { resolve, relative } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ // Audit CSS values, including new files. Material colors need counted, documented exceptions.
7
+ const root = resolve(process.argv[2] || '.');
8
+ const configPath = resolve(root, process.argv[3] || 'design-system.audit.json');
9
+ const config = JSON.parse(await readFile(configPath, 'utf8'));
10
+ const tokenPath = fileURLToPath(new URL('../dist/tokens.css', import.meta.url));
11
+ const withoutComments = source => source.replace(/\/\*[\s\S]*?\*\//g, '');
12
+ const tokenSource = withoutComments(await readFile(tokenPath, 'utf8'));
13
+ const systemTokens = new Set([...tokenSource.matchAll(/(--[\w-]+)\s*:/g)].map(match => match[1]));
14
+ const rawColors = /#[\da-f]{8}\b|#[\da-f]{6}\b|#[\da-f]{4}\b|#[\da-f]{3}\b|\b(?:rgba?|hsla?|oklch|oklab|lab|lch|color)\([^)]*\)/gi;
15
+ const errors = [];
16
+ const files = [];
17
+ async function walk(directory) {
18
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
19
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
20
+ const path = resolve(directory, entry.name);
21
+ if (entry.isDirectory()) await walk(path);
22
+ else if (entry.name.endsWith('.css')) files.push(path);
23
+ }
24
+ }
25
+ for (const directory of config.paths || ['src']) await walk(resolve(root, directory));
26
+ for (const file of files) {
27
+ const path = relative(root, file).replaceAll('\\', '/');
28
+ const source = withoutComments(await readFile(file, 'utf8'));
29
+ const exception = config.exceptions?.[path];
30
+ if (exception && !exception.reason?.trim()) errors.push(`${path}: exception needs a reason`);
31
+ const counts = {};
32
+ for (const match of source.matchAll(rawColors)) {
33
+ const value = match[0].toLowerCase().replace(/\s+/g, ' ');
34
+ counts[value] = (counts[value] || 0) + 1;
35
+ }
36
+ for (const [value, count] of Object.entries(counts)) {
37
+ if (count > (exception?.colors?.[value] || 0)) errors.push(`${path}: raw color ${value} (${count} uses; allowed ${exception?.colors?.[value] || 0})`);
38
+ }
39
+ for (const match of source.matchAll(/(--[\w-]+)\s*:/g)) {
40
+ if (systemTokens.has(match[1]) && !config.overrideTokens?.[path]?.includes(match[1])) {
41
+ errors.push(`${path}: shared token ${match[1]} must be changed in the design system`);
42
+ }
43
+ }
44
+ }
45
+ if (errors.length) {
46
+ console.error(errors.join('\n'));
47
+ process.exitCode = 1;
48
+ } else console.log(`Design-system CSS audit passed: ${files.length} files, ${systemTokens.size} shared tokens.`);
@@ -0,0 +1,12 @@
1
+ import { cpSync, mkdirSync, rmSync } from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, resolve } from 'node:path';
5
+
6
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
7
+ rmSync(resolve(root, 'dist'), { recursive: true, force: true });
8
+ mkdirSync(resolve(root, 'dist'), { recursive: true });
9
+ execFileSync(resolve(root, 'node_modules/.bin/tsc'), ['-p', resolve(root, 'tsconfig.json')], { stdio: 'inherit' });
10
+ for (const name of ['tokens.css', 'components.css']) {
11
+ cpSync(resolve(root, 'src', name), resolve(root, 'dist', name));
12
+ }
@@ -0,0 +1,75 @@
1
+ @font-face{font-family:'DM Sans';src:url('./docs/assets/fonts/dm-sans.woff2') format('woff2');font-display:swap;font-weight:100 900}
2
+ @font-face{font-family:'Noto Sans KR';src:url('./docs/assets/fonts/noto-kr-subset.woff2') format('woff2');font-display:swap;font-weight:100 900}
3
+ *,*::before,*::after{box-sizing:border-box}
4
+ html{scroll-behavior:smooth}
5
+ body{margin:0;background:var(--paper);color:var(--ink);font-family:'DM Sans','Noto Sans KR',system-ui,sans-serif;-webkit-font-smoothing:antialiased}
6
+ button,input{font:inherit}
7
+ a{color:inherit}
8
+ button{cursor:pointer}
9
+ button:disabled{cursor:not-allowed}
10
+ :focus-visible{outline:3px solid var(--accent-text);outline-offset:3px}
11
+ ::selection{background:var(--accent-solid);color:var(--on-accent)}
12
+ .skip-link{position:absolute;z-index:20;left:16px;top:-60px;padding:12px 18px;background:var(--accent-solid);color:var(--on-accent)}
13
+ .skip-link:focus{top:12px}
14
+ .catalog-shell{max-width:1680px;margin:auto;padding:0 clamp(22px,4vw,72px)}
15
+ .masthead{height:98px;display:flex;align-items:center;gap:48px;border-bottom:1px solid var(--line)}
16
+ .brand{display:flex;align-items:center;gap:12px;text-decoration:none;font-size:10px;line-height:1.2;font-weight:800;letter-spacing:.1em}
17
+ .brand-mark{display:flex;align-items:center;justify-content:center;width:35px;height:35px;border:2px solid var(--ink);font-size:20px;font-weight:800;letter-spacing:-.12em}
18
+ .brand-mark span{color:var(--accent-text)}
19
+ .masthead nav{display:flex;gap:clamp(18px,2.5vw,40px);margin-left:auto}
20
+ .masthead nav a{text-decoration:none;font-size:12px;font-weight:700}
21
+ .masthead nav a:hover,.text-action:hover,.footer a:hover{color:var(--accent-text)}
22
+ .masthead-version,.hero-index{font-size:10px;font-weight:800;letter-spacing:.12em;color:var(--muted);white-space:nowrap}
23
+ .hero{min-height:720px;position:relative;display:flex;flex-direction:column;justify-content:space-between;padding:47px 0 0;overflow:hidden}
24
+ .hero::after{content:'';position:absolute;right:-12%;top:7%;width:46%;aspect-ratio:1;border:1px solid var(--line);border-radius:50%;box-shadow:0 0 0 95px color-mix(in srgb,var(--accent-solid) 4%,transparent),0 0 0 190px color-mix(in srgb,var(--accent-solid) 3%,transparent);pointer-events:none}
25
+ .hero-top,.hero-bottom,.hero-grid{position:relative;z-index:1;display:flex;justify-content:space-between;gap:20px}
26
+ .eyebrow{display:inline-flex;align-items:center;gap:10px;font-size:10px;font-weight:800;letter-spacing:.16em;color:var(--accent-text)}
27
+ .signal-dot{width:7px;height:7px;border-radius:50%;background:var(--accent-solid)}
28
+ .hero h1{position:relative;z-index:1;margin:84px 0 42px;font-size:clamp(64px,10.8vw,172px);line-height:.93;letter-spacing:-.09em;font-weight:800}
29
+ .hero h1 em{font-style:normal;color:var(--accent-text)}
30
+ .hero-bottom{align-items:end;padding-bottom:62px}
31
+ .hero-bottom p{max-width:530px;margin:0;color:var(--muted);font-size:clamp(16px,1.4vw,20px);line-height:1.7;word-break:keep-all}
32
+ .text-action{min-width:168px;display:flex;justify-content:space-between;align-items:center;gap:30px;border-bottom:1px solid var(--ink);padding-bottom:10px;text-decoration:none;font-size:12px;font-weight:800}
33
+ .hero-grid{padding:22px 0;border-top:1px solid var(--line);font-size:10px;font-weight:800;letter-spacing:.12em;color:var(--muted)}
34
+ .catalog-section{padding:clamp(85px,9vw,150px) 0;border-bottom:1px solid var(--line);scroll-margin-top:20px}
35
+ .section-heading{display:flex;justify-content:space-between;align-items:end;gap:30px;margin-bottom:50px}
36
+ .section-heading h2{margin:16px 0 0;font-size:clamp(38px,5.3vw,76px);line-height:1;letter-spacing:-.075em}
37
+ .section-heading>p{max-width:335px;margin:0 0 3px;color:var(--muted);font-size:14px;line-height:1.7;word-break:keep-all}
38
+ .foundation-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
39
+ .sample-panel{min-width:0;min-height:520px;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-card);padding:clamp(25px,3vw,46px);display:flex;flex-direction:column}
40
+ .panel-top,.control-card-head{display:flex;justify-content:space-between;gap:15px;font-size:10px;letter-spacing:.12em;font-weight:800;color:var(--muted)}
41
+ .type-display{margin:auto 0 25px;font-size:clamp(130px,16vw,250px);font-weight:700;line-height:.9;letter-spacing:-.12em}
42
+ .type-dot{color:var(--accent-text)}
43
+ .type-panel h3,.control-card h3{margin:0 0 13px;font-size:clamp(24px,2.5vw,38px);line-height:1.16;letter-spacing:-.065em}
44
+ .type-panel>p{max-width:455px;margin:0;color:var(--muted);font-size:14px;line-height:1.75;word-break:keep-all}
45
+ .type-meta{display:flex;gap:24px;margin-top:32px;font-size:10px;letter-spacing:.1em;font-weight:800;color:var(--muted)}
46
+ .swatch-stack{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:repeat(2,minmax(150px,1fr));gap:8px;margin:42px 0 22px;flex:1}
47
+ .swatch{min-width:0;display:flex;flex-direction:column;justify-content:space-between;padding:18px;border:1px solid var(--line);font-size:20px;font-weight:700;letter-spacing:-.04em;border-radius:var(--radius-control)}
48
+ .swatch code{font-family:inherit;font-size:10px;letter-spacing:0;font-weight:700}
49
+ .swatch-paper{background:var(--paper);color:var(--ink)}.swatch-surface{background:var(--surface-raised);color:var(--ink)}.swatch-accent{background:var(--accent-solid);color:var(--on-accent);border-color:transparent}.swatch-inverse{background:var(--inverse-surface);color:var(--inverse-ink);border-color:transparent}
50
+ .sample-note{margin:0;color:var(--muted);font-size:12px;line-height:1.65}
51
+ .token-strip{display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin-top:16px;border:1px solid var(--line);border-radius:var(--radius-card)}
52
+ .token-strip>div{min-width:0;display:grid;grid-template-columns:17px 1fr;gap:4px 10px;padding:20px 24px;border-right:1px solid var(--line)}
53
+ .token-strip>div:last-child{border-right:0}.token-strip code{font-family:inherit;font-size:11px;font-weight:800}.token-strip small{grid-column:2;color:var(--muted);font-size:11px}
54
+ .token-chip{width:16px;height:16px;display:block;border-radius:50%;border:1px solid var(--line)}.token-ink{background:var(--ink)}.token-muted{background:var(--muted)}.token-line{background:var(--line)}.token-accent{background:var(--accent-text)}
55
+ .control-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
56
+ .control-card{min-width:0;min-height:335px;padding:clamp(24px,3vw,40px);border:1px solid var(--line);border-radius:var(--radius-card);background:var(--surface-raised)}
57
+ .control-card h3{margin:35px 0 30px;font-size:28px}
58
+ .button-row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}.sample-button{min-height:44px;padding:12px 17px;border-radius:var(--radius-control);font-size:12px;font-weight:800}.sample-button.primary{background:var(--accent-solid);color:var(--on-accent);border:1px solid var(--accent-solid)}.sample-button.primary:not(:disabled):hover{filter:brightness(.88)}.sample-button.secondary{color:var(--ink);background:transparent;border:1px solid var(--line)}.sample-button.secondary:hover{border-color:var(--accent-text);color:var(--accent-text)}.sample-button:disabled{opacity:.42}.sample-button span{padding-left:15px}
59
+ .control-feedback,.form-card p{margin:22px 0 0;color:var(--muted);font-size:12px;line-height:1.6}
60
+ .sample-fieldset{border:0;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:8px}.sample-fieldset legend{width:100%;margin-bottom:10px;color:var(--muted);font-size:12px}.sample-choice{position:relative}.sample-choice input,.segmented input,.accent-choices input{position:absolute;opacity:0;width:100%;height:100%;inset:0;margin:0;cursor:pointer}.sample-choice span{display:block;padding:11px 16px;min-height:44px;border:1px solid var(--line);border-radius:var(--radius-control);font-size:12px;font-weight:700}.sample-choice input:checked+span{border-color:var(--accent-text);color:var(--accent-text);box-shadow:inset 0 0 0 1px var(--accent-text)}.sample-choice input:focus-visible+span,.segmented input:focus-visible+span,.accent-choices input:focus-visible+span{outline:3px solid var(--accent-text);outline-offset:3px}
61
+ .check-row{display:flex;align-items:center;gap:10px;margin-top:25px;font-size:12px;font-weight:700}.check-row input{width:18px;height:18px;accent-color:var(--accent-solid)}
62
+ .form-card label{display:block;font-size:12px;font-weight:800;margin:16px 0 8px}.form-card input{width:100%;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:var(--radius-control);background:var(--paper);color:var(--ink);outline:none}.form-card input:focus{border-color:var(--accent-text);box-shadow:0 0 0 2px var(--accent-text)}.form-card input:disabled{opacity:.5;background:var(--surface)}.form-card p{margin:8px 0 20px}
63
+ .status-line{display:flex;flex-wrap:wrap;gap:8px}.status-pill{display:inline-flex;align-items:center;gap:7px;min-height:32px;padding:5px 10px;border:1px solid var(--line);border-radius:999px;font-size:11px;font-weight:800}.status-pill.selected{border-color:var(--accent-text);color:var(--accent-text)}.status-pill i{width:6px;height:6px;border-radius:50%;background:currentColor}.status-pill.muted{color:var(--muted);opacity:.55}.status-preview{margin-top:25px;padding:24px;background:var(--inverse-surface);color:var(--inverse-ink);border-radius:var(--radius-card)}.status-preview span{font-size:10px;font-weight:800;letter-spacing:.14em;opacity:.65}.status-preview strong{display:block;margin-top:16px;font-size:24px;letter-spacing:-.06em}.status-preview p{font-size:12px;opacity:.72;margin:5px 0 0}
64
+ .preferences-section{padding-bottom:145px}.settings-layout{display:grid;grid-template-columns:.85fr 1.15fr;gap:clamp(32px,7vw,120px);align-items:start}.settings-intro{position:sticky;top:40px}.index-large{font-size:clamp(80px,12vw,170px);font-weight:800;letter-spacing:-.1em;line-height:1;color:var(--accent-text)}.index-large span{font-size:18px;letter-spacing:0;color:var(--muted)}.settings-intro p{max-width:410px;margin:30px 0;color:var(--muted);font-size:18px;line-height:1.7;word-break:keep-all}.settings-current{display:inline-block;border-left:3px solid var(--accent-solid);padding:8px 0 8px 14px;font-size:12px;font-weight:800}
65
+ .settings-form{border-top:1px solid var(--line)}.settings-form fieldset{border:0;border-bottom:1px solid var(--line);margin:0;padding:26px 0 28px}.settings-form legend{width:100%;display:flex;justify-content:space-between;margin-bottom:16px;font-size:14px;font-weight:800}.settings-form legend span{font-size:11px;color:var(--muted)}.segmented,.accent-choices{display:flex;flex-wrap:wrap;gap:8px}.segmented label,.accent-choices label{position:relative;cursor:pointer}.segmented span{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:10px 18px;border:1px solid var(--line);border-radius:var(--radius-control);font-size:12px;font-weight:700}.segmented input:checked+span{border-color:var(--accent-text);box-shadow:inset 0 0 0 1px var(--accent-text);color:var(--accent-text)}.segmented label:hover span,.accent-choices label:hover>span:last-child{border-color:var(--accent-text)}
66
+ .accent-choices label{display:flex;align-items:center;gap:8px;min-height:44px;padding:6px 12px;border:1px solid var(--line);border-radius:var(--radius-control);font-size:12px;font-weight:700}.accent-choices label:has(input:checked){border-color:var(--accent-text);box-shadow:inset 0 0 0 1px var(--accent-text)}.accent-choices label:has(input:focus-visible){outline:3px solid var(--accent-text);outline-offset:3px}.accent-circle{width:22px;height:22px;border-radius:50%}.cobalt{background:#204cfc}.forest{background:#087451}.coral{background:#b93d20}
67
+ .reset-button{margin-top:20px;display:flex;justify-content:space-between;width:100%;padding:15px 0;border:0;border-bottom:1px solid var(--ink);background:none;color:var(--ink);font-size:12px;font-weight:800}.reset-button:hover{color:var(--accent-text);border-color:var(--accent-text)}
68
+ .footer{display:flex;justify-content:space-between;gap:20px;align-items:center;padding:34px 0 50px;font-size:11px}.footer>span{font-weight:800;letter-spacing:.1em}.footer p{color:var(--muted)}.footer a{text-decoration:none;font-weight:800}
69
+ :root[data-density='compact'] .catalog-section{padding-top:calc(clamp(85px,9vw,150px)*var(--space-factor));padding-bottom:calc(clamp(85px,9vw,150px)*var(--space-factor))}:root[data-density='compact'] .sample-panel,:root[data-density='compact'] .control-card{padding:calc(clamp(24px,3vw,40px)*var(--space-factor))}
70
+ :root[data-motion='reduced'] *, :root[data-motion='reduced'] *::before, :root[data-motion='reduced'] *::after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}
71
+ @media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
72
+ @media(max-width:900px){.masthead-version{display:none}.foundation-grid,.control-grid,.settings-layout{grid-template-columns:1fr}.settings-intro{position:static}.hero{min-height:640px}.hero::after{right:-25%;width:65%}.sample-panel{min-height:450px}.settings-intro p{font-size:16px}}
73
+ @media(max-width:650px){.catalog-shell{padding:0 20px}.masthead{height:auto;min-height:80px;flex-wrap:wrap;gap:8px 20px;padding:16px 0}.masthead nav{order:2;width:100%;margin:0;justify-content:space-between;padding:8px 0}.masthead nav a{font-size:11px}.hero{min-height:650px}.hero h1{margin:100px 0 30px;font-size:clamp(58px,13vw,90px)}.hero-bottom{display:block}.hero-bottom p{font-size:15px}.text-action{margin-top:28px;max-width:200px}.hero-grid span:last-child{display:none}.section-heading{display:block}.section-heading>p{margin-top:24px}.sample-panel{min-height:420px}.swatch-stack{grid-template-rows:repeat(2,120px)}.token-strip{grid-template-columns:repeat(2,1fr)}.token-strip>div:nth-child(2){border-right:0}.token-strip>div:nth-child(-n+2){border-bottom:1px solid var(--line)}.control-card{min-height:0}.control-card-head{font-size:9px}.footer{align-items:start;flex-direction:column}.footer p{margin:0}}
74
+
75
+ .component-specimen{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin-top:22px;padding-top:20px;border-top:1px solid var(--line)}.component-specimen>span{width:100%;font-size:10px;letter-spacing:.1em;font-weight:800;color:var(--muted)}.component-specimen small{color:var(--muted);font-size:11px}
@@ -0,0 +1,63 @@
1
+ import {
2
+ defaultPreferences, effectiveMotion, parsePreferences, preferencesKey,
3
+ resolvedTheme, serializePreferences, validatePreferences,
4
+ } from './dist/preferences.js';
5
+
6
+ const root = document.documentElement;
7
+ const form = document.getElementById('settings-form');
8
+ const current = document.getElementById('settings-current');
9
+ const themeQuery = window.matchMedia('(prefers-color-scheme: dark)');
10
+ const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
11
+
12
+ function read() {
13
+ try { return parsePreferences(localStorage.getItem(preferencesKey)); }
14
+ catch { return { ...defaultPreferences }; }
15
+ }
16
+
17
+ let preferences = read();
18
+
19
+ function apply() {
20
+ root.dataset.theme = resolvedTheme(preferences.theme, themeQuery.matches);
21
+ root.dataset.themePreference = preferences.theme;
22
+ root.dataset.accent = preferences.accent;
23
+ root.dataset.density = preferences.density;
24
+ root.dataset.corners = preferences.corners;
25
+ root.dataset.motion = effectiveMotion(preferences.motion, motionQuery.matches);
26
+ for (const [key, value] of Object.entries(preferences)) {
27
+ const input = form.querySelector(`input[name="${key}"][value="${value}"]`);
28
+ if (input) input.checked = true;
29
+ }
30
+ const labels = { theme: { system: '기기 설정', light: '밝게', dark: '어둡게' }, accent: { cobalt: '코발트', forest: '포레스트', coral: '코랄' } };
31
+ current.textContent = `${labels.theme[preferences.theme]} · ${labels.accent[preferences.accent]} · ${preferences.density === 'compact' ? '촘촘한 간격' : '기본 간격'}`;
32
+ document.getElementById('theme-toggle-label').textContent = labels.theme[preferences.theme];
33
+ document.getElementById('theme-toggle-specimen').setAttribute('aria-label', `현재 테마 ${labels.theme[preferences.theme]}. 다음 모드로 변경`);
34
+ }
35
+
36
+ function save(next) {
37
+ preferences = validatePreferences(next);
38
+ apply();
39
+ try { localStorage.setItem(preferencesKey, serializePreferences(preferences)); }
40
+ catch { /* Controls still work in this tab. */ }
41
+ }
42
+
43
+ form.addEventListener('change', event => {
44
+ const input = event.target;
45
+ if (!(input instanceof HTMLInputElement)) return;
46
+ save({ ...preferences, [input.name]: input.value });
47
+ });
48
+ document.getElementById('reset-preferences').addEventListener('click', () => save(defaultPreferences));
49
+ document.getElementById('theme-toggle-specimen').addEventListener('click', () => {
50
+ const themes = ['system', 'light', 'dark'];
51
+ save({ ...preferences, theme: themes[(themes.indexOf(preferences.theme) + 1) % themes.length] });
52
+ });
53
+ document.getElementById('action-button').addEventListener('click', () => {
54
+ document.getElementById('action-feedback').textContent = '동작 확인 완료 · 버튼이 활성화되어 있습니다.';
55
+ });
56
+ window.addEventListener('storage', event => {
57
+ if (event.key !== preferencesKey && event.key !== null) return;
58
+ preferences = read();
59
+ apply();
60
+ });
61
+ themeQuery.addEventListener('change', apply);
62
+ motionQuery.addEventListener('change', apply);
63
+ apply();
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: hyeonse-design-system
3
+ description: Build, extend, map, migrate, review, or document interfaces using Hyeonse Design System in personal projects. Use for design-system work; skip unrelated routine code edits.
4
+ ---
5
+
6
+ # Hyeonse Design System
7
+
8
+ Use the installed package and this skill's [visual contract](references/foundations.md) as the visual contract. Inspect the target app and the user's requested scope before choosing an operation. Preserve existing behavior and purpose; do not expand a screen request into an app-wide migration.
9
+
10
+ ## Route the request
11
+
12
+ - New screen or system foundation: read [build](references/build.md).
13
+ - Extend an existing pattern or component: read [build](references/build.md) and the affected source/call sites.
14
+ - Map hardcoded values to tokens: read [mapping](references/mapping.md).
15
+ - Move an app onto the system: read [migration](references/migration.md).
16
+ - Review a screen or document a component: read [review and docs](references/review-docs.md).
17
+
18
+ Read only the relevant reference. Start from the role and purpose of each element, then select tokens and states. Check the rendered result at relevant widths and input states when a browser is available. Report what was actually checked and any unavailable proof. Commit, push, publish, deploy, install a global skill, or message others only when the user explicitly authorizes that action.
@@ -0,0 +1,7 @@
1
+ # Package contract
2
+
3
+ The public package is `@hyeonse/design-system` with `./tokens.css`, `./components.css`, `./preferences`, and `./react` exports. Import the two CSS files for tokens and the current React preference UI styles. `.hds-*` currently styles `ThemeToggle` and `PreferencesPanel`; the package does not export general Button or Input React components.
4
+
5
+ `./preferences` exports `preferencesKey`, `defaultPreferences`, validation/parse/serialize helpers, `resolvedTheme`, `effectiveMotion`, `createPreferenceScript(storageKey?)`, and `preferenceScript`. Place the initial script in the document head before CSS paints. Its default storage key is `hyeonse-site-preferences`.
6
+
7
+ `./react` exports `PreferencesProvider`, `usePreferences`, `ThemeToggle`, and `PreferencesPanel`. The Provider accepts `storageKey?` and `eventName?` (default `hyeonse:preferences`) and offers `preferences`, `setPreference`, and `resetPreferences` through the hook. The two controls accept `locale='ko'|'en'`. Match a custom initial-script storage key with the Provider key. Inspect installed package types and actual target framework integration before coding against this contract.
@@ -0,0 +1,7 @@
1
+ # Build or extend
2
+
3
+ Inspect the target route, its neighboring patterns, and [the package contract](api.md). Name the screen's purpose and primary action. Use `--paper`, `--ink`, `--surface`, `--line`, and role-appropriate accent tokens before adding new values. Keep typography and whitespace hierarchy recognizable: large editorial heading, compact labels, clear content measure, crisp controls.
4
+
5
+ For a new control, define its actual states: idle, hover, focus, selected, disabled, and any loading or error state that the behavior needs. Use semantic HTML and keyboard behavior. Do not describe a visual CSS sample as a packaged React component. Extend the package only when the requested use case needs a reusable contract; otherwise implement a local screen.
6
+
7
+ Verify the changed screen in light/dark, relevant accents, narrow/wide widths, keyboard focus, and reduced motion when those states are affected. Compare the rendered result with the purpose and user request, not a generic aesthetic score.
@@ -0,0 +1,7 @@
1
+ # Visual contract
2
+
3
+ The default is white paper (`--paper: #fff`), dark ink (`--ink: #101114`), and cobalt (`--accent-solid: #204cfc`). Forest (`#087451`) and coral (`#b93d20`) are alternate accents. Dark mode uses `#111318` paper and separate lighter `--accent-text` values for readable text and focus. Use `--accent-solid` with `--on-accent` for filled actions; use `--accent-text` for links, selected outlines, and focus. `--surface` groups content, `--surface-raised` elevates a card, `--line` divides, and `--muted` carries supporting information.
4
+
5
+ Use DM Sans for English and Noto Sans KR for Korean when those font assets are available in the consumer app. The package does not ship fonts. Prefer large editorial headings, compact labels, readable body measure, and crisp controls. Base radii are 0px for cards and 2px for controls; the soft preference changes them to 20px and 12px. Density is expressed through `--space-factor` (1 or .8), which components must actually use for spacing to change.
6
+
7
+ Preferences: theme `system/light/dark`, accent `cobalt/forest/coral`, density `comfortable/compact`, corners `crisp/soft`, motion `full/reduced`. The root receives resolved `data-theme` plus `data-accent`, `data-density`, `data-corners`, and effective `data-motion`. System reduced motion takes priority. Preserve keyboard access, visible focus, and text labels; never convey a state only by color or motion.
@@ -0,0 +1,7 @@
1
+ # Token mapping
2
+
3
+ Inventory the requested file, screen, or diff. For each hardcoded style, record its element, purpose, current value, candidate role token, and whether replacement preserves contrast and meaning. `--accent-solid` is for filled emphasis; `--accent-text` is for readable text, borders, and focus on ordinary surfaces. `--surface` groups content; `--surface-raised` separates a foreground card. A similar hex value alone does not justify a mapping.
4
+
5
+ For a read-only audit, report candidates and uncertain cases without editing. For an authorized migration, change only the agreed scope and check the actual render across supported settings. Propose a new token only for a recurring semantic role that current tokens cannot express.
6
+
7
+ For CSS enforcement in a consumer app, use the `hds-audit <consumer-root> [config-relative-path]` CLI when available. Keep exceptions file-specific with a stated reason and expected count. Its CSS-only scope does not prove that script or 3D colors conform.
@@ -0,0 +1,5 @@
1
+ # Migrate a screen or app
2
+
3
+ Inventory routes, existing theme storage, CSS variables, fonts, and controls. Compare the target's current behavior with [the package contract](api.md). Migrate in the requested unit of work: introduce tokens and CSS, connect preferences only where needed, then replace local styling in a bounded area. Keep the target app's data flow, routes, and product language intact.
4
+
5
+ If preference persistence already exists, plan the storage key and event integration so existing users do not silently lose a choice. Check first paint, cross-tab changes, system theme, narrow layout, keyboard access, and reduced motion where affected. Record remaining unmigrated areas explicitly rather than claiming complete adoption.
@@ -0,0 +1,5 @@
1
+ # Review or document
2
+
3
+ For review, inspect the rendered screen and its actual interactions. Rank findings by impact on task completion, readability, access, and consistency. Give an element, trigger, observed effect, and actionable correction. Distinguish an observed defect from a code-only suspicion and do not invent browser proof.
4
+
5
+ For component documentation, read implementation and real call sites first. State purpose, when to use, public API, meaningful states, keyboard behavior, responsive behavior, and known limits. Include an example that compiles against the exported API. If the element is only showcase markup or a local app pattern, label it that way instead of documenting it as a package export.
@@ -0,0 +1,105 @@
1
+ .hds-themeToggle {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: 9px;
6
+ min-width: 44px;
7
+ min-height: var(--control-min-size);
8
+ padding: 8px 12px;
9
+ border: 1px solid var(--line);
10
+ border-radius: var(--radius-control);
11
+ color: var(--ink);
12
+ background: var(--paper);
13
+ font: inherit;
14
+ font-size: var(--text-control);
15
+ font-weight: 700;
16
+ cursor: pointer;
17
+ }
18
+
19
+ .hds-themeToggle svg { display: block; width: 19px; height: 19px; flex: none; }
20
+ .hds-themeToggle:hover { border-color: var(--accent-text); color: var(--accent-text); }
21
+ .hds-themeToggle:focus-visible, .hds-panel button:focus-visible, .hds-radioOption input:focus-visible + span {
22
+ outline: 3px solid var(--accent-text);
23
+ outline-offset: 3px;
24
+ }
25
+
26
+ .hds-panel {
27
+ box-sizing: border-box;
28
+ width: 100%;
29
+ padding: clamp(20px, 4vw, 48px);
30
+ border: 1px solid var(--line);
31
+ border-radius: var(--radius-card);
32
+ color: var(--ink);
33
+ background: var(--surface);
34
+ font-family: var(--font-sans);
35
+ }
36
+
37
+ .hds-heading {
38
+ display: flex;
39
+ align-items: end;
40
+ justify-content: space-between;
41
+ gap: 24px;
42
+ padding-bottom: 26px;
43
+ border-bottom: 1px solid var(--line);
44
+ }
45
+
46
+ .hds-heading h2 { margin: 10px 0 0; font-size: clamp(30px, 4vw, 54px); line-height: 1.1; letter-spacing: -.06em; }
47
+ .hds-heading p { margin: 0 0 3px; color: var(--muted); font-size: 13px; }
48
+ .hds-kicker { color: var(--accent-text); font-size: 10px; font-weight: 800; letter-spacing: .13em; }
49
+ .hds-groups { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 clamp(18px, 4vw, 48px); }
50
+ .hds-group { min-width: 0; margin: 0; padding: 22px 0 26px; border: 0; border-bottom: 1px solid var(--line); }
51
+ .hds-group legend { display: block; float: left; width: 100%; margin: 0 0 13px; padding: 0; font-size: 13px; font-weight: 800; }
52
+ .hds-group legend + * { clear: both; }
53
+ .hds-options, .hds-accents { display: flex; flex-wrap: wrap; gap: 8px; }
54
+ .hds-radioOption { position: relative; display: inline-flex; min-height: var(--control-min-size); cursor: pointer; }
55
+ .hds-radioOption input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
56
+ .hds-radioOption span, .hds-accentButton {
57
+ box-sizing: border-box;
58
+ display: inline-flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ min-height: var(--control-min-size);
62
+ padding: 8px 14px;
63
+ border: 1px solid var(--line);
64
+ border-radius: var(--radius-control);
65
+ color: var(--ink);
66
+ background: var(--surface-raised);
67
+ font: inherit;
68
+ font-size: var(--text-control);
69
+ font-weight: 700;
70
+ line-height: 1.3;
71
+ white-space: nowrap;
72
+ }
73
+ .hds-radioOption input:checked + span { border-color: var(--accent-text); box-shadow: inset 0 0 0 1px var(--accent-text); color: var(--accent-text); }
74
+ .hds-radioOption:hover span, .hds-accentButton:hover { border-color: var(--accent-text); }
75
+ .hds-accentButton { gap: 9px; cursor: pointer; }
76
+ .hds-accentButton[aria-pressed='true'] { border-color: var(--accent-text); box-shadow: inset 0 0 0 1px var(--accent-text); }
77
+ .hds-swatch { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; color: var(--on-accent); font-size: 14px; font-weight: 900; }
78
+ .hds-cobalt .hds-swatch { background: var(--palette-cobalt); }
79
+ .hds-forest .hds-swatch { background: var(--palette-forest); }
80
+ .hds-coral .hds-swatch { background: var(--palette-coral); }
81
+
82
+ .hds-footer { display: flex; justify-content: space-between; align-items: center; gap: 22px; padding-top: 28px; }
83
+ .hds-footer p { margin: 0; color: var(--muted); font-size: var(--text-control); line-height: 1.65; }
84
+ .hds-footer button {
85
+ flex: none;
86
+ min-height: var(--control-min-size);
87
+ padding: 8px 0;
88
+ border: 0;
89
+ border-bottom: 1px solid currentColor;
90
+ border-radius: 0;
91
+ color: var(--ink);
92
+ background: transparent;
93
+ font: inherit;
94
+ font-size: var(--text-control);
95
+ font-weight: 800;
96
+ cursor: pointer;
97
+ }
98
+ .hds-footer button:hover { color: var(--accent-text); }
99
+
100
+ @media (max-width: 720px) {
101
+ .hds-heading { display: block; }
102
+ .hds-heading p { margin-top: 16px; }
103
+ .hds-groups { grid-template-columns: 1fr; }
104
+ .hds-footer { align-items: start; flex-direction: column; gap: 14px; }
105
+ }