@yugnex/nexui 2.0.0

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 (138) hide show
  1. package/LICENSE +59 -0
  2. package/css/nexui-base.css +157 -0
  3. package/css/nexui-icons.css +86 -0
  4. package/css/nexui-tokens.css +113 -0
  5. package/css/nexui.css +16 -0
  6. package/dist/assets/geometry.d.ts +98 -0
  7. package/dist/assets/geometry.d.ts.map +1 -0
  8. package/dist/assets/geometry.js +114 -0
  9. package/dist/assets/geometry.js.map +1 -0
  10. package/dist/assets/typography.d.ts +3 -0
  11. package/dist/assets/typography.d.ts.map +1 -0
  12. package/dist/assets/typography.js +178 -0
  13. package/dist/assets/typography.js.map +1 -0
  14. package/dist/core/compiler.d.ts +30 -0
  15. package/dist/core/compiler.d.ts.map +1 -0
  16. package/dist/core/compiler.js +124 -0
  17. package/dist/core/compiler.js.map +1 -0
  18. package/dist/core/cx.d.ts +7 -0
  19. package/dist/core/cx.d.ts.map +1 -0
  20. package/dist/core/cx.js +34 -0
  21. package/dist/core/cx.js.map +1 -0
  22. package/dist/core/matrix.d.ts +118 -0
  23. package/dist/core/matrix.d.ts.map +1 -0
  24. package/dist/core/matrix.js +180 -0
  25. package/dist/core/matrix.js.map +1 -0
  26. package/dist/index.d.ts +27 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +74 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/primitives/avatar.d.ts +8 -0
  31. package/dist/primitives/avatar.d.ts.map +1 -0
  32. package/dist/primitives/avatar.js +146 -0
  33. package/dist/primitives/avatar.js.map +1 -0
  34. package/dist/primitives/badge.d.ts +8 -0
  35. package/dist/primitives/badge.d.ts.map +1 -0
  36. package/dist/primitives/badge.js +88 -0
  37. package/dist/primitives/badge.js.map +1 -0
  38. package/dist/primitives/button.d.ts +10 -0
  39. package/dist/primitives/button.d.ts.map +1 -0
  40. package/dist/primitives/button.js +137 -0
  41. package/dist/primitives/button.js.map +1 -0
  42. package/dist/primitives/checkbox.d.ts +13 -0
  43. package/dist/primitives/checkbox.d.ts.map +1 -0
  44. package/dist/primitives/checkbox.js +107 -0
  45. package/dist/primitives/checkbox.js.map +1 -0
  46. package/dist/primitives/input.d.ts +14 -0
  47. package/dist/primitives/input.d.ts.map +1 -0
  48. package/dist/primitives/input.js +177 -0
  49. package/dist/primitives/input.js.map +1 -0
  50. package/dist/primitives/panel.d.ts +9 -0
  51. package/dist/primitives/panel.d.ts.map +1 -0
  52. package/dist/primitives/panel.js +101 -0
  53. package/dist/primitives/panel.js.map +1 -0
  54. package/dist/primitives/progress.d.ts +8 -0
  55. package/dist/primitives/progress.d.ts.map +1 -0
  56. package/dist/primitives/progress.js +105 -0
  57. package/dist/primitives/progress.js.map +1 -0
  58. package/dist/primitives/separator.d.ts +8 -0
  59. package/dist/primitives/separator.d.ts.map +1 -0
  60. package/dist/primitives/separator.js +69 -0
  61. package/dist/primitives/separator.js.map +1 -0
  62. package/dist/primitives/skeleton.d.ts +8 -0
  63. package/dist/primitives/skeleton.d.ts.map +1 -0
  64. package/dist/primitives/skeleton.js +61 -0
  65. package/dist/primitives/skeleton.js.map +1 -0
  66. package/dist/primitives/spinner.d.ts +8 -0
  67. package/dist/primitives/spinner.d.ts.map +1 -0
  68. package/dist/primitives/spinner.js +64 -0
  69. package/dist/primitives/spinner.js.map +1 -0
  70. package/dist/primitives/status-ring.d.ts +8 -0
  71. package/dist/primitives/status-ring.d.ts.map +1 -0
  72. package/dist/primitives/status-ring.js +101 -0
  73. package/dist/primitives/status-ring.js.map +1 -0
  74. package/dist/primitives/switch.d.ts +12 -0
  75. package/dist/primitives/switch.d.ts.map +1 -0
  76. package/dist/primitives/switch.js +124 -0
  77. package/dist/primitives/switch.js.map +1 -0
  78. package/dist/primitives/text-stream.d.ts +23 -0
  79. package/dist/primitives/text-stream.d.ts.map +1 -0
  80. package/dist/primitives/text-stream.js +167 -0
  81. package/dist/primitives/text-stream.js.map +1 -0
  82. package/dist/tokens/colors.d.ts +127 -0
  83. package/dist/tokens/colors.d.ts.map +1 -0
  84. package/dist/tokens/colors.js +135 -0
  85. package/dist/tokens/colors.js.map +1 -0
  86. package/dist/tokens/motion.d.ts +37 -0
  87. package/dist/tokens/motion.d.ts.map +1 -0
  88. package/dist/tokens/motion.js +93 -0
  89. package/dist/tokens/motion.js.map +1 -0
  90. package/dist/tokens/shadows.d.ts +34 -0
  91. package/dist/tokens/shadows.d.ts.map +1 -0
  92. package/dist/tokens/shadows.js +45 -0
  93. package/dist/tokens/shadows.js.map +1 -0
  94. package/dist/tokens/spacing.d.ts +69 -0
  95. package/dist/tokens/spacing.d.ts.map +1 -0
  96. package/dist/tokens/spacing.js +71 -0
  97. package/dist/tokens/spacing.js.map +1 -0
  98. package/dist/tokens/type.d.ts +166 -0
  99. package/dist/tokens/type.d.ts.map +1 -0
  100. package/dist/tokens/type.js +215 -0
  101. package/dist/tokens/type.js.map +1 -0
  102. package/fonts/NexuiIcons.woff2 +0 -0
  103. package/fonts/NexuiMono-Regular.otf +0 -0
  104. package/fonts/NexuiMono-Regular.woff2 +0 -0
  105. package/fonts/NexuiSans-Bold.otf +0 -0
  106. package/fonts/NexuiSans-Bold.woff2 +0 -0
  107. package/fonts/NexuiSans-Medium.otf +0 -0
  108. package/fonts/NexuiSans-Medium.woff2 +0 -0
  109. package/fonts/NexuiSans-Regular.otf +0 -0
  110. package/fonts/NexuiSans-Regular.woff2 +0 -0
  111. package/native/Cargo.toml +16 -0
  112. package/native/src/lib.rs +127 -0
  113. package/nexui-utils.css +485 -0
  114. package/package.json +58 -0
  115. package/src/assets/geometry.ts +144 -0
  116. package/src/assets/typography.ts +184 -0
  117. package/src/core/compiler.ts +139 -0
  118. package/src/core/cx.ts +50 -0
  119. package/src/core/matrix.ts +195 -0
  120. package/src/index.ts +78 -0
  121. package/src/primitives/avatar.ts +159 -0
  122. package/src/primitives/badge.ts +98 -0
  123. package/src/primitives/button.ts +149 -0
  124. package/src/primitives/checkbox.ts +113 -0
  125. package/src/primitives/input.ts +187 -0
  126. package/src/primitives/panel.ts +111 -0
  127. package/src/primitives/progress.ts +112 -0
  128. package/src/primitives/separator.ts +73 -0
  129. package/src/primitives/skeleton.ts +68 -0
  130. package/src/primitives/spinner.ts +71 -0
  131. package/src/primitives/status-ring.ts +109 -0
  132. package/src/primitives/switch.ts +134 -0
  133. package/src/primitives/text-stream.ts +187 -0
  134. package/src/tokens/colors.ts +149 -0
  135. package/src/tokens/motion.ts +97 -0
  136. package/src/tokens/shadows.ts +58 -0
  137. package/src/tokens/spacing.ts +79 -0
  138. package/src/tokens/type.ts +224 -0
@@ -0,0 +1,71 @@
1
+ // @yugnex/nexui — NexSpinner Web Component
2
+ // Loading spinner. Lightweight — just CSS animation, no JS after render.
3
+ // Attributes:
4
+ // size: xs | sm | md | lg | xl (default: md)
5
+ // color: accent | live | success | error | muted (default: accent)
6
+ // label: accessible label (default: "Loading")
7
+
8
+ import { nexui_compiler } from "../core/compiler";
9
+
10
+ export class NexSpinner extends HTMLElement {
11
+ static get observedAttributes() {
12
+ return ["size", "color", "label"];
13
+ }
14
+
15
+ constructor() {
16
+ super();
17
+ this.attachShadow({ mode: "open" });
18
+ }
19
+
20
+ connectedCallback() { this.render(); }
21
+ attributeChangedCallback() { this.render(); }
22
+
23
+ private render() {
24
+ if (!this.shadowRoot) return;
25
+
26
+ const size = this.getAttribute("size") ?? "md";
27
+ const color = this.getAttribute("color") ?? "accent";
28
+ const label = this.getAttribute("label") ?? "Loading";
29
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
30
+
31
+ const szMap: Record<string, { px: number; sw: number }> = {
32
+ xs: { px: 12, sw: 1.5 },
33
+ sm: { px: 16, sw: 2 },
34
+ md: { px: 24, sw: 2.5 },
35
+ lg: { px: 32, sw: 3 },
36
+ xl: { px: 48, sw: 3.5 },
37
+ };
38
+ const { px, sw } = szMap[size] ?? szMap.md;
39
+
40
+ const colorMap: Record<string, string> = {
41
+ accent: "var(--nx-accent, #E89010)",
42
+ live: "var(--nx-live, #0FD4C6)",
43
+ success: "var(--nx-success, #22C55E)",
44
+ error: "var(--nx-error, #EF4444)",
45
+ muted: "var(--nx-text-4, #484F58)",
46
+ };
47
+ const spinColor = colorMap[color] ?? colorMap.accent;
48
+
49
+ this.shadowRoot.innerHTML = `
50
+ <style>
51
+ ${themeCSS}
52
+ :host { display: inline-flex; align-items: center; justify-content: center; }
53
+ .ring {
54
+ width: ${px}px;
55
+ height: ${px}px;
56
+ border-radius: 50%;
57
+ border: ${sw}px solid rgba(255,255,255,0.08);
58
+ border-top-color: ${spinColor};
59
+ animation: nx-spin 700ms linear infinite;
60
+ }
61
+ @media (prefers-reduced-motion: reduce) {
62
+ .ring { animation-duration: 2s; }
63
+ }
64
+ @keyframes nx-spin { to { transform: rotate(360deg); } }
65
+ </style>
66
+ <div class="ring" role="status" aria-label="${label}">
67
+ <span style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)">${label}</span>
68
+ </div>
69
+ `;
70
+ }
71
+ }
@@ -0,0 +1,109 @@
1
+ // @yugnex/nexui — NexStatusRing Web Component
2
+ // Circular progress ring with animated entry.
3
+ // Attributes: score (0-100), label, color, size
4
+
5
+ import { nexui_compiler } from "../core/compiler";
6
+
7
+ export class NexStatusRing extends HTMLElement {
8
+ static get observedAttributes() {
9
+ return ["score", "label", "color", "size"];
10
+ }
11
+
12
+ constructor() {
13
+ super();
14
+ this.attachShadow({ mode: "open" });
15
+ }
16
+
17
+ connectedCallback() {
18
+ this.render();
19
+ }
20
+
21
+ attributeChangedCallback() {
22
+ this.render();
23
+ }
24
+
25
+ private render() {
26
+ if (!this.shadowRoot) return;
27
+
28
+ const score = Math.min(100, Math.max(0, parseInt(this.getAttribute("score") ?? "0", 10)));
29
+ const label = this.getAttribute("label") ?? "METRIC";
30
+ const color = this.getAttribute("color") ?? "var(--nx-accent, #E89010)";
31
+ const size = Math.max(40, parseInt(this.getAttribute("size") ?? "80", 10));
32
+
33
+ const cx = size / 2;
34
+ const cy = size / 2;
35
+ const radius = (size / 2) - (size * 0.075);
36
+ const sw = size * 0.075;
37
+ const circumference = 2 * Math.PI * radius;
38
+ const targetOffset = circumference - (score / 100) * circumference;
39
+ const fontSize = Math.round(size * 0.175);
40
+ const labelSize = Math.round(size * 0.08);
41
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
42
+
43
+ // Key technique: render at full offset (empty ring), then in the next frame
44
+ // update to the target offset so the CSS transition actually fires.
45
+ // Without this two-frame approach the element starts at the final value
46
+ // and the transition is never observed.
47
+ this.shadowRoot.innerHTML = `
48
+ <style>
49
+ ${themeCSS}
50
+ :host {
51
+ display: inline-flex;
52
+ flex-direction: column;
53
+ align-items: center;
54
+ font-family: var(--nx-font-mono, 'JetBrains Mono', monospace);
55
+ }
56
+ .ring-arc {
57
+ transition: stroke-dashoffset 700ms cubic-bezier(0, 0, 0.2, 1);
58
+ transform-origin: center;
59
+ transform: rotate(-90deg);
60
+ }
61
+ .label {
62
+ font-size: ${labelSize}px;
63
+ color: var(--nx-text-3, #6E7681);
64
+ margin-top: ${Math.round(size * 0.04)}px;
65
+ text-transform: uppercase;
66
+ letter-spacing: 0.06em;
67
+ user-select: none;
68
+ }
69
+ </style>
70
+ <svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" aria-label="${label}: ${score}%" role="img">
71
+ <circle
72
+ cx="${cx}" cy="${cy}" r="${radius}"
73
+ fill="none"
74
+ stroke="var(--nx-border, rgba(255,255,255,0.08))"
75
+ stroke-width="${sw}"
76
+ />
77
+ <circle
78
+ class="ring-arc"
79
+ cx="${cx}" cy="${cy}" r="${radius}"
80
+ fill="none"
81
+ stroke="${color}"
82
+ stroke-width="${sw}"
83
+ stroke-linecap="round"
84
+ stroke-dasharray="${circumference}"
85
+ stroke-dashoffset="${circumference}"
86
+ />
87
+ <text
88
+ x="${cx}" y="${cy + fontSize * 0.35}"
89
+ text-anchor="middle"
90
+ fill="var(--nx-text, #E6EDF3)"
91
+ font-size="${fontSize}"
92
+ font-weight="600"
93
+ font-family="inherit"
94
+ >${score}</text>
95
+ </svg>
96
+ <div class="label">${label}</div>
97
+ `;
98
+
99
+ // Two-frame update triggers the CSS transition
100
+ const arc = this.shadowRoot.querySelector<SVGCircleElement>(".ring-arc");
101
+ if (arc) {
102
+ requestAnimationFrame(() => {
103
+ requestAnimationFrame(() => {
104
+ arc.style.strokeDashoffset = String(targetOffset);
105
+ });
106
+ });
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,134 @@
1
+ // @yugnex/nexui — NexSwitch Web Component
2
+ // Toggle switch with label and ARIA.
3
+ // Attributes:
4
+ // checked: boolean
5
+ // disabled: boolean
6
+ // label: string
7
+ // size: sm | md | lg
8
+ // color: accent | live | success (default: accent)
9
+
10
+ import { nexui_compiler } from "../core/compiler";
11
+
12
+ export class NexSwitch extends HTMLElement {
13
+ private _checked = false;
14
+
15
+ static get observedAttributes() {
16
+ return ["checked", "disabled", "label", "size", "color"];
17
+ }
18
+
19
+ constructor() {
20
+ super();
21
+ this.attachShadow({ mode: "open" });
22
+ }
23
+
24
+ connectedCallback() {
25
+ this._checked = this.hasAttribute("checked");
26
+ this.render();
27
+ this.shadowRoot?.querySelector(".track")?.addEventListener("click", () => this.toggle());
28
+ this.shadowRoot?.querySelector(".track")?.addEventListener("keydown", (e) => {
29
+ if ((e as KeyboardEvent).key === " " || (e as KeyboardEvent).key === "Enter") {
30
+ e.preventDefault();
31
+ this.toggle();
32
+ }
33
+ });
34
+ }
35
+
36
+ attributeChangedCallback(name: string, _old: string, val: string) {
37
+ if (name === "checked") this._checked = val !== null;
38
+ this.render();
39
+ }
40
+
41
+ get checked() { return this._checked; }
42
+ set checked(v: boolean) {
43
+ this._checked = v;
44
+ v ? this.setAttribute("checked", "") : this.removeAttribute("checked");
45
+ this.render();
46
+ }
47
+
48
+ private toggle() {
49
+ if (this.hasAttribute("disabled")) return;
50
+ this.checked = !this._checked;
51
+ this.dispatchEvent(new CustomEvent("change", { detail: { checked: this._checked }, bubbles: true }));
52
+ }
53
+
54
+ private render() {
55
+ if (!this.shadowRoot) return;
56
+
57
+ const size = this.getAttribute("size") ?? "md";
58
+ const color = this.getAttribute("color") ?? "accent";
59
+ const label = this.getAttribute("label") ?? "";
60
+ const disabled = this.hasAttribute("disabled");
61
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
62
+
63
+ const colorMap: Record<string, string> = {
64
+ accent: "var(--nx-accent, #E89010)",
65
+ live: "var(--nx-live, #0FD4C6)",
66
+ success: "var(--nx-success, #22C55E)",
67
+ };
68
+ const activeColor = colorMap[color] ?? colorMap.accent;
69
+
70
+ const dims = {
71
+ sm: { w: 28, h: 16, thumb: 12, fs: "11px" },
72
+ md: { w: 36, h: 20, thumb: 16, fs: "12px" },
73
+ lg: { w: 44, h: 24, thumb: 20, fs: "13px" },
74
+ };
75
+ const d = dims[size as keyof typeof dims] ?? dims.md;
76
+ const thumbTravel = d.w - d.thumb - 4;
77
+
78
+ this.shadowRoot.innerHTML = `
79
+ <style>
80
+ ${themeCSS}
81
+ :host { display: inline-flex; align-items: center; gap: 8px; }
82
+ .track {
83
+ position: relative;
84
+ width: ${d.w}px;
85
+ height: ${d.h}px;
86
+ border-radius: 9999px;
87
+ background: ${this._checked ? activeColor : "var(--nx-bg-elevated, #1C2128)"};
88
+ border: 1px solid ${this._checked ? "transparent" : "var(--nx-border-strong, rgba(255,255,255,0.16))"};
89
+ cursor: ${disabled ? "not-allowed" : "pointer"};
90
+ opacity: ${disabled ? 0.45 : 1};
91
+ transition: background 150ms ease, border-color 150ms ease;
92
+ outline: none;
93
+ flex-shrink: 0;
94
+ }
95
+ .track:focus-visible {
96
+ box-shadow: 0 0 0 2px var(--nx-bg-base, #0D1117), 0 0 0 4px ${activeColor};
97
+ }
98
+ .thumb {
99
+ position: absolute;
100
+ top: 50%;
101
+ left: 2px;
102
+ transform: translateY(-50%) translateX(${this._checked ? `${thumbTravel}px` : "0"});
103
+ width: ${d.thumb}px;
104
+ height: ${d.thumb}px;
105
+ border-radius: 50%;
106
+ background: ${this._checked ? "var(--nx-bg-base, #0D1117)" : "var(--nx-text-4, #484F58)"};
107
+ transition: transform 150ms cubic-bezier(0.34,1.56,0.64,1), background 150ms ease;
108
+ pointer-events: none;
109
+ }
110
+ .label {
111
+ font-family: var(--nx-font-sans, system-ui, sans-serif);
112
+ font-size: ${d.fs};
113
+ color: ${disabled ? "var(--nx-text-4, #484F58)" : "var(--nx-text-2, #8B949E)"};
114
+ user-select: none;
115
+ cursor: ${disabled ? "not-allowed" : "pointer"};
116
+ }
117
+ </style>
118
+ <div class="track" role="switch" aria-checked="${this._checked}" tabindex="${disabled ? -1 : 0}" part="track">
119
+ <div class="thumb" part="thumb"></div>
120
+ </div>
121
+ ${label ? `<span class="label" part="label">${label}</span>` : ""}
122
+ `;
123
+
124
+ // Re-attach events after innerHTML reset
125
+ const track = this.shadowRoot.querySelector(".track");
126
+ track?.addEventListener("click", () => this.toggle());
127
+ track?.addEventListener("keydown", (e) => {
128
+ if ((e as KeyboardEvent).key === " " || (e as KeyboardEvent).key === "Enter") {
129
+ e.preventDefault();
130
+ this.toggle();
131
+ }
132
+ });
133
+ }
134
+ }
@@ -0,0 +1,187 @@
1
+ // @yugnex/nexui — NexTextStream Web Component
2
+ // Live log stream viewer with virtual tail rendering.
3
+ // XSS-safe: all user content set via textContent, never innerHTML.
4
+ // Performance: incremental DOM append, not full rebuild per message.
5
+
6
+ import { nexui_compiler } from "../core/compiler";
7
+
8
+ type LogFlag = "error" | "verify" | "system" | "warn" | "default";
9
+
10
+ function detectFlag(text: string): LogFlag {
11
+ const lower = text.toLowerCase();
12
+ if (text.includes("🚨") || lower.includes("error") || lower.includes("fatal")) return "error";
13
+ if (text.includes("⚠") || lower.includes("warn")) return "warn";
14
+ if (text.includes("✓") || lower.includes("verified") || lower.includes("pass")) return "verify";
15
+ if (text.includes("✦") || lower.includes("system") || lower.includes("[sys]")) return "system";
16
+ return "default";
17
+ }
18
+
19
+ const FLAG_COLORS: Record<LogFlag, string> = {
20
+ error: "var(--nx-error, #EF4444)",
21
+ warn: "var(--nx-warning, #EAB308)",
22
+ verify: "var(--nx-success, #22C55E)",
23
+ system: "var(--nx-accent, #E89010)",
24
+ default: "var(--nx-text, #E6EDF3)",
25
+ };
26
+
27
+ export class NexTextStream extends HTMLElement {
28
+ private logBuffer: string[] = [];
29
+ private lineElements: HTMLDivElement[] = [];
30
+ private viewport: HTMLDivElement | null = null;
31
+ private container: HTMLDivElement | null = null;
32
+ private lineCount = 0;
33
+
34
+ static readonly MAX_BUFFER = 5000;
35
+ static readonly VISIBLE_ROWS = 50;
36
+
37
+ static get observedAttributes() {
38
+ return ["max-rows", "show-numbers"];
39
+ }
40
+
41
+ constructor() {
42
+ super();
43
+ this.attachShadow({ mode: "open" });
44
+ }
45
+
46
+ connectedCallback() {
47
+ this.setupLayout();
48
+ }
49
+
50
+ attributeChangedCallback() {
51
+ // No re-render needed for these attrs — they affect future appends only
52
+ }
53
+
54
+ private setupLayout() {
55
+ if (!this.shadowRoot) return;
56
+
57
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
58
+
59
+ const style = document.createElement("style");
60
+ style.textContent = `
61
+ ${themeCSS}
62
+ :host {
63
+ display: block;
64
+ background-color: var(--nx-bg-void, #040610);
65
+ border: 1px solid var(--nx-border, #1e293b);
66
+ border-radius: 6px;
67
+ padding: 12px;
68
+ font-family: var(--nx-font-mono, 'JetBrains Mono', monospace);
69
+ font-size: 11px;
70
+ line-height: 1.8;
71
+ color: var(--nx-text, #E6EDF3);
72
+ overflow: hidden;
73
+ }
74
+ .viewport {
75
+ max-height: 400px;
76
+ overflow-y: auto;
77
+ scrollbar-width: thin;
78
+ scrollbar-color: rgba(255,255,255,0.12) transparent;
79
+ }
80
+ .viewport::-webkit-scrollbar { width: 4px; }
81
+ .viewport::-webkit-scrollbar-track { background: transparent; }
82
+ .viewport::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 2px; }
83
+ .row {
84
+ display: flex;
85
+ gap: 10px;
86
+ white-space: pre-wrap;
87
+ word-break: break-all;
88
+ padding: 1px 0;
89
+ }
90
+ .num {
91
+ color: var(--nx-text-4, #484F58);
92
+ user-select: none;
93
+ min-width: 36px;
94
+ text-align: right;
95
+ flex-shrink: 0;
96
+ }
97
+ .text {
98
+ flex: 1;
99
+ min-width: 0;
100
+ }
101
+ `;
102
+
103
+ const viewport = document.createElement("div");
104
+ viewport.className = "viewport";
105
+
106
+ const container = document.createElement("div");
107
+ container.setAttribute("aria-live", "polite");
108
+ container.setAttribute("aria-label", "Log stream");
109
+ viewport.appendChild(container);
110
+
111
+ this.shadowRoot.appendChild(style);
112
+ this.shadowRoot.appendChild(viewport);
113
+
114
+ this.viewport = viewport;
115
+ this.container = container;
116
+ }
117
+
118
+ /** Push a single log line. Safe for high-frequency calls (e.g. SSE tokens). */
119
+ public pushLogTrace(message: string) {
120
+ // Enforce buffer ceiling
121
+ if (this.logBuffer.length >= NexTextStream.MAX_BUFFER) {
122
+ this.logBuffer.shift();
123
+ // Remove the oldest visible row element
124
+ const oldest = this.lineElements.shift();
125
+ if (oldest && this.container) this.container.removeChild(oldest);
126
+ }
127
+
128
+ this.logBuffer.push(message);
129
+ this.lineCount++;
130
+ this.appendRow(message, this.lineCount);
131
+
132
+ // Trim visible rows beyond the virtual window
133
+ const maxVisible = parseInt(this.getAttribute("max-rows") ?? "50", 10);
134
+ while (this.lineElements.length > maxVisible) {
135
+ const old = this.lineElements.shift();
136
+ if (old && this.container) this.container.removeChild(old);
137
+ }
138
+
139
+ this.scrollToBottom();
140
+ }
141
+
142
+ /** Push multiple lines at once (batch). More efficient than repeated pushLogTrace. */
143
+ public pushBatch(messages: string[]) {
144
+ for (const msg of messages) this.pushLogTrace(msg);
145
+ }
146
+
147
+ /** Clear all log content. */
148
+ public clear() {
149
+ this.logBuffer = [];
150
+ this.lineElements = [];
151
+ this.lineCount = 0;
152
+ if (this.container) this.container.textContent = "";
153
+ }
154
+
155
+ private appendRow(text: string, lineNumber: number) {
156
+ if (!this.container) return;
157
+
158
+ const showNums = this.getAttribute("show-numbers") !== "false";
159
+ const flag = detectFlag(text);
160
+ const color = FLAG_COLORS[flag];
161
+
162
+ const row = document.createElement("div");
163
+ row.className = "row";
164
+
165
+ if (showNums) {
166
+ const num = document.createElement("span");
167
+ num.className = "num";
168
+ num.textContent = String(lineNumber);
169
+ row.appendChild(num);
170
+ }
171
+
172
+ const textSpan = document.createElement("span");
173
+ textSpan.className = "text";
174
+ textSpan.style.color = color;
175
+ textSpan.textContent = text; // textContent — never innerHTML
176
+ row.appendChild(textSpan);
177
+
178
+ this.container.appendChild(row);
179
+ this.lineElements.push(row);
180
+ }
181
+
182
+ private scrollToBottom() {
183
+ if (this.viewport) {
184
+ this.viewport.scrollTop = this.viewport.scrollHeight;
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,149 @@
1
+ // @yugnex/nexui — Sovereign Color System
2
+ // Zero external dependencies. All values hand-authored.
3
+
4
+ export const NEXUI_PALETTE = {
5
+ void: {
6
+ 0: "#000000",
7
+ 50: "#05070C",
8
+ 100: "#0A0D14",
9
+ 200: "#0D1117",
10
+ 300: "#161B22",
11
+ 400: "#1C2128",
12
+ 500: "#21262D",
13
+ 600: "#30363D",
14
+ 700: "#3D444D",
15
+ 800: "#484F58",
16
+ 900: "#6E7681",
17
+ 950: "#8B949E",
18
+ },
19
+ amber: {
20
+ 50: "#FFF8E7",
21
+ 100: "#FDECC8",
22
+ 200: "#F9D07A",
23
+ 300: "#F5B342",
24
+ 400: "#E89010",
25
+ 500: "#C97A0E",
26
+ 600: "#A3630B",
27
+ 700: "#7D4D09",
28
+ 800: "#573606",
29
+ 900: "#3A2304",
30
+ },
31
+ teal: {
32
+ 50: "#E6FFFE",
33
+ 100: "#CCFFFE",
34
+ 200: "#99FFFD",
35
+ 300: "#4DFCF7",
36
+ 400: "#0FD4C6",
37
+ 500: "#0EB5A9",
38
+ 600: "#0B8A81",
39
+ 700: "#086059",
40
+ 800: "#053D38",
41
+ 900: "#021F1C",
42
+ },
43
+ green: {
44
+ 50: "#F0FDF4",
45
+ 100: "#DCFCE7",
46
+ 200: "#BBF7D0",
47
+ 300: "#86EFAC",
48
+ 400: "#4ADE80",
49
+ 500: "#22C55E",
50
+ 600: "#16A34A",
51
+ 700: "#15803D",
52
+ 800: "#166534",
53
+ 900: "#14532D",
54
+ },
55
+ red: {
56
+ 50: "#FEF2F2",
57
+ 100: "#FEE2E2",
58
+ 200: "#FECACA",
59
+ 300: "#FCA5A5",
60
+ 400: "#F87171",
61
+ 500: "#EF4444",
62
+ 600: "#DC2626",
63
+ 700: "#B91C1C",
64
+ 800: "#991B1B",
65
+ 900: "#7F1D1D",
66
+ },
67
+ yellow: {
68
+ 400: "#FACC15",
69
+ 500: "#EAB308",
70
+ 600: "#CA8A04",
71
+ },
72
+ } as const;
73
+
74
+ // Semantic layer — what components reference, never raw palette values
75
+ export const NEXUI_SEMANTIC_VOID = {
76
+ // Surfaces (ground → highest elevation)
77
+ "bg-void": NEXUI_PALETTE.void[50],
78
+ "bg-base": NEXUI_PALETTE.void[200],
79
+ "bg-surface": NEXUI_PALETTE.void[300],
80
+ "bg-elevated": NEXUI_PALETTE.void[400],
81
+ "bg-overlay": NEXUI_PALETTE.void[500],
82
+
83
+ // Borders
84
+ "border": "rgba(255,255,255,0.08)",
85
+ "border-strong": "rgba(255,255,255,0.16)",
86
+ "border-focus": "rgba(232,144,16,0.60)",
87
+
88
+ // Text hierarchy
89
+ "text-primary": "#E6EDF3",
90
+ "text-secondary": NEXUI_PALETTE.void[950],
91
+ "text-dim": NEXUI_PALETTE.void[900],
92
+ "text-ghost": NEXUI_PALETTE.void[800],
93
+ "text-inverse": NEXUI_PALETTE.void[50],
94
+
95
+ // Accent (amber — primary brand)
96
+ "accent": NEXUI_PALETTE.amber[400],
97
+ "accent-dim": "rgba(232,144,16,0.09)",
98
+ "accent-border": "rgba(232,144,16,0.22)",
99
+ "accent-text": NEXUI_PALETTE.amber[300],
100
+
101
+ // Live / activity (teal)
102
+ "live": NEXUI_PALETTE.teal[400],
103
+ "live-dim": "rgba(15,212,198,0.09)",
104
+ "live-border": "rgba(15,212,198,0.22)",
105
+
106
+ // Status
107
+ "success": NEXUI_PALETTE.green[500],
108
+ "success-dim": "rgba(34,197,94,0.10)",
109
+ "error": NEXUI_PALETTE.red[500],
110
+ "error-dim": "rgba(239,68,68,0.10)",
111
+ "warning": NEXUI_PALETTE.yellow[500],
112
+ "warning-dim": "rgba(234,179,8,0.10)",
113
+ } as const;
114
+
115
+ export const NEXUI_SEMANTIC_TERMINAL = {
116
+ "bg-void": "#000000",
117
+ "bg-base": "#050505",
118
+ "bg-surface": "#0C0E12",
119
+ "bg-elevated": "#111418",
120
+ "bg-overlay": "#161B22",
121
+
122
+ "border": "#1E293B",
123
+ "border-strong": "#2D3748",
124
+ "border-focus": "rgba(16,185,129,0.60)",
125
+
126
+ "text-primary": "#10B981",
127
+ "text-secondary": "#6EE7B7",
128
+ "text-dim": "#34D399",
129
+ "text-ghost": "#065F46",
130
+ "text-inverse": "#000000",
131
+
132
+ "accent": NEXUI_PALETTE.amber[400],
133
+ "accent-dim": "rgba(232,144,16,0.09)",
134
+ "accent-border": "rgba(232,144,16,0.22)",
135
+ "accent-text": NEXUI_PALETTE.amber[300],
136
+
137
+ "live": "#10B981",
138
+ "live-dim": "rgba(16,185,129,0.09)",
139
+ "live-border": "rgba(16,185,129,0.22)",
140
+
141
+ "success": "#10B981",
142
+ "success-dim": "rgba(16,185,129,0.10)",
143
+ "error": NEXUI_PALETTE.red[500],
144
+ "error-dim": "rgba(239,68,68,0.10)",
145
+ "warning": NEXUI_PALETTE.yellow[500],
146
+ "warning-dim": "rgba(234,179,8,0.10)",
147
+ } as const;
148
+
149
+ export type NexuiSemanticToken = keyof typeof NEXUI_SEMANTIC_VOID;