jspsych-tangram 0.0.12 → 0.0.14

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 (49) hide show
  1. package/dist/construct/index.browser.js +283 -64
  2. package/dist/construct/index.browser.js.map +1 -1
  3. package/dist/construct/index.browser.min.js +11 -11
  4. package/dist/construct/index.browser.min.js.map +1 -1
  5. package/dist/construct/index.cjs +283 -64
  6. package/dist/construct/index.cjs.map +1 -1
  7. package/dist/construct/index.d.ts +36 -0
  8. package/dist/construct/index.js +283 -64
  9. package/dist/construct/index.js.map +1 -1
  10. package/dist/index.cjs +394 -94
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.ts +84 -0
  13. package/dist/index.js +394 -94
  14. package/dist/index.js.map +1 -1
  15. package/dist/nback/index.browser.js +112 -37
  16. package/dist/nback/index.browser.js.map +1 -1
  17. package/dist/nback/index.browser.min.js +11 -11
  18. package/dist/nback/index.browser.min.js.map +1 -1
  19. package/dist/nback/index.cjs +112 -37
  20. package/dist/nback/index.cjs.map +1 -1
  21. package/dist/nback/index.d.ts +24 -0
  22. package/dist/nback/index.js +112 -37
  23. package/dist/nback/index.js.map +1 -1
  24. package/dist/prep/index.browser.js +278 -65
  25. package/dist/prep/index.browser.js.map +1 -1
  26. package/dist/prep/index.browser.min.js +11 -11
  27. package/dist/prep/index.browser.min.js.map +1 -1
  28. package/dist/prep/index.cjs +278 -65
  29. package/dist/prep/index.cjs.map +1 -1
  30. package/dist/prep/index.d.ts +24 -0
  31. package/dist/prep/index.js +278 -65
  32. package/dist/prep/index.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/core/components/board/BoardView.tsx +372 -124
  35. package/src/core/components/board/GameBoard.tsx +39 -44
  36. package/src/core/components/board/useGameBoard.ts +52 -0
  37. package/src/core/components/index.ts +4 -2
  38. package/src/core/components/pieces/BlueprintRing.tsx +100 -67
  39. package/src/core/components/pieces/useBlueprintRing.ts +39 -0
  40. package/src/core/config/config.ts +25 -10
  41. package/src/plugins/tangram-construct/ConstructionApp.tsx +7 -1
  42. package/src/plugins/tangram-construct/index.ts +22 -1
  43. package/src/plugins/tangram-nback/NBackApp.tsx +88 -29
  44. package/src/plugins/tangram-nback/index.ts +14 -0
  45. package/src/plugins/tangram-prep/PrepApp.tsx +7 -1
  46. package/src/plugins/tangram-prep/index.ts +14 -0
  47. package/tangram-construct.min.js +11 -11
  48. package/tangram-nback.min.js +11 -11
  49. package/tangram-prep.min.js +11 -11
@@ -16660,30 +16660,41 @@ var TangramPrepPlugin = (function (jspsych) {
16660
16660
 
16661
16661
  const CONFIG = {
16662
16662
  color: {
16663
+ background: "#fff7e0ff",
16663
16664
  bands: {
16664
- silhouette: { fillEven: "#fff2ccff", fillOdd: "#fff2ccff", stroke: "#b1b1b1" },
16665
- workspace: { fillEven: "#fff2ccff", fillOdd: "#fff2ccff", stroke: "#b1b1b1" }
16665
+ silhouette: { fillEven: "#ffffff", fillOdd: "#ffffff", stroke: "#b1b1b1" },
16666
+ workspace: { fillEven: "#ffffff", fillOdd: "#ffffff", stroke: "#b1b1b1" }
16666
16667
  },
16667
- completion: { fill: "#ccfff2", stroke: "#13da57" },
16668
+ completion: { fill: "#ccffcc", stroke: "#13da57" },
16668
16669
  silhouetteMask: "#374151",
16669
16670
  anchors: { invalid: "#7dd3fc", valid: "#475569" },
16670
- piece: { draggingFill: "#8e7cc3ff", validFill: "#8e7cc3ff", invalidFill: "#ef4444", invalidStroke: "#dc2626", selectedStroke: "#674ea7", allGreenStroke: "#86efac", borderStroke: "#674ea7" },
16671
+ // validFill used here for placed composites
16672
+ piece: { draggingFill: "#8e7cc3", validFill: "#8e7cc3", invalidFill: "#d55c00", invalidStroke: "#dc2626", selectedStroke: "#674ea7", allGreenStroke: "#86efac", borderStroke: "#674ea7" },
16671
16673
  ui: { light: "#60a5fa", dark: "#1d4ed8" },
16672
16674
  blueprint: { fill: "#374151", selectedStroke: "#111827", badgeFill: "#000000", labelFill: "#ffffff" },
16673
- tangramDecomposition: { stroke: "#fef2cc" }
16675
+ tangramDecomposition: { stroke: "#fef2cc" },
16676
+ primitiveColors: [
16677
+ // from seaborn "colorblind" palette, 6 colors, with red omitted
16678
+ "#0173b2",
16679
+ "#de8f05",
16680
+ "#029e73",
16681
+ "#cc78bc",
16682
+ "#ca9161"
16683
+ ]
16674
16684
  },
16675
16685
  opacity: {
16676
- blueprint: 0.4,
16686
+ blueprint: 0.6,
16677
16687
  silhouetteMask: 0.25,
16678
16688
  //anchors: { valid: 0.80, invalid: 0.50 },
16679
16689
  anchors: { invalid: 0, valid: 0 },
16680
- piece: { invalid: 0.35, dragging: 0.75, locked: 1, normal: 1 }
16690
+ piece: { invalid: 1, dragging: 1, locked: 1, normal: 1 }
16681
16691
  },
16682
16692
  size: {
16683
- stroke: { bandPx: 5, pieceSelectedPx: 3, allGreenStrokePx: 10, pieceBorderPx: 2, tangramDecompositionPx: 1 },
16693
+ stroke: { bandPx: 5, pieceSelectedPx: 5, allGreenStrokePx: 10, pieceBorderPx: 2, tangramDecompositionPx: 1 },
16684
16694
  anchorRadiusPx: { valid: 1, invalid: 1 },
16685
16695
  badgeFontPx: 16,
16686
- centerBadge: { fractionOfOuterR: 0.15, minPx: 20, marginPx: 4 }
16696
+ centerBadge: { fractionOfOuterR: 0.15, minPx: 20, marginPx: 4 },
16697
+ invalidMarker: { sizePx: 10, strokePx: 4 }
16687
16698
  },
16688
16699
  layout: {
16689
16700
  grid: { stepPx: 20, unitPx: 40 },
@@ -16701,7 +16712,8 @@ var TangramPrepPlugin = (function (jspsych) {
16701
16712
  game: {
16702
16713
  snapRadiusPx: 15,
16703
16714
  showBorders: false,
16704
- hideTouchingBorders: true
16715
+ hideTouchingBorders: true,
16716
+ silhouettesBelowPieces: true
16705
16717
  }
16706
16718
  };
16707
16719
 
@@ -17485,6 +17497,31 @@ var TangramPrepPlugin = (function (jspsych) {
17485
17497
  function pathD(poly) {
17486
17498
  return `M ${poly.map((pt) => `${pt.x} ${pt.y}`).join(" L ")} Z`;
17487
17499
  }
17500
+ function getPieceColor(blueprint, usePrimitiveColors, defaultColor, primitiveColorIndices) {
17501
+ if (!usePrimitiveColors) {
17502
+ return defaultColor;
17503
+ }
17504
+ if ("kind" in blueprint) {
17505
+ const kind = blueprint.kind;
17506
+ const kindToIndex = {
17507
+ "square": 0,
17508
+ "smalltriangle": 1,
17509
+ "parallelogram": 2,
17510
+ "medtriangle": 3,
17511
+ "largetriangle": 4
17512
+ };
17513
+ const primitiveIndex = kindToIndex[kind];
17514
+ if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
17515
+ const colorIndex = primitiveColorIndices[primitiveIndex];
17516
+ const color = CONFIG.color.primitiveColors[colorIndex];
17517
+ if (color) {
17518
+ return color;
17519
+ }
17520
+ }
17521
+ return defaultColor;
17522
+ }
17523
+ return defaultColor;
17524
+ }
17488
17525
  function BoardView(props) {
17489
17526
  const {
17490
17527
  controller,
@@ -17504,6 +17541,9 @@ var TangramPrepPlugin = (function (jspsych) {
17504
17541
  dragInvalid,
17505
17542
  lockedPieceId,
17506
17543
  showTangramDecomposition,
17544
+ usePrimitiveColorsBlueprints,
17545
+ usePrimitiveColorsTargets,
17546
+ primitiveColorIndices,
17507
17547
  svgRef,
17508
17548
  setPieceRef,
17509
17549
  onPiecePointerDown,
@@ -17514,6 +17554,144 @@ var TangramPrepPlugin = (function (jspsych) {
17514
17554
  onCenterBadgePointerDown
17515
17555
  } = props;
17516
17556
  const VW = viewBox.w, VH = viewBox.h;
17557
+ const renderSilhouettes = () => layout.sectors.map((s) => {
17558
+ const sectorCfg = controller.state.cfg.sectors.find((ss) => ss.id === s.id);
17559
+ if (showTangramDecomposition && sectorCfg?.silhouette.primitiveDecomposition) {
17560
+ const primitiveDecomposition = sectorCfg.silhouette.primitiveDecomposition;
17561
+ const rect = rectForBand(layout, s, "silhouette", 1);
17562
+ const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
17563
+ const placedPolys = placeSilhouetteGridAlignedAsPolys(rawPolys, scaleS, { cx: rect.cx, cy: rect.cy });
17564
+ return /* @__PURE__ */ React.createElement("g", { key: `sil-decomposed-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => {
17565
+ const primInfo = primitiveDecomposition[i];
17566
+ let fillColor = CONFIG.color.silhouetteMask;
17567
+ if (usePrimitiveColorsTargets && primInfo?.kind && primitiveColorIndices) {
17568
+ const kindToIndex = {
17569
+ "square": 0,
17570
+ "smalltriangle": 1,
17571
+ "parallelogram": 2,
17572
+ "medtriangle": 3,
17573
+ "largetriangle": 4
17574
+ };
17575
+ const primitiveIndex = kindToIndex[primInfo.kind];
17576
+ if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
17577
+ const colorIndex = primitiveColorIndices[primitiveIndex];
17578
+ const color = CONFIG.color.primitiveColors[colorIndex];
17579
+ if (color) {
17580
+ fillColor = color;
17581
+ }
17582
+ }
17583
+ }
17584
+ return /* @__PURE__ */ React.createElement(
17585
+ "path",
17586
+ {
17587
+ key: `prim-fill-${i}`,
17588
+ d: pathD(scaledPoly),
17589
+ fill: fillColor,
17590
+ opacity: CONFIG.opacity.silhouetteMask,
17591
+ stroke: "none"
17592
+ }
17593
+ );
17594
+ }));
17595
+ } else {
17596
+ const placedPolys = placedSilBySector.get(s.id) ?? [];
17597
+ if (!placedPolys.length) return null;
17598
+ return /* @__PURE__ */ React.createElement("g", { key: `sil-${s.id}`, pointerEvents: "none" }, placedPolys.map((poly, i) => /* @__PURE__ */ React.createElement("path", { key: i, d: pathD(poly), fill: CONFIG.color.silhouetteMask, opacity: CONFIG.opacity.silhouetteMask })));
17599
+ }
17600
+ });
17601
+ const renderPieces = (piecesFilter) => {
17602
+ const piecesToRender = pieces;
17603
+ return piecesToRender.sort((a, b) => {
17604
+ if (draggingId === a.id) return 1;
17605
+ if (draggingId === b.id) return -1;
17606
+ return 0;
17607
+ }).map((p) => {
17608
+ const bp = controller.getBlueprint(p.blueprintId);
17609
+ const bb = boundsOfBlueprint(bp, (k) => controller.getPrimitive(k));
17610
+ const isDragging = draggingId === p.id;
17611
+ const locked = p.sectorId && controller.isSectorCompleted(p.sectorId);
17612
+ const isConnectivityLocked = lockedPieceId === p.id;
17613
+ selectedPieceId === p.id;
17614
+ const isCarriedInvalid = isDragging && dragInvalid;
17615
+ const translateX = p.x - bb.min.x;
17616
+ const translateY = p.y - bb.min.y;
17617
+ const validFillColor = getPieceColor(
17618
+ bp,
17619
+ usePrimitiveColorsBlueprints || false,
17620
+ CONFIG.color.piece.validFill,
17621
+ primitiveColorIndices || [0, 1, 2, 3, 4]
17622
+ );
17623
+ const draggingFillColor = getPieceColor(
17624
+ bp,
17625
+ usePrimitiveColorsBlueprints || false,
17626
+ CONFIG.color.piece.draggingFill,
17627
+ primitiveColorIndices || [0, 1, 2, 3, 4]
17628
+ );
17629
+ return /* @__PURE__ */ React.createElement("g", { key: p.id, ref: setPieceRef(p.id), transform: `translate(${translateX}, ${translateY})`, style: { cursor: locked ? "default" : clickMode ? "pointer" : "grab" }, pointerEvents: clickMode && isDragging ? "none" : "auto" }, ("shape" in bp ? bp.shape : []).map((poly, idx) => {
17630
+ const showBorders = shouldShowBorders();
17631
+ shouldUseSelectiveBorders(p.blueprintId);
17632
+ return /* @__PURE__ */ React.createElement(React.Fragment, { key: idx }, /* @__PURE__ */ React.createElement(
17633
+ "path",
17634
+ {
17635
+ d: pathD(poly),
17636
+ fill: isConnectivityLocked ? CONFIG.color.piece.invalidFill : isCarriedInvalid ? CONFIG.color.piece.invalidFill : isDragging ? draggingFillColor : validFillColor,
17637
+ opacity: isConnectivityLocked ? CONFIG.opacity.piece.invalid : isCarriedInvalid ? CONFIG.opacity.piece.invalid : isDragging ? CONFIG.opacity.piece.dragging : locked ? CONFIG.opacity.piece.locked : CONFIG.opacity.piece.normal,
17638
+ stroke: "none",
17639
+ onPointerDown: (e) => onPiecePointerDown(e, p)
17640
+ }
17641
+ ), showBorders);
17642
+ }), isDragging && isCarriedInvalid && "shape" in bp && bp.shape.length > 0 && (() => {
17643
+ const { cx, cy } = polysAABB$1(bp.shape);
17644
+ const size = CONFIG.size.invalidMarker.sizePx;
17645
+ const strokeWidth = CONFIG.size.invalidMarker.strokePx;
17646
+ const borderWidth = strokeWidth + 2;
17647
+ return /* @__PURE__ */ React.createElement("g", { key: "invalid-marker" }, /* @__PURE__ */ React.createElement(
17648
+ "line",
17649
+ {
17650
+ x1: cx - size,
17651
+ y1: cy - size,
17652
+ x2: cx + size,
17653
+ y2: cy + size,
17654
+ stroke: "white",
17655
+ strokeWidth: borderWidth,
17656
+ strokeLinecap: "round"
17657
+ }
17658
+ ), /* @__PURE__ */ React.createElement(
17659
+ "line",
17660
+ {
17661
+ x1: cx - size,
17662
+ y1: cy - size,
17663
+ x2: cx + size,
17664
+ y2: cy + size,
17665
+ stroke: CONFIG.color.piece.invalidStroke,
17666
+ strokeWidth,
17667
+ strokeLinecap: "round"
17668
+ }
17669
+ ), /* @__PURE__ */ React.createElement(
17670
+ "line",
17671
+ {
17672
+ x1: cx + size,
17673
+ y1: cy - size,
17674
+ x2: cx - size,
17675
+ y2: cy + size,
17676
+ stroke: "white",
17677
+ strokeWidth: borderWidth,
17678
+ strokeLinecap: "round"
17679
+ }
17680
+ ), /* @__PURE__ */ React.createElement(
17681
+ "line",
17682
+ {
17683
+ x1: cx + size,
17684
+ y1: cy - size,
17685
+ x2: cx - size,
17686
+ y2: cy + size,
17687
+ stroke: CONFIG.color.piece.invalidStroke,
17688
+ strokeWidth,
17689
+ strokeLinecap: "round"
17690
+ }
17691
+ ));
17692
+ })());
17693
+ });
17694
+ };
17517
17695
  const centerView = controller.state.blueprintView;
17518
17696
  const bps = centerView === "primitives" ? controller.state.primitives : controller.state.quickstash;
17519
17697
  const QS_SLOTS = controller.state.cfg.maxQuickstashSlots;
@@ -17534,6 +17712,12 @@ var TangramPrepPlugin = (function (jspsych) {
17534
17712
  const bb = boundsOfBlueprint(bp, (k) => controller.getPrimitive(k));
17535
17713
  const cx = bb.min.x + bb.width / 2;
17536
17714
  const cy = bb.min.y + bb.height / 2;
17715
+ const fillColor = getPieceColor(
17716
+ bp,
17717
+ usePrimitiveColorsBlueprints || false,
17718
+ CONFIG.color.blueprint.fill,
17719
+ primitiveColorIndices || [0, 1, 2, 3, 4]
17720
+ );
17537
17721
  return /* @__PURE__ */ React.createElement(
17538
17722
  "g",
17539
17723
  {
@@ -17545,7 +17729,7 @@ var TangramPrepPlugin = (function (jspsych) {
17545
17729
  {
17546
17730
  key: idx,
17547
17731
  d: pathD(poly),
17548
- fill: CONFIG.color.blueprint.fill,
17732
+ fill: fillColor,
17549
17733
  opacity: CONFIG.opacity.blueprint,
17550
17734
  stroke: "none",
17551
17735
  strokeWidth: 0,
@@ -17569,7 +17753,7 @@ var TangramPrepPlugin = (function (jspsych) {
17569
17753
  onPointerDown: (e) => {
17570
17754
  onRootPointerDown(e);
17571
17755
  },
17572
- style: { background: "#f5f5f5", touchAction: "none", userSelect: "none" }
17756
+ style: { background: CONFIG.color.background, touchAction: "none", userSelect: "none" }
17573
17757
  },
17574
17758
  layout.sectors.map((s, i) => {
17575
17759
  const done = !!controller.state.sectors[s.id].completedAt;
@@ -17579,35 +17763,7 @@ var TangramPrepPlugin = (function (jspsych) {
17579
17763
  const work = layout.bands.workspace;
17580
17764
  return /* @__PURE__ */ React.createElement("g", { key: `bands-${s.id}` }, controller.state.cfg.target === "workspace" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("path", { d: wedgePath(layout.cx, layout.cy, sil[0], sil[1], s.start, s.end), fill: baseSil, stroke: CONFIG.color.bands.silhouette.stroke, strokeWidth: CONFIG.size.stroke.bandPx, pointerEvents: "none" }), /* @__PURE__ */ React.createElement("path", { d: wedgePath(layout.cx, layout.cy, work[0], work[1], s.start, s.end), fill: done ? CONFIG.color.completion.fill : baseWork, stroke: done ? CONFIG.color.completion.stroke : CONFIG.color.bands.workspace.stroke, strokeWidth: CONFIG.size.stroke.bandPx, pointerEvents: "none" })) : /* @__PURE__ */ React.createElement("path", { d: wedgePath(layout.cx, layout.cy, sil[0], sil[1], s.start, s.end), fill: done ? CONFIG.color.completion.fill : baseSil, stroke: done ? CONFIG.color.completion.stroke : CONFIG.color.bands.silhouette.stroke, strokeWidth: CONFIG.size.stroke.bandPx, pointerEvents: "none" }));
17581
17765
  }),
17582
- pieces.sort((a, b) => {
17583
- if (draggingId === a.id) return 1;
17584
- if (draggingId === b.id) return -1;
17585
- return 0;
17586
- }).map((p) => {
17587
- const bp = controller.getBlueprint(p.blueprintId);
17588
- const bb = boundsOfBlueprint(bp, (k) => controller.getPrimitive(k));
17589
- const isDragging = draggingId === p.id;
17590
- const locked = p.sectorId && controller.isSectorCompleted(p.sectorId);
17591
- const isConnectivityLocked = lockedPieceId === p.id;
17592
- selectedPieceId === p.id;
17593
- const isCarriedInvalid = isDragging && dragInvalid;
17594
- const translateX = p.x - bb.min.x;
17595
- const translateY = p.y - bb.min.y;
17596
- return /* @__PURE__ */ React.createElement("g", { key: p.id, ref: setPieceRef(p.id), transform: `translate(${translateX}, ${translateY})`, style: { cursor: locked ? "default" : clickMode ? "pointer" : "grab" }, pointerEvents: clickMode && isDragging ? "none" : "auto" }, ("shape" in bp ? bp.shape : []).map((poly, idx) => {
17597
- const showBorders = shouldShowBorders();
17598
- shouldUseSelectiveBorders(p.blueprintId);
17599
- return /* @__PURE__ */ React.createElement(React.Fragment, { key: idx }, /* @__PURE__ */ React.createElement(
17600
- "path",
17601
- {
17602
- d: pathD(poly),
17603
- fill: isConnectivityLocked ? CONFIG.color.piece.invalidFill : isCarriedInvalid ? CONFIG.color.piece.invalidFill : isDragging ? CONFIG.color.piece.draggingFill : CONFIG.color.piece.validFill,
17604
- opacity: isConnectivityLocked ? CONFIG.opacity.piece.invalid : isCarriedInvalid ? CONFIG.opacity.piece.invalid : isDragging ? CONFIG.opacity.piece.dragging : locked ? CONFIG.opacity.piece.locked : CONFIG.opacity.piece.normal,
17605
- stroke: "none",
17606
- onPointerDown: (e) => onPiecePointerDown(e, p)
17607
- }
17608
- ), showBorders);
17609
- }));
17610
- }),
17766
+ /* @__PURE__ */ React.createElement(React.Fragment, null, renderSilhouettes(), renderPieces()) ,
17611
17767
  layout.sectors.map((s) => {
17612
17768
  const sectorCfg = controller.state.cfg.sectors.find((ss) => ss.id === s.id);
17613
17769
  if (showTangramDecomposition && sectorCfg?.silhouette.primitiveDecomposition) {
@@ -17615,23 +17771,32 @@ var TangramPrepPlugin = (function (jspsych) {
17615
17771
  const rect = rectForBand(layout, s, "silhouette", 1);
17616
17772
  const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
17617
17773
  const placedPolys = placeSilhouetteGridAlignedAsPolys(rawPolys, scaleS, { cx: rect.cx, cy: rect.cy });
17618
- return /* @__PURE__ */ React.createElement("g", { key: `sil-decomposed-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => /* @__PURE__ */ React.createElement(React.Fragment, { key: `prim-${i}` }, /* @__PURE__ */ React.createElement(
17619
- "path",
17620
- {
17621
- d: pathD(scaledPoly),
17622
- fill: CONFIG.color.silhouetteMask,
17623
- opacity: CONFIG.opacity.silhouetteMask,
17624
- stroke: "none"
17625
- }
17626
- ), /* @__PURE__ */ React.createElement(
17627
- "path",
17628
- {
17629
- d: pathD(scaledPoly),
17630
- fill: "none",
17631
- stroke: CONFIG.color.tangramDecomposition.stroke,
17632
- strokeWidth: CONFIG.size.stroke.tangramDecompositionPx
17774
+ return /* @__PURE__ */ React.createElement("g", { key: `sil-decomposed-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => {
17775
+ const primInfo = primitiveDecomposition[i];
17776
+ if (usePrimitiveColorsTargets && primInfo?.kind && primitiveColorIndices) {
17777
+ const kindToIndex = {
17778
+ "square": 0,
17779
+ "smalltriangle": 1,
17780
+ "parallelogram": 2,
17781
+ "medtriangle": 3,
17782
+ "largetriangle": 4
17783
+ };
17784
+ const primitiveIndex = kindToIndex[primInfo.kind];
17785
+ if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
17786
+ primitiveColorIndices[primitiveIndex];
17787
+ }
17633
17788
  }
17634
- ))));
17789
+ return /* @__PURE__ */ React.createElement(
17790
+ "path",
17791
+ {
17792
+ key: `prim-fill-${i}`,
17793
+ d: pathD(scaledPoly),
17794
+ fill: "none",
17795
+ opacity: 0,
17796
+ stroke: "none"
17797
+ }
17798
+ );
17799
+ }));
17635
17800
  } else {
17636
17801
  const placedPolys = placedSilBySector.get(s.id) ?? [];
17637
17802
  if (!placedPolys.length) return null;
@@ -17722,7 +17887,25 @@ var TangramPrepPlugin = (function (jspsych) {
17722
17887
  const by = layout.cy + blueprintRingR * Math.sin(theta);
17723
17888
  return renderBlueprintGlyph(bp, bx, by);
17724
17889
  }),
17725
- controller.state.endedAt && /* @__PURE__ */ React.createElement("g", { pointerEvents: "none" }, /* @__PURE__ */ React.createElement("circle", { cx: layout.cx, cy: layout.cy, r: layout.outerR - 3, fill: "none", stroke: CONFIG.color.piece.allGreenStroke, strokeWidth: CONFIG.size.stroke.allGreenStrokePx, opacity: 0 }, /* @__PURE__ */ React.createElement("animate", { attributeName: "opacity", from: "0", to: "1", dur: "160ms", fill: "freeze" }), /* @__PURE__ */ React.createElement("animate", { attributeName: "opacity", from: "1", to: "0", begin: "560ms", dur: "520ms", fill: "freeze" })))
17890
+ controller.state.endedAt && /* @__PURE__ */ React.createElement("g", { pointerEvents: "none" }, /* @__PURE__ */ React.createElement("circle", { cx: layout.cx, cy: layout.cy, r: layout.outerR - 3, fill: "none", stroke: CONFIG.color.piece.allGreenStroke, strokeWidth: CONFIG.size.stroke.allGreenStrokePx, opacity: 0 }, /* @__PURE__ */ React.createElement("animate", { attributeName: "opacity", from: "0", to: "1", dur: "160ms", fill: "freeze" }), /* @__PURE__ */ React.createElement("animate", { attributeName: "opacity", from: "1", to: "0", begin: "560ms", dur: "520ms", fill: "freeze" }))),
17891
+ showTangramDecomposition && layout.sectors.map((s) => {
17892
+ const sectorCfg = controller.state.cfg.sectors.find((ss) => ss.id === s.id);
17893
+ if (!sectorCfg?.silhouette.primitiveDecomposition) return null;
17894
+ const primitiveDecomposition = sectorCfg.silhouette.primitiveDecomposition;
17895
+ const rect = rectForBand(layout, s, "silhouette", 1);
17896
+ const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
17897
+ const placedPolys = placeSilhouetteGridAlignedAsPolys(rawPolys, scaleS, { cx: rect.cx, cy: rect.cy });
17898
+ return /* @__PURE__ */ React.createElement("g", { key: `sil-borders-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => /* @__PURE__ */ React.createElement(
17899
+ "path",
17900
+ {
17901
+ key: `prim-border-${i}`,
17902
+ d: pathD(scaledPoly),
17903
+ fill: "none",
17904
+ stroke: CONFIG.color.tangramDecomposition.stroke,
17905
+ strokeWidth: CONFIG.size.stroke.tangramDecompositionPx
17906
+ }
17907
+ )));
17908
+ })
17726
17909
  );
17727
17910
  }
17728
17911
 
@@ -19663,7 +19846,10 @@ var TangramPrepPlugin = (function (jspsych) {
19663
19846
  onPieceRemove,
19664
19847
  onInteraction,
19665
19848
  onTrialEnd,
19666
- onControllerReady
19849
+ onControllerReady,
19850
+ usePrimitiveColorsBlueprints,
19851
+ usePrimitiveColorsTargets,
19852
+ primitiveColorIndices
19667
19853
  } = props;
19668
19854
  const [timeRemaining, setTimeRemaining] = React.useState(timeLimitMs > 0 ? Math.floor(timeLimitMs / 1e3) : 0);
19669
19855
  const controller = React.useMemo(() => {
@@ -19987,7 +20173,7 @@ var TangramPrepPlugin = (function (jspsych) {
19987
20173
  justifyContent: "center",
19988
20174
  alignItems: "center",
19989
20175
  padding: "20px",
19990
- background: "#f5f5f5",
20176
+ background: CONFIG.color.background,
19991
20177
  flex: "0 0 auto"
19992
20178
  };
19993
20179
  const headerContentStyle = {
@@ -20003,14 +20189,20 @@ var TangramPrepPlugin = (function (jspsych) {
20003
20189
  lineHeight: 1.5,
20004
20190
  textAlign: "center"
20005
20191
  };
20192
+ const scaleX = svgDimensions.width / viewBox.w;
20193
+ const rightEdgeOfSemicircleLogical = viewBox.w / 2 + layout.outerR;
20194
+ const distanceFromRightEdgeLogical = viewBox.w - rightEdgeOfSemicircleLogical;
20195
+ const distanceFromRightEdgePx = distanceFromRightEdgeLogical * scaleX;
20196
+ const charWidth = 24 * 0.6;
20197
+ const offsetLeft = charWidth * 5;
20006
20198
  const timerStyle = {
20007
20199
  position: "absolute",
20008
- right: 0,
20200
+ right: `${distanceFromRightEdgePx + offsetLeft}px`,
20009
20201
  fontSize: "24px",
20010
20202
  fontWeight: "bold",
20011
20203
  fontFamily: "monospace",
20012
20204
  color: "#333",
20013
- minWidth: "80px",
20205
+ whiteSpace: "nowrap",
20014
20206
  textAlign: "right"
20015
20207
  };
20016
20208
  const gameboardWrapperStyle = {
@@ -20055,6 +20247,9 @@ var TangramPrepPlugin = (function (jspsych) {
20055
20247
  onCenterBadgePointerDown,
20056
20248
  showTangramDecomposition: showTangramDecomposition ?? false,
20057
20249
  scaleS,
20250
+ usePrimitiveColorsBlueprints: usePrimitiveColorsBlueprints ?? false,
20251
+ usePrimitiveColorsTargets: usePrimitiveColorsTargets ?? false,
20252
+ primitiveColorIndices: primitiveColorIndices ?? [0, 1, 2, 3, 4],
20058
20253
  ...eventCallbacks
20059
20254
  }
20060
20255
  ))));
@@ -20221,7 +20416,9 @@ var TangramPrepPlugin = (function (jspsych) {
20221
20416
  quickstashMacros,
20222
20417
  primitiveOrder,
20223
20418
  onInteraction,
20224
- onTrialEnd
20419
+ onTrialEnd,
20420
+ usePrimitiveColorsBlueprints,
20421
+ primitiveColorIndices
20225
20422
  } = params;
20226
20423
  const { onInteraction: _, onTrialEnd: __, ...trialParams } = params;
20227
20424
  const PRIMITIVE_BLUEPRINTS_ORDERED = [...PRIMITIVE_BLUEPRINTS].sort((a, b) => primitiveOrder.indexOf(a.kind) - primitiveOrder.indexOf(b.kind));
@@ -20313,7 +20510,9 @@ var TangramPrepPlugin = (function (jspsych) {
20313
20510
  ...params.instructions && { instructions: params.instructions },
20314
20511
  onControllerReady: handleControllerReady,
20315
20512
  ...onInteraction && { onInteraction },
20316
- ...onTrialEnd && { onTrialEnd }
20513
+ ...onTrialEnd && { onTrialEnd },
20514
+ ...usePrimitiveColorsBlueprints !== void 0 && { usePrimitiveColorsBlueprints },
20515
+ ...primitiveColorIndices !== void 0 && { primitiveColorIndices }
20317
20516
  }));
20318
20517
  return { root, display_element, jsPsych };
20319
20518
  }
@@ -20379,6 +20578,18 @@ var TangramPrepPlugin = (function (jspsych) {
20379
20578
  onTrialEnd: {
20380
20579
  type: jspsych.ParameterType.FUNCTION,
20381
20580
  default: void 0
20581
+ },
20582
+ /** Whether to use distinct colors for each primitive shape type in blueprints */
20583
+ use_primitive_colors_blueprints: {
20584
+ type: jspsych.ParameterType.BOOL,
20585
+ default: false,
20586
+ description: "Whether each primitive shape type should have its own distinct color in the blueprint dock area"
20587
+ },
20588
+ /** Indices mapping primitives to colors from the color palette */
20589
+ primitive_color_indices: {
20590
+ type: jspsych.ParameterType.OBJECT,
20591
+ default: [0, 1, 2, 3, 4],
20592
+ description: "Array of 5 integers indexing into primitiveColors array, mapping [square, smalltriangle, parallelogram, medtriangle, largetriangle] to colors"
20382
20593
  }
20383
20594
  },
20384
20595
  data: {
@@ -20423,7 +20634,9 @@ var TangramPrepPlugin = (function (jspsych) {
20423
20634
  primitiveOrder: trial.primitive_order,
20424
20635
  instructions: trial.instructions,
20425
20636
  onInteraction: trial.onInteraction,
20426
- onTrialEnd: wrappedOnTrialEnd
20637
+ onTrialEnd: wrappedOnTrialEnd,
20638
+ usePrimitiveColorsBlueprints: trial.use_primitive_colors_blueprints,
20639
+ primitiveColorIndices: trial.primitive_color_indices
20427
20640
  };
20428
20641
  const { root, display_element: element, jsPsych } = startPrepTrial(display_element, params, this.jsPsych);
20429
20642
  element.__reactContext = { root, jsPsych };