@dumiflex/wp-kit 0.0.0-stage → 0.3.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 (128) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/LICENSE +674 -0
  3. package/README.md +76 -3
  4. package/package.json +77 -6
  5. package/src/Avatar.vue +83 -0
  6. package/src/Badge.vue +97 -0
  7. package/src/Banner.vue +118 -0
  8. package/src/BookmarkButton.vue +27 -0
  9. package/src/BundleMeta.vue +51 -0
  10. package/src/Button.vue +103 -0
  11. package/src/Card.vue +68 -0
  12. package/src/Checkbox.vue +107 -0
  13. package/src/Chip.vue +97 -0
  14. package/src/ChipToggle.vue +92 -0
  15. package/src/ChoiceCards.vue +133 -0
  16. package/src/CloseButton.vue +69 -0
  17. package/src/CodeBlock.vue +119 -0
  18. package/src/ColorField.vue +206 -0
  19. package/src/ColorPicker.vue +201 -0
  20. package/src/Combobox.vue +334 -0
  21. package/src/ConfirmDialog.vue +163 -0
  22. package/src/ConfirmModal.vue +147 -0
  23. package/src/ConstraintMatrixPreview.vue +708 -0
  24. package/src/ContextMenu.vue +379 -0
  25. package/src/CopyButton.vue +156 -0
  26. package/src/DataTable.vue +292 -0
  27. package/src/DatePicker.vue +234 -0
  28. package/src/DescriptionList.vue +73 -0
  29. package/src/DiffView.vue +68 -0
  30. package/src/Disclosure.vue +166 -0
  31. package/src/DragHandle.vue +49 -0
  32. package/src/Drawer.vue +131 -0
  33. package/src/DropZone.vue +196 -0
  34. package/src/DropdownMenu.vue +72 -0
  35. package/src/EmptyState.vue +111 -0
  36. package/src/Field.vue +28 -0
  37. package/src/HsvPicker.vue +208 -0
  38. package/src/Icon.vue +39 -0
  39. package/src/IconPicker.vue +209 -0
  40. package/src/ImageGrid.vue +125 -0
  41. package/src/InlineEdit.vue +183 -0
  42. package/src/Input.vue +99 -0
  43. package/src/Kbd.vue +56 -0
  44. package/src/KindChip.vue +53 -0
  45. package/src/Lightbox.vue +173 -0
  46. package/src/ListFilter.vue +85 -0
  47. package/src/ListRow.vue +204 -0
  48. package/src/LoadingMark.vue +161 -0
  49. package/src/Logo.vue +60 -0
  50. package/src/Modal.vue +76 -0
  51. package/src/ModalShell.vue +107 -0
  52. package/src/Pagination.vue +106 -0
  53. package/src/Popover.vue +171 -0
  54. package/src/PostCard.vue +211 -0
  55. package/src/ProgressBar.vue +90 -0
  56. package/src/RadioGroup.vue +177 -0
  57. package/src/RangeSlider.vue +183 -0
  58. package/src/RatingBadge.vue +42 -0
  59. package/src/RelativeDate.vue +35 -0
  60. package/src/ResizeHandle.vue +143 -0
  61. package/src/SearchInput.vue +199 -0
  62. package/src/Select.vue +641 -0
  63. package/src/Separator.vue +54 -0
  64. package/src/Skeleton.vue +82 -0
  65. package/src/Slider.vue +198 -0
  66. package/src/SortableList.vue +260 -0
  67. package/src/SplitPane.vue +68 -0
  68. package/src/StarButton.vue +40 -0
  69. package/src/Stepper.vue +141 -0
  70. package/src/SupportLinks.vue +93 -0
  71. package/src/TagInput.vue +300 -0
  72. package/src/Textarea.vue +57 -0
  73. package/src/Timeline.vue +92 -0
  74. package/src/ToastCard.vue +119 -0
  75. package/src/ToastHost.vue +40 -0
  76. package/src/Toggle.vue +36 -0
  77. package/src/TokenizedValue.vue +310 -0
  78. package/src/Toolbar.vue +110 -0
  79. package/src/VirtualList.vue +87 -0
  80. package/src/VisuallyHidden.vue +29 -0
  81. package/src/WpButton.vue +75 -0
  82. package/src/WpCheck.vue +102 -0
  83. package/src/WpIconButton.vue +107 -0
  84. package/src/WpImageZoom.vue +544 -0
  85. package/src/WpNumber.vue +183 -0
  86. package/src/WpSegmented.vue +175 -0
  87. package/src/WpSelect.vue +89 -0
  88. package/src/WpSwitch.vue +163 -0
  89. package/src/WpTabs.vue +188 -0
  90. package/src/_modal-motion.css +33 -0
  91. package/src/_skeleton.css +50 -0
  92. package/src/axis-color.ts +42 -0
  93. package/src/button.css +169 -0
  94. package/src/calendar.ts +59 -0
  95. package/src/clipboard.ts +50 -0
  96. package/src/colour-presets.ts +12 -0
  97. package/src/diff.ts +51 -0
  98. package/src/drop-zone.ts +41 -0
  99. package/src/flip.ts +237 -0
  100. package/src/floating.ts +99 -0
  101. package/src/hsv.ts +100 -0
  102. package/src/icon-catalog.ts +52 -0
  103. package/src/image-zoom.ts +93 -0
  104. package/src/logo-paths.ts +21 -0
  105. package/src/matrix-axis.ts +91 -0
  106. package/src/pagination.ts +27 -0
  107. package/src/platform-keys.ts +29 -0
  108. package/src/rich-tokenize.ts +357 -0
  109. package/src/select-types.ts +32 -0
  110. package/src/slider-math.ts +45 -0
  111. package/src/sortable.ts +22 -0
  112. package/src/theme/fonts/OFL-Fraunces.txt +93 -0
  113. package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -0
  114. package/src/theme/fonts/OFL-Manrope.txt +93 -0
  115. package/src/theme/fonts/fraunces-latin-wght-normal.woff2 +0 -0
  116. package/src/theme/fonts/jetbrains-mono-latin.woff2 +0 -0
  117. package/src/theme/fonts/manrope-latin-wght-normal.woff2 +0 -0
  118. package/src/theme/looks.css +428 -0
  119. package/src/theme/palettes.css +254 -0
  120. package/src/theme/tokens.css +2894 -0
  121. package/src/tokens.ts +56 -0
  122. package/src/tooltip.css +43 -0
  123. package/src/tooltip.ts +146 -0
  124. package/src/useDialogFocus.ts +73 -0
  125. package/src/useGrowableField.ts +357 -0
  126. package/src/useModalKeyShield.ts +116 -0
  127. package/src/useToast.ts +48 -0
  128. package/src/wp-logo.svg +14 -0
@@ -0,0 +1,708 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Read-only grouped-axis constraint matrix preview.
4
+ *
5
+ * Ported from the sister project's interactive `ConstraintMatrix.vue` with all
6
+ * editing interaction stripped: no click/keydown handlers, no CellRulePopover,
7
+ * no Teleport, no sticky-band measurement, no defineExpose, no emit.
8
+ *
9
+ * Two rendering paths:
10
+ * - Grouped: when `targetGroups` or `sourceGroups` are non-empty the grid
11
+ * grows column-axis bands, per-axis row header chips, and a labelled
12
+ * "uncategorized" bucket. The `groupHue`/`groupLabel`/`isBucket` logic
13
+ * comes from `matrix-axis.ts` (Task 2, same repo).
14
+ * - Flat: when both group maps are empty the grid is the community's legacy
15
+ * single-row header layout (back-compat for old payloads that predate axis
16
+ * grouping). Source/target tint applied via CSS vars.
17
+ *
18
+ * Exceptions use `TokenizedValue` so `$var` / `@{ref}` tokens render as
19
+ * coloured chips instead of raw strings. No `v-html`.
20
+ *
21
+ * CSS uses `.wp-cn-*` prefix (community namespace) throughout, with band/chip/
22
+ * eyebrow rules ported from the sister `.wp-mx-*` rules and renamed.
23
+ * Sticky frozen headers are omitted in favour of a simple `overflow-x: auto`
24
+ * wrapper — correctness before complexity for a read-only preview.
25
+ */
26
+ import { computed } from "vue";
27
+ import TokenizedValue from "./TokenizedValue.vue";
28
+ import {
29
+ groupHue as tagHue,
30
+ groupLabel,
31
+ isBucket,
32
+ orderByGroups,
33
+ toGroups,
34
+ } from "./matrix-axis";
35
+
36
+ const props = defineProps<{
37
+ rows: string[];
38
+ cols: string[];
39
+ matrix: Record<string, Record<string, { mode: string; factor: number }>>;
40
+ sourceName: string;
41
+ targetName: string;
42
+ sourceGroups: Record<string, string[]>;
43
+ targetGroups: Record<string, string[]>;
44
+ exceptions: { source: string; target: string; mode: string; factor: number }[];
45
+ targetSelect: string | null;
46
+ refMap: ReadonlyMap<string, string>;
47
+ }>();
48
+
49
+ const orderedRows = computed(() => orderByGroups(props.rows, props.sourceGroups));
50
+ const orderedCols = computed(() => orderByGroups(props.cols, props.targetGroups));
51
+ const rowGroups = computed(() => toGroups(orderedRows.value));
52
+ const colGroups = computed(() => toGroups(orderedCols.value));
53
+ const hasColBands = computed(() => colGroups.value.some((g) => !isBucket(g)));
54
+ const hasRowGroups = computed(() => rowGroups.value.some((g) => !isBucket(g)));
55
+
56
+ // `only` (schema v6, linked picks): when the source fires, targets without a
57
+ // rule of their own in that row are excluded.
58
+ type CnMode = "allow" | "boost" | "reduce" | "exclude" | "only";
59
+ const MODE_GLYPH: Record<string, string> = {
60
+ only: "✓",
61
+ boost: "↑",
62
+ reduce: "↓",
63
+ exclude: "×",
64
+ allow: "·",
65
+ };
66
+ const MODE_LABEL: Record<CnMode, string> = {
67
+ boost: "Boost",
68
+ reduce: "Reduce",
69
+ exclude: "Exclude",
70
+ allow: "Neutral",
71
+ only: "Only",
72
+ };
73
+
74
+ function normalizeMode(raw: string | null | undefined): CnMode {
75
+ return raw === "exclude" || raw === "boost" || raw === "reduce" || raw === "only" ? raw : "allow";
76
+ }
77
+
78
+ /** A row holding an `only` cell shuts out its neutral cells at runtime; the
79
+ * preview marks them so the link reads the same as in the extension. */
80
+ function isImpliedOut(row: string, col: string): boolean {
81
+ if (cellAt(row, col).mode !== "allow") return false;
82
+ return Object.values(props.matrix?.[row] ?? {}).some((c) => c?.mode === "only");
83
+ }
84
+
85
+ function cellAt(row: string, col: string): { mode: CnMode; factor: number } {
86
+ const c = props.matrix?.[row]?.[col];
87
+ return {
88
+ mode: normalizeMode(c?.mode),
89
+ factor: typeof c?.factor === "number" ? c.factor : 1,
90
+ };
91
+ }
92
+
93
+ function fmtFactor(f: number): string {
94
+ if (!Number.isFinite(f)) return "1";
95
+ if (f >= 10) return f.toFixed(0);
96
+ return f.toFixed(2).replace(/\.?0+$/, "");
97
+ }
98
+ </script>
99
+
100
+ <template>
101
+ <div class="wp-cn">
102
+ <!-- Axis header -->
103
+ <div class="wp-cn-head">
104
+ <span
105
+ class="wp-cn-axis wp-cn-axis--src"
106
+ data-test="cn-axis-src"
107
+ >
108
+ {{ sourceName }}
109
+ </span>
110
+ <i class="pi pi-arrow-right wp-cn-head__arrow" />
111
+ <span
112
+ class="wp-cn-axis wp-cn-axis--tgt"
113
+ data-test="cn-axis-tgt"
114
+ >
115
+ {{ targetName }}
116
+ </span>
117
+ <span
118
+ v-if="targetSelect"
119
+ class="wp-cn-target-select"
120
+ title="Library reach selector — which downstream target instances this constraint covers"
121
+ >
122
+ target: {{ targetSelect }}
123
+ </span>
124
+ </div>
125
+
126
+ <!-- Matrix grid -->
127
+ <div
128
+ v-if="rows.length"
129
+ class="wp-cn-grid-wrap"
130
+ >
131
+ <table class="wp-cn-table">
132
+ <thead>
133
+ <!-- Band row: axis names spanning their tags (grouped path only) -->
134
+ <tr>
135
+ <th
136
+ class="wp-cn-corner"
137
+ :rowspan="hasColBands ? 2 : 1"
138
+ >
139
+ <span class="chip">
140
+ <span class="row-1">↓ source</span>
141
+ <span class="row-2">→ target</span>
142
+ </span>
143
+ </th>
144
+ <template v-if="hasColBands">
145
+ <th
146
+ v-for="(grp, gi) in colGroups"
147
+ :key="`band-${gi}`"
148
+ class="wp-cn-band"
149
+ :class="{ 'wp-cn-band--bucket': isBucket(grp) }"
150
+ :colspan="grp.tags.length"
151
+ :style="{ '--ax': tagHue(grp, 'target', true) }"
152
+ :title="groupLabel(grp)"
153
+ >
154
+ <span class="chip">{{ groupLabel(grp) }}</span>
155
+ </th>
156
+ </template>
157
+ <!-- Flat path: single tag header row -->
158
+ <template v-else>
159
+ <th
160
+ v-for="c in orderedCols"
161
+ :key="c.tag"
162
+ class="wp-cn-colhead"
163
+ :style="{ '--ax': 'var(--wp-constraint-target)' }"
164
+ >
165
+ <span class="chip">{{ c.tag }}</span>
166
+ </th>
167
+ </template>
168
+ </tr>
169
+ <!-- Tag row (grouped path only): per-tag headers below the bands -->
170
+ <tr v-if="hasColBands">
171
+ <template
172
+ v-for="(grp, gi) in colGroups"
173
+ :key="`coltags-${gi}`"
174
+ >
175
+ <th
176
+ v-for="(tag, ti) in grp.tags"
177
+ :key="tag"
178
+ class="wp-cn-colhead"
179
+ :class="{ 'group-start': !isBucket(grp) && ti === 0 }"
180
+ :style="{ '--ax': tagHue(grp, 'target', hasColBands) }"
181
+ >
182
+ <span class="chip">{{ tag }}</span>
183
+ </th>
184
+ </template>
185
+ </tr>
186
+ </thead>
187
+ <tbody>
188
+ <template
189
+ v-for="(grp, gi) in rowGroups"
190
+ :key="`rowgrp-${gi}`"
191
+ >
192
+ <!-- Group header chip: named multi-tag axis OR labelled uncategorized bucket -->
193
+ <tr
194
+ v-if="(!isBucket(grp) && grp.tags.length > 1) || (isBucket(grp) && hasRowGroups)"
195
+ class="wp-cn-grp-row"
196
+ >
197
+ <th
198
+ class="wp-cn-grp-head"
199
+ :class="{ 'wp-cn-grp-head--bucket': isBucket(grp) }"
200
+ :colspan="orderedCols.length + 1"
201
+ :style="{ '--ax': tagHue(grp, 'source', true) }"
202
+ :title="groupLabel(grp)"
203
+ >
204
+ <span class="chip">{{ groupLabel(grp) }}</span>
205
+ </th>
206
+ </tr>
207
+ <!-- Tag rows -->
208
+ <tr
209
+ v-for="tag in grp.tags"
210
+ :key="tag"
211
+ >
212
+ <th
213
+ class="wp-cn-rowhead"
214
+ :class="{
215
+ 'group-start': !isBucket(grp) && tag === grp.tags[0],
216
+ solo: !isBucket(grp) && grp.tags.length === 1,
217
+ }"
218
+ :style="{ '--ax': tagHue(grp, 'source', hasRowGroups) }"
219
+ >
220
+ <!-- Solo named axis: fold axis name in as an eyebrow above the tag -->
221
+ <span
222
+ v-if="!isBucket(grp) && grp.tags.length === 1"
223
+ class="chip"
224
+ >
225
+ <span class="eye">{{ grp.axisName }}</span>
226
+ <span class="v">{{ tag }}</span>
227
+ </span>
228
+ <span
229
+ v-else
230
+ class="chip"
231
+ >{{ tag }}</span>
232
+ </th>
233
+ <td
234
+ v-for="c in orderedCols"
235
+ :key="c.tag"
236
+ class="wp-cn-td"
237
+ >
238
+ <span
239
+ class="wp-cn-cell"
240
+ :class="{ 'wp-cn-cell--implied-out': isImpliedOut(tag, c.tag) }"
241
+ :data-mode="cellAt(tag, c.tag).mode"
242
+ >
243
+ <span class="wp-cn-cell__glyph">{{ isImpliedOut(tag, c.tag) ? "×" : MODE_GLYPH[cellAt(tag, c.tag).mode] }}</span>
244
+ <span
245
+ v-if="cellAt(tag, c.tag).mode === 'boost' || cellAt(tag, c.tag).mode === 'reduce'"
246
+ class="wp-cn-cell__factor"
247
+ >×{{ fmtFactor(cellAt(tag, c.tag).factor) }}</span>
248
+ </span>
249
+ </td>
250
+ </tr>
251
+ </template>
252
+ </tbody>
253
+ </table>
254
+ </div>
255
+
256
+ <!-- Legend -->
257
+ <div class="wp-cn-legend">
258
+ <span><b data-mode="boost">↑</b> boost</span>
259
+ <span><b data-mode="reduce">↓</b> reduce</span>
260
+ <span><b data-mode="exclude">×</b> exclude</span>
261
+ <span><b data-mode="neutral">·</b> neutral</span>
262
+ <span><b data-mode="only">✓</b> only (links picks)</span>
263
+ </div>
264
+
265
+ <!-- Exceptions (library-only) -->
266
+ <div
267
+ v-if="exceptions.length"
268
+ class="wp-cn-exceptions"
269
+ >
270
+ <div class="wp-cn-exceptions__label">
271
+ Exceptions
272
+ </div>
273
+ <div
274
+ v-for="(exc, i) in exceptions"
275
+ :key="`exc-${i}`"
276
+ class="wp-cn-exc-row"
277
+ :class="`wp-cn-exc-row--${exc.mode}`"
278
+ :data-test="`cn-exc-${i}`"
279
+ >
280
+ <span class="wp-cn-exc__pair">
281
+ <span class="wp-cn-exc__src">
282
+ <TokenizedValue
283
+ :value="exc.source"
284
+ :uuid-to-name="refMap"
285
+ />
286
+ </span>
287
+ <i class="pi pi-arrow-right wp-cn-exc__arrow" />
288
+ <span class="wp-cn-exc__tgt">
289
+ <TokenizedValue
290
+ :value="exc.target"
291
+ :uuid-to-name="refMap"
292
+ />
293
+ </span>
294
+ </span>
295
+ <span
296
+ class="wp-cn-exc__mode"
297
+ :class="`wp-cn-exc__mode--${exc.mode}`"
298
+ >
299
+ <span class="wp-cn-exc__glyph">{{ MODE_GLYPH[exc.mode] }}</span>
300
+ {{ MODE_LABEL[normalizeMode(exc.mode)] }}
301
+ </span>
302
+ <span
303
+ v-if="exc.mode === 'boost' || exc.mode === 'reduce'"
304
+ class="wp-cn-exc__factor"
305
+ >×{{ fmtFactor(exc.factor) }}</span>
306
+ </div>
307
+ </div>
308
+ </div>
309
+ </template>
310
+
311
+ <style scoped>
312
+ /* ── Container ─────────────────────────────────────────────── */
313
+ .wp-cn {
314
+ display: flex;
315
+ flex-direction: column;
316
+ gap: 14px;
317
+ }
318
+
319
+ /* ── Axis header ───────────────────────────────────────────── */
320
+ .wp-cn-head {
321
+ display: flex;
322
+ align-items: center;
323
+ gap: 8px;
324
+ flex-wrap: wrap;
325
+ }
326
+ .wp-cn-head__arrow {
327
+ font-size: 11px;
328
+ color: var(--wp-text-dim);
329
+ }
330
+ .wp-cn-axis {
331
+ display: inline-flex;
332
+ align-items: center;
333
+ padding: 3px 9px;
334
+ border-radius: min(4px, var(--wp-radius-cap, 999px));
335
+ font-size: 11px;
336
+ font-weight: 600;
337
+ letter-spacing: 0.02em;
338
+ white-space: nowrap;
339
+ }
340
+ .wp-cn-axis--src {
341
+ color: var(--wp-constraint-source-text);
342
+ background: color-mix(in oklab, var(--wp-constraint-source) 14%, transparent);
343
+ border: 1px solid color-mix(in oklab, var(--wp-constraint-source) 35%, transparent);
344
+ }
345
+ .wp-cn-axis--tgt {
346
+ color: var(--wp-constraint-target-text);
347
+ background: color-mix(in oklab, var(--wp-constraint-target) 14%, transparent);
348
+ border: 1px solid color-mix(in oklab, var(--wp-constraint-target) 35%, transparent);
349
+ }
350
+ .wp-cn-target-select {
351
+ font-size: 10.5px;
352
+ color: var(--wp-text-muted);
353
+ background: var(--wp-bg-3);
354
+ padding: 2px 8px;
355
+ border-radius: var(--wp-radius-cap, 999px);
356
+ font-family: var(--wp-font-mono);
357
+ }
358
+
359
+ /* ── Grid wrapper (simple scroll, no sticky headers) ───────── */
360
+ /* Frame hugs the table (align-self instead of stretching to the card width)
361
+ and scrolls on BOTH axes for wide/tall matrices — mirrors the sister's
362
+ inline-block grid. */
363
+ .wp-cn-grid-wrap {
364
+ align-self: flex-start;
365
+ max-width: 100%;
366
+ overflow: auto;
367
+ background: var(--wp-bg-1);
368
+ border: 1px solid var(--wp-border);
369
+ border-radius: min(6px, var(--wp-radius-cap, 999px));
370
+ padding: 0;
371
+ }
372
+ /* `separate` + zero spacing; the 4px inter-cell gap comes from th/td padding,
373
+ so the columns the <th> headers form line up exactly with the body <td>s. */
374
+ .wp-cn-table {
375
+ border-collapse: separate;
376
+ border-spacing: 0;
377
+ }
378
+ .wp-cn-table th,
379
+ .wp-cn-table td {
380
+ vertical-align: middle;
381
+ padding: 2px;
382
+ }
383
+ /* The <td> stays a real table cell (so columns align); the inner
384
+ .wp-cn-cell carries the fixed width + flex layout. Putting flex on the
385
+ <td> itself pulls it out of the table formatting context and breaks
386
+ alignment. */
387
+ .wp-cn-td {
388
+ position: relative;
389
+ }
390
+
391
+ /* ── Corner chip ───────────────────────────────────────────── */
392
+ .wp-cn-corner {
393
+ background: var(--wp-bg-2);
394
+ }
395
+ .wp-cn-corner .chip {
396
+ display: flex;
397
+ flex-direction: column;
398
+ justify-content: center;
399
+ width: 96px;
400
+ min-height: 36px;
401
+ height: 100%;
402
+ padding: 5px 8px;
403
+ border-radius: min(4px, var(--wp-radius-cap, 999px));
404
+ font-family: var(--wp-font-mono);
405
+ font-size: 9px;
406
+ line-height: 1.3;
407
+ text-align: left;
408
+ background: color-mix(in srgb, var(--wp-text) 4%, var(--wp-bg-2));
409
+ }
410
+ .wp-cn-corner .row-1 {
411
+ color: var(--wp-constraint-source-text);
412
+ }
413
+ .wp-cn-corner .row-2 {
414
+ color: var(--wp-constraint-target-text);
415
+ }
416
+
417
+ /* ── Column band — axis name spanning its tags ─────────────── */
418
+ .wp-cn-band .chip {
419
+ display: flex;
420
+ align-items: center;
421
+ justify-content: center;
422
+ height: 22px;
423
+ padding: 0 8px;
424
+ font: 700 9px var(--wp-font-sans, sans-serif);
425
+ letter-spacing: 0.06em;
426
+ text-transform: uppercase;
427
+ white-space: nowrap;
428
+ overflow: hidden;
429
+ text-overflow: ellipsis;
430
+ border-radius: min(4px, var(--wp-radius-cap, 999px)) min(4px, var(--wp-radius-cap, 999px)) 0 0;
431
+ color: color-mix(in srgb, var(--ax) 82%, var(--wp-text));
432
+ background: color-mix(in srgb, var(--ax) 15%, var(--wp-bg-2));
433
+ border-bottom: 2px solid color-mix(in srgb, var(--ax) 55%, transparent);
434
+ }
435
+ /* Uncategorized bucket: dashed edge instead of solid axis accent */
436
+ .wp-cn-band--bucket .chip {
437
+ border-bottom-style: dashed;
438
+ }
439
+ .wp-cn-grp-head--bucket .chip {
440
+ border-left-style: dashed;
441
+ }
442
+
443
+ /* ── Column tag chip ───────────────────────────────────────── */
444
+ .wp-cn-colhead .chip {
445
+ display: flex;
446
+ align-items: center;
447
+ justify-content: center;
448
+ width: 64px;
449
+ min-height: 28px;
450
+ padding: 3px 2px;
451
+ font: 700 10px var(--wp-font-sans, sans-serif);
452
+ border-radius: 0 0 min(4px, var(--wp-radius-cap, 999px)) min(4px, var(--wp-radius-cap, 999px));
453
+ color: color-mix(in srgb, var(--ax) 72%, var(--wp-text));
454
+ background: color-mix(in srgb, var(--ax) 9%, var(--wp-bg-2));
455
+ }
456
+ .wp-cn-colhead.group-start .chip {
457
+ border-left: 2px solid color-mix(in srgb, var(--ax) 45%, transparent);
458
+ }
459
+ /* Flat path: col header with the default target tint */
460
+ .wp-cn-colhead {
461
+ color: var(--wp-constraint-target-text);
462
+ }
463
+
464
+ /* ── Row group header chip — names the axis above its tags ─── */
465
+ .wp-cn-grp-row .wp-cn-grp-head {
466
+ text-align: left;
467
+ padding: 4px 0 1px;
468
+ }
469
+ .wp-cn-grp-head .chip {
470
+ display: inline-flex;
471
+ align-items: center;
472
+ max-width: 220px;
473
+ height: 22px;
474
+ padding: 0 11px;
475
+ font: 700 9px var(--wp-font-sans, sans-serif);
476
+ letter-spacing: 0.1em;
477
+ text-transform: uppercase;
478
+ white-space: nowrap;
479
+ overflow: hidden;
480
+ text-overflow: ellipsis;
481
+ border-radius: 0 min(4px, var(--wp-radius-cap, 999px)) min(4px, var(--wp-radius-cap, 999px)) 0;
482
+ color: color-mix(in srgb, var(--ax) 80%, var(--wp-text));
483
+ background: color-mix(in srgb, var(--ax) 16%, var(--wp-bg-2));
484
+ border-left: 3px solid var(--ax);
485
+ }
486
+
487
+ /* ── Row tag chip ──────────────────────────────────────────── */
488
+ .wp-cn-rowhead .chip {
489
+ display: flex;
490
+ align-items: center;
491
+ justify-content: flex-start;
492
+ width: 96px;
493
+ height: 36px;
494
+ padding: 0 10px;
495
+ font: 700 10px var(--wp-font-sans, sans-serif);
496
+ text-align: left;
497
+ border-radius: 0 min(4px, var(--wp-radius-cap, 999px)) min(4px, var(--wp-radius-cap, 999px)) 0;
498
+ color: color-mix(in srgb, var(--ax) 76%, var(--wp-text));
499
+ background: color-mix(in srgb, var(--ax) 10%, var(--wp-bg-2));
500
+ border-left: 2px solid color-mix(in srgb, var(--ax) 48%, transparent);
501
+ }
502
+ /* Solo named axis — eyebrow + value stacked */
503
+ .wp-cn-rowhead.solo .chip {
504
+ flex-direction: column;
505
+ align-items: flex-start;
506
+ justify-content: center;
507
+ gap: 2px;
508
+ border-left-width: 3px;
509
+ border-left-color: var(--ax);
510
+ }
511
+ .wp-cn-rowhead.solo .eye {
512
+ max-width: 78px;
513
+ overflow: hidden;
514
+ text-overflow: ellipsis;
515
+ white-space: nowrap;
516
+ font: 700 7px var(--wp-font-sans, sans-serif);
517
+ letter-spacing: 0.08em;
518
+ text-transform: uppercase;
519
+ color: color-mix(in srgb, var(--ax) 72%, var(--wp-text-dim));
520
+ }
521
+ .wp-cn-rowhead.solo .v {
522
+ font: 600 11px var(--wp-font-sans, sans-serif);
523
+ color: var(--wp-text);
524
+ }
525
+
526
+ /* ── Matrix cells ──────────────────────────────────────────── */
527
+ /* Inner cell box — fixed 64×36 so every column lines up; read-only (no
528
+ cursor, no hover lift). */
529
+ .wp-cn-cell {
530
+ width: 64px;
531
+ height: 36px;
532
+ box-sizing: border-box;
533
+ display: inline-flex;
534
+ align-items: center;
535
+ justify-content: center;
536
+ gap: 4px;
537
+ border-radius: min(4px, var(--wp-radius-cap, 999px));
538
+ font-family: var(--wp-font-mono);
539
+ font-size: 12px;
540
+ font-weight: 600;
541
+ }
542
+ .wp-cn-cell__glyph {
543
+ font-size: 13px;
544
+ line-height: 1;
545
+ }
546
+ .wp-cn-cell__factor {
547
+ font-size: 10.5px;
548
+ font-weight: 700;
549
+ }
550
+ .wp-cn-cell[data-mode="neutral"],
551
+ .wp-cn-cell[data-mode="allow"] {
552
+ color: var(--wp-text-dim);
553
+ background: color-mix(in srgb, var(--wp-text) 5%, transparent);
554
+ border: 1px dashed color-mix(in srgb, var(--wp-text) 10%, transparent);
555
+ }
556
+ .wp-cn-cell[data-mode="boost"] {
557
+ color: var(--wp-success);
558
+ background: color-mix(in srgb, var(--wp-success) 22%, transparent);
559
+ border: 1px solid color-mix(in srgb, var(--wp-success) 45%, transparent);
560
+ }
561
+ .wp-cn-cell[data-mode="reduce"] {
562
+ color: var(--wp-warn);
563
+ background: color-mix(in srgb, var(--wp-warn) 22%, transparent);
564
+ border: 1px solid color-mix(in srgb, var(--wp-warn) 45%, transparent);
565
+ }
566
+ .wp-cn-cell[data-mode="only"] {
567
+ color: var(--wp-info);
568
+ background: color-mix(in srgb, var(--wp-info) 22%, transparent);
569
+ border: 1px solid color-mix(in srgb, var(--wp-info) 45%, transparent);
570
+ }
571
+ .wp-cn-cell.wp-cn-cell--implied-out {
572
+ color: color-mix(in srgb, var(--wp-danger) 60%, var(--wp-text-dim));
573
+ border-color: color-mix(in srgb, var(--wp-danger) 30%, transparent);
574
+ }
575
+ .wp-cn-cell[data-mode="exclude"] {
576
+ color: var(--wp-danger);
577
+ background: color-mix(in srgb, var(--wp-danger) 22%, transparent);
578
+ border: 1px solid color-mix(in srgb, var(--wp-danger) 45%, transparent);
579
+ }
580
+
581
+ /* ── Legend ────────────────────────────────────────────────── */
582
+ .wp-cn-legend {
583
+ display: flex;
584
+ flex-wrap: wrap;
585
+ gap: 14px;
586
+ font-size: 11px;
587
+ color: var(--wp-text-muted);
588
+ }
589
+ .wp-cn-legend b {
590
+ font-family: var(--wp-font-mono);
591
+ color: var(--wp-text);
592
+ }
593
+ .wp-cn-legend b[data-mode="boost"] { color: var(--wp-success); }
594
+ .wp-cn-legend b[data-mode="reduce"] { color: var(--wp-warn); }
595
+ .wp-cn-legend b[data-mode="exclude"] { color: var(--wp-danger); }
596
+ .wp-cn-legend b[data-mode="neutral"] { color: var(--wp-text-dim); }
597
+ .wp-cn-legend b[data-mode="only"] { color: var(--wp-info); }
598
+
599
+ /* ── Exceptions ────────────────────────────────────────────── */
600
+ .wp-cn-exceptions {
601
+ display: flex;
602
+ flex-direction: column;
603
+ gap: 4px;
604
+ }
605
+ .wp-cn-exceptions__label {
606
+ font-size: 9px;
607
+ text-transform: uppercase;
608
+ letter-spacing: 0.14em;
609
+ color: var(--wp-text-dim);
610
+ margin-bottom: 2px;
611
+ }
612
+ .wp-cn-exc-row {
613
+ display: flex;
614
+ align-items: center;
615
+ gap: 8px;
616
+ padding: 5px 8px;
617
+ border-radius: min(4px, var(--wp-radius-cap, 999px));
618
+ border-left: 3px solid transparent;
619
+ background: var(--wp-bg-3);
620
+ }
621
+ .wp-cn-exc-row--boost {
622
+ border-left-color: color-mix(in oklab, var(--wp-success) 60%, transparent);
623
+ background: color-mix(in oklab, var(--wp-success) 6%, var(--wp-bg-3));
624
+ }
625
+ .wp-cn-exc-row--reduce {
626
+ border-left-color: color-mix(in oklab, var(--wp-warn) 60%, transparent);
627
+ background: color-mix(in oklab, var(--wp-warn) 6%, var(--wp-bg-3));
628
+ }
629
+ .wp-cn-exc-row--only {
630
+ border-left-color: color-mix(in oklab, var(--wp-info) 60%, transparent);
631
+ background: color-mix(in oklab, var(--wp-info) 6%, var(--wp-bg-3));
632
+ }
633
+ .wp-cn-exc-row--exclude {
634
+ border-left-color: color-mix(in oklab, var(--wp-danger) 60%, transparent);
635
+ background: color-mix(in oklab, var(--wp-danger) 6%, var(--wp-bg-3));
636
+ }
637
+ .wp-cn-exc__pair {
638
+ flex: 1;
639
+ display: inline-flex;
640
+ align-items: center;
641
+ gap: 6px;
642
+ min-width: 0;
643
+ flex-wrap: wrap;
644
+ }
645
+ .wp-cn-exc__src,
646
+ .wp-cn-exc__tgt {
647
+ padding: 1px 6px;
648
+ border-radius: min(3px, var(--wp-radius-cap, 999px));
649
+ }
650
+ .wp-cn-exc__src {
651
+ color: var(--wp-constraint-source-text);
652
+ background: color-mix(in oklab, var(--wp-constraint-source) 12%, transparent);
653
+ border: 1px solid color-mix(in oklab, var(--wp-constraint-source) 30%, transparent);
654
+ }
655
+ .wp-cn-exc__tgt {
656
+ color: var(--wp-constraint-target-text);
657
+ background: color-mix(in oklab, var(--wp-constraint-target) 12%, transparent);
658
+ border: 1px solid color-mix(in oklab, var(--wp-constraint-target) 30%, transparent);
659
+ }
660
+ .wp-cn-exc__arrow {
661
+ font-size: 9px;
662
+ color: var(--wp-text-dim);
663
+ }
664
+ .wp-cn-exc__mode {
665
+ display: inline-flex;
666
+ align-items: center;
667
+ gap: 4px;
668
+ font-size: 9px;
669
+ font-weight: 600;
670
+ text-transform: uppercase;
671
+ letter-spacing: 0.04em;
672
+ padding: 2px 7px;
673
+ border-radius: min(3px, var(--wp-radius-cap, 999px));
674
+ white-space: nowrap;
675
+ border: 1px solid var(--wp-border);
676
+ color: var(--wp-text);
677
+ }
678
+ .wp-cn-exc__glyph {
679
+ font-family: var(--wp-font-mono);
680
+ font-size: 11px;
681
+ line-height: 1;
682
+ }
683
+ .wp-cn-exc__mode--boost {
684
+ color: var(--wp-success);
685
+ background: color-mix(in oklab, var(--wp-success) 20%, transparent);
686
+ border-color: color-mix(in oklab, var(--wp-success) 40%, transparent);
687
+ }
688
+ .wp-cn-exc__mode--reduce {
689
+ color: var(--wp-warn);
690
+ background: color-mix(in oklab, var(--wp-warn) 20%, transparent);
691
+ border-color: color-mix(in oklab, var(--wp-warn) 40%, transparent);
692
+ }
693
+ .wp-cn-exc__mode--only {
694
+ color: var(--wp-info);
695
+ background: color-mix(in oklab, var(--wp-info) 20%, transparent);
696
+ border-color: color-mix(in oklab, var(--wp-info) 40%, transparent);
697
+ }
698
+ .wp-cn-exc__mode--exclude {
699
+ color: var(--wp-danger);
700
+ background: color-mix(in oklab, var(--wp-danger) 20%, transparent);
701
+ border-color: color-mix(in oklab, var(--wp-danger) 40%, transparent);
702
+ }
703
+ .wp-cn-exc__factor {
704
+ font-family: var(--wp-font-mono);
705
+ font-size: 10.5px;
706
+ color: var(--wp-text-muted);
707
+ }
708
+ </style>