custom-elements-ts 0.0.17 → 0.2.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 (82) hide show
  1. package/.eslintrc.json +47 -0
  2. package/.github/workflows/ci.yml +49 -0
  3. package/.prettierrc +7 -0
  4. package/LICENSE +20 -0
  5. package/README.md +279 -37
  6. package/assets/readme-header.png +0 -0
  7. package/assets/social-preview.jpg +0 -0
  8. package/demos/counter/counter.element.html +1 -0
  9. package/demos/counter/counter.element.scss +234 -0
  10. package/demos/counter/counter.element.ts +68 -0
  11. package/demos/counter/index.html +205 -0
  12. package/demos/counter/index.ts +1 -0
  13. package/demos/site/code-example/code-example.element.scss +168 -0
  14. package/demos/site/code-example/code-example.element.ts +88 -0
  15. package/demos/site/event-log/event-log.element.scss +179 -0
  16. package/demos/site/event-log/event-log.element.ts +123 -0
  17. package/demos/site/favicon.svg +14 -0
  18. package/demos/site/index.html +368 -0
  19. package/demos/site/index.ts +19 -0
  20. package/demos/site/llms.txt +53 -0
  21. package/demos/site/message/message.element.scss +75 -0
  22. package/demos/site/message/message.element.ts +76 -0
  23. package/demos/site/og-image.png +0 -0
  24. package/demos/site/scroll-restoration.ts +28 -0
  25. package/demos/site/source-toggle/source-toggle.ts +88 -0
  26. package/demos/site/source-viewer/source-viewer.element.scss +292 -0
  27. package/demos/site/source-viewer/source-viewer.element.ts +138 -0
  28. package/demos/site/source-viewer/sources.generated.ts +11 -0
  29. package/demos/site/styles/site.css +1135 -0
  30. package/demos/site/styles/tokens.css +56 -0
  31. package/demos/site/toast/toast.element.scss +110 -0
  32. package/demos/site/toast/toast.element.ts +63 -0
  33. package/demos/todo-dashboard/index.html +141 -0
  34. package/demos/todo-dashboard/index.ts +4 -0
  35. package/demos/todo-dashboard/todo-dashboard.element.scss +1150 -0
  36. package/demos/todo-dashboard/todo-dashboard.element.ts +333 -0
  37. package/demos/todo-dashboard/todo-filters.element.ts +54 -0
  38. package/demos/todo-dashboard/todo-item.element.ts +127 -0
  39. package/demos/todo-dashboard/todo-stats.element.ts +189 -0
  40. package/package.json +73 -24
  41. package/src/custom-element.ts +206 -0
  42. package/src/index.ts +8 -0
  43. package/src/listen.ts +70 -0
  44. package/src/prop.ts +92 -0
  45. package/src/signal.ts +66 -0
  46. package/src/state.ts +141 -0
  47. package/src/template-runtime.ts +783 -0
  48. package/src/toggle.ts +66 -0
  49. package/src/tsconfig.json +24 -0
  50. package/src/util.ts +33 -0
  51. package/src/watch.ts +14 -0
  52. package/tests/basic.spec.ts +70 -0
  53. package/tests/custom-element.spec.ts +77 -0
  54. package/tests/dispatch.spec.ts +52 -0
  55. package/tests/init.spec.ts +94 -0
  56. package/tests/listen.spec.ts +118 -0
  57. package/tests/map-shallow-state.spec.ts +184 -0
  58. package/tests/prop.spec.ts +118 -0
  59. package/tests/signal.spec.ts +117 -0
  60. package/tests/templating-runtime.spec.ts +575 -0
  61. package/tests/toggle.spec.ts +92 -0
  62. package/tests/watch.spec.ts +183 -0
  63. package/tools/build.js +128 -0
  64. package/tools/bundle.js +167 -0
  65. package/tools/generate-sources.js +76 -0
  66. package/tools/rollup-config.js +70 -0
  67. package/tools/start.js +194 -0
  68. package/tsconfig.json +38 -0
  69. package/vite.config.mts +30 -0
  70. package/bundles/custom-elements-ts.umd.js +0 -359
  71. package/bundles/custom-elements-ts.umd.js.map +0 -1
  72. package/esm2015/custom-elements-ts.js +0 -283
  73. package/esm2015/custom-elements-ts.js.map +0 -1
  74. package/esm5/custom-element.d.ts +0 -12
  75. package/esm5/custom-elements-ts.js +0 -344
  76. package/esm5/custom-elements-ts.js.map +0 -1
  77. package/esm5/index.d.ts +0 -5
  78. package/esm5/listen.d.ts +0 -17
  79. package/esm5/prop.d.ts +0 -2
  80. package/esm5/toggle.d.ts +0 -1
  81. package/esm5/util.d.ts +0 -4
  82. package/esm5/watch.d.ts +0 -1
@@ -0,0 +1,234 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ cts-counter
3
+ A single-button card showcasing @State(). Sharp radii, Geist
4
+ typography, TS-blue accent. Aligned with demos/site tokens.
5
+ ───────────────────────────────────────────────────────────── */
6
+
7
+ :host {
8
+ --bg: #0a0c10;
9
+ --bg-elev-1: #11141a;
10
+ --line: rgba(255, 255, 255, 0.06);
11
+ --line-strong: rgba(255, 255, 255, 0.10);
12
+ --fg: #e6ecf3;
13
+ --fg-muted: #97a1ad;
14
+ --fg-subtle: #5b6370;
15
+ --accent: #3178c6;
16
+ --accent-bright: #4189d6;
17
+ --accent-fg: #9cc0eb;
18
+ --accent-soft: rgba(49, 120, 198, 0.14);
19
+ --accent-line: rgba(49, 120, 198, 0.42);
20
+
21
+ --radius-xs: 2px;
22
+ --radius-sm: 2px;
23
+ --radius-md: 3px;
24
+ --radius-lg: 4px;
25
+
26
+ --ease: cubic-bezier(0.22, 1, 0.36, 1);
27
+ --dur-fast: 140ms;
28
+ --dur: 220ms;
29
+
30
+ display: inline-block;
31
+ color: var(--fg);
32
+ font-family:
33
+ "Geist", "Inter", ui-sans-serif, system-ui, -apple-system,
34
+ "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
35
+ -webkit-font-smoothing: antialiased;
36
+ text-rendering: optimizeLegibility;
37
+ }
38
+
39
+ * {
40
+ box-sizing: border-box;
41
+ }
42
+
43
+ /* ── Card button ─────────────────────────────────────────── */
44
+
45
+ .card {
46
+ position: relative;
47
+ display: grid;
48
+ gap: 18px;
49
+ width: 320px;
50
+ padding: 22px 24px 22px;
51
+ margin: 0;
52
+ border: 1px solid var(--line-strong);
53
+ border-radius: var(--radius-lg);
54
+ background:
55
+ radial-gradient(120% 100% at 0% 0%, rgba(49, 120, 198, 0.07) 0%, transparent 55%),
56
+ linear-gradient(180deg, var(--bg-elev-1) 0%, #0f131b 100%);
57
+ color: inherit;
58
+ font: inherit;
59
+ text-align: left;
60
+ cursor: pointer;
61
+ overflow: hidden;
62
+ box-shadow:
63
+ 0 1px 0 rgba(255, 255, 255, 0.04) inset,
64
+ 0 22px 50px -28px rgba(0, 0, 0, 0.65),
65
+ 0 8px 24px -12px rgba(0, 0, 0, 0.45);
66
+ transition:
67
+ border-color var(--dur-fast) var(--ease),
68
+ box-shadow var(--dur-fast) var(--ease),
69
+ transform var(--dur-fast) var(--ease);
70
+ animation: card-in 520ms var(--ease) both;
71
+ }
72
+
73
+ .card::before {
74
+ content: "";
75
+ position: absolute;
76
+ left: 0;
77
+ right: 0;
78
+ bottom: 0;
79
+ height: 2px;
80
+ background: linear-gradient(
81
+ 90deg,
82
+ transparent 0%,
83
+ var(--accent-line) 30%,
84
+ var(--accent-bright) 50%,
85
+ var(--accent-line) 70%,
86
+ transparent 100%
87
+ );
88
+ opacity: 0;
89
+ transform: translateX(-30%);
90
+ transition:
91
+ opacity var(--dur) var(--ease),
92
+ transform var(--dur-slow, 420ms) var(--ease);
93
+ }
94
+
95
+ .card:hover {
96
+ border-color: rgba(255, 255, 255, 0.18);
97
+ }
98
+
99
+ .card:hover::before {
100
+ opacity: 1;
101
+ transform: translateX(0);
102
+ }
103
+
104
+ .card:focus-visible {
105
+ outline: none;
106
+ border-color: var(--accent-line);
107
+ box-shadow:
108
+ 0 1px 0 rgba(255, 255, 255, 0.04) inset,
109
+ 0 0 0 3px var(--accent-soft),
110
+ 0 22px 50px -28px rgba(0, 0, 0, 0.65);
111
+ }
112
+
113
+ .card:active {
114
+ transform: translateY(1px);
115
+ }
116
+
117
+ @keyframes card-in {
118
+ from { opacity: 0; transform: translateY(8px); }
119
+ to { opacity: 1; transform: translateY(0); }
120
+ }
121
+
122
+ /* ── Kicker ──────────────────────────────────────────────── */
123
+
124
+ .kicker {
125
+ display: inline-flex;
126
+ align-items: center;
127
+ gap: 8px;
128
+ color: var(--fg-muted);
129
+ font-family: "Geist Mono", "JetBrains Mono", ui-monospace, SFMono-Regular,
130
+ Menlo, Consolas, "Courier New", monospace;
131
+ font-size: 11px;
132
+ font-weight: 500;
133
+ letter-spacing: 0.14em;
134
+ text-transform: uppercase;
135
+ }
136
+
137
+ .kicker-dot {
138
+ width: 6px;
139
+ height: 6px;
140
+ background: var(--accent);
141
+ box-shadow: 0 0 10px var(--accent-line);
142
+ }
143
+
144
+ /* ── Display ─────────────────────────────────────────────── */
145
+
146
+ .display {
147
+ display: inline-flex;
148
+ align-items: baseline;
149
+ gap: 0;
150
+ font-family: "Geist Mono", "JetBrains Mono", ui-monospace, SFMono-Regular,
151
+ Menlo, Consolas, "Courier New", monospace;
152
+ font-size: 76px;
153
+ font-weight: 600;
154
+ line-height: 0.95;
155
+ letter-spacing: -0.045em;
156
+ font-variant-numeric: tabular-nums;
157
+ }
158
+
159
+ .display-mute {
160
+ color: var(--fg-subtle);
161
+ opacity: 0.35;
162
+ }
163
+
164
+ .display-num {
165
+ color: var(--fg);
166
+ background: linear-gradient(180deg, var(--fg) 0%, #c8d3e0 100%);
167
+ -webkit-background-clip: text;
168
+ background-clip: text;
169
+ -webkit-text-fill-color: transparent;
170
+ }
171
+
172
+ /* ── Hint row ────────────────────────────────────────────── */
173
+
174
+ .hint {
175
+ display: inline-flex;
176
+ align-items: center;
177
+ justify-content: space-between;
178
+ gap: 12px;
179
+ padding-top: 14px;
180
+ border-top: 1px solid var(--line);
181
+ color: var(--fg-muted);
182
+ font-size: 12px;
183
+ letter-spacing: -0.005em;
184
+ }
185
+
186
+ .hint-text {
187
+ color: var(--fg-muted);
188
+ }
189
+
190
+ .hint-chip {
191
+ display: inline-flex;
192
+ align-items: center;
193
+ height: 22px;
194
+ padding: 0 8px;
195
+ border: 1px solid var(--line-strong);
196
+ border-radius: var(--radius-xs);
197
+ background: rgba(255, 255, 255, 0.02);
198
+ color: var(--accent-fg);
199
+ font-family: "Geist Mono", "JetBrains Mono", ui-monospace, SFMono-Regular,
200
+ Menlo, Consolas, "Courier New", monospace;
201
+ font-size: 11px;
202
+ font-weight: 600;
203
+ letter-spacing: 0.02em;
204
+ transition:
205
+ border-color var(--dur-fast) var(--ease),
206
+ background var(--dur-fast) var(--ease),
207
+ transform var(--dur-fast) var(--ease);
208
+ }
209
+
210
+ .card:hover .hint-chip {
211
+ border-color: var(--accent-line);
212
+ background: var(--accent-soft);
213
+ transform: translateY(-1px);
214
+ }
215
+
216
+ .card:active .hint-chip {
217
+ transform: translateY(0);
218
+ }
219
+
220
+ /* ── Responsive ──────────────────────────────────────────── */
221
+
222
+ @media (max-width: 380px) {
223
+ .card { width: 100%; }
224
+ .display { font-size: 64px; }
225
+ }
226
+
227
+ @media (prefers-reduced-motion: reduce) {
228
+ .card,
229
+ .card::before,
230
+ .hint-chip {
231
+ animation: none;
232
+ transition: none;
233
+ }
234
+ }
@@ -0,0 +1,68 @@
1
+ import {
2
+ CustomElement,
3
+ Dispatch,
4
+ DispatchEmitter,
5
+ State,
6
+ TemplateResult,
7
+ Watch,
8
+ html,
9
+ } from 'custom-elements-ts';
10
+
11
+ @CustomElement({
12
+ tag: 'cts-counter',
13
+ styleUrl: './counter.element.scss',
14
+ })
15
+ export class CounterElement extends HTMLElement {
16
+ @State() count = 0;
17
+
18
+ @Dispatch('counter.change') counterChange!: DispatchEmitter;
19
+
20
+ @Watch('count')
21
+ handleCountChange(change: { new: number }) {
22
+ this.counterChange.emit({
23
+ bubbles: true,
24
+ composed: true,
25
+ detail: {
26
+ count: change.new,
27
+ },
28
+ });
29
+ }
30
+
31
+ render(): TemplateResult {
32
+ const display = this.format(this.count);
33
+ return html`
34
+ <button class="card" type="button" @click=${this.increment}>
35
+ <span class="kicker">
36
+ <span class="kicker-dot"></span>
37
+ counter · @State()
38
+ </span>
39
+
40
+ <span class="display" aria-live="polite">
41
+ <span class="display-mute">${display.mute}</span
42
+ ><span class="display-num">${display.num}</span>
43
+ </span>
44
+
45
+ <span class="hint">
46
+ <span class="hint-text">tap anywhere to count</span>
47
+ <span class="hint-chip">+1</span>
48
+ </span>
49
+ </button>
50
+ `;
51
+ }
52
+
53
+ private increment() {
54
+ this.count++;
55
+ }
56
+
57
+ // Pad to at least 4 digits and split into a muted lead and a bright tail
58
+ // so the number reads like a stopwatch — e.g. 0042, 0420, 4200, 42000.
59
+ private format(value: number): { mute: string; num: string } {
60
+ const raw = String(value);
61
+ const padded = raw.length >= 4 ? raw : `${'0000'.slice(raw.length)}${raw}`;
62
+ const tail = padded.length - raw.length;
63
+ return {
64
+ mute: padded.slice(0, tail),
65
+ num: padded.slice(tail),
66
+ };
67
+ }
68
+ }
@@ -0,0 +1,205 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <base href="/" />
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
7
+ <meta name="theme-color" content="#0a0c10" />
8
+ <title>Counter — custom-elements-ts demo</title>
9
+
10
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12
+ <link
13
+ rel="stylesheet"
14
+ href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600;700&display=swap"
15
+ />
16
+
17
+ <style>
18
+ :root {
19
+ --bg: #0a0c10;
20
+ }
21
+
22
+ * {
23
+ box-sizing: border-box;
24
+ }
25
+
26
+ html,
27
+ body {
28
+ margin: 0;
29
+ background: var(--bg);
30
+ color: #e6ecf3;
31
+ }
32
+
33
+ body {
34
+ position: relative;
35
+ min-height: 100dvh;
36
+ display: grid;
37
+ place-items: center;
38
+ padding: 32px 24px;
39
+ background:
40
+ radial-gradient(60% 50% at 50% -10%, rgba(49, 120, 198, 0.18) 0%, transparent 60%),
41
+ radial-gradient(40% 40% at 50% 110%, rgba(49, 120, 198, 0.1) 0%, transparent 60%),
42
+ var(--bg);
43
+ font-family:
44
+ 'Geist',
45
+ 'Inter',
46
+ ui-sans-serif,
47
+ system-ui,
48
+ -apple-system,
49
+ BlinkMacSystemFont,
50
+ 'Segoe UI',
51
+ sans-serif;
52
+ -webkit-font-smoothing: antialiased;
53
+ text-rendering: optimizeLegibility;
54
+ overflow-x: hidden;
55
+ }
56
+
57
+ body::before {
58
+ content: '';
59
+ position: fixed;
60
+ inset: 0;
61
+ pointer-events: none;
62
+ background-image:
63
+ linear-gradient(to right, rgba(255, 255, 255, 0.025) 1px, transparent 1px),
64
+ linear-gradient(to bottom, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
65
+ background-size: 56px 56px;
66
+ mask-image: radial-gradient(ellipse 60% 60% at 50% 50%, #000 0%, transparent 75%);
67
+ -webkit-mask-image: radial-gradient(ellipse 60% 60% at 50% 50%, #000 0%, transparent 75%);
68
+ z-index: 0;
69
+ }
70
+
71
+ .stage {
72
+ position: relative;
73
+ z-index: 1;
74
+ display: grid;
75
+ gap: 22px;
76
+ justify-items: center;
77
+ }
78
+
79
+ .corner {
80
+ position: fixed;
81
+ top: 24px;
82
+ left: 24px;
83
+ z-index: 2;
84
+ display: inline-flex;
85
+ align-items: center;
86
+ gap: 10px;
87
+ color: #97a1ad;
88
+ font-family:
89
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
90
+ font-size: 11px;
91
+ letter-spacing: 0.08em;
92
+ text-transform: uppercase;
93
+ text-decoration: none;
94
+ }
95
+
96
+ .corner svg {
97
+ color: #3178c6;
98
+ }
99
+
100
+ .corner:hover {
101
+ color: #e6ecf3;
102
+ }
103
+
104
+ .caption {
105
+ max-width: 320px;
106
+ margin: 0;
107
+ color: #5b6370;
108
+ font-size: 12px;
109
+ line-height: 1.55;
110
+ text-align: center;
111
+ text-wrap: balance;
112
+ }
113
+
114
+ .caption code {
115
+ padding: 1px 5px;
116
+ border: 1px solid rgba(255, 255, 255, 0.08);
117
+ border-radius: 2px;
118
+ background: rgba(255, 255, 255, 0.02);
119
+ color: #97a1ad;
120
+ font-family:
121
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
122
+ font-size: 11px;
123
+ }
124
+
125
+ .observer {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ gap: 8px;
129
+ min-height: 32px;
130
+ padding: 0 10px;
131
+ border: 1px solid rgba(255, 255, 255, 0.08);
132
+ border-radius: 2px;
133
+ background: rgba(255, 255, 255, 0.02);
134
+ color: #97a1ad;
135
+ font-family:
136
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
137
+ font-size: 11px;
138
+ letter-spacing: 0.02em;
139
+ }
140
+
141
+ .observer output {
142
+ color: #9cc0eb;
143
+ font-weight: 600;
144
+ font-variant-numeric: tabular-nums;
145
+ }
146
+
147
+ cts-counter {
148
+ position: relative;
149
+ z-index: 1;
150
+ }
151
+
152
+ @media (max-width: 380px) {
153
+ .corner {
154
+ top: 16px;
155
+ left: 16px;
156
+ }
157
+ }
158
+ </style>
159
+ </head>
160
+ <body>
161
+ <a class="corner" href="https://github.com/geocine/custom-elements-ts">
162
+ <svg
163
+ viewBox="0 0 24 24"
164
+ fill="none"
165
+ stroke="currentColor"
166
+ stroke-width="2.25"
167
+ stroke-linecap="round"
168
+ stroke-linejoin="round"
169
+ width="14"
170
+ height="14"
171
+ aria-hidden="true"
172
+ >
173
+ <path d="M8.5 7.5 4 12l4.5 4.5" />
174
+ <path d="M15.5 7.5 20 12l-4.5 4.5" />
175
+ <path d="M14 5l-4 14" />
176
+ </svg>
177
+ custom-elements-ts · counter demo
178
+ </a>
179
+
180
+ <div class="stage">
181
+ <cts-counter></cts-counter>
182
+ <div class="observer" aria-live="polite">
183
+ <span>counter.change</span>
184
+ <output id="counter-signal">0</output>
185
+ </div>
186
+ <p class="caption">
187
+ Click the card. The element owns <code>@State()</code> and emits
188
+ <code>counter.change</code> for outside consumers.
189
+ </p>
190
+ </div>
191
+
192
+ <script src="node_modules/@webcomponents/custom-elements/src/native-shim.js"></script>
193
+ <script src="node_modules/@webcomponents/custom-elements/custom-elements.min.js"></script>
194
+ <script src="counter.umd.js"></script>
195
+ <script>
196
+ const counter = document.querySelector('cts-counter');
197
+ const signal = document.querySelector('#counter-signal');
198
+
199
+ counter.addEventListener('counter.change', (event) => {
200
+ signal.value = event.detail.count;
201
+ });
202
+ </script>
203
+ </body>
204
+ </html>
205
+ <!-- livereload -->
@@ -0,0 +1 @@
1
+ export * from './counter.element';
@@ -0,0 +1,168 @@
1
+ /* Lives inside <article class="window">; the chrome owns background + radius. */
2
+
3
+ :host {
4
+ display: block;
5
+ padding: 22px 26px 26px;
6
+ font-family: "Geist Mono", "JetBrains Mono", ui-monospace, SFMono-Regular,
7
+ Menlo, Consolas, "Courier New", monospace;
8
+ font-size: 13.5px;
9
+ line-height: 1.65;
10
+ color: #d6dce5;
11
+ background: transparent;
12
+ overflow-x: auto;
13
+ max-height: 520px;
14
+ -webkit-font-smoothing: antialiased;
15
+
16
+ @media (min-width: 980px) {
17
+ font-size: 14px;
18
+ padding: 28px 32px 32px;
19
+ }
20
+
21
+ /* Custom scrollbar so the code area doesn't ship a chunky default bar */
22
+ scrollbar-width: thin;
23
+ scrollbar-color: rgba(255, 255, 255, 0.10) transparent;
24
+
25
+ &::-webkit-scrollbar {
26
+ height: 8px;
27
+ width: 8px;
28
+ }
29
+ &::-webkit-scrollbar-thumb {
30
+ background: rgba(255, 255, 255, 0.10);
31
+ border-radius: 2px;
32
+ }
33
+ &::-webkit-scrollbar-track { background: transparent; }
34
+ }
35
+
36
+ pre,
37
+ pre[class*="language-"] {
38
+ margin: 0;
39
+ padding: 0;
40
+ background: transparent;
41
+ border: 0;
42
+ border-radius: 0;
43
+ color: inherit;
44
+ font-family: inherit;
45
+ font-size: inherit;
46
+ line-height: inherit;
47
+ text-shadow: none;
48
+ white-space: pre;
49
+ word-spacing: normal;
50
+ word-break: normal;
51
+ -moz-tab-size: 2;
52
+ -o-tab-size: 2;
53
+ tab-size: 2;
54
+ hyphens: none;
55
+ overflow: visible;
56
+ }
57
+
58
+ code,
59
+ code[class*="language-"] {
60
+ background: transparent;
61
+ color: inherit;
62
+ font-family: inherit;
63
+ font-size: inherit;
64
+ line-height: inherit;
65
+ text-shadow: none;
66
+ white-space: pre;
67
+ }
68
+
69
+ /* ─── Prism token theme — VS Code "Dark Modern" inspired, TS blue led ─── */
70
+
71
+ .token {
72
+ &.comment,
73
+ &.prolog,
74
+ &.doctype,
75
+ &.cdata {
76
+ color: #6e7681;
77
+ font-style: italic;
78
+ }
79
+
80
+ &.punctuation {
81
+ color: #c9d1d9;
82
+ }
83
+
84
+ /* identifier-blue — properties, tags, attribute-name (e.g. tag:, template:) */
85
+ &.property,
86
+ &.tag,
87
+ &.attr-name {
88
+ color: #9cdcfe;
89
+ }
90
+
91
+ /* TS-blue — keywords (import, export, class, extends, new) */
92
+ &.keyword,
93
+ &.boolean {
94
+ color: #4189d6;
95
+ }
96
+
97
+ /* teal — class names, types */
98
+ &.class-name {
99
+ color: #4ec9b0;
100
+ }
101
+
102
+ &.constant,
103
+ &.symbol {
104
+ color: #4189d6;
105
+ }
106
+
107
+ /* light green — numbers */
108
+ &.number {
109
+ color: #b5cea8;
110
+ }
111
+
112
+ /* warm muted — strings (the only "warm" color, used sparingly) */
113
+ &.string,
114
+ &.char,
115
+ &.attr-value,
116
+ &.builtin,
117
+ &.inserted {
118
+ color: #ce9178;
119
+ }
120
+
121
+ /* selectors stay muted */
122
+ &.selector {
123
+ color: #d7ba7d;
124
+ }
125
+
126
+ &.variable {
127
+ color: #d6dce5;
128
+ }
129
+
130
+ &.operator,
131
+ &.entity {
132
+ color: #d6dce5;
133
+ }
134
+
135
+ &.url {
136
+ color: #4189d6;
137
+ text-decoration: underline;
138
+ }
139
+
140
+ &.atrule {
141
+ color: #c586c0;
142
+ }
143
+
144
+ /* function names — soft cream so they don't fight the blues */
145
+ &.function {
146
+ color: #dcdcaa;
147
+ }
148
+
149
+ &.regex {
150
+ color: #d16969;
151
+ }
152
+
153
+ &.important {
154
+ color: #f47067;
155
+ font-weight: 600;
156
+ }
157
+
158
+ &.bold { font-weight: 600; }
159
+ &.italic { font-style: italic; }
160
+ &.deleted { color: #f47067; }
161
+ }
162
+
163
+ .namespace { opacity: .65; }
164
+
165
+ .language-css .token.string,
166
+ .style .token.string {
167
+ color: #ce9178;
168
+ }