@klodd/ds 3.3.1 → 3.4.1

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 (33) hide show
  1. package/SKILL.md +56 -0
  2. package/bin/klodd-ds.js +25 -0
  3. package/bin/precache.js +43 -0
  4. package/bin/sync.js +33 -0
  5. package/bin/verify.js +66 -0
  6. package/css/00-primitives.css +13 -3
  7. package/css/base/typography.css +3 -5
  8. package/css/components/dropdown.css +1 -1
  9. package/css/components/setting-row.css +1 -1
  10. package/css/components/tab-bar.css +1 -1
  11. package/css/components/tooltip.css +1 -1
  12. package/css/utilities.css +101 -67
  13. package/js/hero-roll.js +5 -5
  14. package/package.json +12 -3
  15. package/references/01-tokens.md +182 -0
  16. package/references/02-components.md +260 -0
  17. package/references/03-quality-bar.md +238 -0
  18. package/references/04-locked-decisions/0001-three-layer-token-architecture.md +41 -0
  19. package/references/04-locked-decisions/0002-bem-naming.md +37 -0
  20. package/references/04-locked-decisions/0003-radix-colors-oklch.md +39 -0
  21. package/references/04-locked-decisions/0004-pixel-numeric-spacing.md +31 -0
  22. package/references/04-locked-decisions/0005-data-app-theming.md +42 -0
  23. package/references/04-locked-decisions/0006-mauve-neutral.md +40 -0
  24. package/references/04-locked-decisions/0007-font-weight-400-500.md +46 -0
  25. package/references/04-locked-decisions/0008-npm-package-distribution.md +40 -0
  26. package/references/05-open-decisions/0001-tx-row-to-list-row-migration-CLOSED.md +80 -0
  27. package/references/05-open-decisions/0002-parallel-classnames-cleanup-CLOSED.md +75 -0
  28. package/references/05-open-decisions/0003-js-duplicates-migration-CLOSED.md +79 -0
  29. package/references/05-open-decisions/0004-halsomentorn-legacy-css.md +51 -0
  30. package/references/05-open-decisions/0005-cat-class-location-CLOSED.md +37 -0
  31. package/references/05-open-decisions/0006-bar-kind-template-migration-CLOSED.md +75 -0
  32. package/references/05-open-decisions/0007-ekonom-base-import-gap-CLOSED.md +111 -0
  33. package/references/05-open-decisions/0008-legacy-token-migration.md +73 -0
package/SKILL.md ADDED
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: klodd-ds
3
+ description: Design memory for the @klodd/ds shared design system across all Klodd apps (Jubb, Ekonom, future apps). Use this skill whenever working on UI components, design tokens, theming, CSS class names, spacing, color, typography, accessibility, or any visual or interaction decision in an app that imports @klodd/ds. Also use when questions arise about component APIs, token architecture, BEM naming, whether to add new tokens vs reuse existing, or how to theme a new app. Contains locked architectural decisions (do not re-litigate without explicit instruction) and open migration items (still under discussion).
4
+ ---
5
+
6
+ # @klodd/ds — Design Memory
7
+
8
+ ## Vad detta är
9
+ Gemensamt designsystem på npm (`@klodd/ds@3.x`). Tre-lagers tokens,
10
+ 33+ komponenter, konsumeras av Jubb och Ekonom. Fler appar planerade.
11
+ Denna Skill fångar både reglerna ("vad") och resonemanget ("varför").
12
+
13
+ ## Läs i denna ordning
14
+ 1. `references/01-tokens.md` — token-arkitektur (primitives → semantic → per-app)
15
+ 2. `references/02-components.md` — komponentkatalog med anti-patterns
16
+ 3. `references/03-quality-bar.md` — icke-förhandlingsbara krav
17
+
18
+ <locked_rules>
19
+ - ALDRIG hårdkoda färg, spacing, radius eller font-size. Alltid semantic token.
20
+ - BEM-namngivning för alla CSS-klasser (.block__element--modifier).
21
+ - WCAG AA minimum på alla interaktiva komponenter.
22
+ - Spacing är pixel-numerisk: --space-N där N = exakt antal pixlar (4, 8, 12, 16...).
23
+ - Per-app token-lager overridar bara semantic — aldrig primitives direkt.
24
+ - Font-weight: bara --fw-regular (400) eller --fw-medium (500).
25
+ Undantag: display-siffror (hero-roll) explicit font-weight: 600.
26
+ - Komponenter refererar ALDRIG --gray-N, --blue-N, --plum-N direkt.
27
+ </locked_rules>
28
+
29
+ ## Apps och theming
30
+ - `data-app="jubb"` → Blue accent (Radix Blue Dark), Mauve neutral
31
+ - `data-app="ekonom"` → Plum accent (Radix Plum Dark), Plum-tonad bakgrund
32
+ - Ny app: lägg till ramp i 00-primitives.css + 5-raders apps/foo.css
33
+
34
+ ## Besluts-records
35
+ - `references/04-locked-decisions/` — låsta beslut. Förklara, ändra inte.
36
+ - `references/05-open-decisions/` — öppna punkter. Presentera alternativen.
37
+
38
+ ## Komponent-val
39
+ 1. Kolla `references/02-components.md` för befintlig komponent
40
+ 2. Föredra komposition över fork
41
+ 3. Saknas den genuint — föreslå var den passar i token-modellen FÖRST
42
+
43
+ ## Uppdatera paketet
44
+ ```bash
45
+ cd ~/dev/klodd-ds
46
+ npm version patch|minor|major
47
+ npm publish --access public
48
+ cd ~/dev/Jubb && npm install @klodd/ds@latest && npm run build:ds
49
+ cd ~/dev/Ekonom && npm install @klodd/ds@latest && npm run build:ds
50
+ ```
51
+
52
+ ## När du är osäker
53
+ Läs relevant ADR. Täcks frågan inte av något ADR — flagga det explicit:
54
+ "Jag ser inget beslut på detta — ska vi skapa ett i references/05-open-decisions/?"
55
+
56
+ Updated: 2026-05-07 (matchar @klodd/ds 3.2.0)
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /* @klodd/ds CLI-dispatcher.
3
+ *
4
+ * Anvandning fran ett app-repo (Jubb, Ekonom, framtida appar):
5
+ * npx @klodd/ds sync - kopiera paketets CSS/JS till app-repots ds-mapp
6
+ * npx @klodd/ds verify - diff:a committade DS-filer mot paketet (CI-gate)
7
+ * npx @klodd/ds precache - generera PRECACHE_URLS fran main.css + base.html
8
+ *
9
+ * Konfiguration: klodd-ds.json i app-repots rot.
10
+ * { "cssTarget": "app/static/css/ds", "jsTarget": "app/static/js/ds" } */
11
+ 'use strict';
12
+
13
+ const cmd = process.argv[2];
14
+ const valid = new Set(['sync', 'verify', 'precache']);
15
+
16
+ if (!valid.has(cmd)) {
17
+ console.error('Anvandning: npx @klodd/ds <sync|verify|precache>');
18
+ console.error('');
19
+ console.error(' sync Kopiera paketets CSS/JS till app-repots ds-mapp.');
20
+ console.error(' verify Diff:a committade DS-filer mot paketet (CI-gate).');
21
+ console.error(' precache Generera PRECACHE_URLS fran main.css + base.html.');
22
+ process.exit(1);
23
+ }
24
+
25
+ require('./' + cmd + '.js');
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ /* @klodd/ds precache - genererar PRECACHE_URLS fran main.css @imports
3
+ * och base.html <link>-taggar.
4
+ * Kor: npx @klodd/ds precache
5
+ * Output: lista av URL:er att klistra in i sw.js
6
+ *
7
+ * Notera: hanterar bara direkta @import i main.css (inte nested).
8
+ * Ekonom har bundles/ekonom.css som importerar nestat - kompletterar
9
+ * manuellt vid jamforelse mot befintlig PRECACHE_URLS. */
10
+ 'use strict';
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const cssPath = path.join(process.cwd(), 'app/static/css/main.css');
16
+ const htmlPath = path.join(process.cwd(), 'app/templates/base.html');
17
+
18
+ if (!fs.existsSync(cssPath)) {
19
+ console.error(`Saknas: ${cssPath}`);
20
+ process.exit(1);
21
+ }
22
+ if (!fs.existsSync(htmlPath)) {
23
+ console.error(`Saknas: ${htmlPath}`);
24
+ process.exit(1);
25
+ }
26
+
27
+ const mainCss = fs.readFileSync(cssPath, 'utf8');
28
+ const baseHtml = fs.readFileSync(htmlPath, 'utf8');
29
+
30
+ const cssImports = [...mainCss.matchAll(/@import url\(['"]?(.+?)['"]?\)/g)]
31
+ .map((m) => '/static/css/' + m[1].replace(/^\.\//, ''));
32
+
33
+ const htmlLinks = [...baseHtml.matchAll(/<link[^>]+rel=["']stylesheet["'][^>]+href=["']([^"'?]+)/g)]
34
+ .map((m) => m[1]);
35
+
36
+ const allUrls = [...new Set([...cssImports, ...htmlLinks])].sort();
37
+
38
+ console.log('// Auto-genererat av npx @klodd/ds precache');
39
+ console.log('// Kor kommandot igen om main.css eller base.html andras.');
40
+ console.log('// Hanterar bara direkta @imports - jamfor mot befintlig SW-lista.');
41
+ console.log('const PRECACHE_URLS = [');
42
+ allUrls.forEach((url) => console.log(` '${url}',`));
43
+ console.log('];');
package/bin/sync.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ /* @klodd/ds sync - kopierar paketets CSS och JS till app-repot.
3
+ * Kor: npx @klodd/ds sync
4
+ * Forutsatter att CWD ar app-repots rot.
5
+ * Laser cssTarget/jsTarget fran klodd-ds.json i CWD, eller anvander default.
6
+ *
7
+ * Cross-platform via fs.cpSync (Node 16.7+) - inte POSIX cp -r. */
8
+ 'use strict';
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ const pkgDir = path.dirname(require.resolve('@klodd/ds/package.json'));
14
+ const cssSource = path.join(pkgDir, 'css');
15
+ const jsSource = path.join(pkgDir, 'js');
16
+
17
+ const configPath = path.join(process.cwd(), 'klodd-ds.json');
18
+ const config = fs.existsSync(configPath)
19
+ ? JSON.parse(fs.readFileSync(configPath, 'utf8'))
20
+ : { cssTarget: 'app/static/css/ds', jsTarget: 'app/static/js/ds' };
21
+
22
+ const cssTarget = path.join(process.cwd(), config.cssTarget);
23
+ const jsTarget = path.join(process.cwd(), config.jsTarget);
24
+
25
+ fs.mkdirSync(cssTarget, { recursive: true });
26
+ fs.mkdirSync(jsTarget, { recursive: true });
27
+
28
+ fs.cpSync(cssSource, cssTarget, { recursive: true });
29
+ if (fs.existsSync(jsSource)) {
30
+ fs.cpSync(jsSource, jsTarget, { recursive: true });
31
+ }
32
+
33
+ console.log(`OK @klodd/ds synkad till ${config.cssTarget} och ${config.jsTarget}`);
package/bin/verify.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ /* @klodd/ds verify - diff:ar committade DS-filer mot paket-kallan.
3
+ * Kor i CI: npx @klodd/ds verify
4
+ * Fail:ar med exit code 1 om drift detekteras. */
5
+ 'use strict';
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const crypto = require('crypto');
10
+
11
+ const pkgDir = path.dirname(require.resolve('@klodd/ds/package.json'));
12
+ const configPath = path.join(process.cwd(), 'klodd-ds.json');
13
+ const config = fs.existsSync(configPath)
14
+ ? JSON.parse(fs.readFileSync(configPath, 'utf8'))
15
+ : { cssTarget: 'app/static/css/ds', jsTarget: 'app/static/js/ds' };
16
+
17
+ function hashFile(filePath) {
18
+ return crypto.createHash('sha256')
19
+ .update(fs.readFileSync(filePath))
20
+ .digest('hex');
21
+ }
22
+
23
+ function listFilesRec(dir) {
24
+ const out = [];
25
+ const stack = [{ abs: dir, rel: '' }];
26
+ while (stack.length) {
27
+ const { abs, rel } = stack.pop();
28
+ for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
29
+ const childAbs = path.join(abs, entry.name);
30
+ const childRel = rel ? path.join(rel, entry.name) : entry.name;
31
+ if (entry.isDirectory()) stack.push({ abs: childAbs, rel: childRel });
32
+ else if (entry.isFile()) out.push(childRel);
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+
38
+ function diffDirs(sourceDir, targetRel) {
39
+ const drifted = [];
40
+ if (!fs.existsSync(sourceDir)) return drifted;
41
+ const targetAbs = path.join(process.cwd(), targetRel);
42
+ for (const file of listFilesRec(sourceDir)) {
43
+ const src = path.join(sourceDir, file);
44
+ const tgt = path.join(targetAbs, file);
45
+ if (!fs.existsSync(tgt)) {
46
+ drifted.push(`SAKNAS: ${targetRel}/${file}`);
47
+ } else if (hashFile(src) !== hashFile(tgt)) {
48
+ drifted.push(`DRIFT: ${targetRel}/${file}`);
49
+ }
50
+ }
51
+ return drifted;
52
+ }
53
+
54
+ const cssDrift = diffDirs(path.join(pkgDir, 'css'), config.cssTarget);
55
+ const jsDrift = diffDirs(path.join(pkgDir, 'js'), config.jsTarget);
56
+ const allDrift = [...cssDrift, ...jsDrift];
57
+
58
+ if (allDrift.length > 0) {
59
+ console.error('FAIL: @klodd/ds drift detekterad:');
60
+ allDrift.forEach((d) => console.error(' ' + d));
61
+ console.error('\nKor `npm run build:ds` for att synka.');
62
+ process.exit(1);
63
+ }
64
+
65
+ console.log('OK: alla DS-filer matchar @klodd/ds-paketet.');
66
+ process.exit(0);
@@ -349,11 +349,21 @@
349
349
 
350
350
  /* ================================================================
351
351
  ==== Z-INDEX
352
+ Hierarki (lagst forst):
353
+ - tooltip: hover-popups, ovanpa allt vanligt content
354
+ - dropdown: dropdown-menyer, autocomplete, kontextmenyer
355
+ - sticky: sticky-headers, fixed bottom-nav
356
+ - modal: overlays, sheets, modals
357
+ - spinner: upload-spinner, blockerar interaktion
352
358
  ================================================================ */
353
359
  :root {
354
- --z-nav: 100;
355
- --z-overlay: 200;
356
- --z-spinner: 9999;
360
+ --z-tooltip: 50;
361
+ --z-dropdown: 100;
362
+ --z-nav: 100; /* alias for --z-dropdown - bottom-nav-niva */
363
+ --z-sticky: 150;
364
+ --z-modal: 200;
365
+ --z-overlay: 200; /* alias for --z-modal - bibehalls bakat-kompat */
366
+ --z-spinner: 9999;
357
367
  }
358
368
 
359
369
 
@@ -1,8 +1,6 @@
1
- /* Inter font (Linear-stack) - laddas fran Google Fonts om enheten
2
- saknar den. CSP-kraven i app-repona inkluderar redan
3
- fonts.googleapis.com (style-src) och fonts.gstatic.com (font-src).
4
- 400 = --fw-regular, 500 = --fw-medium per font-weight-policy. */
5
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap');
1
+ /* Inter laddas INTE via Google Fonts (render-blocking, medvetet borttaget).
2
+ * Stacken faller tillbaka pa SF Pro Display pa iOS och system-ui pa ovriga.
3
+ * Se --font-sans i 00-primitives.css. */
6
4
 
7
5
 
8
6
  /* ================================================================
@@ -31,7 +31,7 @@
31
31
  border-radius: var(--radius-10);
32
32
  padding: var(--space-4) 0;
33
33
  box-shadow: 0 4px 16px color-mix(in oklch, black 40%, transparent);
34
- z-index: var(--z-dropdown, 100);
34
+ z-index: var(--z-dropdown);
35
35
  display: none;
36
36
  }
37
37
 
@@ -172,7 +172,7 @@
172
172
  background: var(--surface-page);
173
173
  border-radius: 50%;
174
174
  transition: transform var(--dur-medium) var(--ease-spring-snappy);
175
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
175
+ box-shadow: var(--shadow-card);
176
176
  }
177
177
  .setting-toggle input:checked + .setting-toggle__track {
178
178
  background: var(--accent-9);
@@ -42,7 +42,7 @@
42
42
  background: var(--surface-raised);
43
43
  color: var(--text-default);
44
44
  font-weight: var(--fw-medium);
45
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
45
+ box-shadow: var(--shadow-card);
46
46
  }
47
47
 
48
48
  @media (hover: hover) and (pointer: fine) {
@@ -35,7 +35,7 @@
35
35
  pointer-events: none;
36
36
  opacity: 0;
37
37
  transition: opacity var(--dur-fast) var(--ease-out);
38
- z-index: var(--z-tooltip, 50);
38
+ z-index: var(--z-tooltip);
39
39
  }
40
40
 
41
41
  .tooltip-wrapper:hover .tooltip,
package/css/utilities.css CHANGED
@@ -1,81 +1,115 @@
1
1
  /* ================================================================
2
- utilities.css
3
- Single-purpose helper-classes for sma layout-detaljer.
4
- Bygger pa pixel-numerisk konvention (.m-0, .mt-12, .gap-8).
5
-
6
- Ingen styling-logik - bara en CSS-property per klass.
2
+ ds/utilities.css
3
+ Single-purpose helpers. Anvands sparsamt for layout-tweaks som
4
+ inte motiverar en namngiven komponentklass. Inte Tailwind-style
5
+ explosion, utan ett medvetet litet set.
7
6
  ================================================================ */
8
7
 
9
- /* Margin */
8
+
9
+ /* Display ------------------------------------------------------- */
10
+ .flex { display: flex; }
11
+ .flex-col { display: flex; flex-direction: column; }
12
+ .inline { display: inline; }
13
+ .inline-block { display: inline-block; }
14
+ .inline-flex { display: inline-flex; }
15
+ .block { display: block; }
16
+ .is-hidden { display: none; }
17
+
18
+ /* Text wrapping (Sprint 50/CSP A+: ersatter inline word-break-style) */
19
+ .break-word { word-break: break-word; }
20
+
21
+
22
+ /* Flex --------------------------------------------------------- */
23
+ .items-center { align-items: center; }
24
+ .items-baseline { align-items: baseline; }
25
+ .items-start { align-items: flex-start; }
26
+ .items-end { align-items: flex-end; }
27
+ .justify-between { justify-content: space-between; }
28
+ .flex-wrap { flex-wrap: wrap; }
29
+ .flex-1 { flex: 1; }
30
+ .flex-2 { flex: 2; }
31
+
32
+
33
+ /* Gaps --------------------------------------------------------- */
34
+ .gap-sm { gap: var(--space-8); }
35
+ .gap-md { gap: var(--space-12); }
36
+ .gap-lg { gap: var(--space-20); }
37
+ .gap-14 { gap: var(--space-14); }
38
+
39
+
40
+ /* Margins (numerisk pixel-skala + namnvarianter) ---------------- */
10
41
  .m-0 { margin: 0; }
11
42
  .mt-0 { margin-top: 0; }
43
+ .mt-2 { margin-top: var(--space-2); }
12
44
  .mt-4 { margin-top: var(--space-4); }
13
- .mt-8 { margin-top: var(--space-8); }
45
+ .mt-6 { margin-top: var(--space-6); }
46
+ .mt-8, .mt-sm { margin-top: var(--space-8); }
14
47
  .mt-12 { margin-top: var(--space-12); }
15
- .mt-16 { margin-top: var(--space-16); }
16
- .mt-20 { margin-top: var(--space-20); }
17
- .mt-24 { margin-top: var(--space-24); }
48
+ .mt-16, .mt-md { margin-top: var(--space-16); }
49
+ .mt-24, .mt-lg { margin-top: var(--space-24); }
50
+
18
51
  .mb-0 { margin-bottom: 0; }
52
+ .mb-2 { margin-bottom: var(--space-2); }
19
53
  .mb-4 { margin-bottom: var(--space-4); }
20
- .mb-8 { margin-bottom: var(--space-8); }
54
+ .mb-6 { margin-bottom: var(--space-6); }
55
+ .mb-8, .mb-sm { margin-bottom: var(--space-8); }
56
+ .mb-10 { margin-bottom: var(--space-10); }
21
57
  .mb-12 { margin-bottom: var(--space-12); }
22
- .mb-16 { margin-bottom: var(--space-16); }
23
- .mb-20 { margin-bottom: var(--space-20); }
24
- .mb-24 { margin-bottom: var(--space-24); }
25
-
26
- /* Padding */
27
- .p-0 { padding: 0; }
28
- .pt-0 { padding-top: 0; }
29
- .pb-0 { padding-bottom: 0; }
30
-
31
- /* Display */
32
- .is-hidden { display: none !important; }
33
- .flex { display: flex; }
34
- .inline-flex { display: inline-flex; }
35
-
36
- /* Width */
37
- .w-full { width: 100%; }
38
-
39
- /* Flex */
40
- .gap-4 { gap: var(--space-4); }
41
- .gap-8 { gap: var(--space-8); }
42
- .gap-12 { gap: var(--space-12); }
43
- .gap-16 { gap: var(--space-16); }
44
- .flex-column { flex-direction: column; }
45
- .align-center { align-items: center; }
46
- .align-self-start { align-self: flex-start; }
47
- .justify-between { justify-content: space-between; }
58
+ .mb-14 { margin-bottom: var(--space-14); }
59
+ .mb-16, .mb-md { margin-bottom: var(--space-16); }
60
+ .mb-24, .mb-lg { margin-bottom: var(--space-24); }
61
+
62
+ .mr-sm { margin-right: var(--space-8); }
63
+ .ml-auto { margin-left: auto; }
64
+
65
+
66
+ /* Padding ------------------------------------------------------- */
67
+ .py-sm { padding-top: var(--space-8); padding-bottom: var(--space-8); }
68
+
69
+
70
+ /* Width / max-width -------------------------------------------- */
71
+ .w-full { width: 100%; }
72
+ .w-auto { width: auto; }
73
+ .w-100 { width: 100px; }
74
+ .max-w-narrow { max-width: 120px; }
75
+ .max-w-200 { max-width: 200px; }
76
+ .max-w-medium { max-width: 220px; }
77
+
48
78
 
49
- /* Position (CSP-migration: ersatter style="position: ...") */
79
+ /* Min-width (for flex-form-rows) ------------------------------- */
80
+ .min-w-140 { min-width: 140px; }
81
+ .min-w-180 { min-width: 180px; }
82
+
83
+
84
+ /* Text helpers ------------------------------------------------- */
85
+ .text-right { text-align: right; }
86
+ .text-center { text-align: center; }
87
+
88
+
89
+ /* Cursors ------------------------------------------------------ */
90
+ .cursor-pointer { cursor: pointer; }
91
+
92
+
93
+ /* Position (CSP-migration: ersatter style="position: ..."-attribut) */
50
94
  .absolute { position: absolute; }
51
95
  .absolute-fill { position: absolute; inset: 0; }
52
- .relative { position: relative; }
53
96
 
54
- /* Text */
55
- .text-center { text-align: center; }
56
- .text-right { text-align: right; }
57
- .break-word { word-break: break-word; overflow-wrap: anywhere; }
58
- .tabular-nums { font-variant-numeric: tabular-nums; }
59
-
60
- /* Text-color (semantic shortcuts) */
61
- .text-default { color: var(--text-default); }
62
- .text-subtle { color: var(--text-subtle); }
63
- .text-muted { color: var(--text-muted); }
64
- .text-disabled { color: var(--text-disabled); }
65
- .text-positive { color: var(--positive); }
66
- .text-negative { color: var(--negative); }
67
- .text-warning { color: var(--warning); }
68
- .text-accent { color: var(--accent-text); }
69
-
70
- /* Visibility */
71
- .visually-hidden {
72
- position: absolute;
73
- width: 1px;
74
- height: 1px;
75
- padding: 0;
76
- margin: -1px;
77
- overflow: hidden;
78
- clip: rect(0, 0, 0, 0);
79
- white-space: nowrap;
80
- border: 0;
81
- }
97
+
98
+ /* Self-alignment (flex-children) */
99
+ .align-self-start { align-self: flex-start; }
100
+
101
+
102
+ /* Composite flex (vanlig kombination i demos och paneler) */
103
+ .flex-col-gap { display: flex; flex-direction: column; gap: var(--space-8); }
104
+
105
+
106
+ /* Status-color shortcuts (legacy aliases). */
107
+ .positive { color: var(--positive); }
108
+ .negative { color: var(--negative); }
109
+ .accent { color: var(--accent); }
110
+ .warning { color: var(--warning); }
111
+
112
+
113
+ /* Text-color utilities (CSP-migration: ersatter style="color: var(--text-X)") */
114
+ .text-subtle { color: var(--text-subtle); }
115
+ .text-muted { color: var(--text-muted); }
package/js/hero-roll.js CHANGED
@@ -17,7 +17,8 @@
17
17
  marker. Animation kor vid forsta page-load efter att PWA/tab
18
18
  stangts helt. Sprint D3:s `body[data-first-load]` (cookie-baserad)
19
19
  fungerar bara for helt nya anvandare; J.5-trigger fungerar for
20
- return-anvandare som har `ekonom_seen`-cookie satt sedan tidigare.
20
+ return-anvandare som har appens first-load-cookie (satts av servern)
21
+ sedan tidigare.
21
22
  2. Manadsbyte via < / > i manads-pillen pa /avstamning
22
23
 
23
24
  Sprint J.4 (CE-rapport: 000 kr syns lite val lange + hela processen
@@ -58,10 +59,9 @@
58
59
  // Sprint J.5: sessionStorage-marker for fresh-session-detektering.
59
60
  // sessionStorage rensas vid PWA-stangning/tab-close, persisterar
60
61
  // mellan reloads/nav inom samma session.
61
- // Delad sessionStorage-key for hero-roll. Bytt fran ekonom_hero_session
62
- // 2026-05-08 nar komponenten porterades tillbaka till Ekonom som shared-
63
- // system. Befintliga session-markers fran gamla nyckeln tappar persist
64
- // engang per anvandare - ny markering satts vid nasta kallstart.
62
+ // Nyckel: ds_hero_session (app-agnostisk). Delad mellan alla appar
63
+ // som anvander @klodd/ds sa samma session-marker fungerar oavsett
64
+ // vilken app som kor.
65
65
  const SESSION_KEY = 'ds_hero_session';
66
66
 
67
67
  let hasRunInitial = false;
package/package.json CHANGED
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "@klodd/ds",
3
- "version": "3.3.1",
4
- "description": "Klodd Design System - shared tokens, typography, components and JS for Jubb, Ekonom, and future apps. v2.0 inkluderar all komponentkod och delad JS - app-repona haller bara data och affarslogik.",
3
+ "version": "3.4.1",
4
+ "description": "Klodd Design System - shared tokens, typography, components and JS for Jubb, Ekonom, and future apps. v3.4 inkluderar sync/verify/precache CLI sa app-repona inte langre handhaller egna build-script. v3.4.1 tar bort Google Fonts @import + gor hero-roll-kommentarer app-agnostiska.",
5
5
  "main": "css/index.css",
6
+ "bin": {
7
+ "klodd-ds": "./bin/klodd-ds.js"
8
+ },
6
9
  "files": [
7
10
  "css/",
8
- "js/"
11
+ "js/",
12
+ "bin/",
13
+ "SKILL.md",
14
+ "references/"
9
15
  ],
16
+ "engines": {
17
+ "node": ">=16.7"
18
+ },
10
19
  "keywords": [
11
20
  "design-system",
12
21
  "css",