@ntmats/hc-ui 0.8.0 → 0.10.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.
package/README.md CHANGED
@@ -95,4 +95,4 @@ Fixed colors that stay the same in both themes are HeroClix tokens: `color-dial-
95
95
 
96
96
  `PhysicalCardView` is the printed card in HCUnits' format (375×525 px, absolute positions), scaled to its container's width: front and back side by side while each keeps about 86% of its size, otherwise one under the other, never above 1.2×. With `fitHeight` the front also fits the visible height of the scrolling area found by `fitHeightWithin`. Everything comes resolved: the backgrounds (`images.front`, `images.back`, `images.legacy`, `layout.rarityImage`, `layout.dialImage`: addresses the app serves; HCUI's are in its `public/card/` and do not ship here), the title (its type shrinks from 28px to fit), header lines, team abilities (buttons that open a tip, with the shield the caller passes in `teamAbilityIcon`), the collection number and set symbol, the token (`portrait` drawn by the caller with the class the card gives, `terrain` art or an `object` ring in its color), the bystander's token replica, keyphrases, the rules (already translated, with an optional `icon`; tapping a paragraph calls `onCopy(copyText)`), improved abilities, the printed dial as a node (`dial.node`, usually `CombatDialView` with `presentation="printed"`), the terrain dial, points (buttons that call `onSelectPoint`, an object's line or nothing) and the black back bar. Rules that do not fit the front move to the backs, two columns each, one block at a time (`nextPhysicalCardDistribution`); the split starts over when `contentKey` changes. Under a mouse the card tilts up to 3° toward the pointer, with parallax and a shine (nearly still over the dial); on a phone only the focus view moves, by the motion sensor (`requestPhysicalCardMotionAccess` asks iOS on the first touch). Holding the card with a finger for 450 ms calls `onRequestFocus`; the caller opens `PhysicalCardFocusView` (a modal `<dialog>` over a dimmed, blurred backdrop; Esc and a tap outside, once the opening finger is lifted, call `onClose`) with a second card with `focus`, which shows one side at a time and draws `focusActions({ nextSide, backs, flip })` under it (the library's `Button` `outline` there gets the surface fill). Tips are controlled as in the dial: hover with a mouse opens a passive tip described by the control, a press or Enter opens it as a dialog, leaving closes the passive one; the caller keeps `tip` and draws it in `tipSlot`. Paper, ink, shine and grain follow the theme (`color-card-*`, `filter-card-background`, `blend-card-*`, `opacity-card-grain*`); the printed black and white, the team-up band and the terrain dial are fixed (`color-card-print-*`, `color-card-team-up`, `color-card-terrain-panel*`). Letters are upright (`--hc-rond: 0` on the card, which its printed dial reads too). Motion only with a fine pointer or in the phone's focus view, and none under `prefers-reduced-motion`.
97
97
 
98
- `MapBoardView` draws a map already fetched (`map`: the hcmaps JSON, `HCMapJson`) with the hcmaps engine (`HCMap`) in a canvas named by `canvasLabel`, square-cornered and white in both themes (`color-map-board`), and shades the inspected square or draws the inspected edge (`selection`, from `inspectMap`; `color-map-selection*`). `onInspect(x, y, clientX, clientY)` gets the pointer in squares: mouse and pen while passing over, touch on press; `onLeave` when the mouse leaves. Other attributes (role, `tabIndex`, `onKeyDown`, `aria-*`) go to the container, so the caller adds the keyboard (HCUI: arrows move one square, Esc closes). `MapBoardPlaceholder` is the empty grid while it loads, a busy status whose `label` is visually hidden by the library. `MapTooltipView` is fixed next to `anchor` without leaving the screen (portal it out of a clipping container): the title, a wall's color, and for each square (one, or two on an edge) its coordinate, terrain and elevation swatches with the caller's names, the environment (a node, e.g. `MapEnvironmentView`, or `environmentNote`) and notes. `MapSwatch` is the board's square in a legend; `MapLegendView` lists them (the 3D model's legend) with children after; `MapEnvironmentView` shows indoor/outdoor with the caller's icons (black line icons, turned light in the dark theme by `filter-map-environment-icon`) and names. `renderMapThumbnail(json, height)` returns a PNG data URL. The engines are plain functions with no DOM beyond a canvas context: `inspectMap` (square, edge or wall at a point, in squares), `mapSquare`, `mapMaxLevel`, `mapElevationLevel`, `mapSquareCoordinate`; `buildMapScene` (the 3D model's heights, walls, ramps and ziplines), the orthographic camera (`makeMapProjection`, `pickMapScene`, `zoomMapCamera`, `clampMapPan`) and `drawMapScene` (Canvas 2D). The 3D model's component, its gestures and its icons stay in the app.
98
+ `MapBoardView` draws a map already fetched (`map`: the hcmaps JSON, `HCMapJson`) with the hcmaps engine (`HCMap`) in a canvas named by `canvasLabel`, square-cornered and white in both themes (`color-map-board`), and shades the inspected square or draws the inspected edge (`selection`, from `inspectMap`; `color-map-selection*`). `onInspect(x, y, clientX, clientY)` gets the pointer in squares: mouse and pen while passing over, touch on press; `onLeave` when the mouse leaves. Other attributes (role, `tabIndex`, `onKeyDown`, `aria-*`) go to the container, so the caller adds the keyboard (HCUI: arrows move one square, Esc closes). `MapBoardPlaceholder` is the empty grid while it loads, a busy status whose `label` is visually hidden by the library. `MapTooltipView` is fixed next to `anchor` without leaving the screen (portal it out of a clipping container): the title, a wall's color, and for each square (one, or two on an edge) its coordinate, terrain and elevation swatches with the caller's names, the environment (a node, e.g. `MapEnvironmentView`, or `environmentNote`) and notes. `MapSwatch` is the board's square in a legend; `MapLegendView` lists them (the 3D model's legend) with children after; `MapEnvironmentView` shows indoor/outdoor with the caller's icons (black line icons, turned light in the dark theme by `filter-map-environment-icon`) and names. `renderMapThumbnail(json, height)` returns a PNG data URL. The engines are plain functions with no DOM beyond a canvas context: `inspectMap` (square, edge or wall at a point, in squares), `mapSquare`, `mapMaxLevel`, `mapElevationLevel`, `mapSquareCoordinate`; `buildMapScene` (the 3D model's heights, walls, ramps and ziplines), the camera (`makeMapProjection`, `pickMapScene`, `zoomMapCamera`, `clampMapPan`) and `drawMapScene` (Canvas 2D). The camera is parallel by default; `makeMapProjection(scene, camera, viewport, { perspective: MAP_PERSPECTIVE_DISTANCE })` adds a light perspective (near squares larger, far ones smaller) in the same screen box, with `ground` still exact and `factor(x, y, z)` telling how much a point grew, e.g. to size a figure drawn over the model. `MapBoardView` draws again when the browser gives its canvas back blank (`contextrestored`). The 3D model's component, its gestures and its icons stay in the app.
package/dist/index.d.ts CHANGED
@@ -953,9 +953,18 @@ export interface CombatDialViewProps {
953
953
  /**
954
954
  * `comfortable` (default): 40px cells. `compact`: 34px cells, 6px gaps, 13px numbers and
955
955
  * tighter point chips, for dense lists (HCUI's compact dial of the unit view on phones).
956
+ * `dense`: 25px cells and rows, 3px between rows, 11–12px numbers, the compact header,
957
+ * for a dial next to other content (the Arena's table card).
956
958
  * The printed presentation keeps its own measures.
957
959
  */
958
- density?: 'comfortable' | 'compact';
960
+ density?: 'comfortable' | 'compact' | 'dense';
961
+ /**
962
+ * How the current click is marked. `bar` (default): a muted column with a thin accent
963
+ * bar on top. `framed`: the column is framed in light gray behind the cells and the click
964
+ * number becomes a filled circle in the text color, easier to find mid-game; the power
965
+ * colors stay intact. Ignored by the printed presentation.
966
+ */
967
+ currentMark?: 'bar' | 'framed';
959
968
  /** Without it the dial is static: it neither marks nor changes the click; only cells with a tip react. */
960
969
  onClickChange?: (click: number) => void;
961
970
  onSelectPoint: (point: CombatDialPoint) => void;
@@ -1503,8 +1512,24 @@ export interface MapProjection {
1503
1512
  depth(x: number, y: number): number;
1504
1513
  /** From the screen to the map, on a plane at height `z`. */
1505
1514
  ground(px: number, py: number, z: number): [number, number];
1515
+ /**
1516
+ * How much a point at (x, y, z) is enlarged on screen compared with the parallel
1517
+ * projection: above 1 near the camera, below 1 far from it. Always 1 without
1518
+ * `perspective`. Optional so hand-made projections still type-check.
1519
+ */
1520
+ factor?(x: number, y: number, z: number): number;
1521
+ }
1522
+ export interface MapProjectionOptions {
1523
+ /**
1524
+ * A light perspective, as the camera's distance in half-diagonals of the scene
1525
+ * (`MAP_PERSPECTIVE_DISTANCE` = 4; larger is gentler). Off by default: a parallel
1526
+ * projection, where near and far squares are the same size.
1527
+ */
1528
+ perspective?: number;
1506
1529
  }
1507
- export declare function makeMapProjection(scene: MapScene, camera: MapCamera, viewport: MapViewport): MapProjection;
1530
+ /** The camera distance the Arena's table uses for `perspective`. */
1531
+ export declare const MAP_PERSPECTIVE_DISTANCE = 4;
1532
+ export declare function makeMapProjection(scene: MapScene, camera: MapCamera, viewport: MapViewport, { perspective }?: MapProjectionOptions): MapProjection;
1508
1533
  /** The map's center may go as far as any of its corners reaches the middle of the screen. */
1509
1534
  export declare function clampMapPan(scene: MapScene, camera: MapCamera, viewport: MapViewport): MapCamera;
1510
1535
  /** Zooms keeping the point at `anchor` (measured from the screen's center) still. */
package/dist/index.js CHANGED
@@ -1574,6 +1574,7 @@ var CombatDialView = forwardRef(function CombatDialView2({
1574
1574
  labels,
1575
1575
  presentation = "default",
1576
1576
  density = "comfortable",
1577
+ currentMark = "bar",
1577
1578
  onClickChange,
1578
1579
  onSelectPoint,
1579
1580
  tip,
@@ -1677,7 +1678,7 @@ var CombatDialView = forwardRef(function CombatDialView2({
1677
1678
  COMBAT_DIAL_STATS.map((stat) => /* @__PURE__ */ jsx("div", { className: "hc-ui-combat-dial__cell", "data-kind": "ko", children: labels.koCell }, stat))
1678
1679
  ] })
1679
1680
  ] }, rowIndex);
1680
- return /* @__PURE__ */ jsxs("section", { ref, className: "hc-ui-combat-dial", "data-presentation": presentation, "data-density": density === "compact" ? "compact" : void 0, "data-static": flag(!tracksClick), children: [
1681
+ return /* @__PURE__ */ jsxs("section", { ref, className: "hc-ui-combat-dial", "data-presentation": presentation, "data-density": density === "comfortable" ? void 0 : density, "data-current-mark": currentMark === "framed" ? "framed" : void 0, "data-static": flag(!tracksClick), children: [
1681
1682
  presentation !== "printed" && /* @__PURE__ */ jsxs("header", { className: "hc-ui-combat-dial__header", children: [
1682
1683
  /* @__PURE__ */ jsxs("div", { className: "hc-ui-combat-dial__header-top", children: [
1683
1684
  /* @__PURE__ */ jsx("span", { className: "hc-ui-combat-dial__label", children: labels.heading }),
@@ -2757,7 +2758,8 @@ var MAP_DEFAULT_CAMERA = { azimuth: -32, tilt: 52, zoom: 1, panX: 0, panY: 0 };
2757
2758
  var MAP_TILT_RANGE = [6, 90];
2758
2759
  var MAP_ZOOM_RANGE = [1, 6];
2759
2760
  var clampMapValue = (value, min, max) => Math.min(max, Math.max(min, value));
2760
- function makeMapProjection(scene, camera, viewport) {
2761
+ var MAP_PERSPECTIVE_DISTANCE = 4;
2762
+ function makeMapProjection(scene, camera, viewport, { perspective } = {}) {
2761
2763
  const rad = camera.azimuth * Math.PI / 180, tilt = camera.tilt * Math.PI / 180;
2762
2764
  const sin = Math.sin(rad), cos = Math.cos(rad), sinT = Math.sin(tilt), cosT = Math.cos(tilt);
2763
2765
  const cx2 = scene.width / 2, cy = scene.height / 2, midZ = (scene.maxTop + MAP_SCENE_BASE_Z) / 2;
@@ -2766,7 +2768,7 @@ function makeMapProjection(scene, camera, viewport) {
2766
2768
  const fit = Math.min(viewport.width / (halfWidth * 2.06), viewport.height / (halfHeight * 2.06));
2767
2769
  const scale = fit * camera.zoom;
2768
2770
  const ox = viewport.width / 2 + camera.panX, oy = viewport.height / 2 + camera.panY;
2769
- return {
2771
+ const parallel = {
2770
2772
  scale,
2771
2773
  sin,
2772
2774
  cos,
@@ -2780,6 +2782,44 @@ function makeMapProjection(scene, camera, viewport) {
2780
2782
  ground(px, py, z) {
2781
2783
  const rx = (px - ox) / scale, ry = ((py - oy) / scale + (z - midZ) * cosT) / sinT;
2782
2784
  return [cx2 + rx * cos + ry * sin, cy - rx * sin + ry * cos];
2785
+ },
2786
+ factor: () => 1
2787
+ };
2788
+ if (!perspective || !(perspective > 0)) return parallel;
2789
+ const [sx, sy] = parallel.point(cx2, cy, midZ);
2790
+ const D = perspective * Math.max(1, halfWidth, (scene.maxTop - MAP_SCENE_BASE_Z) / 2);
2791
+ const toward = (x, y, z) => parallel.depth(x, y) * cosT + (z - midZ) * sinT;
2792
+ const raw = (x, y, z) => D / Math.max(D * 0.05, D - toward(x, y, z));
2793
+ const corners = [[0, 0], [scene.width, 0], [scene.width, scene.height], [0, scene.height]];
2794
+ const extremes = corners.flatMap(([x, y]) => [[x, y, scene.maxTop], [x, y, MAP_SCENE_BASE_Z]]);
2795
+ const box = (points) => {
2796
+ const xs = points.map((p) => p[0]), ys = points.map((p) => p[1]);
2797
+ return [Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys)];
2798
+ };
2799
+ const [flatW, flatH] = box(extremes.map(([x, y, z]) => parallel.point(x, y, z)));
2800
+ const [deepW, deepH] = box(extremes.map(([x, y, z]) => {
2801
+ const [px, py] = parallel.point(x, y, z), f = raw(x, y, z);
2802
+ return [sx + (px - sx) * f, sy + (py - sy) * f];
2803
+ }));
2804
+ const norm = Math.max(flatW > 1e-9 ? deepW / flatW : 0, flatH > 1e-9 ? deepH / flatH : 0) || 1;
2805
+ const factor = (x, y, z) => raw(x, y, z) / norm;
2806
+ return {
2807
+ scale,
2808
+ sin,
2809
+ cos,
2810
+ factor,
2811
+ depth: parallel.depth,
2812
+ point(x, y, z) {
2813
+ const [px, py] = parallel.point(x, y, z), f = factor(x, y, z);
2814
+ return [sx + (px - sx) * f, sy + (py - sy) * f];
2815
+ },
2816
+ ground(px, py, z) {
2817
+ const zc = z - midZ;
2818
+ const v = (py - sy) / scale * norm;
2819
+ const denom = D * sinT + v * cosT;
2820
+ const along = Math.abs(denom) < 1e-9 ? 0 : (v * D - v * zc * sinT + zc * cosT * D) / denom;
2821
+ const f = D / Math.max(D * 0.05, D - along * cosT - zc * sinT) / norm;
2822
+ return parallel.ground(sx + (px - sx) / f, sy + (py - sy) / f, z);
2783
2823
  }
2784
2824
  };
2785
2825
  }
@@ -2833,11 +2873,9 @@ function pickMapScene(scene, view, px, py) {
2833
2873
  const p00 = view.point(cell.x, cell.y, cell.top), p10 = view.point(cell.x + 1, cell.y, cell.top);
2834
2874
  const p11 = view.point(cell.x + 1, cell.y + 1, cell.top), p01 = view.point(cell.x, cell.y + 1, cell.top);
2835
2875
  if (inside(px, py, [p00, p10, p11, p01])) {
2836
- const ax = p10[0] - p00[0], ay = p10[1] - p00[1], bx = p01[0] - p00[0], by = p01[1] - p00[1];
2837
- const det = ax * by - ay * bx;
2838
- if (!det) return { x: cell.x + 0.5, y: cell.y + 0.5 };
2839
- const u = ((px - p00[0]) * by - (py - p00[1]) * bx) / det, v = ((py - p00[1]) * ax - (px - p00[0]) * ay) / det;
2840
- return { x: cell.x + clampMapValue(u, 0, 1), y: cell.y + clampMapValue(v, 0, 1) };
2876
+ const [gx, gy] = view.ground(px, py, cell.top);
2877
+ if (!Number.isFinite(gx) || !Number.isFinite(gy)) return { x: cell.x + 0.5, y: cell.y + 0.5 };
2878
+ return { x: clampMapValue(gx, cell.x, cell.x + 1), y: clampMapValue(gy, cell.y, cell.y + 1) };
2841
2879
  }
2842
2880
  for (const side of MAP_CELL_SIDES) {
2843
2881
  if (side.dx * view.sin + side.dy * view.cos <= 0) continue;
@@ -3188,14 +3226,19 @@ var MapBoardView = forwardRef(function MapBoardView2({ map, canvasLabel, selecti
3188
3226
  useEffect(() => {
3189
3227
  const canvas = canvasRef.current;
3190
3228
  if (!canvas) return;
3191
- const ctx = canvas.getContext("2d");
3192
- if (!ctx) return;
3193
- try {
3194
- ctx.clearRect(0, 0, canvas.width, canvas.height);
3195
- new HCMap(map).draw(ctx, { grid });
3196
- } catch {
3197
- failed.current?.();
3198
- }
3229
+ const draw = () => {
3230
+ const ctx = canvas.getContext("2d");
3231
+ if (!ctx) return;
3232
+ try {
3233
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
3234
+ new HCMap(map).draw(ctx, { grid });
3235
+ } catch {
3236
+ failed.current?.();
3237
+ }
3238
+ };
3239
+ draw();
3240
+ canvas.addEventListener("contextrestored", draw);
3241
+ return () => canvas.removeEventListener("contextrestored", draw);
3199
3242
  }, [map, grid]);
3200
3243
  function pointer(event) {
3201
3244
  const bounds = event.currentTarget.getBoundingClientRect();
@@ -3306,6 +3349,7 @@ export {
3306
3349
  MAP_GRID_SOFT_COLOR,
3307
3350
  MAP_GRID_TERRAIN_SHADE,
3308
3351
  MAP_INDOOR_EDGE_COLOR,
3352
+ MAP_PERSPECTIVE_DISTANCE,
3309
3353
  MAP_SCENE_BASE_Z,
3310
3354
  MAP_SCENE_LEVEL_HEIGHT,
3311
3355
  MAP_TERRAIN_EDGE_COLORS,
package/dist/styles.css CHANGED
@@ -1915,6 +1915,47 @@
1915
1915
  [data-hc-ds] .hc-ui-combat-dial[data-density='compact']:not([data-presentation='printed']) :is(.hc-ui-combat-dial__cell[data-kind='value'], .hc-ui-combat-dial__range) { font-size: var(--hc-font-size-sm); }
1916
1916
  [data-hc-ds] .hc-ui-combat-dial[data-density='compact']:not([data-presentation='printed']) .hc-ui-combat-dial__special-icon { width: 18px; height: 18px; }
1917
1917
 
1918
+ /* `density="dense"`: the compact header (point chips) with 25px cells and rows, 3px between
1919
+ rows and 6px between clicks, 11px click numbers and 12px values, 15px special icons:
1920
+ a dial that sits beside other content (the Arena's table card). */
1921
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__header { gap: 0; }
1922
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__points { gap: var(--hc-space-1); padding: var(--hc-space-1) 0; }
1923
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__chip { gap: var(--hc-space-2); padding: var(--hc-space-2); border-radius: var(--hc-radius-lg); }
1924
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__grid-wrap { padding: 5px 0; }
1925
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__grid { gap: 6px; }
1926
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__col { grid-template-rows: repeat(5, 25px); gap: 3px; }
1927
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__col[data-start]::before { top: 28px; }
1928
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__cell { width: 25px; height: 25px; }
1929
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__cell[data-tip]::after { inset: -2px; }
1930
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__cell[data-kind='number'] { font-size: 11px; }
1931
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) :is(.hc-ui-combat-dial__cell:is([data-kind='value'], [data-kind='ko']), .hc-ui-combat-dial__range) { font-size: 12px; }
1932
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__special-icon { width: 15px; height: 15px; }
1933
+ [data-hc-ds] .hc-ui-combat-dial[data-density='dense']:not([data-presentation='printed']) .hc-ui-combat-dial__bolt { height: 11px; }
1934
+
1935
+ /* `currentMark="framed"`: the current click's column is framed in light gray behind its
1936
+ cells (a little larger than them) and its number becomes a filled circle in the text
1937
+ color. Shape and contrast mark it without relying on color; the power colors stay. */
1938
+ [data-hc-ds] .hc-ui-combat-dial[data-current-mark='framed']:not([data-presentation='printed']) .hc-ui-combat-dial__col[data-current] {
1939
+ isolation: isolate;
1940
+ background: none;
1941
+ box-shadow: none;
1942
+ }
1943
+ [data-hc-ds] .hc-ui-combat-dial[data-current-mark='framed']:not([data-presentation='printed']) .hc-ui-combat-dial__col[data-current]::after {
1944
+ content: '';
1945
+ position: absolute;
1946
+ inset: -4px -3px;
1947
+ z-index: -1;
1948
+ border-radius: 8px;
1949
+ background: var(--hc-color-bg-muted-2);
1950
+ box-shadow: inset 0 0 0 1px var(--hc-color-border);
1951
+ }
1952
+ [data-hc-ds] .hc-ui-combat-dial[data-current-mark='framed']:not([data-presentation='printed']) .hc-ui-combat-dial__col[data-current] .hc-ui-combat-dial__cell[data-kind='number'] {
1953
+ border-radius: 50%;
1954
+ background: var(--hc-color-text-primary);
1955
+ color: var(--hc-color-text-inverse);
1956
+ font-weight: 800;
1957
+ }
1958
+
1918
1959
  /* `presentation="printed"`: the physical card's dial, at HCUnits' measures (20px
1919
1960
  cells, 23px between clicks, 24px between rows); the white legend sits on the
1920
1961
  card's dark window. Its numbers use the `font` shorthand, with ROND right after it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ntmats/hc-ui",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "description": "Shared HCUI/Arena React presentation primitives.",
6
6
  "license": "UNLICENSED",