@wire-dsl/engine 0.5.0 → 0.7.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/dist/index.cjs CHANGED
@@ -2220,8 +2220,8 @@ var IRStyleSchema = import_zod.z.object({
2220
2220
  var IRNodeStyleSchema = import_zod.z.object({
2221
2221
  padding: import_zod.z.string().optional(),
2222
2222
  gap: import_zod.z.string().optional(),
2223
- align: import_zod.z.enum(["left", "center", "right", "justify"]).optional(),
2224
- justify: import_zod.z.enum(["start", "center", "end"]).optional(),
2223
+ justify: import_zod.z.enum(["start", "center", "end", "stretch", "spaceBetween", "spaceAround"]).optional(),
2224
+ align: import_zod.z.enum(["start", "center", "end"]).optional(),
2225
2225
  background: import_zod.z.string().optional()
2226
2226
  });
2227
2227
  var IRMetaSchema = import_zod.z.object({
@@ -2507,6 +2507,9 @@ ${messages}`);
2507
2507
  if (layoutParams.gap !== void 0) {
2508
2508
  style.gap = String(layoutParams.gap);
2509
2509
  }
2510
+ if (layoutParams.justify !== void 0) {
2511
+ style.justify = layoutParams.justify;
2512
+ }
2510
2513
  if (layoutParams.align !== void 0) {
2511
2514
  style.align = layoutParams.align;
2512
2515
  }
@@ -2586,13 +2589,13 @@ ${messages}`);
2586
2589
  "Heading",
2587
2590
  "Text",
2588
2591
  "Label",
2592
+ "Paragraph",
2589
2593
  "Image",
2590
2594
  "Card",
2591
2595
  "Stat",
2592
2596
  "Topbar",
2593
2597
  "Table",
2594
2598
  "Chart",
2595
- "ChartPlaceholder",
2596
2599
  "Textarea",
2597
2600
  "Select",
2598
2601
  "Checkbox",
@@ -2927,19 +2930,19 @@ var ICON_SIZES_BY_DENSITY = {
2927
2930
  comfortable: { xs: 14, sm: 16, md: 20, lg: 28, xl: 36 }
2928
2931
  };
2929
2932
  var ICON_BUTTON_SIZES_BY_DENSITY = {
2930
- compact: { sm: 20, md: 24, lg: 32 },
2931
- normal: { sm: 24, md: 32, lg: 40 },
2932
- comfortable: { sm: 28, md: 40, lg: 48 }
2933
+ compact: { xs: 16, sm: 20, md: 24, lg: 32, xl: 40 },
2934
+ normal: { xs: 20, sm: 24, md: 32, lg: 40, xl: 48 },
2935
+ comfortable: { xs: 24, sm: 28, md: 40, lg: 48, xl: 56 }
2933
2936
  };
2934
2937
  var CONTROL_HEIGHTS_BY_DENSITY = {
2935
- compact: { sm: 28, md: 32, lg: 36 },
2936
- normal: { sm: 36, md: 40, lg: 48 },
2937
- comfortable: { sm: 40, md: 48, lg: 56 }
2938
+ compact: { xs: 24, sm: 28, md: 32, lg: 36, xl: 44 },
2939
+ normal: { xs: 28, sm: 36, md: 40, lg: 48, xl: 56 },
2940
+ comfortable: { xs: 32, sm: 40, md: 48, lg: 56, xl: 64 }
2938
2941
  };
2939
2942
  var ACTION_CONTROL_HEIGHTS_BY_DENSITY = {
2940
- compact: { sm: 20, md: 24, lg: 32 },
2941
- normal: { sm: 24, md: 32, lg: 40 },
2942
- comfortable: { sm: 28, md: 40, lg: 48 }
2943
+ compact: { xs: 24, sm: 28, md: 32, lg: 36, xl: 44 },
2944
+ normal: { xs: 28, sm: 36, md: 40, lg: 48, xl: 56 },
2945
+ comfortable: { xs: 32, sm: 40, md: 48, lg: 56, xl: 64 }
2943
2946
  };
2944
2947
  var CONTROL_PADDING_BY_DENSITY = {
2945
2948
  compact: { none: 0, xs: 4, sm: 8, md: 10, lg: 14, xl: 18 },
@@ -3153,8 +3156,9 @@ var LayoutEngine = class {
3153
3156
  }
3154
3157
  });
3155
3158
  } else {
3156
- const align = node.style.align || "justify";
3157
- if (align === "justify") {
3159
+ const justify = node.style.justify || "stretch";
3160
+ const crossAlign = node.style.align || "start";
3161
+ if (justify === "stretch") {
3158
3162
  let currentX = x;
3159
3163
  const childWidth = this.calculateChildWidth(children.length, width, gap);
3160
3164
  let stackHeight = 0;
@@ -3176,43 +3180,44 @@ var LayoutEngine = class {
3176
3180
  });
3177
3181
  } else {
3178
3182
  const childWidths = [];
3183
+ const childHeights = [];
3179
3184
  const explicitHeightFlags = [];
3180
- const blockButtonIndices = /* @__PURE__ */ new Set();
3185
+ const flexIndices = /* @__PURE__ */ new Set();
3181
3186
  let stackHeight = 0;
3182
3187
  children.forEach((childRef, index) => {
3183
3188
  const childNode = this.nodes[childRef.ref];
3184
- let childWidth = this.getIntrinsicComponentWidth(childNode);
3185
- let childHeight = this.getComponentHeight();
3186
3189
  const hasExplicitHeight = childNode?.kind === "component" && !!childNode.props.height;
3187
3190
  const hasExplicitWidth = childNode?.kind === "component" && !!childNode.props.width;
3188
3191
  const isBlockButton = childNode?.kind === "component" && childNode.componentType === "Button" && !hasExplicitWidth && this.parseBooleanProp(childNode.props.block, false);
3189
- if (isBlockButton) {
3192
+ const isFlexContainer = !hasExplicitWidth && childNode?.kind === "container" && !this.containerHasIntrinsicWidth(childNode);
3193
+ let childWidth;
3194
+ if (isBlockButton || isFlexContainer) {
3190
3195
  childWidth = 0;
3191
- blockButtonIndices.add(index);
3196
+ flexIndices.add(index);
3192
3197
  } else if (hasExplicitWidth) {
3193
3198
  childWidth = Number(childNode.props.width);
3194
- }
3195
- if (hasExplicitHeight) {
3196
- childHeight = Number(childNode.props.height);
3199
+ } else {
3200
+ childWidth = this.getIntrinsicWidth(childNode, width);
3197
3201
  }
3198
3202
  childWidths.push(childWidth);
3203
+ childHeights.push(this.getComponentHeight());
3199
3204
  explicitHeightFlags.push(hasExplicitHeight);
3200
3205
  });
3201
3206
  const totalGapWidth = gap * Math.max(0, children.length - 1);
3202
- if (blockButtonIndices.size > 0) {
3207
+ if (flexIndices.size > 0) {
3203
3208
  const fixedWidth = childWidths.reduce((sum, w, idx) => {
3204
- return blockButtonIndices.has(idx) ? sum : sum + w;
3209
+ return flexIndices.has(idx) ? sum : sum + w;
3205
3210
  }, 0);
3206
3211
  const remainingWidth = width - totalGapWidth - fixedWidth;
3207
- const widthPerBlock = Math.max(1, remainingWidth / blockButtonIndices.size);
3208
- blockButtonIndices.forEach((index) => {
3209
- childWidths[index] = widthPerBlock;
3212
+ const widthPerFlex = Math.max(1, remainingWidth / flexIndices.size);
3213
+ flexIndices.forEach((idx) => {
3214
+ childWidths[idx] = widthPerFlex;
3210
3215
  });
3211
3216
  }
3212
3217
  children.forEach((childRef, index) => {
3213
3218
  const childNode = this.nodes[childRef.ref];
3214
- let childHeight = this.getComponentHeight();
3215
3219
  const childWidth = childWidths[index];
3220
+ let childHeight = this.getComponentHeight();
3216
3221
  if (explicitHeightFlags[index] && childNode?.kind === "component") {
3217
3222
  childHeight = Number(childNode.props.height);
3218
3223
  } else if (childNode?.kind === "container") {
@@ -3220,21 +3225,37 @@ var LayoutEngine = class {
3220
3225
  } else if (childNode?.kind === "component") {
3221
3226
  childHeight = this.getIntrinsicComponentHeight(childNode, childWidth);
3222
3227
  }
3228
+ childHeights[index] = childHeight;
3223
3229
  stackHeight = Math.max(stackHeight, childHeight);
3224
3230
  });
3225
3231
  const totalChildWidth = childWidths.reduce((sum, w) => sum + w, 0);
3226
3232
  const totalContentWidth = totalChildWidth + totalGapWidth;
3227
3233
  let startX = x;
3228
- if (align === "center") {
3234
+ let dynamicGap = gap;
3235
+ if (justify === "center") {
3229
3236
  startX = x + (width - totalContentWidth) / 2;
3230
- } else if (align === "right") {
3237
+ } else if (justify === "end") {
3231
3238
  startX = x + width - totalContentWidth;
3239
+ } else if (justify === "spaceBetween") {
3240
+ startX = x;
3241
+ dynamicGap = children.length > 1 ? (width - totalChildWidth) / (children.length - 1) : 0;
3242
+ } else if (justify === "spaceAround") {
3243
+ const spacing = children.length > 0 ? (width - totalChildWidth) / children.length : 0;
3244
+ startX = x + spacing / 2;
3245
+ dynamicGap = spacing;
3232
3246
  }
3233
3247
  let currentX = startX;
3234
3248
  children.forEach((childRef, index) => {
3235
3249
  const childWidth = childWidths[index];
3236
- this.calculateNode(childRef.ref, currentX, y, childWidth, stackHeight, "stack");
3237
- currentX += childWidth + gap;
3250
+ const childHeight = childHeights[index];
3251
+ let childY = y;
3252
+ if (crossAlign === "center") {
3253
+ childY = y + Math.round((stackHeight - childHeight) / 2);
3254
+ } else if (crossAlign === "end") {
3255
+ childY = y + stackHeight - childHeight;
3256
+ }
3257
+ this.calculateNode(childRef.ref, currentX, childY, childWidth, childHeight, "stack");
3258
+ currentX += childWidth + dynamicGap;
3238
3259
  });
3239
3260
  }
3240
3261
  }
@@ -3594,7 +3615,7 @@ var LayoutEngine = class {
3594
3615
  wrapTextToLines(text, maxWidth, fontSize) {
3595
3616
  const normalized = text.replace(/\r\n/g, "\n");
3596
3617
  const paragraphs = normalized.split("\n");
3597
- const charWidth = fontSize * 0.6;
3618
+ const charWidth = fontSize * 0.5;
3598
3619
  const safeWidth = Math.max(maxWidth, charWidth);
3599
3620
  const maxCharsPerLine = Math.max(1, Math.floor(safeWidth / charWidth));
3600
3621
  const lines = [];
@@ -3632,12 +3653,14 @@ var LayoutEngine = class {
3632
3653
  getIntrinsicComponentHeight(node, availableWidth) {
3633
3654
  if (node.kind !== "component") return this.getComponentHeight();
3634
3655
  const controlSize = String(node.props.size || "md");
3656
+ const actionControlSize = String(node.props.size || "sm");
3635
3657
  const density = this.style.density || "normal";
3636
3658
  const inputControlHeight = resolveControlHeight(controlSize, density);
3637
- const actionControlHeight = resolveActionControlHeight(controlSize, density);
3659
+ const actionControlHeight = resolveActionControlHeight(actionControlSize, density);
3638
3660
  const controlLabelOffset = node.componentType === "Input" || node.componentType === "Textarea" || node.componentType === "Select" ? this.getControlLabelOffset(String(node.props.label || "")) : (node.componentType === "Button" || node.componentType === "IconButton") && this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
3639
3661
  if (node.componentType === "Image") {
3640
3662
  const placeholder = String(node.props.placeholder || "landscape");
3663
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
3641
3664
  const aspectRatios = {
3642
3665
  landscape: 16 / 9,
3643
3666
  portrait: 2 / 3,
@@ -3645,7 +3668,7 @@ var LayoutEngine = class {
3645
3668
  icon: 1,
3646
3669
  avatar: 1
3647
3670
  };
3648
- const ratio = aspectRatios[placeholder] || 16 / 9;
3671
+ const ratio = isCircle ? 1 : aspectRatios[placeholder] || 16 / 9;
3649
3672
  const explicitHeight = Number(node.props.height);
3650
3673
  if (!isNaN(explicitHeight) && explicitHeight > 0) {
3651
3674
  return explicitHeight;
@@ -3697,13 +3720,19 @@ var LayoutEngine = class {
3697
3720
  }
3698
3721
  return Math.max(1, Math.ceil(wrappedHeight + verticalPadding * 2));
3699
3722
  }
3700
- if (node.componentType === "Text") {
3723
+ if (node.componentType === "Text" || node.componentType === "Paragraph") {
3701
3724
  const content = String(node.props.text || "");
3702
- const { fontSize, lineHeight } = this.getTextMetricsForDensity();
3725
+ const { fontSize: defaultFontSize, lineHeight } = this.getTextMetricsForDensity();
3726
+ const sizeProp = String(node.props.size || "");
3727
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
3728
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
3703
3729
  const lineHeightPx = Math.ceil(fontSize * lineHeight);
3704
3730
  const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
3705
3731
  const lines = this.wrapTextToLines(content, maxWidth, fontSize);
3706
3732
  const wrappedHeight = Math.max(1, lines.length) * lineHeightPx;
3733
+ if (sizeProp) {
3734
+ return wrappedHeight;
3735
+ }
3707
3736
  return Math.max(this.getComponentHeight(), wrappedHeight);
3708
3737
  }
3709
3738
  if (node.componentType === "Alert") {
@@ -3732,7 +3761,7 @@ var LayoutEngine = class {
3732
3761
  if (node.componentType === "Modal") return 300;
3733
3762
  if (node.componentType === "Card") return 120;
3734
3763
  if (node.componentType === "Stat") return 120;
3735
- if (node.componentType === "Chart" || node.componentType === "ChartPlaceholder") return 250;
3764
+ if (node.componentType === "Chart") return 250;
3736
3765
  if (node.componentType === "List") {
3737
3766
  const itemsFromProps = String(node.props.items || "").split(",").map((item) => item.trim()).filter(Boolean);
3738
3767
  const parsedItemsMock = Number(node.props.itemsMock ?? 4);
@@ -3743,7 +3772,14 @@ var LayoutEngine = class {
3743
3772
  const contentHeight = titleHeight + itemCount * itemHeight;
3744
3773
  return Math.max(this.getComponentHeight(), contentHeight);
3745
3774
  }
3746
- if (node.componentType === "Topbar") return 56;
3775
+ if (node.componentType === "Topbar") {
3776
+ const TOPBAR_HEIGHTS = { sm: 44, md: 56, lg: 72 };
3777
+ return TOPBAR_HEIGHTS[String(node.props.size || "md")] ?? 56;
3778
+ }
3779
+ if (node.componentType === "Tabs") {
3780
+ const TABS_HEIGHTS = { sm: 32, md: 44, lg: 52 };
3781
+ return TABS_HEIGHTS[String(node.props.size || "md")] ?? 44;
3782
+ }
3747
3783
  if (node.componentType === "Divider") return 1;
3748
3784
  if (node.componentType === "Separate") return this.getSeparateSize(node);
3749
3785
  if (node.componentType === "Input" || node.componentType === "Select") {
@@ -3752,11 +3788,57 @@ var LayoutEngine = class {
3752
3788
  if (node.componentType === "Button" || node.componentType === "IconButton" || node.componentType === "Link") {
3753
3789
  return actionControlHeight + controlLabelOffset;
3754
3790
  }
3791
+ if (node.componentType === "Badge" || node.componentType === "Chip") {
3792
+ const BADGE_HEIGHTS = { xs: 28, sm: 36, md: 40, lg: 48, xl: 56 };
3793
+ const badgeSize = String(node.props.size || "md");
3794
+ return BADGE_HEIGHTS[badgeSize] ?? 40;
3795
+ }
3755
3796
  return this.getComponentHeight();
3756
3797
  }
3757
3798
  getControlLabelOffset(label) {
3758
3799
  return label.trim().length > 0 ? 18 : 0;
3759
3800
  }
3801
+ /**
3802
+ * Returns true when a container's width can be calculated from its children
3803
+ * (i.e. it is a horizontal non-stretch stack). False means the container
3804
+ * behaves like `flex-grow:1` and should absorb remaining space.
3805
+ */
3806
+ containerHasIntrinsicWidth(node) {
3807
+ if (node.kind !== "container") return false;
3808
+ return node.containerType === "stack" && String(node.params.direction || "vertical") === "horizontal" && (node.style.justify || "stretch") !== "stretch";
3809
+ }
3810
+ /**
3811
+ * Returns the natural (intrinsic) width of any node — component or container.
3812
+ * For horizontal non-stretch containers the width is the sum of their children's
3813
+ * intrinsic widths plus gaps, capped at `availableWidth`. All other containers
3814
+ * are assumed to take the full available width (they stretch or grow).
3815
+ */
3816
+ getIntrinsicWidth(node, availableWidth) {
3817
+ if (!node) return 120;
3818
+ if (node.kind === "component") {
3819
+ return this.getIntrinsicComponentWidth(node);
3820
+ }
3821
+ if (node.kind === "container") {
3822
+ if (this.containerHasIntrinsicWidth(node)) {
3823
+ const gap = this.resolveSpacing(node.style.gap);
3824
+ const padding = this.resolveSpacing(node.style.padding);
3825
+ const innerAvailable = Math.max(0, availableWidth - padding * 2);
3826
+ const children = node.children ?? [];
3827
+ let total = padding * 2;
3828
+ children.forEach((childRef, idx) => {
3829
+ const child = this.nodes[childRef.ref];
3830
+ total += this.getIntrinsicWidth(child, innerAvailable);
3831
+ if (idx < children.length - 1) total += gap;
3832
+ });
3833
+ return Math.min(total, availableWidth);
3834
+ }
3835
+ return availableWidth;
3836
+ }
3837
+ if (node.kind === "instance") {
3838
+ return availableWidth;
3839
+ }
3840
+ return 120;
3841
+ }
3760
3842
  getIntrinsicComponentWidth(node) {
3761
3843
  if (!node || node.kind !== "component") {
3762
3844
  return 120;
@@ -3766,7 +3848,7 @@ var LayoutEngine = class {
3766
3848
  return resolveIconSize(size, this.style.density || "normal");
3767
3849
  }
3768
3850
  if (node.componentType === "IconButton") {
3769
- const size = String(node.props.size || "md");
3851
+ const size = String(node.props.size || "sm");
3770
3852
  const density = this.style.density || "normal";
3771
3853
  const baseSize = resolveIconButtonSize(size, density);
3772
3854
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -3824,7 +3906,13 @@ var LayoutEngine = class {
3824
3906
  }
3825
3907
  if (node.componentType === "Badge" || node.componentType === "Chip") {
3826
3908
  const text = String(node.props.text || "");
3827
- return Math.max(50, text.length * 7 + 16);
3909
+ const size = String(node.props.size || "md");
3910
+ const BADGE_CHAR_W = { xs: 6, sm: 6.5, md: 7, lg: 7.5, xl: 8.5 };
3911
+ const BADGE_PAD_X = { xs: 5, sm: 6, md: 8, lg: 10, xl: 14 };
3912
+ const customPadding = node.props.padding !== void 0 ? Number(node.props.padding) : void 0;
3913
+ const padX = customPadding !== void 0 && !isNaN(customPadding) ? customPadding : BADGE_PAD_X[size] ?? 8;
3914
+ const charW = BADGE_CHAR_W[size] ?? 7;
3915
+ return Math.max(padX * 4, text.length * charW + padX * 2);
3828
3916
  }
3829
3917
  return 120;
3830
3918
  }
@@ -4108,34 +4196,358 @@ MockDataGenerator.HEADER_TO_MOCK = {
4108
4196
  var ColorResolver = class {
4109
4197
  constructor() {
4110
4198
  this.customColors = {};
4111
- // Named colors palette
4199
+ // Named colors palette — Full Material Design + utility aliases
4112
4200
  this.namedColors = {
4113
- // Grayscale
4201
+ // ── Utility ──────────────────────────────────────────────────────────────
4114
4202
  white: "#FFFFFF",
4115
4203
  black: "#000000",
4116
- gray: "#6B7280",
4117
- slate: "#64748B",
4204
+ gray: "#9E9E9E",
4205
+ // alias for grey 500
4206
+ grey: "#9E9E9E",
4207
+ slate: "#607D8B",
4208
+ // alias for blue_grey 500
4118
4209
  zinc: "#71717A",
4119
- // Colors
4120
- red: "#EF4444",
4121
- orange: "#F97316",
4122
- yellow: "#EAB308",
4123
- lime: "#84CC16",
4124
- green: "#22C55E",
4210
+ // Extra well-known non-Material names (kept for backward compat)
4125
4211
  emerald: "#10B981",
4126
- teal: "#14B8A6",
4127
- cyan: "#06B6D4",
4128
- blue: "#3B82F6",
4129
- indigo: "#6366F1",
4130
4212
  violet: "#8B5CF6",
4131
- purple: "#A855F7",
4132
4213
  fuchsia: "#D946EF",
4133
- pink: "#EC4899",
4134
4214
  rose: "#F43F5E",
4135
- // Light variants
4136
- "red-light": "#FEE2E2",
4137
- "blue-light": "#DBEAFE",
4138
- "green-light": "#DCFCE7"
4215
+ // Light utility variants
4216
+ "red-light": "#FFCDD2",
4217
+ "blue-light": "#BBDEFB",
4218
+ "green-light": "#C8E6C9",
4219
+ // ── Red ──────────────────────────────────────────────────────────────────
4220
+ red: "#F44336",
4221
+ // 500
4222
+ red_50: "#FFEBEE",
4223
+ red_100: "#FFCDD2",
4224
+ red_200: "#EF9A9A",
4225
+ red_300: "#E57373",
4226
+ red_400: "#EF5350",
4227
+ red_500: "#F44336",
4228
+ red_600: "#E53935",
4229
+ red_700: "#D32F2F",
4230
+ red_800: "#C62828",
4231
+ red_900: "#B71C1C",
4232
+ red_A100: "#FF8A80",
4233
+ red_A200: "#FF5252",
4234
+ red_A400: "#FF1744",
4235
+ red_A700: "#D50000",
4236
+ // ── Pink ─────────────────────────────────────────────────────────────────
4237
+ pink: "#E91E63",
4238
+ // 500
4239
+ pink_50: "#FCE4EC",
4240
+ pink_100: "#F8BBD0",
4241
+ pink_200: "#F48FB1",
4242
+ pink_300: "#F06292",
4243
+ pink_400: "#EC407A",
4244
+ pink_500: "#E91E63",
4245
+ pink_600: "#D81B60",
4246
+ pink_700: "#C2185B",
4247
+ pink_800: "#AD1457",
4248
+ pink_900: "#880E4F",
4249
+ pink_A100: "#FF80AB",
4250
+ pink_A200: "#FF4081",
4251
+ pink_A400: "#F50057",
4252
+ pink_A700: "#C51162",
4253
+ // ── Purple ───────────────────────────────────────────────────────────────
4254
+ purple: "#9C27B0",
4255
+ // 500
4256
+ purple_50: "#F3E5F5",
4257
+ purple_100: "#E1BEE7",
4258
+ purple_200: "#CE93D8",
4259
+ purple_300: "#BA68C8",
4260
+ purple_400: "#AB47BC",
4261
+ purple_500: "#9C27B0",
4262
+ purple_600: "#8E24AA",
4263
+ purple_700: "#7B1FA2",
4264
+ purple_800: "#6A1B9A",
4265
+ purple_900: "#4A148C",
4266
+ purple_A100: "#EA80FC",
4267
+ purple_A200: "#E040FB",
4268
+ purple_A400: "#D500F9",
4269
+ purple_A700: "#AA00FF",
4270
+ // ── Deep Purple ──────────────────────────────────────────────────────────
4271
+ deep_purple: "#673AB7",
4272
+ // 500
4273
+ deep_purple_50: "#EDE7F6",
4274
+ deep_purple_100: "#D1C4E9",
4275
+ deep_purple_200: "#B39DDB",
4276
+ deep_purple_300: "#9575CD",
4277
+ deep_purple_400: "#7E57C2",
4278
+ deep_purple_500: "#673AB7",
4279
+ deep_purple_600: "#5E35B1",
4280
+ deep_purple_700: "#512DA8",
4281
+ deep_purple_800: "#4527A0",
4282
+ deep_purple_900: "#311B92",
4283
+ deep_purple_A100: "#B388FF",
4284
+ deep_purple_A200: "#7C4DFF",
4285
+ deep_purple_A400: "#651FFF",
4286
+ deep_purple_A700: "#6200EA",
4287
+ // ── Indigo ───────────────────────────────────────────────────────────────
4288
+ indigo: "#3F51B5",
4289
+ // 500
4290
+ indigo_50: "#E8EAF6",
4291
+ indigo_100: "#C5CAE9",
4292
+ indigo_200: "#9FA8DA",
4293
+ indigo_300: "#7986CB",
4294
+ indigo_400: "#5C6BC0",
4295
+ indigo_500: "#3F51B5",
4296
+ indigo_600: "#3949AB",
4297
+ indigo_700: "#303F9F",
4298
+ indigo_800: "#283593",
4299
+ indigo_900: "#1A237E",
4300
+ indigo_A100: "#8C9EFF",
4301
+ indigo_A200: "#536DFE",
4302
+ indigo_A400: "#3D5AFE",
4303
+ indigo_A700: "#304FFE",
4304
+ // ── Blue ─────────────────────────────────────────────────────────────────
4305
+ blue: "#2196F3",
4306
+ // 500
4307
+ blue_50: "#E3F2FD",
4308
+ blue_100: "#BBDEFB",
4309
+ blue_200: "#90CAF9",
4310
+ blue_300: "#64B5F6",
4311
+ blue_400: "#42A5F5",
4312
+ blue_500: "#2196F3",
4313
+ blue_600: "#1E88E5",
4314
+ blue_700: "#1976D2",
4315
+ blue_800: "#1565C0",
4316
+ blue_900: "#0D47A1",
4317
+ blue_A100: "#82B1FF",
4318
+ blue_A200: "#448AFF",
4319
+ blue_A400: "#2979FF",
4320
+ blue_A700: "#2962FF",
4321
+ // ── Light Blue ───────────────────────────────────────────────────────────
4322
+ light_blue: "#03A9F4",
4323
+ // 500
4324
+ light_blue_50: "#E1F5FE",
4325
+ light_blue_100: "#B3E5FC",
4326
+ light_blue_200: "#81D4FA",
4327
+ light_blue_300: "#4FC3F7",
4328
+ light_blue_400: "#29B6F6",
4329
+ light_blue_500: "#03A9F4",
4330
+ light_blue_600: "#039BE5",
4331
+ light_blue_700: "#0288D1",
4332
+ light_blue_800: "#0277BD",
4333
+ light_blue_900: "#01579B",
4334
+ light_blue_A100: "#80D8FF",
4335
+ light_blue_A200: "#40C4FF",
4336
+ light_blue_A400: "#00B0FF",
4337
+ light_blue_A700: "#0091EA",
4338
+ // ── Cyan ─────────────────────────────────────────────────────────────────
4339
+ cyan: "#00BCD4",
4340
+ // 500
4341
+ cyan_50: "#E0F7FA",
4342
+ cyan_100: "#B2EBF2",
4343
+ cyan_200: "#80DEEA",
4344
+ cyan_300: "#4DD0E1",
4345
+ cyan_400: "#26C6DA",
4346
+ cyan_500: "#00BCD4",
4347
+ cyan_600: "#00ACC1",
4348
+ cyan_700: "#0097A7",
4349
+ cyan_800: "#00838F",
4350
+ cyan_900: "#006064",
4351
+ cyan_A100: "#84FFFF",
4352
+ cyan_A200: "#18FFFF",
4353
+ cyan_A400: "#00E5FF",
4354
+ cyan_A700: "#00B8D4",
4355
+ // ── Teal ─────────────────────────────────────────────────────────────────
4356
+ teal: "#009688",
4357
+ // 500
4358
+ teal_50: "#E0F2F1",
4359
+ teal_100: "#B2DFDB",
4360
+ teal_200: "#80CBC4",
4361
+ teal_300: "#4DB6AC",
4362
+ teal_400: "#26A69A",
4363
+ teal_500: "#009688",
4364
+ teal_600: "#00897B",
4365
+ teal_700: "#00796B",
4366
+ teal_800: "#00695C",
4367
+ teal_900: "#004D40",
4368
+ teal_A100: "#A7FFEB",
4369
+ teal_A200: "#64FFDA",
4370
+ teal_A400: "#1DE9B6",
4371
+ teal_A700: "#00BFA5",
4372
+ // ── Green ────────────────────────────────────────────────────────────────
4373
+ green: "#4CAF50",
4374
+ // 500
4375
+ green_50: "#E8F5E9",
4376
+ green_100: "#C8E6C9",
4377
+ green_200: "#A5D6A7",
4378
+ green_300: "#81C784",
4379
+ green_400: "#66BB6A",
4380
+ green_500: "#4CAF50",
4381
+ green_600: "#43A047",
4382
+ green_700: "#388E3C",
4383
+ green_800: "#2E7D32",
4384
+ green_900: "#1B5E20",
4385
+ green_A100: "#B9F6CA",
4386
+ green_A200: "#69F0AE",
4387
+ green_A400: "#00E676",
4388
+ green_A700: "#00C853",
4389
+ // ── Light Green ──────────────────────────────────────────────────────────
4390
+ light_green: "#8BC34A",
4391
+ // 500
4392
+ light_green_50: "#F1F8E9",
4393
+ light_green_100: "#DCEDC8",
4394
+ light_green_200: "#C5E1A5",
4395
+ light_green_300: "#AED581",
4396
+ light_green_400: "#9CCC65",
4397
+ light_green_500: "#8BC34A",
4398
+ light_green_600: "#7CB342",
4399
+ light_green_700: "#689F38",
4400
+ light_green_800: "#558B2F",
4401
+ light_green_900: "#33691E",
4402
+ light_green_A100: "#CCFF90",
4403
+ light_green_A200: "#B2FF59",
4404
+ light_green_A400: "#76FF03",
4405
+ light_green_A700: "#64DD17",
4406
+ // ── Lime ─────────────────────────────────────────────────────────────────
4407
+ lime: "#CDDC39",
4408
+ // 500
4409
+ lime_50: "#F9FBE7",
4410
+ lime_100: "#F0F4C3",
4411
+ lime_200: "#E6EE9C",
4412
+ lime_300: "#DCE775",
4413
+ lime_400: "#D4E157",
4414
+ lime_500: "#CDDC39",
4415
+ lime_600: "#C0CA33",
4416
+ lime_700: "#AFB42B",
4417
+ lime_800: "#9E9D24",
4418
+ lime_900: "#827717",
4419
+ lime_A100: "#F4FF81",
4420
+ lime_A200: "#EEFF41",
4421
+ lime_A400: "#C6FF00",
4422
+ lime_A700: "#AEEA00",
4423
+ // ── Yellow ───────────────────────────────────────────────────────────────
4424
+ yellow: "#FFEB3B",
4425
+ // 500
4426
+ yellow_50: "#FFFDE7",
4427
+ yellow_100: "#FFF9C4",
4428
+ yellow_200: "#FFF59D",
4429
+ yellow_300: "#FFF176",
4430
+ yellow_400: "#FFEE58",
4431
+ yellow_500: "#FFEB3B",
4432
+ yellow_600: "#FDD835",
4433
+ yellow_700: "#F9A825",
4434
+ yellow_800: "#F57F17",
4435
+ yellow_900: "#F57F17",
4436
+ yellow_A100: "#FFFF8D",
4437
+ yellow_A200: "#FFFF00",
4438
+ yellow_A400: "#FFEA00",
4439
+ yellow_A700: "#FFD600",
4440
+ // ── Amber ────────────────────────────────────────────────────────────────
4441
+ amber: "#FFC107",
4442
+ // 500
4443
+ amber_50: "#FFF8E1",
4444
+ amber_100: "#FFECB3",
4445
+ amber_200: "#FFE082",
4446
+ amber_300: "#FFD54F",
4447
+ amber_400: "#FFCA28",
4448
+ amber_500: "#FFC107",
4449
+ amber_600: "#FFB300",
4450
+ amber_700: "#FFA000",
4451
+ amber_800: "#FF8F00",
4452
+ amber_900: "#FF6F00",
4453
+ amber_A100: "#FFE57F",
4454
+ amber_A200: "#FFD740",
4455
+ amber_A400: "#FFC400",
4456
+ amber_A700: "#FFAB00",
4457
+ // ── Orange ───────────────────────────────────────────────────────────────
4458
+ orange: "#FF9800",
4459
+ // 500
4460
+ orange_50: "#FFF3E0",
4461
+ orange_100: "#FFE0B2",
4462
+ orange_200: "#FFCC80",
4463
+ orange_300: "#FFB74D",
4464
+ orange_400: "#FFA726",
4465
+ orange_500: "#FF9800",
4466
+ orange_600: "#FB8C00",
4467
+ orange_700: "#F57C00",
4468
+ orange_800: "#EF6C00",
4469
+ orange_900: "#E65100",
4470
+ orange_A100: "#FFD180",
4471
+ orange_A200: "#FFAB40",
4472
+ orange_A400: "#FF9100",
4473
+ orange_A700: "#FF6D00",
4474
+ // ── Deep Orange ──────────────────────────────────────────────────────────
4475
+ deep_orange: "#FF5722",
4476
+ // 500
4477
+ deep_orange_50: "#FBE9E7",
4478
+ deep_orange_100: "#FFCCBC",
4479
+ deep_orange_200: "#FFAB91",
4480
+ deep_orange_300: "#FF8A65",
4481
+ deep_orange_400: "#FF7043",
4482
+ deep_orange_500: "#FF5722",
4483
+ deep_orange_600: "#F4511E",
4484
+ deep_orange_700: "#E64A19",
4485
+ deep_orange_800: "#D84315",
4486
+ deep_orange_900: "#BF360C",
4487
+ deep_orange_A100: "#FF9E80",
4488
+ deep_orange_A200: "#FF6E40",
4489
+ deep_orange_A400: "#FF3D00",
4490
+ deep_orange_A700: "#DD2C00",
4491
+ // ── Brown ────────────────────────────────────────────────────────────────
4492
+ brown: "#795548",
4493
+ // 500
4494
+ brown_50: "#EFEBE9",
4495
+ brown_100: "#D7CCC8",
4496
+ brown_200: "#BCAAA4",
4497
+ brown_300: "#A1887F",
4498
+ brown_400: "#8D6E63",
4499
+ brown_500: "#795548",
4500
+ brown_600: "#6D4C41",
4501
+ brown_700: "#5D4037",
4502
+ brown_800: "#4E342E",
4503
+ brown_900: "#3E2723",
4504
+ // ── Grey ─────────────────────────────────────────────────────────────────
4505
+ grey_50: "#FAFAFA",
4506
+ grey_100: "#F5F5F5",
4507
+ grey_200: "#EEEEEE",
4508
+ grey_300: "#E0E0E0",
4509
+ grey_400: "#BDBDBD",
4510
+ grey_500: "#9E9E9E",
4511
+ grey_600: "#757575",
4512
+ grey_700: "#616161",
4513
+ grey_800: "#424242",
4514
+ grey_900: "#212121",
4515
+ // aliases
4516
+ gray_50: "#FAFAFA",
4517
+ gray_100: "#F5F5F5",
4518
+ gray_200: "#EEEEEE",
4519
+ gray_300: "#E0E0E0",
4520
+ gray_400: "#BDBDBD",
4521
+ gray_500: "#9E9E9E",
4522
+ gray_600: "#757575",
4523
+ gray_700: "#616161",
4524
+ gray_800: "#424242",
4525
+ gray_900: "#212121",
4526
+ // ── Blue Grey ────────────────────────────────────────────────────────────
4527
+ blue_grey: "#607D8B",
4528
+ // 500
4529
+ blue_grey_50: "#ECEFF1",
4530
+ blue_grey_100: "#CFD8DC",
4531
+ blue_grey_200: "#B0BEC5",
4532
+ blue_grey_300: "#90A4AE",
4533
+ blue_grey_400: "#78909C",
4534
+ blue_grey_500: "#607D8B",
4535
+ blue_grey_600: "#546E7A",
4536
+ blue_grey_700: "#455A64",
4537
+ blue_grey_800: "#37474F",
4538
+ blue_grey_900: "#263238",
4539
+ // aliases
4540
+ blue_gray: "#607D8B",
4541
+ blue_gray_50: "#ECEFF1",
4542
+ blue_gray_100: "#CFD8DC",
4543
+ blue_gray_200: "#B0BEC5",
4544
+ blue_gray_300: "#90A4AE",
4545
+ blue_gray_400: "#78909C",
4546
+ blue_gray_500: "#607D8B",
4547
+ blue_gray_600: "#546E7A",
4548
+ blue_gray_700: "#455A64",
4549
+ blue_gray_800: "#37474F",
4550
+ blue_gray_900: "#263238"
4139
4551
  };
4140
4552
  }
4141
4553
  /**
@@ -4783,7 +5195,8 @@ var SVGRenderer = class {
4783
5195
  if (node.containerType === "split") {
4784
5196
  this.renderSplitDecoration(node, pos, containerGroup);
4785
5197
  }
4786
- if (node.children.length === 0 && this.options.showDiagnostics) {
5198
+ const isCellContainer = node.meta?.source === "cell";
5199
+ if (node.children.length === 0 && this.options.showDiagnostics && !isCellContainer) {
4787
5200
  containerGroup.push(this.renderEmptyContainerDiagnostic(pos, node.containerType));
4788
5201
  } else {
4789
5202
  node.children.forEach((childRef) => {
@@ -4830,7 +5243,6 @@ var SVGRenderer = class {
4830
5243
  case "Table":
4831
5244
  return this.renderTable(node, pos);
4832
5245
  case "Chart":
4833
- case "ChartPlaceholder":
4834
5246
  return this.renderChartPlaceholder(node, pos);
4835
5247
  case "Breadcrumbs":
4836
5248
  return this.renderBreadcrumbs(node, pos);
@@ -4839,6 +5251,8 @@ var SVGRenderer = class {
4839
5251
  // Text/Content components
4840
5252
  case "Text":
4841
5253
  return this.renderText(node, pos);
5254
+ case "Paragraph":
5255
+ return this.renderParagraph(node, pos);
4842
5256
  case "Label":
4843
5257
  return this.renderLabel(node, pos);
4844
5258
  case "Code":
@@ -4918,7 +5332,7 @@ var SVGRenderer = class {
4918
5332
  renderButton(node, pos) {
4919
5333
  const text = String(node.props.text || "Button");
4920
5334
  const variant = String(node.props.variant || "default");
4921
- const size = String(node.props.size || "md");
5335
+ const size = String(node.props.size || "sm");
4922
5336
  const disabled = this.parseBooleanProp(node.props.disabled, false);
4923
5337
  const density = this.ir.project.style.density || "normal";
4924
5338
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -4993,7 +5407,7 @@ var SVGRenderer = class {
4993
5407
  renderLink(node, pos) {
4994
5408
  const text = String(node.props.text || "Link");
4995
5409
  const variant = String(node.props.variant || "primary");
4996
- const size = String(node.props.size || "md");
5410
+ const size = String(node.props.size || "sm");
4997
5411
  const density = this.ir.project.style.density || "normal";
4998
5412
  const fontSize = this.tokens.button.fontSize;
4999
5413
  const fontWeight = this.tokens.button.fontWeight;
@@ -5099,7 +5513,33 @@ var SVGRenderer = class {
5099
5513
  const variant = String(node.props.variant || "default");
5100
5514
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
5101
5515
  const showBorder = this.parseBooleanProp(node.props.border, false);
5102
- const showBackground = this.parseBooleanProp(node.props.background, false);
5516
+ const bgPropStr = String(node.props.background ?? "");
5517
+ let resolvedBg = null;
5518
+ if (bgPropStr === "true") {
5519
+ resolvedBg = this.renderTheme.cardBg;
5520
+ } else if (bgPropStr && bgPropStr !== "false") {
5521
+ if (bgPropStr.startsWith("#") || bgPropStr.startsWith("rgb")) {
5522
+ resolvedBg = bgPropStr;
5523
+ } else if (this.colorResolver.hasColor(bgPropStr)) {
5524
+ resolvedBg = this.colorResolver.resolveColor(bgPropStr, this.renderTheme.cardBg);
5525
+ }
5526
+ }
5527
+ const showBackground = resolvedBg !== null;
5528
+ const colorPropStr = String(node.props.color ?? "");
5529
+ let titleColor = this.renderTheme.text;
5530
+ let subtitleColor = this.renderTheme.textMuted;
5531
+ if (colorPropStr && colorPropStr !== "false") {
5532
+ let resolvedTitleColor = null;
5533
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
5534
+ resolvedTitleColor = colorPropStr;
5535
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
5536
+ resolvedTitleColor = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
5537
+ }
5538
+ if (resolvedTitleColor) {
5539
+ titleColor = resolvedTitleColor;
5540
+ subtitleColor = this.hexToRgba(resolvedTitleColor, 0.65);
5541
+ }
5542
+ }
5103
5543
  const radiusMap = {
5104
5544
  none: 0,
5105
5545
  sm: 4,
@@ -5111,7 +5551,7 @@ var SVGRenderer = class {
5111
5551
  const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
5112
5552
  let svg = `<g${this.getDataNodeId(node)}>`;
5113
5553
  if (showBorder || showBackground) {
5114
- const bg = showBackground ? this.renderTheme.cardBg : "none";
5554
+ const bg = resolvedBg ?? "none";
5115
5555
  const stroke = showBorder ? this.renderTheme.border : "none";
5116
5556
  svg += `
5117
5557
  <rect x="${pos.x}" y="${pos.y}"
@@ -5127,13 +5567,13 @@ var SVGRenderer = class {
5127
5567
  font-family="Arial, Helvetica, sans-serif"
5128
5568
  font-size="18"
5129
5569
  font-weight="600"
5130
- fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
5570
+ fill="${titleColor}">${this.escapeXml(topbar.visibleTitle)}</text>`;
5131
5571
  if (topbar.hasSubtitle) {
5132
5572
  svg += `
5133
5573
  <text x="${topbar.textX}" y="${topbar.subtitleY}"
5134
5574
  font-family="Arial, Helvetica, sans-serif"
5135
5575
  font-size="13"
5136
- fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
5576
+ fill="${subtitleColor}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
5137
5577
  }
5138
5578
  if (topbar.leftIcon) {
5139
5579
  svg += `
@@ -5614,10 +6054,16 @@ var SVGRenderer = class {
5614
6054
  // ============================================================================
5615
6055
  renderText(node, pos) {
5616
6056
  const text = String(node.props.text || "Text content");
5617
- const fontSize = this.tokens.text.fontSize;
6057
+ const sizeProp = String(node.props.size || "");
6058
+ const defaultFontSize = this.tokens.text.fontSize;
6059
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
6060
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
5618
6061
  const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
6062
+ const bold = this.parseBooleanProp(node.props.bold, false);
6063
+ const italic = this.parseBooleanProp(node.props.italic, false);
5619
6064
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
5620
- const firstLineY = pos.y + fontSize;
6065
+ const totalTextHeight = lines.length * lineHeightPx;
6066
+ const firstLineY = pos.y + Math.round(Math.max(0, (pos.height - totalTextHeight) / 2)) + fontSize;
5621
6067
  const tspans = lines.map(
5622
6068
  (line, index) => `<tspan x="${pos.x}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
5623
6069
  ).join("");
@@ -5625,13 +6071,54 @@ var SVGRenderer = class {
5625
6071
  <text x="${pos.x}" y="${firstLineY}"
5626
6072
  font-family="Arial, Helvetica, sans-serif"
5627
6073
  font-size="${fontSize}"
6074
+ font-weight="${bold ? "700" : "400"}"
6075
+ font-style="${italic ? "italic" : "normal"}"
5628
6076
  fill="${this.renderTheme.text}">${tspans}</text>
5629
6077
  </g>`;
5630
6078
  }
6079
+ renderParagraph(node, pos) {
6080
+ const text = String(node.props.text || "");
6081
+ const sizeProp = String(node.props.size || "");
6082
+ const defaultFontSize = this.tokens.text.fontSize;
6083
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
6084
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
6085
+ const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
6086
+ const bold = this.parseBooleanProp(node.props.bold, false);
6087
+ const italic = this.parseBooleanProp(node.props.italic, false);
6088
+ const align = String(node.props.align || "left");
6089
+ const lines = this.wrapTextToLines(text, pos.width, fontSize);
6090
+ const totalTextHeight = lines.length * lineHeightPx;
6091
+ const firstLineY = pos.y + Math.round(Math.max(0, (pos.height - totalTextHeight) / 2)) + fontSize;
6092
+ let textX;
6093
+ let textAnchor;
6094
+ if (align === "center") {
6095
+ textX = pos.x + pos.width / 2;
6096
+ textAnchor = "middle";
6097
+ } else if (align === "right") {
6098
+ textX = pos.x + pos.width;
6099
+ textAnchor = "end";
6100
+ } else {
6101
+ textX = pos.x;
6102
+ textAnchor = "start";
6103
+ }
6104
+ const tspans = lines.map(
6105
+ (line, index) => `<tspan x="${textX}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
6106
+ ).join("");
6107
+ return `<g${this.getDataNodeId(node)}>
6108
+ <text x="${textX}" y="${firstLineY}"
6109
+ font-family="Arial, Helvetica, sans-serif"
6110
+ font-size="${fontSize}"
6111
+ font-weight="${bold ? "700" : "400"}"
6112
+ font-style="${italic ? "italic" : "normal"}"
6113
+ fill="${this.renderTheme.text}"
6114
+ text-anchor="${textAnchor}">${tspans}</text>
6115
+ </g>`;
6116
+ }
5631
6117
  renderLabel(node, pos) {
5632
6118
  const text = String(node.props.text || "Label");
6119
+ const textY = pos.y + Math.round(pos.height / 2) + 4;
5633
6120
  return `<g${this.getDataNodeId(node)}>
5634
- <text x="${pos.x}" y="${pos.y + 12}"
6121
+ <text x="${pos.x}" y="${textY}"
5635
6122
  font-family="Arial, Helvetica, sans-serif"
5636
6123
  font-size="12"
5637
6124
  fill="${this.renderTheme.textMuted}">${this.escapeXml(text)}</text>
@@ -5869,34 +6356,145 @@ var SVGRenderer = class {
5869
6356
  const tabs = itemsStr ? itemsStr.split(",").map((t) => t.trim()) : ["Tab 1", "Tab 2", "Tab 3"];
5870
6357
  const activeProp = node.props.active ?? 0;
5871
6358
  const activeIndex = Number.isFinite(Number(activeProp)) ? Math.max(0, Math.floor(Number(activeProp))) : 0;
5872
- const accentColor = this.resolveAccentColor();
6359
+ const variant = String(node.props.variant || "default");
6360
+ const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
6361
+ const radiusMap = { none: 0, sm: 4, md: 6, lg: 10, full: 20 };
6362
+ const tabRadius = radiusMap[String(node.props.radius || "md")] ?? 6;
6363
+ const sizeMap = { sm: 32, md: 44, lg: 52 };
6364
+ const tabHeight = pos.height > 0 ? pos.height : sizeMap[String(node.props.size || "md")] ?? 44;
6365
+ const fontSize = 13;
6366
+ const textY = pos.y + Math.round(tabHeight / 2) + Math.round(fontSize * 0.4);
6367
+ const iconsStr = String(node.props.icons || "");
6368
+ const iconList = iconsStr ? iconsStr.split(",").map((s) => s.trim()) : [];
6369
+ const isFlat = this.parseBooleanProp(node.props.flat, false);
6370
+ const showBorder = this.parseBooleanProp(node.props.border, true);
5873
6371
  const tabWidth = pos.width / tabs.length;
5874
- let svg = `<g${this.getDataNodeId(node)}>
5875
- <!-- Tab headers -->`;
6372
+ const colorPropStr = String(node.props.color ?? "");
6373
+ let textColorOverride = null;
6374
+ if (colorPropStr && colorPropStr !== "false") {
6375
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
6376
+ textColorOverride = colorPropStr;
6377
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
6378
+ textColorOverride = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
6379
+ }
6380
+ }
6381
+ const activeTextColor = textColorOverride ?? (isFlat ? accentColor : "white");
6382
+ const inactiveTextColor = textColorOverride ? this.hexToRgba(textColorOverride, 0.55) : this.renderTheme.textMuted;
6383
+ const hasRadius = tabRadius > 0;
6384
+ const clipId = hasRadius ? `tc${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 12) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}` : "";
6385
+ let svg = "";
6386
+ if (hasRadius) {
6387
+ svg += `<defs><clipPath id="${clipId}"><rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="${tabRadius}"/></clipPath></defs>`;
6388
+ }
6389
+ svg += `<g${this.getDataNodeId(node)}>`;
6390
+ if (hasRadius) {
6391
+ svg += `<g clip-path="url(#${clipId})">`;
6392
+ }
6393
+ if (!isFlat) {
6394
+ svg += `
6395
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${tabHeight}"
6396
+ fill="${this.renderTheme.bg}"/>`;
6397
+ }
5876
6398
  tabs.forEach((tab, i) => {
5877
6399
  const tabX = pos.x + i * tabWidth;
5878
6400
  const isActive = i === activeIndex;
5879
- svg += `
5880
- <rect x="${tabX}" y="${pos.y}"
5881
- width="${tabWidth}" height="44"
5882
- fill="${isActive ? accentColor : "transparent"}"
5883
- stroke="${isActive ? "none" : this.renderTheme.border}"
5884
- stroke-width="1"/>
5885
- <text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
5886
- font-family="Arial, Helvetica, sans-serif"
5887
- font-size="13"
5888
- font-weight="${isActive ? "600" : "500"}"
5889
- fill="${isActive ? "white" : this.renderTheme.text}"
6401
+ const iconName = iconList[i] || "";
6402
+ const tabIconSvg = iconName ? getIcon(iconName) : null;
6403
+ const iconSize = 14;
6404
+ const iconGap = 4;
6405
+ const charW = fontSize * 0.58;
6406
+ const textEst = tab.length * charW;
6407
+ const totalW = tabIconSvg ? iconSize + iconGap + textEst : textEst;
6408
+ const groupX = tabX + Math.round((tabWidth - totalW) / 2);
6409
+ const iconOffY = pos.y + Math.round((tabHeight - iconSize) / 2);
6410
+ if (isFlat) {
6411
+ if (isActive) {
6412
+ svg += `
6413
+ <rect x="${tabX + 6}" y="${pos.y + tabHeight - 3}"
6414
+ width="${tabWidth - 12}" height="3"
6415
+ rx="1.5"
6416
+ fill="${accentColor}"/>`;
6417
+ }
6418
+ if (tabIconSvg) {
6419
+ svg += `
6420
+ <g transform="translate(${groupX}, ${iconOffY})">
6421
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
6422
+ stroke="${isActive ? accentColor : this.renderTheme.textMuted}"
6423
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6424
+ ${this.extractSvgContent(tabIconSvg)}
6425
+ </svg>
6426
+ </g>
6427
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
6428
+ font-family="Arial, Helvetica, sans-serif"
6429
+ font-size="${fontSize}"
6430
+ font-weight="${isActive ? "600" : "500"}"
6431
+ fill="${isActive ? activeTextColor : inactiveTextColor}">${this.escapeXml(tab)}</text>`;
6432
+ } else {
6433
+ svg += `
6434
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
6435
+ font-family="Arial, Helvetica, sans-serif"
6436
+ font-size="${fontSize}"
6437
+ font-weight="${isActive ? "600" : "500"}"
6438
+ fill="${isActive ? activeTextColor : inactiveTextColor}"
5890
6439
  text-anchor="middle">${this.escapeXml(tab)}</text>`;
6440
+ }
6441
+ } else {
6442
+ svg += `
6443
+ <rect x="${tabX}" y="${pos.y}"
6444
+ width="${tabWidth}" height="${tabHeight}"
6445
+ rx="${!showBorder && hasRadius && isActive ? tabRadius : 0}"
6446
+ fill="${isActive ? accentColor : "transparent"}"
6447
+ ${!isActive && showBorder ? `stroke="${this.renderTheme.border}" stroke-width="0.5"` : ""}/>`;
6448
+ if (tabIconSvg) {
6449
+ svg += `
6450
+ <g transform="translate(${groupX}, ${iconOffY})">
6451
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
6452
+ stroke="${isActive ? activeTextColor : this.renderTheme.textMuted}"
6453
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6454
+ ${this.extractSvgContent(tabIconSvg)}
6455
+ </svg>
6456
+ </g>
6457
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
6458
+ font-family="Arial, Helvetica, sans-serif"
6459
+ font-size="${fontSize}"
6460
+ font-weight="${isActive ? "600" : "500"}"
6461
+ fill="${isActive ? activeTextColor : this.renderTheme.text}">${this.escapeXml(tab)}</text>`;
6462
+ } else {
6463
+ svg += `
6464
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
6465
+ font-family="Arial, Helvetica, sans-serif"
6466
+ font-size="${fontSize}"
6467
+ font-weight="${isActive ? "600" : "500"}"
6468
+ fill="${isActive ? activeTextColor : this.renderTheme.text}"
6469
+ text-anchor="middle">${this.escapeXml(tab)}</text>`;
6470
+ }
6471
+ }
5891
6472
  });
5892
- svg += `
6473
+ const contentY = pos.y + tabHeight;
6474
+ const contentH = pos.height - tabHeight;
6475
+ if (contentH >= 20 && !isFlat) {
6476
+ svg += `
5893
6477
  <!-- Tab content area -->
5894
- <rect x="${pos.x}" y="${pos.y + 44}"
5895
- width="${pos.width}" height="${pos.height - 44}"
5896
- fill="${this.renderTheme.cardBg}"
5897
- stroke="${this.renderTheme.border}"
5898
- stroke-width="1"/>
6478
+ <rect x="${pos.x}" y="${contentY}"
6479
+ width="${pos.width}" height="${contentH}"
6480
+ fill="${this.renderTheme.cardBg}"
6481
+ ${showBorder ? `stroke="${this.renderTheme.border}" stroke-width="1"` : ""}/>`;
6482
+ } else if (isFlat && showBorder) {
6483
+ svg += `
6484
+ <line x1="${pos.x}" y1="${contentY}" x2="${pos.x + pos.width}" y2="${contentY}"
6485
+ stroke="${this.renderTheme.border}" stroke-width="1"/>`;
6486
+ }
6487
+ if (hasRadius) {
6488
+ svg += `
5899
6489
  </g>`;
6490
+ if (!isFlat && showBorder) {
6491
+ svg += `
6492
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
6493
+ rx="${tabRadius}" fill="none"
6494
+ stroke="${this.renderTheme.border}" stroke-width="1"/>`;
6495
+ }
6496
+ }
6497
+ svg += "\n </g>";
5900
6498
  return svg;
5901
6499
  }
5902
6500
  renderDivider(node, pos) {
@@ -5963,15 +6561,20 @@ var SVGRenderer = class {
5963
6561
  const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
5964
6562
  const bgColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.renderTheme.border;
5965
6563
  const textColor = hasExplicitVariantColor ? "white" : this.renderTheme.text;
6564
+ const size = String(node.props.size || "md");
6565
+ const BADGE_FONT_SIZES = { xs: 9, sm: 11, md: 12, lg: 13, xl: 15 };
6566
+ const fontSize = BADGE_FONT_SIZES[size] ?? this.tokens.badge.fontSize;
6567
+ const customPadding = node.props.padding !== void 0 ? Number(node.props.padding) : void 0;
6568
+ const BADGE_PAD_X = { xs: 5, sm: 6, md: 8, lg: 10, xl: 14 };
6569
+ const paddingX = customPadding !== void 0 && !isNaN(customPadding) ? customPadding : BADGE_PAD_X[size] ?? this.tokens.badge.paddingX;
5966
6570
  const badgeRadius = this.tokens.badge.radius === "pill" ? pos.height / 2 : this.tokens.badge.radius;
5967
- const fontSize = this.tokens.badge.fontSize;
5968
6571
  return `<g${this.getDataNodeId(node)}>
5969
6572
  <rect x="${pos.x}" y="${pos.y}"
5970
6573
  width="${pos.width}" height="${pos.height}"
5971
6574
  rx="${badgeRadius}"
5972
6575
  fill="${bgColor}"
5973
6576
  stroke="none"/>
5974
- <text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}"
6577
+ <text x="${pos.x + paddingX + (pos.width - paddingX * 2) / 2}" y="${pos.y + pos.height / 2 + fontSize * 0.35}"
5975
6578
  font-family="Arial, Helvetica, sans-serif"
5976
6579
  font-size="${fontSize}"
5977
6580
  font-weight="600"
@@ -6180,11 +6783,20 @@ var SVGRenderer = class {
6180
6783
  return svg;
6181
6784
  }
6182
6785
  renderImage(node, pos) {
6183
- const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
6786
+ const placeholder = String(node.props.type || "landscape").toLowerCase();
6184
6787
  const placeholderIcon = String(node.props.icon || "").trim();
6185
6788
  const variant = String(node.props.variant || "").trim();
6186
6789
  const placeholderIconSvg = placeholder === "icon" && placeholderIcon ? getIcon(placeholderIcon) : null;
6187
6790
  const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
6791
+ const hasExplicitHeight = node.props.height !== void 0 && !isNaN(Number(node.props.height)) && Number(node.props.height) > 0;
6792
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
6793
+ const useClip = isCircle || hasExplicitHeight;
6794
+ const clipId = useClip ? `iclip${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 16) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}` : "";
6795
+ const imgCx = pos.x + pos.width / 2;
6796
+ const imgCy = pos.y + pos.height / 2;
6797
+ const imgR = Math.min(pos.width, pos.height) / 2;
6798
+ const defsHtml = useClip ? `<defs><clipPath id="${clipId}">${isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="4"/>`}</clipPath></defs>` : "";
6799
+ const clipAttr = useClip ? ` clip-path="url(#${clipId})"` : "";
6188
6800
  const aspectRatios = {
6189
6801
  landscape: 16 / 9,
6190
6802
  portrait: 2 / 3,
@@ -6193,12 +6805,24 @@ var SVGRenderer = class {
6193
6805
  avatar: 1
6194
6806
  };
6195
6807
  const ratio = aspectRatios[placeholder] || 16 / 9;
6196
- const maxSize = Math.min(pos.width, pos.height) * 0.8;
6197
- let iconWidth = maxSize;
6198
- let iconHeight = maxSize / ratio;
6199
- if (iconHeight > pos.height * 0.8) {
6200
- iconHeight = pos.height * 0.8;
6201
- iconWidth = iconHeight * ratio;
6808
+ let iconWidth;
6809
+ let iconHeight;
6810
+ if (isCircle) {
6811
+ const diameter = 2 * imgR;
6812
+ iconWidth = diameter;
6813
+ iconHeight = diameter / ratio;
6814
+ if (iconHeight < diameter) {
6815
+ iconHeight = diameter;
6816
+ iconWidth = diameter * ratio;
6817
+ }
6818
+ } else {
6819
+ const maxSize = Math.min(pos.width, pos.height) * 0.8;
6820
+ iconWidth = maxSize;
6821
+ iconHeight = maxSize / ratio;
6822
+ if (iconHeight > pos.height * 0.8) {
6823
+ iconHeight = pos.height * 0.8;
6824
+ iconWidth = iconHeight * ratio;
6825
+ }
6202
6826
  }
6203
6827
  const offsetX = pos.x + (pos.width - iconWidth) / 2;
6204
6828
  const offsetY = pos.y + (pos.height - iconHeight) / 2;
@@ -6211,17 +6835,18 @@ var SVGRenderer = class {
6211
6835
  const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
6212
6836
  const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
6213
6837
  const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
6214
- return `<g${this.getDataNodeId(node)}>
6838
+ const iconBorderShape = isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>`;
6839
+ return `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
6215
6840
  <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${bgColor}" rx="4"/>
6216
6841
  <g transform="translate(${iconOffsetX}, ${iconOffsetY})">
6217
6842
  <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6218
6843
  ${this.extractSvgContent(placeholderIconSvg)}
6219
6844
  </svg>
6220
6845
  </g>
6221
- <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
6846
+ ${iconBorderShape}
6222
6847
  </g>`;
6223
6848
  }
6224
- let svg = `<g${this.getDataNodeId(node)}>
6849
+ let svg = `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
6225
6850
  <!-- Image Background -->
6226
6851
  <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${imageBg}"/>`;
6227
6852
  if (["landscape", "portrait", "square"].includes(placeholder)) {
@@ -6279,10 +6904,10 @@ var SVGRenderer = class {
6279
6904
  width="${personWidth * 0.6}" height="${personHeight * 0.5}"
6280
6905
  fill="#666" rx="3"/>`;
6281
6906
  }
6907
+ const borderShape = isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>`;
6282
6908
  svg += `
6283
6909
  <!-- Border -->
6284
- <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
6285
- fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
6910
+ ${borderShape}
6286
6911
  </g>`;
6287
6912
  return svg;
6288
6913
  }
@@ -6386,10 +7011,29 @@ var SVGRenderer = class {
6386
7011
  </g>`;
6387
7012
  }
6388
7013
  const iconSize = this.getIconSize(size);
6389
- const offsetX = pos.x + (pos.width - iconSize) / 2;
6390
- const offsetY = pos.y + (pos.height - iconSize) / 2;
7014
+ const paddingPx = Math.max(0, Number(node.props.padding || 0));
7015
+ const renderedIconSize = Math.max(4, iconSize - paddingPx * 2);
7016
+ const offsetX = pos.x + (pos.width - renderedIconSize) / 2;
7017
+ const offsetY = pos.y + (pos.height - renderedIconSize) / 2;
7018
+ const isIconCircle = this.parseBooleanProp(node.props.circle, false);
7019
+ if (isIconCircle) {
7020
+ const cx = pos.x + pos.width / 2;
7021
+ const cy = pos.y + pos.height / 2;
7022
+ const r = Math.min(pos.width, pos.height) / 2;
7023
+ const iconClipId = `iconclip${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 16) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}`;
7024
+ const bgColor = this.hexToRgba(iconColor, 0.12);
7025
+ return `<defs><clipPath id="${iconClipId}"><circle cx="${cx}" cy="${cy}" r="${r}"/></clipPath></defs><g${this.getDataNodeId(node)} clip-path="url(#${iconClipId})">
7026
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="${bgColor}"/>
7027
+ <g transform="translate(${offsetX}, ${offsetY})">
7028
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7029
+ ${this.extractSvgContent(iconSvg)}
7030
+ </svg>
7031
+ </g>
7032
+ </g>
7033
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${this.hexToRgba(iconColor, 0.35)}" stroke-width="1"/>`;
7034
+ }
6391
7035
  const wrappedSvg = `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
6392
- <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7036
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6393
7037
  ${this.extractSvgContent(iconSvg)}
6394
7038
  </svg>
6395
7039
  </g>`;
@@ -6398,7 +7042,7 @@ var SVGRenderer = class {
6398
7042
  renderIconButton(node, pos) {
6399
7043
  const iconName = String(node.props.icon || "help-circle");
6400
7044
  const variant = String(node.props.variant || "default");
6401
- const size = String(node.props.size || "md");
7045
+ const size = String(node.props.size || "sm");
6402
7046
  const disabled = String(node.props.disabled || "false") === "true";
6403
7047
  const density = this.ir.project.style.density || "normal";
6404
7048
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
@@ -6526,7 +7170,7 @@ var SVGRenderer = class {
6526
7170
  wrapTextToLines(text, maxWidth, fontSize) {
6527
7171
  const normalized = text.replace(/\r\n/g, "\n");
6528
7172
  const paragraphs = normalized.split("\n");
6529
- const charWidth = fontSize * 0.6;
7173
+ const charWidth = fontSize * 0.5;
6530
7174
  const safeWidth = Math.max(maxWidth || 0, charWidth);
6531
7175
  const maxCharsPerLine = Math.max(1, Math.floor(safeWidth / charWidth));
6532
7176
  const lines = [];
@@ -6768,8 +7412,8 @@ var SVGRenderer = class {
6768
7412
  return false;
6769
7413
  }
6770
7414
  const direction = String(parent.params.direction || "vertical");
6771
- const align = parent.style.align || "justify";
6772
- return direction === "horizontal" && align === "justify";
7415
+ const justify = parent.style.justify || "stretch";
7416
+ return direction === "horizontal" && justify === "stretch";
6773
7417
  }
6774
7418
  buildParentContainerIndex() {
6775
7419
  this.parentContainerByChildId.clear();
@@ -7641,7 +8285,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
7641
8285
  renderButton(node, pos) {
7642
8286
  const text = String(node.props.text || "Button");
7643
8287
  const variant = String(node.props.variant || "default");
7644
- const size = String(node.props.size || "md");
8288
+ const size = String(node.props.size || "sm");
7645
8289
  const density = this.ir.project.style.density || "normal";
7646
8290
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
7647
8291
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
@@ -7748,7 +8392,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
7748
8392
  renderIconButton(node, pos) {
7749
8393
  const iconName = String(node.props.icon || "help-circle");
7750
8394
  const variant = String(node.props.variant || "default");
7751
- const size = String(node.props.size || "md");
8395
+ const size = String(node.props.size || "sm");
7752
8396
  const density = this.ir.project.style.density || "normal";
7753
8397
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
7754
8398
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -8001,26 +8645,58 @@ var SketchSVGRenderer = class extends SVGRenderer {
8001
8645
  const variant = String(node.props.variant || "default");
8002
8646
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
8003
8647
  const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
8004
- let svg = `<g${this.getDataNodeId(node)}>
8648
+ const bgPropStr = String(node.props.background ?? "");
8649
+ let sketchTopbarBg = this.renderTheme.cardBg;
8650
+ if (bgPropStr && bgPropStr !== "false" && bgPropStr !== "true") {
8651
+ if (bgPropStr.startsWith("#") || bgPropStr.startsWith("rgb")) {
8652
+ sketchTopbarBg = bgPropStr;
8653
+ } else if (this.colorResolver.hasColor(bgPropStr)) {
8654
+ sketchTopbarBg = this.colorResolver.resolveColor(bgPropStr, this.renderTheme.cardBg);
8655
+ }
8656
+ }
8657
+ const colorPropStr = String(node.props.color ?? "");
8658
+ let titleColor = this.renderTheme.text;
8659
+ let subtitleColor = this.renderTheme.textMuted;
8660
+ if (colorPropStr && colorPropStr !== "false") {
8661
+ let resolvedTitleColor = null;
8662
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
8663
+ resolvedTitleColor = colorPropStr;
8664
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
8665
+ resolvedTitleColor = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
8666
+ }
8667
+ if (resolvedTitleColor) {
8668
+ titleColor = resolvedTitleColor;
8669
+ subtitleColor = this.hexToRgba(resolvedTitleColor, 0.65);
8670
+ }
8671
+ }
8672
+ const showBorder = this.parseBooleanProp(node.props.border, false);
8673
+ const hasBgProp = bgPropStr === "true" || bgPropStr && bgPropStr !== "false" && bgPropStr !== "";
8674
+ const showBackground = hasBgProp;
8675
+ let svg = `<g${this.getDataNodeId(node)}>`;
8676
+ if (showBorder || showBackground) {
8677
+ const stroke = showBorder ? "#2D3748" : "none";
8678
+ svg += `
8005
8679
  <rect x="${pos.x}" y="${pos.y}"
8006
8680
  width="${pos.width}" height="${pos.height}"
8007
- fill="${this.renderTheme.cardBg}"
8008
- stroke="#2D3748"
8681
+ fill="${sketchTopbarBg}"
8682
+ stroke="${stroke}"
8009
8683
  stroke-width="0.5"
8010
- filter="url(#sketch-rough)"/>
8011
-
8012
- <!-- Title -->
8684
+ filter="url(#sketch-rough)"/>`;
8685
+ }
8686
+ svg += `
8687
+ <!-- Title -->`;
8688
+ svg += `
8013
8689
  <text x="${topbar.textX}" y="${topbar.titleY}"
8014
8690
  font-family="${this.fontFamily}"
8015
8691
  font-size="18"
8016
8692
  font-weight="600"
8017
- fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
8693
+ fill="${titleColor}">${this.escapeXml(topbar.visibleTitle)}</text>`;
8018
8694
  if (topbar.hasSubtitle) {
8019
8695
  svg += `
8020
8696
  <text x="${topbar.textX}" y="${topbar.subtitleY}"
8021
8697
  font-family="${this.fontFamily}"
8022
8698
  font-size="13"
8023
- fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
8699
+ fill="${subtitleColor}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
8024
8700
  }
8025
8701
  if (topbar.leftIcon) {
8026
8702
  svg += `
@@ -8100,8 +8776,13 @@ var SketchSVGRenderer = class extends SVGRenderer {
8100
8776
  */
8101
8777
  renderText(node, pos) {
8102
8778
  const text = String(node.props.text || "Text content");
8103
- const fontSize = this.tokens.text.fontSize;
8779
+ const sizeProp = String(node.props.size || "");
8780
+ const defaultFontSize = this.tokens.text.fontSize;
8781
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
8782
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
8104
8783
  const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
8784
+ const bold = this.parseBooleanProp(node.props.bold, false);
8785
+ const italic = this.parseBooleanProp(node.props.italic, false);
8105
8786
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
8106
8787
  const firstLineY = pos.y + fontSize;
8107
8788
  const tspans = lines.map(
@@ -8111,9 +8792,48 @@ var SketchSVGRenderer = class extends SVGRenderer {
8111
8792
  <text x="${pos.x}" y="${firstLineY}"
8112
8793
  font-family="${this.fontFamily}"
8113
8794
  font-size="${fontSize}"
8795
+ font-weight="${bold ? "700" : "400"}"
8796
+ font-style="${italic ? "italic" : "normal"}"
8114
8797
  fill="${this.renderTheme.text}">${tspans}</text>
8115
8798
  </g>`;
8116
8799
  }
8800
+ renderParagraph(node, pos) {
8801
+ const text = String(node.props.text || "");
8802
+ const sizeProp = String(node.props.size || "");
8803
+ const defaultFontSize = this.tokens.text.fontSize;
8804
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
8805
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
8806
+ const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
8807
+ const bold = this.parseBooleanProp(node.props.bold, false);
8808
+ const italic = this.parseBooleanProp(node.props.italic, false);
8809
+ const align = String(node.props.align || "left");
8810
+ const lines = this.wrapTextToLines(text, pos.width, fontSize);
8811
+ const firstLineY = pos.y + fontSize;
8812
+ let textX;
8813
+ let textAnchor;
8814
+ if (align === "center") {
8815
+ textX = pos.x + pos.width / 2;
8816
+ textAnchor = "middle";
8817
+ } else if (align === "right") {
8818
+ textX = pos.x + pos.width;
8819
+ textAnchor = "end";
8820
+ } else {
8821
+ textX = pos.x;
8822
+ textAnchor = "start";
8823
+ }
8824
+ const tspans = lines.map(
8825
+ (line, index) => `<tspan x="${textX}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
8826
+ ).join("");
8827
+ return `<g${this.getDataNodeId(node)}>
8828
+ <text x="${textX}" y="${firstLineY}"
8829
+ font-family="${this.fontFamily}"
8830
+ font-size="${fontSize}"
8831
+ font-weight="${bold ? "700" : "400"}"
8832
+ font-style="${italic ? "italic" : "normal"}"
8833
+ fill="${this.renderTheme.text}"
8834
+ text-anchor="${textAnchor}">${tspans}</text>
8835
+ </g>`;
8836
+ }
8117
8837
  /**
8118
8838
  * Render label with Comic Sans
8119
8839
  */
@@ -8344,36 +9064,150 @@ var SketchSVGRenderer = class extends SVGRenderer {
8344
9064
  renderTabs(node, pos) {
8345
9065
  const itemsStr = String(node.props.items || "");
8346
9066
  const tabs = itemsStr ? itemsStr.split(",").map((t) => t.trim()) : ["Tab 1", "Tab 2", "Tab 3"];
8347
- const accentColor = this.resolveAccentColor();
9067
+ const activeProp = node.props.active ?? 0;
9068
+ const activeIndex = Number.isFinite(Number(activeProp)) ? Math.max(0, Math.floor(Number(activeProp))) : 0;
9069
+ const variant = String(node.props.variant || "default");
9070
+ const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
9071
+ const radiusMap = { none: 0, sm: 4, md: 6, lg: 10, full: 20 };
9072
+ const tabRadius = radiusMap[String(node.props.radius || "md")] ?? 6;
9073
+ const sizeMap = { sm: 32, md: 44, lg: 52 };
9074
+ const tabHeight = pos.height > 0 ? pos.height : sizeMap[String(node.props.size || "md")] ?? 44;
9075
+ const fontSize = 13;
9076
+ const textY = pos.y + Math.round(tabHeight / 2) + Math.round(fontSize * 0.4);
9077
+ const iconsStr = String(node.props.icons || "");
9078
+ const iconList = iconsStr ? iconsStr.split(",").map((s) => s.trim()) : [];
9079
+ const isFlat = this.parseBooleanProp(node.props.flat, false);
9080
+ const showBorder = this.parseBooleanProp(node.props.border, true);
8348
9081
  const tabWidth = pos.width / tabs.length;
8349
- let svg = `<g${this.getDataNodeId(node)}>
8350
- <!-- Tab headers -->`;
9082
+ const colorPropStr = String(node.props.color ?? "");
9083
+ let textColorOverride = null;
9084
+ if (colorPropStr && colorPropStr !== "false") {
9085
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
9086
+ textColorOverride = colorPropStr;
9087
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
9088
+ textColorOverride = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
9089
+ }
9090
+ }
9091
+ const activeTextColor = textColorOverride ?? (isFlat ? accentColor : "white");
9092
+ const inactiveTextColor = textColorOverride ? this.hexToRgba(textColorOverride, 0.55) : this.renderTheme.textMuted;
9093
+ const hasRadius = tabRadius > 0;
9094
+ const clipId = hasRadius ? `stc${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 12) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}` : "";
9095
+ let svg = "";
9096
+ if (hasRadius) {
9097
+ svg += `<defs><clipPath id="${clipId}"><rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="${tabRadius}"/></clipPath></defs>`;
9098
+ }
9099
+ svg += `<g${this.getDataNodeId(node)}>`;
9100
+ if (hasRadius) {
9101
+ svg += `<g clip-path="url(#${clipId})">`;
9102
+ }
9103
+ if (!isFlat) {
9104
+ svg += `
9105
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${tabHeight}"
9106
+ fill="${this.renderTheme.bg}"/>`;
9107
+ }
8351
9108
  tabs.forEach((tab, i) => {
8352
9109
  const tabX = pos.x + i * tabWidth;
8353
- const isActive = i === 0;
8354
- svg += `
9110
+ const isActive = i === activeIndex;
9111
+ const iconName = iconList[i] || "";
9112
+ const tabIconSvg = iconName ? getIcon(iconName) : null;
9113
+ const iconSize = 14;
9114
+ const iconGap = 4;
9115
+ const charW = fontSize * 0.58;
9116
+ const textEst = tab.length * charW;
9117
+ const totalW = tabIconSvg ? iconSize + iconGap + textEst : textEst;
9118
+ const groupX = tabX + Math.round((tabWidth - totalW) / 2);
9119
+ const iconOffY = pos.y + Math.round((tabHeight - iconSize) / 2);
9120
+ if (isFlat) {
9121
+ if (isActive) {
9122
+ svg += `
9123
+ <rect x="${tabX + 6}" y="${pos.y + tabHeight - 3}"
9124
+ width="${tabWidth - 12}" height="3"
9125
+ rx="1.5"
9126
+ fill="${accentColor}"
9127
+ filter="url(#sketch-rough)"/>`;
9128
+ }
9129
+ if (tabIconSvg) {
9130
+ svg += `
9131
+ <g transform="translate(${groupX}, ${iconOffY})">
9132
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
9133
+ stroke="${isActive ? accentColor : this.renderTheme.textMuted}"
9134
+ stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
9135
+ ${this.extractSvgContent(tabIconSvg)}
9136
+ </svg>
9137
+ </g>
9138
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
9139
+ font-family="${this.fontFamily}"
9140
+ font-size="${fontSize}"
9141
+ font-weight="${isActive ? "600" : "500"}"
9142
+ fill="${isActive ? activeTextColor : inactiveTextColor}">${this.escapeXml(tab)}</text>`;
9143
+ } else {
9144
+ svg += `
9145
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
9146
+ font-family="${this.fontFamily}"
9147
+ font-size="${fontSize}"
9148
+ font-weight="${isActive ? "600" : "500"}"
9149
+ fill="${isActive ? activeTextColor : inactiveTextColor}"
9150
+ text-anchor="middle">${this.escapeXml(tab)}</text>`;
9151
+ }
9152
+ } else {
9153
+ svg += `
8355
9154
  <rect x="${tabX}" y="${pos.y}"
8356
- width="${tabWidth}" height="44"
9155
+ width="${tabWidth}" height="${tabHeight}"
9156
+ rx="${!showBorder && hasRadius && isActive ? tabRadius : 0}"
8357
9157
  fill="${isActive ? accentColor : "transparent"}"
8358
- stroke="${isActive ? accentColor : "#2D3748"}"
9158
+ stroke="${isActive ? accentColor : showBorder ? "#2D3748" : "none"}"
8359
9159
  stroke-width="0.5"
8360
- filter="url(#sketch-rough)"/>
8361
- <text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
9160
+ filter="url(#sketch-rough)"/>`;
9161
+ if (tabIconSvg) {
9162
+ svg += `
9163
+ <g transform="translate(${groupX}, ${iconOffY})">
9164
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
9165
+ stroke="${isActive ? activeTextColor : this.renderTheme.textMuted}"
9166
+ stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
9167
+ ${this.extractSvgContent(tabIconSvg)}
9168
+ </svg>
9169
+ </g>
9170
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
8362
9171
  font-family="${this.fontFamily}"
8363
- font-size="13"
9172
+ font-size="${fontSize}"
9173
+ font-weight="${isActive ? "600" : "500"}"
9174
+ fill="${isActive ? activeTextColor : this.renderTheme.text}">${this.escapeXml(tab)}</text>`;
9175
+ } else {
9176
+ svg += `
9177
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
9178
+ font-family="${this.fontFamily}"
9179
+ font-size="${fontSize}"
8364
9180
  font-weight="${isActive ? "600" : "500"}"
8365
- fill="${isActive ? "white" : this.renderTheme.text}"
9181
+ fill="${isActive ? activeTextColor : this.renderTheme.text}"
8366
9182
  text-anchor="middle">${this.escapeXml(tab)}</text>`;
9183
+ }
9184
+ }
8367
9185
  });
8368
- svg += `
9186
+ const contentY = pos.y + tabHeight;
9187
+ const contentH = pos.height - tabHeight;
9188
+ if (contentH >= 20 && !isFlat) {
9189
+ svg += `
8369
9190
  <!-- Tab content area -->
8370
- <rect x="${pos.x}" y="${pos.y + 44}"
8371
- width="${pos.width}" height="${pos.height - 44}"
9191
+ <rect x="${pos.x}" y="${contentY}"
9192
+ width="${pos.width}" height="${contentH}"
8372
9193
  fill="${this.renderTheme.cardBg}"
8373
- stroke="#2D3748"
8374
- stroke-width="0.5"
8375
- filter="url(#sketch-rough)"/>
9194
+ ${showBorder ? 'stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"' : ""}/>`;
9195
+ } else if (isFlat && showBorder) {
9196
+ svg += `
9197
+ <line x1="${pos.x}" y1="${contentY}" x2="${pos.x + pos.width}" y2="${contentY}"
9198
+ stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"/>`;
9199
+ }
9200
+ if (hasRadius) {
9201
+ svg += `
8376
9202
  </g>`;
9203
+ if (!isFlat && showBorder) {
9204
+ svg += `
9205
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
9206
+ rx="${tabRadius}" fill="none" stroke="#2D3748" stroke-width="0.5"
9207
+ filter="url(#sketch-rough)"/>`;
9208
+ }
9209
+ }
9210
+ svg += "\n </g>";
8377
9211
  return svg;
8378
9212
  }
8379
9213
  /**
@@ -8610,11 +9444,20 @@ var SketchSVGRenderer = class extends SVGRenderer {
8610
9444
  * Render image with sketch filter
8611
9445
  */
8612
9446
  renderImage(node, pos) {
8613
- const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
9447
+ const placeholder = String(node.props.type || "landscape").toLowerCase();
8614
9448
  const iconType = String(node.props.icon || "").trim();
8615
9449
  const variant = String(node.props.variant || "").trim();
8616
9450
  const iconSvg = placeholder === "icon" && iconType.length > 0 ? getIcon(iconType) : null;
8617
9451
  const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
9452
+ const hasExplicitHeight = node.props.height !== void 0 && !isNaN(Number(node.props.height)) && Number(node.props.height) > 0;
9453
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
9454
+ const useClip = isCircle || hasExplicitHeight;
9455
+ const clipId = useClip ? `sclip${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 16) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}` : "";
9456
+ const imgCx = pos.x + pos.width / 2;
9457
+ const imgCy = pos.y + pos.height / 2;
9458
+ const imgR = Math.min(pos.width, pos.height) / 2;
9459
+ const defsHtml = useClip ? `<defs><clipPath id="${clipId}">${isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="4"/>`}</clipPath></defs>` : "";
9460
+ const clipAttr = useClip ? ` clip-path="url(#${clipId})"` : "";
8618
9461
  if (iconSvg) {
8619
9462
  const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
8620
9463
  const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
@@ -8624,7 +9467,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
8624
9467
  const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
8625
9468
  const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
8626
9469
  const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
8627
- return `<g${this.getDataNodeId(node)}>
9470
+ const sketchIconBorder = isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}" fill="none" stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="#2D3748" stroke-width="0.5" rx="4" filter="url(#sketch-rough)"/>`;
9471
+ return `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
8628
9472
  <rect x="${pos.x}" y="${pos.y}"
8629
9473
  width="${pos.width}" height="${pos.height}"
8630
9474
  fill="${bgColor}"
@@ -8635,17 +9479,11 @@ var SketchSVGRenderer = class extends SVGRenderer {
8635
9479
  ${this.extractSvgContent(iconSvg)}
8636
9480
  </svg>
8637
9481
  </g>
8638
- <rect x="${pos.x}" y="${pos.y}"
8639
- width="${pos.width}" height="${pos.height}"
8640
- fill="none"
8641
- stroke="#2D3748"
8642
- stroke-width="0.5"
8643
- rx="4"
8644
- filter="url(#sketch-rough)"/>
9482
+ ${sketchIconBorder}
8645
9483
  </g>`;
8646
9484
  }
8647
- return `<g${this.getDataNodeId(node)}>
8648
- <!-- Image Background -->
9485
+ const sketchCircleBorder = isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}" fill="none" stroke="#2D3748" stroke-width="0.5"/>` : "";
9486
+ return `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
8649
9487
  <rect x="${pos.x}" y="${pos.y}"
8650
9488
  width="${pos.width}" height="${pos.height}"
8651
9489
  fill="${imageBg}"
@@ -8653,14 +9491,12 @@ var SketchSVGRenderer = class extends SVGRenderer {
8653
9491
  stroke-width="0.5"
8654
9492
  rx="4"
8655
9493
  filter="url(#sketch-rough)"/>
8656
-
8657
- <!-- Placeholder icon -->
8658
9494
  <text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2}"
8659
9495
  font-family="${this.fontFamily}"
8660
9496
  font-size="24"
8661
9497
  fill="#666"
8662
9498
  text-anchor="middle">\u{1F5BC}</text>
8663
- </g>`;
9499
+ </g>${sketchCircleBorder}`;
8664
9500
  }
8665
9501
  /**
8666
9502
  * Render breadcrumbs with Comic Sans
@@ -8776,20 +9612,33 @@ var SketchSVGRenderer = class extends SVGRenderer {
8776
9612
  }
8777
9613
  const iconSize = this.getIconSize(size);
8778
9614
  const iconColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
8779
- const offsetX = pos.x + (pos.width - iconSize) / 2;
8780
- const offsetY = pos.y + (pos.height - iconSize) / 2;
9615
+ const paddingPx = Math.max(0, Number(node.props.padding || 0));
9616
+ const renderedIconSize = Math.max(4, iconSize - paddingPx * 2);
9617
+ const offsetX = pos.x + (pos.width - renderedIconSize) / 2;
9618
+ const offsetY = pos.y + (pos.height - renderedIconSize) / 2;
9619
+ const isIconCircle = this.parseBooleanProp(node.props.circle, false);
9620
+ if (isIconCircle) {
9621
+ const cx = pos.x + pos.width / 2;
9622
+ const cy = pos.y + pos.height / 2;
9623
+ const r = Math.min(pos.width, pos.height) / 2;
9624
+ const iconClipId = `sconclip${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 16) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}`;
9625
+ const bgColor = this.hexToRgba(iconColor, 0.1);
9626
+ return `<defs><clipPath id="${iconClipId}"><circle cx="${cx}" cy="${cy}" r="${r}"/></clipPath></defs><g${this.getDataNodeId(node)} clip-path="url(#${iconClipId})">
9627
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="${bgColor}"/>
9628
+ <g transform="translate(${offsetX}, ${offsetY})">
9629
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
9630
+ ${this.extractSvgContent(iconSvg)}
9631
+ </svg>
9632
+ </g>
9633
+ </g>
9634
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${this.hexToRgba(iconColor, 0.35)}" stroke-width="0.5"/>`;
9635
+ }
8781
9636
  return `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
8782
- <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
9637
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
8783
9638
  ${this.extractSvgContent(iconSvg)}
8784
9639
  </svg>
8785
9640
  </g>`;
8786
9641
  }
8787
- /**
8788
- * Render chart placeholder with sketch filter and Comic Sans
8789
- */
8790
- renderChartPlaceholder(node, pos) {
8791
- return super.renderChartPlaceholder(node, pos);
8792
- }
8793
9642
  /**
8794
9643
  * Helper method to get icon SVG
8795
9644
  */