#
tokens: 45037/50000 2/73 files (page 6/8)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 6 of 8. Use http://codebase.md/1yhy/figma-context-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .editorconfig
├── .env.example
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows
│       └── ci.yml
├── .gitignore
├── .husky
│   ├── commit-msg
│   └── pre-commit
├── .lintstagedrc.json
├── .nvmrc
├── .prettierrc
├── CHANGELOG.md
├── commitlint.config.js
├── CONTRIBUTING.md
├── Dockerfile
├── docs
│   ├── en
│   │   ├── absolute-to-relative-research.md
│   │   ├── architecture.md
│   │   ├── cache-architecture.md
│   │   ├── grid-layout-research.md
│   │   ├── icon-detection.md
│   │   ├── layout-detection-research.md
│   │   └── layout-detection.md
│   └── zh-CN
│       ├── absolute-to-relative-research.md
│       ├── architecture.md
│       ├── cache-architecture.md
│       ├── grid-layout-research.md
│       ├── icon-detection.md
│       ├── layout-detection-research.md
│       ├── layout-detection.md
│       └── TODO-feature-enhancements.md
├── eslint.config.js
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── README.md
├── README.zh-CN.md
├── scripts
│   ├── fetch-test-data.ts
│   └── optimize-figma-json.ts
├── smithery.yaml
├── src
│   ├── algorithms
│   │   ├── icon
│   │   │   ├── detector.ts
│   │   │   └── index.ts
│   │   └── layout
│   │       ├── detector.ts
│   │       ├── index.ts
│   │       ├── optimizer.ts
│   │       └── spatial.ts
│   ├── config.ts
│   ├── core
│   │   ├── effects.ts
│   │   ├── layout.ts
│   │   ├── parser.ts
│   │   └── style.ts
│   ├── index.ts
│   ├── prompts
│   │   ├── design-to-code.ts
│   │   └── index.ts
│   ├── resources
│   │   ├── figma-resources.ts
│   │   └── index.ts
│   ├── server.ts
│   ├── services
│   │   ├── cache
│   │   │   ├── cache-manager.ts
│   │   │   ├── disk-cache.ts
│   │   │   ├── index.ts
│   │   │   ├── lru-cache.ts
│   │   │   └── types.ts
│   │   ├── cache.ts
│   │   ├── figma.ts
│   │   └── simplify-node-response.ts
│   ├── types
│   │   ├── figma.ts
│   │   ├── index.ts
│   │   └── simplified.ts
│   └── utils
│       ├── color.ts
│       ├── css.ts
│       ├── file.ts
│       └── validation.ts
├── tests
│   ├── fixtures
│   │   ├── expected
│   │   │   ├── node-240-32163-optimized.json
│   │   │   ├── node-402-34955-optimized.json
│   │   │   └── real-node-data-optimized.json
│   │   └── figma-data
│   │       ├── node-240-32163.json
│   │       ├── node-402-34955.json
│   │       └── real-node-data.json
│   ├── integration
│   │   ├── __snapshots__
│   │   │   ├── layout-optimization.test.ts.snap
│   │   │   └── output-quality.test.ts.snap
│   │   ├── layout-optimization.test.ts
│   │   ├── output-quality.test.ts
│   │   └── parser.test.ts
│   ├── unit
│   │   ├── algorithms
│   │   │   ├── icon-optimization.test.ts
│   │   │   ├── icon.test.ts
│   │   │   └── layout.test.ts
│   │   ├── resources
│   │   │   └── figma-resources.test.ts
│   │   └── services
│   │       └── cache.test.ts
│   └── utils
│       ├── preview-generator.ts
│       ├── preview.ts
│       ├── run-simplification.ts
│       └── viewer.html
├── tsconfig.json
├── tsup.config.ts
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/tests/unit/algorithms/layout.test.ts:
--------------------------------------------------------------------------------

```typescript
   1 | /**
   2 |  * Layout Detection Algorithm Unit Tests
   3 |  *
   4 |  * Tests the layout detection algorithm for inferring Flexbox layouts
   5 |  * from absolutely positioned Figma elements.
   6 |  */
   7 | 
   8 | import { describe, it, expect, beforeAll } from "vitest";
   9 | import * as fs from "fs";
  10 | import * as path from "path";
  11 | import { fileURLToPath } from "url";
  12 | import {
  13 |   extractBoundingBox,
  14 |   toElementRect,
  15 |   groupIntoRows,
  16 |   groupIntoColumns,
  17 |   analyzeGaps,
  18 |   analyzeAlignment,
  19 |   calculateBounds,
  20 |   clusterValues,
  21 |   detectGridLayout,
  22 |   calculateIoU,
  23 |   classifyOverlap,
  24 |   detectOverlappingElements,
  25 |   detectBackgroundElement,
  26 |   LayoutOptimizer,
  27 |   analyzeHomogeneity,
  28 |   filterHomogeneousForGrid,
  29 |   type ElementRect,
  30 |   type BoundingBox,
  31 | } from "~/algorithms/layout/index.js";
  32 | import type { SimplifiedNode } from "~/types/index.js";
  33 | 
  34 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
  35 | const fixturesPath = path.join(__dirname, "../../fixtures");
  36 | 
  37 | // Test data types
  38 | interface FigmaNode {
  39 |   id: string;
  40 |   name: string;
  41 |   type: string;
  42 |   absoluteBoundingBox?: BoundingBox;
  43 |   children?: FigmaNode[];
  44 | }
  45 | 
  46 | // Load test fixture
  47 | function loadTestData(): FigmaNode {
  48 |   const dataPath = path.join(fixturesPath, "figma-data/real-node-data.json");
  49 |   const rawData = JSON.parse(fs.readFileSync(dataPath, "utf-8"));
  50 |   const nodeKey = Object.keys(rawData.nodes)[0];
  51 |   return rawData.nodes[nodeKey].document;
  52 | }
  53 | 
  54 | // Extract child elements with bounding boxes
  55 | function extractChildElements(node: FigmaNode): ElementRect[] {
  56 |   if (!node.children) return [];
  57 | 
  58 |   return node.children
  59 |     .filter((child) => child.absoluteBoundingBox)
  60 |     .map((child, index) => {
  61 |       const box = child.absoluteBoundingBox!;
  62 |       return toElementRect(box, index);
  63 |     });
  64 | }
  65 | 
  66 | describe("Layout Detection Algorithm", () => {
  67 |   let testData: FigmaNode;
  68 | 
  69 |   beforeAll(() => {
  70 |     testData = loadTestData();
  71 |   });
  72 | 
  73 |   describe("Bounding Box Extraction", () => {
  74 |     it("should extract bounding box from CSS styles", () => {
  75 |       const cssStyles = { left: "100px", top: "200px", width: "300px", height: "400px" };
  76 |       const box = extractBoundingBox(cssStyles);
  77 | 
  78 |       expect(box).toBeDefined();
  79 |       expect(box?.x).toBe(100);
  80 |       expect(box?.y).toBe(200);
  81 |       expect(box?.width).toBe(300);
  82 |       expect(box?.height).toBe(400);
  83 |     });
  84 | 
  85 |     it("should convert to ElementRect correctly", () => {
  86 |       const box: BoundingBox = { x: 100, y: 200, width: 300, height: 400 };
  87 |       const rect = toElementRect(box, 0);
  88 | 
  89 |       expect(rect.index).toBe(0);
  90 |       expect(rect.x).toBe(100);
  91 |       expect(rect.y).toBe(200);
  92 |       expect(rect.width).toBe(300);
  93 |       expect(rect.height).toBe(400);
  94 |       expect(rect.right).toBe(400);
  95 |       expect(rect.bottom).toBe(600);
  96 |       expect(rect.centerX).toBe(250);
  97 |       expect(rect.centerY).toBe(400);
  98 |     });
  99 |   });
 100 | 
 101 |   describe("Row Grouping (Y-axis overlap)", () => {
 102 |     it("should group horizontally aligned elements into same row", () => {
 103 |       const elements: ElementRect[] = [
 104 |         toElementRect({ x: 0, y: 10, width: 50, height: 30 }, 0),
 105 |         toElementRect({ x: 60, y: 15, width: 50, height: 30 }, 1),
 106 |         toElementRect({ x: 120, y: 12, width: 50, height: 30 }, 2),
 107 |       ];
 108 | 
 109 |       const rows = groupIntoRows(elements);
 110 |       expect(rows.length).toBe(1);
 111 |       expect(rows[0].length).toBe(3);
 112 |     });
 113 | 
 114 |     it("should separate vertically stacked elements into different rows", () => {
 115 |       const elements: ElementRect[] = [
 116 |         toElementRect({ x: 0, y: 0, width: 100, height: 30 }, 0),
 117 |         toElementRect({ x: 0, y: 50, width: 100, height: 30 }, 1),
 118 |         toElementRect({ x: 0, y: 100, width: 100, height: 30 }, 2),
 119 |       ];
 120 | 
 121 |       const rows = groupIntoRows(elements);
 122 |       // Elements don't overlap on Y-axis, expect multiple rows
 123 |       expect(rows.length).toBeGreaterThanOrEqual(1);
 124 |     });
 125 |   });
 126 | 
 127 |   describe("Column Grouping (X-axis overlap)", () => {
 128 |     it("should group vertically aligned elements into same column", () => {
 129 |       const elements: ElementRect[] = [
 130 |         toElementRect({ x: 10, y: 0, width: 30, height: 50 }, 0),
 131 |         toElementRect({ x: 15, y: 60, width: 30, height: 50 }, 1),
 132 |         toElementRect({ x: 12, y: 120, width: 30, height: 50 }, 2),
 133 |       ];
 134 | 
 135 |       const columns = groupIntoColumns(elements);
 136 |       expect(columns.length).toBe(1);
 137 |       expect(columns[0].length).toBe(3);
 138 |     });
 139 |   });
 140 | 
 141 |   describe("Gap Analysis", () => {
 142 |     it("should detect consistent gaps", () => {
 143 |       const gaps = [16, 16, 16, 16];
 144 |       const result = analyzeGaps(gaps);
 145 | 
 146 |       expect(result.isConsistent).toBe(true);
 147 |       expect(result.average).toBe(16);
 148 |     });
 149 | 
 150 |     it("should detect inconsistent gaps", () => {
 151 |       const gaps = [10, 30, 15, 40];
 152 |       const result = analyzeGaps(gaps);
 153 | 
 154 |       expect(result.isConsistent).toBe(false);
 155 |     });
 156 | 
 157 |     it("should handle gaps with small variance", () => {
 158 |       const gaps = [15, 16, 17, 16];
 159 |       const result = analyzeGaps(gaps);
 160 | 
 161 |       expect(result.isConsistent).toBe(true);
 162 |     });
 163 |   });
 164 | 
 165 |   describe("Alignment Detection", () => {
 166 |     it("should return alignment object with horizontal and vertical properties", () => {
 167 |       const elements: ElementRect[] = [
 168 |         toElementRect({ x: 0, y: 0, width: 100, height: 30 }, 0),
 169 |         toElementRect({ x: 0, y: 40, width: 150, height: 30 }, 1),
 170 |         toElementRect({ x: 0, y: 80, width: 80, height: 30 }, 2),
 171 |       ];
 172 |       const bounds: BoundingBox = { x: 0, y: 0, width: 200, height: 110 };
 173 | 
 174 |       const alignment = analyzeAlignment(elements, bounds);
 175 |       expect(alignment).toHaveProperty("horizontal");
 176 |       expect(alignment).toHaveProperty("vertical");
 177 |     });
 178 | 
 179 |     it("should analyze horizontal alignment", () => {
 180 |       const elements: ElementRect[] = [
 181 |         toElementRect({ x: 50, y: 0, width: 100, height: 30 }, 0),
 182 |         toElementRect({ x: 25, y: 40, width: 150, height: 30 }, 1),
 183 |         toElementRect({ x: 60, y: 80, width: 80, height: 30 }, 2),
 184 |       ];
 185 |       const bounds: BoundingBox = { x: 0, y: 0, width: 200, height: 110 };
 186 | 
 187 |       const alignment = analyzeAlignment(elements, bounds);
 188 |       expect(typeof alignment.horizontal).toBe("string");
 189 |     });
 190 | 
 191 |     it("should analyze vertical alignment", () => {
 192 |       const elements: ElementRect[] = [
 193 |         toElementRect({ x: 0, y: 0, width: 50, height: 100 }, 0),
 194 |         toElementRect({ x: 60, y: 0, width: 50, height: 80 }, 1),
 195 |         toElementRect({ x: 120, y: 0, width: 50, height: 120 }, 2),
 196 |       ];
 197 |       const bounds: BoundingBox = { x: 0, y: 0, width: 170, height: 120 };
 198 | 
 199 |       const alignment = analyzeAlignment(elements, bounds);
 200 |       expect(typeof alignment.vertical).toBe("string");
 201 |     });
 202 |   });
 203 | 
 204 |   describe("Bounds Calculation", () => {
 205 |     it("should calculate correct bounds for multiple elements", () => {
 206 |       const elements: ElementRect[] = [
 207 |         toElementRect({ x: 10, y: 20, width: 50, height: 30 }, 0),
 208 |         toElementRect({ x: 100, y: 5, width: 40, height: 60 }, 1),
 209 |       ];
 210 | 
 211 |       const bounds = calculateBounds(elements);
 212 | 
 213 |       expect(bounds.x).toBe(10);
 214 |       expect(bounds.y).toBe(5);
 215 |       expect(bounds.width).toBe(130);
 216 |       expect(bounds.height).toBe(60);
 217 |     });
 218 | 
 219 |     it("should handle empty array", () => {
 220 |       const bounds = calculateBounds([]);
 221 | 
 222 |       expect(bounds.x).toBe(0);
 223 |       expect(bounds.y).toBe(0);
 224 |       expect(bounds.width).toBe(0);
 225 |       expect(bounds.height).toBe(0);
 226 |     });
 227 |   });
 228 | 
 229 |   describe("Real Figma Data", () => {
 230 |     it("should process real Figma node data", () => {
 231 |       expect(testData).toBeDefined();
 232 |       expect(testData.type).toBe("GROUP");
 233 |       expect(testData.children).toBeDefined();
 234 |     });
 235 | 
 236 |     it("should extract child elements from real data", () => {
 237 |       const elements = extractChildElements(testData);
 238 |       expect(elements.length).toBeGreaterThan(0);
 239 | 
 240 |       elements.forEach((el) => {
 241 |         expect(typeof el.x).toBe("number");
 242 |         expect(typeof el.width).toBe("number");
 243 |         expect(typeof el.index).toBe("number");
 244 |       });
 245 |     });
 246 |   });
 247 | 
 248 |   describe("Value Clustering", () => {
 249 |     it("should cluster similar values together", () => {
 250 |       const values = [10, 12, 11, 50, 51, 52, 100, 101];
 251 |       const clusters = clusterValues(values, 3);
 252 | 
 253 |       expect(clusters.length).toBe(3);
 254 |       expect(clusters[0].count).toBe(3); // 10, 11, 12
 255 |       expect(clusters[1].count).toBe(3); // 50, 51, 52
 256 |       expect(clusters[2].count).toBe(2); // 100, 101
 257 |     });
 258 | 
 259 |     it("should handle empty array", () => {
 260 |       const clusters = clusterValues([]);
 261 |       expect(clusters.length).toBe(0);
 262 |     });
 263 | 
 264 |     it("should handle single value", () => {
 265 |       const clusters = clusterValues([42]);
 266 |       expect(clusters.length).toBe(1);
 267 |       expect(clusters[0].center).toBe(42);
 268 |     });
 269 | 
 270 |     it("should separate distant values", () => {
 271 |       const values = [0, 100, 200, 300];
 272 |       const clusters = clusterValues(values, 3);
 273 | 
 274 |       expect(clusters.length).toBe(4);
 275 |     });
 276 |   });
 277 | 
 278 |   describe("Grid Detection", () => {
 279 |     it("should detect a perfect 2x3 grid", () => {
 280 |       // 2 rows, 3 columns
 281 |       const elements: ElementRect[] = [
 282 |         // Row 1
 283 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 284 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 285 |         toElementRect({ x: 240, y: 0, width: 100, height: 50 }, 2),
 286 |         // Row 2
 287 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 3),
 288 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 4),
 289 |         toElementRect({ x: 240, y: 70, width: 100, height: 50 }, 5),
 290 |       ];
 291 | 
 292 |       const result = detectGridLayout(elements);
 293 | 
 294 |       expect(result.isGrid).toBe(true);
 295 |       expect(result.rowCount).toBe(2);
 296 |       expect(result.columnCount).toBe(3);
 297 |       expect(result.confidence).toBeGreaterThanOrEqual(0.6);
 298 |     });
 299 | 
 300 |     it("should detect a 3x2 grid", () => {
 301 |       // 3 rows, 2 columns
 302 |       const elements: ElementRect[] = [
 303 |         // Row 1
 304 |         toElementRect({ x: 0, y: 0, width: 80, height: 40 }, 0),
 305 |         toElementRect({ x: 100, y: 0, width: 80, height: 40 }, 1),
 306 |         // Row 2
 307 |         toElementRect({ x: 0, y: 60, width: 80, height: 40 }, 2),
 308 |         toElementRect({ x: 100, y: 60, width: 80, height: 40 }, 3),
 309 |         // Row 3
 310 |         toElementRect({ x: 0, y: 120, width: 80, height: 40 }, 4),
 311 |         toElementRect({ x: 100, y: 120, width: 80, height: 40 }, 5),
 312 |       ];
 313 | 
 314 |       const result = detectGridLayout(elements);
 315 | 
 316 |       expect(result.isGrid).toBe(true);
 317 |       expect(result.rowCount).toBe(3);
 318 |       expect(result.columnCount).toBe(2);
 319 |     });
 320 | 
 321 |     it("should calculate consistent row and column gaps", () => {
 322 |       const elements: ElementRect[] = [
 323 |         // Row 1 (gap = 20)
 324 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 325 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 326 |         // Row 2 (row gap = 16)
 327 |         toElementRect({ x: 0, y: 66, width: 100, height: 50 }, 2),
 328 |         toElementRect({ x: 120, y: 66, width: 100, height: 50 }, 3),
 329 |       ];
 330 | 
 331 |       const result = detectGridLayout(elements);
 332 | 
 333 |       expect(result.isGrid).toBe(true);
 334 |       expect(result.rowGap).toBe(16);
 335 |       expect(result.columnGap).toBe(20);
 336 |     });
 337 | 
 338 |     it("should generate track widths and heights", () => {
 339 |       const elements: ElementRect[] = [
 340 |         toElementRect({ x: 0, y: 0, width: 96, height: 64 }, 0), // Using common values
 341 |         toElementRect({ x: 120, y: 0, width: 80, height: 64 }, 1),
 342 |         toElementRect({ x: 0, y: 84, width: 96, height: 40 }, 2),
 343 |         toElementRect({ x: 120, y: 84, width: 80, height: 40 }, 3),
 344 |       ];
 345 | 
 346 |       const result = detectGridLayout(elements);
 347 | 
 348 |       expect(result.isGrid).toBe(true);
 349 |       expect(result.trackWidths.length).toBe(2);
 350 |       expect(result.trackHeights.length).toBe(2);
 351 |       // Track widths should be max of each column (rounded to common values)
 352 |       expect(result.trackWidths[0]).toBe(96);
 353 |       expect(result.trackWidths[1]).toBe(80);
 354 |       // Track heights should be max of each row
 355 |       expect(result.trackHeights[0]).toBe(64);
 356 |       expect(result.trackHeights[1]).toBe(40);
 357 |     });
 358 | 
 359 |     it("should build correct cell map", () => {
 360 |       const elements: ElementRect[] = [
 361 |         toElementRect({ x: 0, y: 0, width: 50, height: 50 }, 0),
 362 |         toElementRect({ x: 60, y: 0, width: 50, height: 50 }, 1),
 363 |         toElementRect({ x: 0, y: 60, width: 50, height: 50 }, 2),
 364 |         toElementRect({ x: 60, y: 60, width: 50, height: 50 }, 3),
 365 |       ];
 366 | 
 367 |       const result = detectGridLayout(elements);
 368 | 
 369 |       expect(result.cellMap.length).toBe(2); // 2 rows
 370 |       expect(result.cellMap[0].length).toBe(2); // 2 columns
 371 |       expect(result.cellMap[0][0]).toBe(0);
 372 |       expect(result.cellMap[0][1]).toBe(1);
 373 |       expect(result.cellMap[1][0]).toBe(2);
 374 |       expect(result.cellMap[1][1]).toBe(3);
 375 |     });
 376 | 
 377 |     it("should NOT detect grid for single row (flex row instead)", () => {
 378 |       const elements: ElementRect[] = [
 379 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 380 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 381 |         toElementRect({ x: 240, y: 0, width: 100, height: 50 }, 2),
 382 |       ];
 383 | 
 384 |       const result = detectGridLayout(elements);
 385 | 
 386 |       expect(result.isGrid).toBe(false);
 387 |     });
 388 | 
 389 |     it("should detect lower confidence for misaligned columns", () => {
 390 |       const elements: ElementRect[] = [
 391 |         // Row 1: columns at x=0, x=120
 392 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 393 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 394 |         // Row 2: columns at x=50, x=200 (misaligned!)
 395 |         toElementRect({ x: 50, y: 70, width: 100, height: 50 }, 2),
 396 |         toElementRect({ x: 200, y: 70, width: 100, height: 50 }, 3),
 397 |       ];
 398 | 
 399 |       const result = detectGridLayout(elements);
 400 | 
 401 |       // Misaligned columns should result in 4 column positions detected
 402 |       // and lower confidence due to alignment issues
 403 |       expect(result.columnCount).toBeGreaterThan(2);
 404 |     });
 405 | 
 406 |     it("should NOT detect grid for too few elements", () => {
 407 |       const elements: ElementRect[] = [
 408 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 409 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 410 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
 411 |       ];
 412 | 
 413 |       const result = detectGridLayout(elements);
 414 | 
 415 |       // 3 elements is not enough for a meaningful grid
 416 |       expect(result.isGrid).toBe(false);
 417 |     });
 418 | 
 419 |     it("should handle grid with varying element sizes in same column", () => {
 420 |       const elements: ElementRect[] = [
 421 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 422 |         toElementRect({ x: 120, y: 0, width: 150, height: 50 }, 1), // wider
 423 |         toElementRect({ x: 0, y: 70, width: 100, height: 80 }, 2), // taller
 424 |         toElementRect({ x: 120, y: 70, width: 150, height: 80 }, 3),
 425 |       ];
 426 | 
 427 |       const result = detectGridLayout(elements);
 428 | 
 429 |       expect(result.isGrid).toBe(true);
 430 |       // Track widths should use max width in column
 431 |       expect(result.trackWidths[1]).toBe(150);
 432 |     });
 433 |   });
 434 | 
 435 |   describe("LayoutOptimizer Grid Integration", () => {
 436 |     // Helper to create SimplifiedNode from position/size
 437 |     function createNode(
 438 |       id: string,
 439 |       left: number,
 440 |       top: number,
 441 |       width: number,
 442 |       height: number,
 443 |     ): SimplifiedNode {
 444 |       return {
 445 |         id,
 446 |         name: `Node ${id}`,
 447 |         type: "FRAME",
 448 |         cssStyles: {
 449 |           left: `${left}px`,
 450 |           top: `${top}px`,
 451 |           width: `${width}px`,
 452 |           height: `${height}px`,
 453 |         },
 454 |       };
 455 |     }
 456 | 
 457 |     it("should detect and apply Grid CSS for 2x2 grid container", () => {
 458 |       const container: SimplifiedNode = {
 459 |         id: "container",
 460 |         name: "Grid Container",
 461 |         type: "FRAME",
 462 |         cssStyles: {
 463 |           width: "260px",
 464 |           height: "140px",
 465 |         },
 466 |         children: [
 467 |           createNode("1", 0, 0, 100, 50),
 468 |           createNode("2", 120, 0, 100, 50),
 469 |           createNode("3", 0, 70, 100, 50),
 470 |           createNode("4", 120, 70, 100, 50),
 471 |         ],
 472 |       };
 473 | 
 474 |       const result = LayoutOptimizer.optimizeContainer(container);
 475 | 
 476 |       expect(result.cssStyles?.display).toBe("grid");
 477 |       expect(result.cssStyles?.gridTemplateColumns).toBeDefined();
 478 |     });
 479 | 
 480 |     it("should apply gap for Grid layout", () => {
 481 |       const container: SimplifiedNode = {
 482 |         id: "container",
 483 |         name: "Grid Container",
 484 |         type: "FRAME",
 485 |         cssStyles: {
 486 |           width: "260px",
 487 |           height: "140px",
 488 |         },
 489 |         children: [
 490 |           createNode("1", 0, 0, 100, 50),
 491 |           createNode("2", 120, 0, 100, 50), // 20px column gap
 492 |           createNode("3", 0, 70, 100, 50), // 20px row gap
 493 |           createNode("4", 120, 70, 100, 50),
 494 |         ],
 495 |       };
 496 | 
 497 |       const result = LayoutOptimizer.optimizeContainer(container);
 498 | 
 499 |       expect(result.cssStyles?.display).toBe("grid");
 500 |       // Should have gap property
 501 |       expect(
 502 |         result.cssStyles?.gap || result.cssStyles?.rowGap || result.cssStyles?.columnGap,
 503 |       ).toBeDefined();
 504 |     });
 505 | 
 506 |     it("should fall back to flex for single row", () => {
 507 |       const container: SimplifiedNode = {
 508 |         id: "container",
 509 |         name: "Row Container",
 510 |         type: "FRAME",
 511 |         cssStyles: {
 512 |           width: "260px",
 513 |           height: "50px",
 514 |         },
 515 |         children: [
 516 |           createNode("1", 0, 0, 100, 50),
 517 |           createNode("2", 120, 0, 100, 50),
 518 |           createNode("3", 240, 0, 100, 50),
 519 |         ],
 520 |       };
 521 | 
 522 |       const result = LayoutOptimizer.optimizeContainer(container);
 523 | 
 524 |       // Should be flex, not grid (single row)
 525 |       expect(result.cssStyles?.display).toBe("flex");
 526 |       expect(result.cssStyles?.gridTemplateColumns).toBeUndefined();
 527 |     });
 528 | 
 529 |     it("should generate correct gridTemplateColumns", () => {
 530 |       // Use common design values (96, 80, 64) that don't get rounded
 531 |       const container: SimplifiedNode = {
 532 |         id: "container",
 533 |         name: "Grid Container",
 534 |         type: "FRAME",
 535 |         cssStyles: {
 536 |           width: "340px",
 537 |           height: "140px",
 538 |         },
 539 |         children: [
 540 |           createNode("1", 0, 0, 96, 48),
 541 |           createNode("2", 116, 0, 80, 48), // different width column
 542 |           createNode("3", 216, 0, 96, 48),
 543 |           createNode("4", 0, 68, 96, 48),
 544 |           createNode("5", 116, 68, 80, 48),
 545 |           createNode("6", 216, 68, 96, 48),
 546 |         ],
 547 |       };
 548 | 
 549 |       const result = LayoutOptimizer.optimizeContainer(container);
 550 | 
 551 |       expect(result.cssStyles?.display).toBe("grid");
 552 |       expect(result.cssStyles?.gridTemplateColumns).toContain("96px");
 553 |       expect(result.cssStyles?.gridTemplateColumns).toContain("80px");
 554 |     });
 555 |   });
 556 | 
 557 |   // ==================== IoU and Overlap Detection Tests ====================
 558 | 
 559 |   describe("IoU (Intersection over Union) Calculation", () => {
 560 |     it("should return 0 for non-overlapping elements", () => {
 561 |       const a = toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0);
 562 |       const b = toElementRect({ x: 200, y: 0, width: 100, height: 100 }, 1);
 563 | 
 564 |       const iou = calculateIoU(a, b);
 565 |       expect(iou).toBe(0);
 566 |     });
 567 | 
 568 |     it("should return 1 for identical elements", () => {
 569 |       const a = toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0);
 570 |       const b = toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 1);
 571 | 
 572 |       const iou = calculateIoU(a, b);
 573 |       expect(iou).toBe(1);
 574 |     });
 575 | 
 576 |     it("should return correct IoU for partial overlap", () => {
 577 |       // 50% overlap on x-axis
 578 |       const a = toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0);
 579 |       const b = toElementRect({ x: 50, y: 0, width: 100, height: 100 }, 1);
 580 | 
 581 |       const iou = calculateIoU(a, b);
 582 |       // Intersection: 50 * 100 = 5000
 583 |       // Union: 100*100 + 100*100 - 5000 = 15000
 584 |       // IoU = 5000 / 15000 = 0.333...
 585 |       expect(iou).toBeCloseTo(0.333, 2);
 586 |     });
 587 | 
 588 |     it("should handle completely contained element", () => {
 589 |       const outer = toElementRect({ x: 0, y: 0, width: 200, height: 200 }, 0);
 590 |       const inner = toElementRect({ x: 50, y: 50, width: 100, height: 100 }, 1);
 591 | 
 592 |       const iou = calculateIoU(outer, inner);
 593 |       // Intersection: 100 * 100 = 10000
 594 |       // Union: 200*200 + 100*100 - 10000 = 40000 + 10000 - 10000 = 40000
 595 |       // IoU = 10000 / 40000 = 0.25
 596 |       expect(iou).toBeCloseTo(0.25, 2);
 597 |     });
 598 |   });
 599 | 
 600 |   describe("Overlap Classification", () => {
 601 |     it("should classify non-overlapping elements as 'none'", () => {
 602 |       const a = toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0);
 603 |       const b = toElementRect({ x: 200, y: 0, width: 100, height: 100 }, 1);
 604 | 
 605 |       const result = classifyOverlap(a, b);
 606 |       expect(result).toBe("none");
 607 |     });
 608 | 
 609 |     it("should classify adjacent elements as 'adjacent'", () => {
 610 |       const a = toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0);
 611 |       const b = toElementRect({ x: 101, y: 0, width: 100, height: 100 }, 1); // 1px gap
 612 | 
 613 |       const result = classifyOverlap(a, b);
 614 |       expect(result).toBe("adjacent");
 615 |     });
 616 | 
 617 |     it("should classify small overlap as 'partial'", () => {
 618 |       const a = toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0);
 619 |       const b = toElementRect({ x: 95, y: 0, width: 100, height: 100 }, 1); // 5% overlap
 620 | 
 621 |       const result = classifyOverlap(a, b);
 622 |       expect(result).toBe("partial");
 623 |     });
 624 | 
 625 |     it("should classify significant overlap as 'significant'", () => {
 626 |       const a = toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0);
 627 |       const b = toElementRect({ x: 50, y: 0, width: 100, height: 100 }, 1); // ~33% IoU
 628 | 
 629 |       const result = classifyOverlap(a, b);
 630 |       expect(result).toBe("significant");
 631 |     });
 632 | 
 633 |     it("should classify contained element as 'contained'", () => {
 634 |       const outer = toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0);
 635 |       const inner = toElementRect({ x: 10, y: 10, width: 80, height: 80 }, 1);
 636 | 
 637 |       const result = classifyOverlap(outer, inner);
 638 |       expect(result).toBe("contained");
 639 |     });
 640 |   });
 641 | 
 642 |   describe("Overlap Detection", () => {
 643 |     it("should separate overlapping elements from flow elements", () => {
 644 |       const elements: ElementRect[] = [
 645 |         toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0),
 646 |         toElementRect({ x: 50, y: 50, width: 100, height: 100 }, 1), // Overlaps with 0
 647 |         toElementRect({ x: 300, y: 0, width: 100, height: 100 }, 2), // No overlap
 648 |         toElementRect({ x: 450, y: 0, width: 100, height: 100 }, 3), // No overlap
 649 |       ];
 650 | 
 651 |       const result = detectOverlappingElements(elements, 0.1);
 652 | 
 653 |       expect(result.stackedElements.length).toBe(2); // Elements 0 and 1
 654 |       expect(result.flowElements.length).toBe(2); // Elements 2 and 3
 655 |       expect(result.stackedIndices.has(0)).toBe(true);
 656 |       expect(result.stackedIndices.has(1)).toBe(true);
 657 |     });
 658 | 
 659 |     it("should return all elements as flow when no overlap", () => {
 660 |       const elements: ElementRect[] = [
 661 |         toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0),
 662 |         toElementRect({ x: 150, y: 0, width: 100, height: 100 }, 1),
 663 |         toElementRect({ x: 300, y: 0, width: 100, height: 100 }, 2),
 664 |       ];
 665 | 
 666 |       const result = detectOverlappingElements(elements, 0.1);
 667 | 
 668 |       expect(result.stackedElements.length).toBe(0);
 669 |       expect(result.flowElements.length).toBe(3);
 670 |     });
 671 | 
 672 |     it("should detect multiple overlapping pairs", () => {
 673 |       const elements: ElementRect[] = [
 674 |         toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0),
 675 |         toElementRect({ x: 50, y: 0, width: 100, height: 100 }, 1), // Overlaps with 0
 676 |         toElementRect({ x: 300, y: 0, width: 100, height: 100 }, 2),
 677 |         toElementRect({ x: 350, y: 0, width: 100, height: 100 }, 3), // Overlaps with 2
 678 |       ];
 679 | 
 680 |       const result = detectOverlappingElements(elements, 0.1);
 681 | 
 682 |       expect(result.stackedElements.length).toBe(4); // All overlap with at least one
 683 |     });
 684 | 
 685 |     it("should use custom IoU threshold", () => {
 686 |       const elements: ElementRect[] = [
 687 |         toElementRect({ x: 0, y: 0, width: 100, height: 100 }, 0),
 688 |         toElementRect({ x: 80, y: 0, width: 100, height: 100 }, 1), // ~11% IoU
 689 |       ];
 690 | 
 691 |       // With 0.1 threshold, should detect overlap
 692 |       const result1 = detectOverlappingElements(elements, 0.1);
 693 |       expect(result1.stackedElements.length).toBe(2);
 694 | 
 695 |       // With 0.5 threshold, should NOT detect overlap
 696 |       const result2 = detectOverlappingElements(elements, 0.5);
 697 |       expect(result2.stackedElements.length).toBe(0);
 698 |     });
 699 |   });
 700 | 
 701 |   describe("Child Style Cleanup", () => {
 702 |     // Helper to create SimplifiedNode with absolute positioning
 703 |     function createAbsoluteNode(
 704 |       id: string,
 705 |       left: number,
 706 |       top: number,
 707 |       width: number,
 708 |       height: number,
 709 |     ): SimplifiedNode {
 710 |       return {
 711 |         id,
 712 |         name: `Node ${id}`,
 713 |         type: "FRAME",
 714 |         cssStyles: {
 715 |           position: "absolute",
 716 |           left: `${left}px`,
 717 |           top: `${top}px`,
 718 |           width: `${width}px`,
 719 |           height: `${height}px`,
 720 |         },
 721 |       };
 722 |     }
 723 | 
 724 |     it("should remove position:absolute from children when parent becomes flex", () => {
 725 |       const child = createAbsoluteNode("1", 0, 0, 100, 50);
 726 |       const cleaned = LayoutOptimizer.cleanChildStylesForLayout(child, "flex");
 727 | 
 728 |       expect(cleaned.cssStyles?.position).toBeUndefined();
 729 |     });
 730 | 
 731 |     it("should remove left/top from children when parent becomes flex", () => {
 732 |       const child = createAbsoluteNode("1", 100, 200, 100, 50);
 733 |       const cleaned = LayoutOptimizer.cleanChildStylesForLayout(child, "flex");
 734 | 
 735 |       expect(cleaned.cssStyles?.left).toBeUndefined();
 736 |       expect(cleaned.cssStyles?.top).toBeUndefined();
 737 |     });
 738 | 
 739 |     it("should keep width/height for flex children", () => {
 740 |       const child = createAbsoluteNode("1", 0, 0, 100, 50);
 741 |       const cleaned = LayoutOptimizer.cleanChildStylesForLayout(child, "flex");
 742 | 
 743 |       expect(cleaned.cssStyles?.width).toBe("100px");
 744 |       expect(cleaned.cssStyles?.height).toBe("50px");
 745 |     });
 746 | 
 747 |     it("should remove all position properties for grid children", () => {
 748 |       const child: SimplifiedNode = {
 749 |         id: "1",
 750 |         name: "Node 1",
 751 |         type: "FRAME",
 752 |         cssStyles: {
 753 |           position: "absolute",
 754 |           left: "10px",
 755 |           top: "20px",
 756 |           right: "30px",
 757 |           bottom: "40px",
 758 |           width: "100px",
 759 |           height: "50px",
 760 |         },
 761 |       };
 762 | 
 763 |       const cleaned = LayoutOptimizer.cleanChildStylesForLayout(child, "grid");
 764 | 
 765 |       expect(cleaned.cssStyles?.position).toBeUndefined();
 766 |       expect(cleaned.cssStyles?.left).toBeUndefined();
 767 |       expect(cleaned.cssStyles?.top).toBeUndefined();
 768 |       expect(cleaned.cssStyles?.right).toBeUndefined();
 769 |       expect(cleaned.cssStyles?.bottom).toBeUndefined();
 770 |     });
 771 | 
 772 |     it("should skip cleaning for stacked elements", () => {
 773 |       const children: SimplifiedNode[] = [
 774 |         createAbsoluteNode("1", 0, 0, 100, 50),
 775 |         createAbsoluteNode("2", 50, 0, 100, 50), // Overlapping
 776 |       ];
 777 | 
 778 |       // Mark index 0 and 1 as stacked
 779 |       const stackedIndices = new Set([0, 1]);
 780 |       const cleaned = LayoutOptimizer.cleanChildrenStyles(children, "flex", stackedIndices);
 781 | 
 782 |       // Stacked elements should keep their absolute positioning
 783 |       expect(cleaned[0].cssStyles?.position).toBe("absolute");
 784 |       expect(cleaned[1].cssStyles?.position).toBe("absolute");
 785 |     });
 786 | 
 787 |     it("should remove default CSS values", () => {
 788 |       const styles = {
 789 |         fontWeight: "400",
 790 |         textAlign: "left",
 791 |         opacity: "1",
 792 |         backgroundColor: "transparent",
 793 |         width: "100px",
 794 |         color: "#000",
 795 |       };
 796 | 
 797 |       const cleaned = LayoutOptimizer.removeDefaultValues(styles);
 798 | 
 799 |       expect(cleaned.fontWeight).toBeUndefined();
 800 |       expect(cleaned.textAlign).toBeUndefined();
 801 |       expect(cleaned.opacity).toBeUndefined();
 802 |       expect(cleaned.backgroundColor).toBeUndefined();
 803 |       expect(cleaned.width).toBe("100px"); // Keep non-default
 804 |       expect(cleaned.color).toBe("#000"); // Keep non-default
 805 |     });
 806 | 
 807 |     it("should remove 0px position values", () => {
 808 |       const styles = {
 809 |         left: "0px",
 810 |         top: "0",
 811 |         right: "10px",
 812 |         width: "100px",
 813 |       };
 814 | 
 815 |       const cleaned = LayoutOptimizer.removeDefaultValues(styles);
 816 | 
 817 |       expect(cleaned.left).toBeUndefined();
 818 |       expect(cleaned.top).toBeUndefined();
 819 |       expect(cleaned.right).toBe("10px"); // Keep non-zero
 820 |       expect(cleaned.width).toBe("100px");
 821 |     });
 822 |   });
 823 | 
 824 |   describe("Integrated Overlap and Cleanup in optimizeContainer", () => {
 825 |     function createAbsoluteNode(
 826 |       id: string,
 827 |       left: number,
 828 |       top: number,
 829 |       width: number,
 830 |       height: number,
 831 |     ): SimplifiedNode {
 832 |       return {
 833 |         id,
 834 |         name: `Node ${id}`,
 835 |         type: "FRAME",
 836 |         cssStyles: {
 837 |           position: "absolute",
 838 |           left: `${left}px`,
 839 |           top: `${top}px`,
 840 |           width: `${width}px`,
 841 |           height: `${height}px`,
 842 |         },
 843 |       };
 844 |     }
 845 | 
 846 |     it("should clean child styles when parent becomes flex", () => {
 847 |       const container: SimplifiedNode = {
 848 |         id: "container",
 849 |         name: "Flex Container",
 850 |         type: "FRAME",
 851 |         cssStyles: { width: "500px", height: "100px" },
 852 |         children: [
 853 |           createAbsoluteNode("1", 0, 0, 100, 50),
 854 |           createAbsoluteNode("2", 120, 0, 100, 50),
 855 |           createAbsoluteNode("3", 240, 0, 100, 50),
 856 |         ],
 857 |       };
 858 | 
 859 |       const result = LayoutOptimizer.optimizeContainer(container);
 860 | 
 861 |       expect(result.cssStyles?.display).toBe("flex");
 862 |       // Children should have position:absolute removed
 863 |       result.children?.forEach((child) => {
 864 |         expect(child.cssStyles?.position).toBeUndefined();
 865 |         expect(child.cssStyles?.left).toBeUndefined();
 866 |         expect(child.cssStyles?.top).toBeUndefined();
 867 |       });
 868 |     });
 869 | 
 870 |     it("should clean child styles when parent becomes grid", () => {
 871 |       const container: SimplifiedNode = {
 872 |         id: "container",
 873 |         name: "Grid Container",
 874 |         type: "FRAME",
 875 |         cssStyles: { width: "260px", height: "140px" },
 876 |         children: [
 877 |           createAbsoluteNode("1", 0, 0, 100, 50),
 878 |           createAbsoluteNode("2", 120, 0, 100, 50),
 879 |           createAbsoluteNode("3", 0, 70, 100, 50),
 880 |           createAbsoluteNode("4", 120, 70, 100, 50),
 881 |         ],
 882 |       };
 883 | 
 884 |       const result = LayoutOptimizer.optimizeContainer(container);
 885 | 
 886 |       expect(result.cssStyles?.display).toBe("grid");
 887 |       // Children should have position:absolute removed
 888 |       result.children?.forEach((child) => {
 889 |         expect(child.cssStyles?.position).toBeUndefined();
 890 |         expect(child.cssStyles?.left).toBeUndefined();
 891 |         expect(child.cssStyles?.top).toBeUndefined();
 892 |       });
 893 |     });
 894 |   });
 895 | 
 896 |   describe("Homogeneity Analysis", () => {
 897 |     it("should detect homogeneous elements with similar sizes", () => {
 898 |       const elements: ElementRect[] = [
 899 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 900 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 901 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
 902 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
 903 |       ];
 904 | 
 905 |       const result = analyzeHomogeneity(elements);
 906 | 
 907 |       expect(result.isHomogeneous).toBe(true);
 908 |       expect(result.widthCV).toBe(0);
 909 |       expect(result.heightCV).toBe(0);
 910 |       expect(result.homogeneousElements.length).toBe(4);
 911 |       expect(result.outlierElements.length).toBe(0);
 912 |     });
 913 | 
 914 |     it("should detect non-homogeneous elements with varying sizes", () => {
 915 |       const elements: ElementRect[] = [
 916 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 917 |         toElementRect({ x: 120, y: 0, width: 200, height: 100 }, 1), // Much larger
 918 |         toElementRect({ x: 0, y: 70, width: 50, height: 25 }, 2), // Much smaller
 919 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
 920 |       ];
 921 | 
 922 |       const result = analyzeHomogeneity(elements);
 923 | 
 924 |       // Elements have high size variance
 925 |       expect(result.widthCV).toBeGreaterThan(0.2);
 926 |     });
 927 | 
 928 |     it("should return not homogeneous for fewer than 4 elements", () => {
 929 |       const elements: ElementRect[] = [
 930 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 931 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 932 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
 933 |       ];
 934 | 
 935 |       const result = analyzeHomogeneity(elements);
 936 | 
 937 |       expect(result.isHomogeneous).toBe(false);
 938 |     });
 939 | 
 940 |     it("should filter by node types when provided", () => {
 941 |       const elements: ElementRect[] = [
 942 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 943 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 944 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
 945 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
 946 |       ];
 947 |       const nodeTypes = ["FRAME", "FRAME", "FRAME", "FRAME"];
 948 | 
 949 |       const result = analyzeHomogeneity(elements, nodeTypes);
 950 | 
 951 |       expect(result.isHomogeneous).toBe(true);
 952 |       expect(result.types).toContain("FRAME");
 953 |     });
 954 | 
 955 |     it("should reject incompatible node types", () => {
 956 |       const elements: ElementRect[] = [
 957 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 958 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 959 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
 960 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
 961 |       ];
 962 |       // TEXT nodes are not allowed in grid
 963 |       const nodeTypes = ["TEXT", "TEXT", "TEXT", "TEXT"];
 964 | 
 965 |       const result = analyzeHomogeneity(elements, nodeTypes);
 966 | 
 967 |       expect(result.isHomogeneous).toBe(false);
 968 |     });
 969 | 
 970 |     it("should separate outliers from homogeneous elements", () => {
 971 |       const elements: ElementRect[] = [
 972 |         // 4 similar-sized elements
 973 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 974 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
 975 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
 976 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
 977 |         // 1 outlier (much larger)
 978 |         toElementRect({ x: 0, y: 140, width: 300, height: 200 }, 4),
 979 |       ];
 980 | 
 981 |       const result = analyzeHomogeneity(elements);
 982 | 
 983 |       expect(result.isHomogeneous).toBe(true);
 984 |       expect(result.homogeneousElements.length).toBe(4);
 985 |       expect(result.outlierElements.length).toBe(1);
 986 |       expect(result.outlierElements[0].index).toBe(4);
 987 |     });
 988 | 
 989 |     it("should use custom size tolerance", () => {
 990 |       const elements: ElementRect[] = [
 991 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
 992 |         toElementRect({ x: 120, y: 0, width: 110, height: 55 }, 1), // 10% larger
 993 |         toElementRect({ x: 0, y: 70, width: 90, height: 45 }, 2), // 10% smaller
 994 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
 995 |       ];
 996 | 
 997 |       // With strict tolerance (5%), should fail
 998 |       const strictResult = analyzeHomogeneity(elements, undefined, 0.05);
 999 |       expect(strictResult.isHomogeneous).toBe(false);
1000 | 
1001 |       // With relaxed tolerance (25%), should pass
1002 |       const relaxedResult = analyzeHomogeneity(elements, undefined, 0.25);
1003 |       expect(relaxedResult.isHomogeneous).toBe(true);
1004 |     });
1005 |   });
1006 | 
1007 |   describe("Filter Homogeneous For Grid", () => {
1008 |     it("should return homogeneous elements for grid detection", () => {
1009 |       const elements: ElementRect[] = [
1010 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
1011 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
1012 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
1013 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
1014 |       ];
1015 | 
1016 |       const result = filterHomogeneousForGrid(elements);
1017 | 
1018 |       expect(result.elements.length).toBe(4);
1019 |       expect(result.gridIndices.size).toBe(4);
1020 |     });
1021 | 
1022 |     it("should return empty array for non-homogeneous elements", () => {
1023 |       const elements: ElementRect[] = [
1024 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
1025 |         toElementRect({ x: 120, y: 0, width: 300, height: 200 }, 1), // Very different
1026 |         toElementRect({ x: 0, y: 70, width: 50, height: 25 }, 2), // Very different
1027 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
1028 |       ];
1029 | 
1030 |       const result = filterHomogeneousForGrid(elements);
1031 | 
1032 |       // Not enough homogeneous elements
1033 |       expect(result.elements.length).toBeLessThan(4);
1034 |     });
1035 | 
1036 |     it("should return empty array for fewer than 4 elements", () => {
1037 |       const elements: ElementRect[] = [
1038 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
1039 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
1040 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
1041 |       ];
1042 | 
1043 |       const result = filterHomogeneousForGrid(elements);
1044 | 
1045 |       expect(result.elements.length).toBe(0);
1046 |       expect(result.gridIndices.size).toBe(0);
1047 |     });
1048 | 
1049 |     it("should filter out outliers and return only homogeneous elements", () => {
1050 |       const elements: ElementRect[] = [
1051 |         // 4 similar-sized elements (grid candidates)
1052 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
1053 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
1054 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
1055 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
1056 |         // Outlier (header or title)
1057 |         toElementRect({ x: 0, y: -30, width: 240, height: 20 }, 4),
1058 |       ];
1059 | 
1060 |       const result = filterHomogeneousForGrid(elements);
1061 | 
1062 |       expect(result.elements.length).toBe(4);
1063 |       // Outlier should not be included
1064 |       expect(result.elements.every((e) => e.index !== 4)).toBe(true);
1065 |       expect(result.gridIndices.has(4)).toBe(false);
1066 |     });
1067 | 
1068 |     it("should work with node types filtering", () => {
1069 |       const elements: ElementRect[] = [
1070 |         toElementRect({ x: 0, y: 0, width: 100, height: 50 }, 0),
1071 |         toElementRect({ x: 120, y: 0, width: 100, height: 50 }, 1),
1072 |         toElementRect({ x: 0, y: 70, width: 100, height: 50 }, 2),
1073 |         toElementRect({ x: 120, y: 70, width: 100, height: 50 }, 3),
1074 |       ];
1075 |       const nodeTypes = ["INSTANCE", "INSTANCE", "INSTANCE", "INSTANCE"];
1076 | 
1077 |       const result = filterHomogeneousForGrid(elements, nodeTypes);
1078 | 
1079 |       expect(result.elements.length).toBe(4);
1080 |       expect(result.gridIndices.size).toBe(4);
1081 |     });
1082 |   });
1083 | 
1084 |   describe("Grid Detection with Real Data", () => {
1085 |     let testData: FigmaNode;
1086 | 
1087 |     beforeAll(() => {
1088 |       testData = loadTestData();
1089 |     });
1090 | 
1091 |     it("should detect grid layout in real Figma data", () => {
1092 |       // Find any container with 4+ children that might form a grid
1093 |       function findGridCandidate(node: FigmaNode): FigmaNode | null {
1094 |         if (node.children && node.children.length >= 4) {
1095 |           const childBoxes = node.children
1096 |             .filter((c) => c.absoluteBoundingBox)
1097 |             .map((c, i) => toElementRect(c.absoluteBoundingBox!, i));
1098 | 
1099 |           if (childBoxes.length >= 4) {
1100 |             const gridResult = detectGridLayout(childBoxes);
1101 |             if (gridResult.isGrid && gridResult.rowCount >= 2) {
1102 |               return node;
1103 |             }
1104 |           }
1105 |         }
1106 | 
1107 |         if (node.children) {
1108 |           for (const child of node.children) {
1109 |             const found = findGridCandidate(child);
1110 |             if (found) return found;
1111 |           }
1112 |         }
1113 |         return null;
1114 |       }
1115 | 
1116 |       const gridCandidate = findGridCandidate(testData);
1117 | 
1118 |       // Test passes if we either find a grid or don't (depends on fixture data)
1119 |       if (gridCandidate) {
1120 |         const childBoxes = gridCandidate
1121 |           .children!.filter((c) => c.absoluteBoundingBox)
1122 |           .map((c, i) => toElementRect(c.absoluteBoundingBox!, i));
1123 | 
1124 |         const gridResult = detectGridLayout(childBoxes);
1125 | 
1126 |         expect(gridResult.isGrid).toBe(true);
1127 |         expect(gridResult.rowCount).toBeGreaterThanOrEqual(2);
1128 |         expect(gridResult.columnCount).toBeGreaterThanOrEqual(2);
1129 |         expect(gridResult.trackWidths.length).toBe(gridResult.columnCount);
1130 |       }
1131 |     });
1132 | 
1133 |     it("should filter homogeneous elements before grid detection", () => {
1134 |       // Find a container with mixed children
1135 |       function findMixedContainer(node: FigmaNode): FigmaNode | null {
1136 |         if (node.children && node.children.length >= 5) {
1137 |           const sizes = node.children
1138 |             .filter((c) => c.absoluteBoundingBox)
1139 |             .map((c) => c.absoluteBoundingBox!.width * c.absoluteBoundingBox!.height);
1140 | 
1141 |           if (sizes.length >= 5) {
1142 |             const maxSize = Math.max(...sizes);
1143 |             const minSize = Math.min(...sizes);
1144 |             // Look for containers where max is at least 2x min (mixed sizes)
1145 |             if (maxSize > minSize * 2) {
1146 |               return node;
1147 |             }
1148 |           }
1149 |         }
1150 | 
1151 |         if (node.children) {
1152 |           for (const child of node.children) {
1153 |             const found = findMixedContainer(child);
1154 |             if (found) return found;
1155 |           }
1156 |         }
1157 |         return null;
1158 |       }
1159 | 
1160 |       const mixedContainer = findMixedContainer(testData);
1161 | 
1162 |       if (mixedContainer) {
1163 |         const childBoxes = mixedContainer
1164 |           .children!.filter((c) => c.absoluteBoundingBox)
1165 |           .map((c, i) => toElementRect(c.absoluteBoundingBox!, i));
1166 | 
1167 |         const nodeTypes = mixedContainer.children!.map((c) => c.type);
1168 | 
1169 |         // Filter should reduce the set
1170 |         const result = filterHomogeneousForGrid(childBoxes, nodeTypes);
1171 | 
1172 |         // Filtered should be less than or equal to original
1173 |         expect(result.elements.length).toBeLessThanOrEqual(childBoxes.length);
1174 |       }
1175 |     });
1176 |   });
1177 | 
1178 |   // ==================== Absolute to Relative Position Conversion ====================
1179 |   describe("Absolute to Relative Position Conversion", () => {
1180 |     describe("collectFlowChildOffsets", () => {
1181 |       it("should collect offsets from flow children only", () => {
1182 |         const children: SimplifiedNode[] = [
1183 |           {
1184 |             id: "1",
1185 |             name: "child1",
1186 |             type: "FRAME",
1187 |             cssStyles: { left: "10px", top: "20px", width: "100px", height: "50px" },
1188 |           },
1189 |           {
1190 |             id: "2",
1191 |             name: "child2",
1192 |             type: "FRAME",
1193 |             cssStyles: { left: "120px", top: "20px", width: "100px", height: "50px" },
1194 |           },
1195 |           {
1196 |             id: "3",
1197 |             name: "stacked",
1198 |             type: "FRAME",
1199 |             cssStyles: { left: "50px", top: "30px", width: "80px", height: "40px" },
1200 |           },
1201 |         ];
1202 | 
1203 |         // Child index 2 is stacked (overlapping)
1204 |         const stackedIndices = new Set([2]);
1205 | 
1206 |         const offsets = LayoutOptimizer.collectFlowChildOffsets(children, stackedIndices);
1207 | 
1208 |         expect(offsets.length).toBe(2); // Only 2 flow children
1209 |         expect(offsets[0].index).toBe(0);
1210 |         expect(offsets[0].left).toBe(10);
1211 |         expect(offsets[0].top).toBe(20);
1212 |         expect(offsets[1].index).toBe(1);
1213 |         expect(offsets[1].left).toBe(120);
1214 |       });
1215 | 
1216 |       it("should skip children without cssStyles", () => {
1217 |         const children: SimplifiedNode[] = [
1218 |           { id: "1", name: "child1", type: "FRAME" },
1219 |           {
1220 |             id: "2",
1221 |             name: "child2",
1222 |             type: "FRAME",
1223 |             cssStyles: { left: "10px", top: "20px", width: "100px", height: "50px" },
1224 |           },
1225 |         ];
1226 | 
1227 |         const offsets = LayoutOptimizer.collectFlowChildOffsets(children, new Set());
1228 | 
1229 |         expect(offsets.length).toBe(1);
1230 |         expect(offsets[0].index).toBe(1);
1231 |       });
1232 |     });
1233 | 
1234 |     describe("inferContainerPadding", () => {
1235 |       it("should infer padding from child offsets", () => {
1236 |         const offsets = [
1237 |           { index: 0, left: 20, top: 15, width: 100, height: 50, right: 120, bottom: 65 },
1238 |           { index: 1, left: 130, top: 15, width: 100, height: 50, right: 230, bottom: 65 },
1239 |         ];
1240 | 
1241 |         const padding = LayoutOptimizer.inferContainerPadding(offsets, 250, 80, "row");
1242 | 
1243 |         expect(padding.paddingLeft).toBe(20);
1244 |         expect(padding.paddingTop).toBe(15);
1245 |         expect(padding.paddingRight).toBe(20); // 250 - 230 = 20
1246 |         expect(padding.paddingBottom).toBe(15); // 80 - 65 = 15
1247 |       });
1248 | 
1249 |       it("should return zero padding for small offsets (<= 2px)", () => {
1250 |         const offsets = [
1251 |           { index: 0, left: 1, top: 2, width: 100, height: 50, right: 101, bottom: 52 },
1252 |         ];
1253 | 
1254 |         const padding = LayoutOptimizer.inferContainerPadding(offsets, 103, 54, "row");
1255 | 
1256 |         expect(padding.paddingLeft).toBe(0); // 1 <= 2
1257 |         expect(padding.paddingTop).toBe(0); // 2 <= 2
1258 |         expect(padding.paddingRight).toBe(0); // 103 - 101 = 2 <= 2
1259 |         expect(padding.paddingBottom).toBe(0); // 54 - 52 = 2 <= 2
1260 |       });
1261 | 
1262 |       it("should return zero padding for empty offsets", () => {
1263 |         const padding = LayoutOptimizer.inferContainerPadding([], 100, 100, "row");
1264 | 
1265 |         expect(padding.paddingLeft).toBe(0);
1266 |         expect(padding.paddingTop).toBe(0);
1267 |         expect(padding.paddingRight).toBe(0);
1268 |         expect(padding.paddingBottom).toBe(0);
1269 |       });
1270 |     });
1271 | 
1272 |     describe("calculateChildMargins", () => {
1273 |       it("should calculate marginTop for row layout with flex-start alignment", () => {
1274 |         const offsets = [
1275 |           { index: 0, left: 10, top: 10, width: 100, height: 50, right: 110, bottom: 60 },
1276 |           { index: 1, left: 120, top: 25, width: 100, height: 30, right: 220, bottom: 55 }, // offset 15px down
1277 |         ];
1278 |         const padding = { paddingTop: 10, paddingRight: 10, paddingBottom: 10, paddingLeft: 10 };
1279 | 
1280 |         const margins = LayoutOptimizer.calculateChildMargins(
1281 |           offsets,
1282 |           padding,
1283 |           "row",
1284 |           "flex-start",
1285 |         );
1286 | 
1287 |         expect(margins.get(0)).toBeUndefined(); // No margin needed
1288 |         expect(margins.get(1)?.marginTop).toBe(15); // 25 - 10 = 15
1289 |       });
1290 | 
1291 |       it("should calculate marginLeft for column layout with flex-start alignment", () => {
1292 |         const offsets = [
1293 |           { index: 0, left: 10, top: 10, width: 100, height: 50, right: 110, bottom: 60 },
1294 |           { index: 1, left: 30, top: 70, width: 80, height: 50, right: 110, bottom: 120 }, // offset 20px right
1295 |         ];
1296 |         const padding = { paddingTop: 10, paddingRight: 10, paddingBottom: 10, paddingLeft: 10 };
1297 | 
1298 |         const margins = LayoutOptimizer.calculateChildMargins(
1299 |           offsets,
1300 |           padding,
1301 |           "column",
1302 |           "flex-start",
1303 |         );
1304 | 
1305 |         expect(margins.get(0)).toBeUndefined(); // No margin needed
1306 |         expect(margins.get(1)?.marginLeft).toBe(20); // 30 - 10 = 20
1307 |       });
1308 | 
1309 |       it("should not add margins for center alignment", () => {
1310 |         const offsets = [
1311 |           { index: 0, left: 10, top: 10, width: 100, height: 50, right: 110, bottom: 60 },
1312 |           { index: 1, left: 120, top: 25, width: 100, height: 30, right: 220, bottom: 55 },
1313 |         ];
1314 |         const padding = { paddingTop: 10, paddingRight: 10, paddingBottom: 10, paddingLeft: 10 };
1315 | 
1316 |         const margins = LayoutOptimizer.calculateChildMargins(offsets, padding, "row", "center");
1317 | 
1318 |         expect(margins.size).toBe(0); // No margins for center alignment
1319 |       });
1320 |     });
1321 | 
1322 |     describe("generatePaddingCSS", () => {
1323 |       it("should generate single value when all padding is equal", () => {
1324 |         const padding = { paddingTop: 10, paddingRight: 10, paddingBottom: 10, paddingLeft: 10 };
1325 |         const css = LayoutOptimizer.generatePaddingCSS(padding);
1326 |         expect(css).toBe("10px");
1327 |       });
1328 | 
1329 |       it("should generate two values when top/bottom and left/right are equal", () => {
1330 |         const padding = { paddingTop: 10, paddingRight: 20, paddingBottom: 10, paddingLeft: 20 };
1331 |         const css = LayoutOptimizer.generatePaddingCSS(padding);
1332 |         expect(css).toBe("10px 20px");
1333 |       });
1334 | 
1335 |       it("should generate three values when left/right are equal", () => {
1336 |         const padding = { paddingTop: 10, paddingRight: 20, paddingBottom: 30, paddingLeft: 20 };
1337 |         const css = LayoutOptimizer.generatePaddingCSS(padding);
1338 |         expect(css).toBe("10px 20px 30px");
1339 |       });
1340 | 
1341 |       it("should generate four values when all padding is different", () => {
1342 |         const padding = { paddingTop: 10, paddingRight: 20, paddingBottom: 30, paddingLeft: 40 };
1343 |         const css = LayoutOptimizer.generatePaddingCSS(padding);
1344 |         expect(css).toBe("10px 20px 30px 40px");
1345 |       });
1346 | 
1347 |       it("should return null when all padding is zero", () => {
1348 |         const padding = { paddingTop: 0, paddingRight: 0, paddingBottom: 0, paddingLeft: 0 };
1349 |         const css = LayoutOptimizer.generatePaddingCSS(padding);
1350 |         expect(css).toBeNull();
1351 |       });
1352 |     });
1353 | 
1354 |     describe("convertAbsoluteToRelative", () => {
1355 |       it("should convert absolute positioning to padding and clean children", () => {
1356 |         const parent: SimplifiedNode = {
1357 |           id: "parent",
1358 |           name: "container",
1359 |           type: "FRAME",
1360 |           cssStyles: { width: "300px", height: "100px" },
1361 |         };
1362 | 
1363 |         const children: SimplifiedNode[] = [
1364 |           {
1365 |             id: "1",
1366 |             name: "child1",
1367 |             type: "FRAME",
1368 |             cssStyles: {
1369 |               position: "absolute",
1370 |               left: "20px",
1371 |               top: "10px",
1372 |               width: "100px",
1373 |               height: "80px",
1374 |             },
1375 |           },
1376 |           {
1377 |             id: "2",
1378 |             name: "child2",
1379 |             type: "FRAME",
1380 |             cssStyles: {
1381 |               position: "absolute",
1382 |               left: "140px",
1383 |               top: "10px",
1384 |               width: "140px",
1385 |               height: "80px",
1386 |             },
1387 |           },
1388 |         ];
1389 | 
1390 |         const result = LayoutOptimizer.convertAbsoluteToRelative(
1391 |           parent,
1392 |           children,
1393 |           "flex",
1394 |           "row",
1395 |           new Set(),
1396 |           "flex-start",
1397 |         );
1398 | 
1399 |         // Should have padding
1400 |         expect(result.parentPaddingStyle).toBe("10px 20px");
1401 | 
1402 |         // Children should not have position: absolute or left/top
1403 |         expect(result.convertedChildren[0].cssStyles?.position).toBeUndefined();
1404 |         expect(result.convertedChildren[0].cssStyles?.left).toBeUndefined();
1405 |         expect(result.convertedChildren[0].cssStyles?.top).toBeUndefined();
1406 |         expect(result.convertedChildren[1].cssStyles?.position).toBeUndefined();
1407 |       });
1408 | 
1409 |       it("should keep stacked elements with absolute positioning", () => {
1410 |         const parent: SimplifiedNode = {
1411 |           id: "parent",
1412 |           name: "container",
1413 |           type: "FRAME",
1414 |           cssStyles: { width: "200px", height: "100px" },
1415 |         };
1416 | 
1417 |         const children: SimplifiedNode[] = [
1418 |           {
1419 |             id: "1",
1420 |             name: "background",
1421 |             type: "RECTANGLE",
1422 |             cssStyles: {
1423 |               position: "absolute",
1424 |               left: "0px",
1425 |               top: "0px",
1426 |               width: "200px",
1427 |               height: "100px",
1428 |             },
1429 |           },
1430 |           {
1431 |             id: "2",
1432 |             name: "content",
1433 |             type: "FRAME",
1434 |             cssStyles: {
1435 |               position: "absolute",
1436 |               left: "20px",
1437 |               top: "10px",
1438 |               width: "160px",
1439 |               height: "80px",
1440 |             },
1441 |           },
1442 |         ];
1443 | 
1444 |         // Child 0 is stacked (background)
1445 |         const stackedIndices = new Set([0]);
1446 | 
1447 |         const result = LayoutOptimizer.convertAbsoluteToRelative(
1448 |           parent,
1449 |           children,
1450 |           "flex",
1451 |           "row",
1452 |           stackedIndices,
1453 |           "flex-start",
1454 |         );
1455 | 
1456 |         // Stacked element should keep absolute positioning
1457 |         expect(result.convertedChildren[0].cssStyles?.position).toBe("absolute");
1458 |         expect(result.convertedChildren[0].cssStyles?.left).toBe("0px");
1459 | 
1460 |         // Flow element should be cleaned
1461 |         expect(result.convertedChildren[1].cssStyles?.position).toBeUndefined();
1462 |       });
1463 |     });
1464 |   });
1465 | 
1466 |   // ==================== Background Element Detection Tests ====================
1467 |   describe("detectBackgroundElement", () => {
1468 |     it("should detect background element at origin matching parent size", () => {
1469 |       const rects: ElementRect[] = [
1470 |         {
1471 |           x: 0,
1472 |           y: 0,
1473 |           width: 400,
1474 |           height: 300,
1475 |           index: 0,
1476 |           right: 400,
1477 |           bottom: 300,
1478 |           centerX: 200,
1479 |           centerY: 150,
1480 |         },
1481 |         {
1482 |           x: 20,
1483 |           y: 20,
1484 |           width: 100,
1485 |           height: 50,
1486 |           index: 1,
1487 |           right: 120,
1488 |           bottom: 70,
1489 |           centerX: 70,
1490 |           centerY: 45,
1491 |         },
1492 |         {
1493 |           x: 150,
1494 |           y: 100,
1495 |           width: 80,
1496 |           height: 40,
1497 |           index: 2,
1498 |           right: 230,
1499 |           bottom: 140,
1500 |           centerX: 190,
1501 |           centerY: 120,
1502 |         },
1503 |       ];
1504 | 
1505 |       const result = detectBackgroundElement(rects, 400, 300);
1506 | 
1507 |       expect(result.hasBackground).toBe(true);
1508 |       expect(result.backgroundIndex).toBe(0);
1509 |       expect(result.contentIndices).toEqual([1, 2]);
1510 |     });
1511 | 
1512 |     it("should not detect background when no element matches parent size", () => {
1513 |       const rects: ElementRect[] = [
1514 |         {
1515 |           x: 10,
1516 |           y: 10,
1517 |           width: 200,
1518 |           height: 150,
1519 |           index: 0,
1520 |           right: 210,
1521 |           bottom: 160,
1522 |           centerX: 110,
1523 |           centerY: 85,
1524 |         },
1525 |         {
1526 |           x: 50,
1527 |           y: 50,
1528 |           width: 100,
1529 |           height: 50,
1530 |           index: 1,
1531 |           right: 150,
1532 |           bottom: 100,
1533 |           centerX: 100,
1534 |           centerY: 75,
1535 |         },
1536 |       ];
1537 | 
1538 |       const result = detectBackgroundElement(rects, 400, 300);
1539 | 
1540 |       expect(result.hasBackground).toBe(false);
1541 |       expect(result.backgroundIndex).toBe(-1);
1542 |     });
1543 | 
1544 |     it("should detect background with small tolerance (within 5%)", () => {
1545 |       const rects: ElementRect[] = [
1546 |         {
1547 |           x: 0,
1548 |           y: 0,
1549 |           width: 395,
1550 |           height: 290,
1551 |           index: 0,
1552 |           right: 395,
1553 |           bottom: 290,
1554 |           centerX: 197.5,
1555 |           centerY: 145,
1556 |         },
1557 |         {
1558 |           x: 20,
1559 |           y: 20,
1560 |           width: 100,
1561 |           height: 50,
1562 |           index: 1,
1563 |           right: 120,
1564 |           bottom: 70,
1565 |           centerX: 70,
1566 |           centerY: 45,
1567 |         },
1568 |       ];
1569 | 
1570 |       const result = detectBackgroundElement(rects, 400, 300);
1571 | 
1572 |       expect(result.hasBackground).toBe(true);
1573 |       expect(result.backgroundIndex).toBe(0);
1574 |     });
1575 | 
1576 |     it("should return empty result for single element", () => {
1577 |       const rects: ElementRect[] = [
1578 |         {
1579 |           x: 0,
1580 |           y: 0,
1581 |           width: 400,
1582 |           height: 300,
1583 |           index: 0,
1584 |           right: 400,
1585 |           bottom: 300,
1586 |           centerX: 200,
1587 |           centerY: 150,
1588 |         },
1589 |       ];
1590 | 
1591 |       const result = detectBackgroundElement(rects, 400, 300);
1592 | 
1593 |       expect(result.hasBackground).toBe(false);
1594 |     });
1595 |   });
1596 | 
1597 |   // ==================== Background Style Extraction Tests ====================
1598 |   describe("extractBackgroundStyles", () => {
1599 |     it("should extract backgroundColor from background element", () => {
1600 |       const bgChild: SimplifiedNode = {
1601 |         id: "bg-1",
1602 |         name: "Background",
1603 |         type: "RECTANGLE",
1604 |         cssStyles: {
1605 |           backgroundColor: "rgba(255, 255, 255, 1)",
1606 |           width: "400px",
1607 |           height: "300px",
1608 |         },
1609 |       };
1610 | 
1611 |       const result = LayoutOptimizer.extractBackgroundStyles(bgChild);
1612 | 
1613 |       expect(result.backgroundColor).toBe("rgba(255, 255, 255, 1)");
1614 |       expect(result.width).toBeUndefined();
1615 |       expect(result.height).toBeUndefined();
1616 |     });
1617 | 
1618 |     it("should extract borderRadius from background element", () => {
1619 |       const bgChild: SimplifiedNode = {
1620 |         id: "bg-1",
1621 |         name: "Background",
1622 |         type: "RECTANGLE",
1623 |         cssStyles: {
1624 |           backgroundColor: "rgba(0, 0, 0, 1)",
1625 |           borderRadius: "8px",
1626 |         },
1627 |       };
1628 | 
1629 |       const result = LayoutOptimizer.extractBackgroundStyles(bgChild);
1630 | 
1631 |       expect(result.backgroundColor).toBe("rgba(0, 0, 0, 1)");
1632 |       expect(result.borderRadius).toBe("8px");
1633 |     });
1634 | 
1635 |     it("should extract boxShadow from background element", () => {
1636 |       const bgChild: SimplifiedNode = {
1637 |         id: "bg-1",
1638 |         name: "Background",
1639 |         type: "RECTANGLE",
1640 |         cssStyles: {
1641 |           backgroundColor: "white",
1642 |           boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
1643 |         },
1644 |       };
1645 | 
1646 |       const result = LayoutOptimizer.extractBackgroundStyles(bgChild);
1647 | 
1648 |       expect(result.boxShadow).toBe("0 2px 4px rgba(0,0,0,0.1)");
1649 |     });
1650 | 
1651 |     it("should return empty object for element without styles", () => {
1652 |       const bgChild: SimplifiedNode = {
1653 |         id: "bg-1",
1654 |         name: "Background",
1655 |         type: "RECTANGLE",
1656 |       };
1657 | 
1658 |       const result = LayoutOptimizer.extractBackgroundStyles(bgChild);
1659 | 
1660 |       expect(Object.keys(result)).toHaveLength(0);
1661 |     });
1662 |   });
1663 | 
1664 |   // ==================== isBackgroundElement Tests ====================
1665 |   describe("isBackgroundElement", () => {
1666 |     it("should return true for valid background element", () => {
1667 |       const child: SimplifiedNode = {
1668 |         id: "bg-1",
1669 |         name: "Background",
1670 |         type: "RECTANGLE",
1671 |         cssStyles: {
1672 |           backgroundColor: "white",
1673 |         },
1674 |       };
1675 | 
1676 |       expect(LayoutOptimizer.isBackgroundElement(0, 0, child)).toBe(true);
1677 |     });
1678 | 
1679 |     it("should return false when index does not match", () => {
1680 |       const child: SimplifiedNode = {
1681 |         id: "bg-1",
1682 |         name: "Background",
1683 |         type: "RECTANGLE",
1684 |         cssStyles: {
1685 |           backgroundColor: "white",
1686 |         },
1687 |       };
1688 | 
1689 |       expect(LayoutOptimizer.isBackgroundElement(1, 0, child)).toBe(false);
1690 |     });
1691 | 
1692 |     it("should return false for non-visual element types", () => {
1693 |       const child: SimplifiedNode = {
1694 |         id: "text-1",
1695 |         name: "Text",
1696 |         type: "TEXT",
1697 |         cssStyles: {
1698 |           backgroundColor: "white",
1699 |         },
1700 |       };
1701 | 
1702 |       expect(LayoutOptimizer.isBackgroundElement(0, 0, child)).toBe(false);
1703 |     });
1704 | 
1705 |     it("should return false for element without visual styles", () => {
1706 |       const child: SimplifiedNode = {
1707 |         id: "bg-1",
1708 |         name: "Background",
1709 |         type: "RECTANGLE",
1710 |         cssStyles: {
1711 |           width: "100px",
1712 |           height: "50px",
1713 |         },
1714 |       };
1715 | 
1716 |       expect(LayoutOptimizer.isBackgroundElement(0, 0, child)).toBe(false);
1717 |     });
1718 |   });
1719 | });
1720 | 
```

--------------------------------------------------------------------------------
/tests/fixtures/figma-data/real-node-data.json:
--------------------------------------------------------------------------------

```json
   1 | {
   2 |   "name": "Vigilkids产品站",
   3 |   "lastModified": "2025-12-05T09:47:37Z",
   4 |   "thumbnailUrl": "https://s3-alpha.figma.com/thumbnails/9a38a8e4-5a00-4c07-a053-e71c7103a167?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAQ4GOSFWCXJW6HYPC%2F20251204%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20251204T000000Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=dab594780137ae82433ff7205024276c20a3abb73855f2e393c7e6373086b892",
   5 |   "version": "2294025171571219775",
   6 |   "role": "owner",
   7 |   "editorType": "figma",
   8 |   "linkAccess": "inherit",
   9 |   "nodes": {
  10 |     "2:674": {
  11 |       "document": {
  12 |         "id": "2:674",
  13 |         "name": "Group 1410104851",
  14 |         "type": "GROUP",
  15 |         "scrollBehavior": "SCROLLS",
  16 |         "children": [
  17 |           {
  18 |             "id": "2:675",
  19 |             "name": "Rectangle 34",
  20 |             "type": "RECTANGLE",
  21 |             "locked": true,
  22 |             "scrollBehavior": "SCROLLS",
  23 |             "blendMode": "PASS_THROUGH",
  24 |             "fills": [
  25 |               {
  26 |                 "blendMode": "NORMAL",
  27 |                 "type": "SOLID",
  28 |                 "color": {
  29 |                   "r": 1,
  30 |                   "g": 1,
  31 |                   "b": 1,
  32 |                   "a": 1
  33 |                 }
  34 |               }
  35 |             ],
  36 |             "strokes": [],
  37 |             "strokeWeight": 1,
  38 |             "strokeAlign": "INSIDE",
  39 |             "cornerRadius": 12,
  40 |             "cornerSmoothing": 0,
  41 |             "absoluteBoundingBox": {
  42 |               "x": 406,
  43 |               "y": 422,
  44 |               "width": 1580,
  45 |               "height": 895
  46 |             },
  47 |             "absoluteRenderBounds": {
  48 |               "x": 406,
  49 |               "y": 422,
  50 |               "width": 1580,
  51 |               "height": 895
  52 |             },
  53 |             "constraints": {
  54 |               "vertical": "TOP",
  55 |               "horizontal": "LEFT"
  56 |             },
  57 |             "effects": [],
  58 |             "interactions": []
  59 |           },
  60 |           {
  61 |             "id": "2:676",
  62 |             "name": "Group 1410104850",
  63 |             "type": "GROUP",
  64 |             "scrollBehavior": "SCROLLS",
  65 |             "children": [
  66 |               {
  67 |                 "id": "2:677",
  68 |                 "name": "Group 1410104480",
  69 |                 "type": "GROUP",
  70 |                 "scrollBehavior": "SCROLLS",
  71 |                 "children": [
  72 |                   {
  73 |                     "id": "2:678",
  74 |                     "name": "添加自定义关键词,当检测到关键词出现时您将接收警报",
  75 |                     "type": "TEXT",
  76 |                     "scrollBehavior": "SCROLLS",
  77 |                     "blendMode": "PASS_THROUGH",
  78 |                     "fills": [
  79 |                       {
  80 |                         "blendMode": "NORMAL",
  81 |                         "type": "SOLID",
  82 |                         "color": {
  83 |                           "r": 0.20000000298023224,
  84 |                           "g": 0.20000000298023224,
  85 |                           "b": 0.20000000298023224,
  86 |                           "a": 1
  87 |                         }
  88 |                       }
  89 |                     ],
  90 |                     "strokes": [],
  91 |                     "strokeWeight": 1,
  92 |                     "strokeAlign": "OUTSIDE",
  93 |                     "absoluteBoundingBox": {
  94 |                       "x": 1021,
  95 |                       "y": 822,
  96 |                       "width": 350,
  97 |                       "height": 20
  98 |                     },
  99 |                     "absoluteRenderBounds": {
 100 |                       "x": 1021.5040283203125,
 101 |                       "y": 825.3939819335938,
 102 |                       "width": 349.033935546875,
 103 |                       "height": 14.15399169921875
 104 |                     },
 105 |                     "constraints": {
 106 |                       "vertical": "TOP",
 107 |                       "horizontal": "LEFT"
 108 |                     },
 109 |                     "characters": "添加自定义关键词,当检测到关键词出现时您将接收警报",
 110 |                     "characterStyleOverrides": [],
 111 |                     "styleOverrideTable": {},
 112 |                     "lineTypes": ["NONE"],
 113 |                     "lineIndentations": [0],
 114 |                     "style": {
 115 |                       "fontFamily": "PingFang SC",
 116 |                       "fontPostScriptName": "PingFangSC-Medium",
 117 |                       "fontStyle": "Medium",
 118 |                       "fontWeight": 500,
 119 |                       "textAutoResize": "WIDTH_AND_HEIGHT",
 120 |                       "fontSize": 14,
 121 |                       "textAlignHorizontal": "CENTER",
 122 |                       "textAlignVertical": "CENTER",
 123 |                       "letterSpacing": 0,
 124 |                       "lineHeightPx": 19.600000381469727,
 125 |                       "lineHeightPercent": 100,
 126 |                       "lineHeightUnit": "INTRINSIC_%"
 127 |                     },
 128 |                     "layoutVersion": 4,
 129 |                     "effects": [],
 130 |                     "interactions": []
 131 |                   },
 132 |                   {
 133 |                     "id": "2:679",
 134 |                     "name": "Group 1410104479",
 135 |                     "type": "GROUP",
 136 |                     "scrollBehavior": "SCROLLS",
 137 |                     "children": [
 138 |                       {
 139 |                         "id": "2:680",
 140 |                         "name": "Group 1410086131",
 141 |                         "type": "GROUP",
 142 |                         "scrollBehavior": "SCROLLS",
 143 |                         "children": [
 144 |                           {
 145 |                             "id": "2:681",
 146 |                             "name": "Rectangle 34625783",
 147 |                             "type": "RECTANGLE",
 148 |                             "scrollBehavior": "SCROLLS",
 149 |                             "boundVariables": {
 150 |                               "fills": [
 151 |                                 {
 152 |                                   "type": "VARIABLE_ALIAS",
 153 |                                   "id": "VariableID:926747ab970a552aee35a6ed34090251e61d5ed8/70:4"
 154 |                                 }
 155 |                               ]
 156 |                             },
 157 |                             "blendMode": "PASS_THROUGH",
 158 |                             "fills": [
 159 |                               {
 160 |                                 "blendMode": "NORMAL",
 161 |                                 "type": "SOLID",
 162 |                                 "color": {
 163 |                                   "r": 0.1411764770746231,
 164 |                                   "g": 0.7803921699523926,
 165 |                                   "b": 0.5647059082984924,
 166 |                                   "a": 1
 167 |                                 },
 168 |                                 "boundVariables": {
 169 |                                   "color": {
 170 |                                     "type": "VARIABLE_ALIAS",
 171 |                                     "id": "VariableID:926747ab970a552aee35a6ed34090251e61d5ed8/70:4"
 172 |                                   }
 173 |                                 }
 174 |                               }
 175 |                             ],
 176 |                             "strokes": [],
 177 |                             "strokeWeight": 1,
 178 |                             "strokeAlign": "INSIDE",
 179 |                             "cornerRadius": 10,
 180 |                             "cornerSmoothing": 0,
 181 |                             "absoluteBoundingBox": {
 182 |                               "x": 1076,
 183 |                               "y": 872,
 184 |                               "width": 240,
 185 |                               "height": 44
 186 |                             },
 187 |                             "absoluteRenderBounds": {
 188 |                               "x": 1076,
 189 |                               "y": 872,
 190 |                               "width": 240,
 191 |                               "height": 44
 192 |                             },
 193 |                             "constraints": {
 194 |                               "vertical": "TOP",
 195 |                               "horizontal": "LEFT"
 196 |                             },
 197 |                             "effects": [],
 198 |                             "interactions": []
 199 |                           },
 200 |                           {
 201 |                             "id": "2:682",
 202 |                             "name": "Group 1410104509",
 203 |                             "type": "GROUP",
 204 |                             "scrollBehavior": "SCROLLS",
 205 |                             "children": [
 206 |                               {
 207 |                                 "id": "2:683",
 208 |                                 "name": "AI生成关键词",
 209 |                                 "type": "TEXT",
 210 |                                 "scrollBehavior": "SCROLLS",
 211 |                                 "blendMode": "PASS_THROUGH",
 212 |                                 "fills": [
 213 |                                   {
 214 |                                     "blendMode": "NORMAL",
 215 |                                     "type": "SOLID",
 216 |                                     "color": {
 217 |                                       "r": 1,
 218 |                                       "g": 1,
 219 |                                       "b": 1,
 220 |                                       "a": 1
 221 |                                     }
 222 |                                   }
 223 |                                 ],
 224 |                                 "strokes": [],
 225 |                                 "strokeWeight": 1,
 226 |                                 "strokeAlign": "OUTSIDE",
 227 |                                 "absoluteBoundingBox": {
 228 |                                   "x": 1170,
 229 |                                   "y": 886,
 230 |                                   "width": 84,
 231 |                                   "height": 16
 232 |                                 },
 233 |                                 "absoluteRenderBounds": {
 234 |                                   "x": 1170.2939453125,
 235 |                                   "y": 886.9739990234375,
 236 |                                   "width": 82.4940185546875,
 237 |                                   "height": 13.4539794921875
 238 |                                 },
 239 |                                 "constraints": {
 240 |                                   "vertical": "TOP",
 241 |                                   "horizontal": "LEFT"
 242 |                                 },
 243 |                                 "characters": "AI生成关键词",
 244 |                                 "characterStyleOverrides": [],
 245 |                                 "styleOverrideTable": {},
 246 |                                 "lineTypes": ["NONE"],
 247 |                                 "lineIndentations": [0],
 248 |                                 "style": {
 249 |                                   "fontFamily": "Roboto",
 250 |                                   "fontPostScriptName": "Roboto-Bold",
 251 |                                   "fontStyle": "Bold",
 252 |                                   "fontWeight": 700,
 253 |                                   "textCase": "UPPER",
 254 |                                   "textAutoResize": "WIDTH_AND_HEIGHT",
 255 |                                   "fontSize": 14,
 256 |                                   "textAlignHorizontal": "CENTER",
 257 |                                   "textAlignVertical": "CENTER",
 258 |                                   "letterSpacing": 0,
 259 |                                   "lineHeightPx": 16.40625,
 260 |                                   "lineHeightPercent": 100,
 261 |                                   "lineHeightUnit": "INTRINSIC_%"
 262 |                                 },
 263 |                                 "layoutVersion": 4,
 264 |                                 "effects": [],
 265 |                                 "interactions": []
 266 |                               },
 267 |                               {
 268 |                                 "id": "2:684",
 269 |                                 "name": "Frame",
 270 |                                 "type": "FRAME",
 271 |                                 "scrollBehavior": "SCROLLS",
 272 |                                 "children": [
 273 |                                   {
 274 |                                     "id": "2:685",
 275 |                                     "name": "Vector",
 276 |                                     "type": "VECTOR",
 277 |                                     "scrollBehavior": "SCROLLS",
 278 |                                     "blendMode": "PASS_THROUGH",
 279 |                                     "fills": [
 280 |                                       {
 281 |                                         "blendMode": "NORMAL",
 282 |                                         "type": "SOLID",
 283 |                                         "color": {
 284 |                                           "r": 1,
 285 |                                           "g": 1,
 286 |                                           "b": 1,
 287 |                                           "a": 1
 288 |                                         }
 289 |                                       }
 290 |                                     ],
 291 |                                     "strokes": [],
 292 |                                     "strokeWeight": 0.1953125,
 293 |                                     "strokeAlign": "INSIDE",
 294 |                                     "absoluteBoundingBox": {
 295 |                                       "x": 1139.833740234375,
 296 |                                       "y": 884.83251953125,
 297 |                                       "width": 18.333126068115234,
 298 |                                       "height": 18.334999084472656
 299 |                                     },
 300 |                                     "absoluteRenderBounds": {
 301 |                                       "x": 1139.833740234375,
 302 |                                       "y": 884.83251953125,
 303 |                                       "width": 18.3331298828125,
 304 |                                       "height": 18.33502197265625
 305 |                                     },
 306 |                                     "constraints": {
 307 |                                       "vertical": "SCALE",
 308 |                                       "horizontal": "SCALE"
 309 |                                     },
 310 |                                     "effects": [],
 311 |                                     "interactions": []
 312 |                                   }
 313 |                                 ],
 314 |                                 "blendMode": "PASS_THROUGH",
 315 |                                 "clipsContent": true,
 316 |                                 "background": [
 317 |                                   {
 318 |                                     "blendMode": "NORMAL",
 319 |                                     "visible": false,
 320 |                                     "type": "SOLID",
 321 |                                     "color": {
 322 |                                       "r": 1,
 323 |                                       "g": 1,
 324 |                                       "b": 1,
 325 |                                       "a": 1
 326 |                                     }
 327 |                                   }
 328 |                                 ],
 329 |                                 "fills": [
 330 |                                   {
 331 |                                     "blendMode": "NORMAL",
 332 |                                     "visible": false,
 333 |                                     "type": "SOLID",
 334 |                                     "color": {
 335 |                                       "r": 1,
 336 |                                       "g": 1,
 337 |                                       "b": 1,
 338 |                                       "a": 1
 339 |                                     }
 340 |                                   }
 341 |                                 ],
 342 |                                 "strokes": [],
 343 |                                 "strokeWeight": 1,
 344 |                                 "strokeAlign": "INSIDE",
 345 |                                 "backgroundColor": {
 346 |                                   "r": 0,
 347 |                                   "g": 0,
 348 |                                   "b": 0,
 349 |                                   "a": 0
 350 |                                 },
 351 |                                 "absoluteBoundingBox": {
 352 |                                   "x": 1139,
 353 |                                   "y": 884,
 354 |                                   "width": 20,
 355 |                                   "height": 20
 356 |                                 },
 357 |                                 "absoluteRenderBounds": {
 358 |                                   "x": 1139,
 359 |                                   "y": 884,
 360 |                                   "width": 20,
 361 |                                   "height": 20
 362 |                                 },
 363 |                                 "constraints": {
 364 |                                   "vertical": "TOP",
 365 |                                   "horizontal": "LEFT"
 366 |                                 },
 367 |                                 "effects": [],
 368 |                                 "interactions": []
 369 |                               }
 370 |                             ],
 371 |                             "blendMode": "PASS_THROUGH",
 372 |                             "clipsContent": false,
 373 |                             "background": [],
 374 |                             "fills": [],
 375 |                             "strokes": [],
 376 |                             "strokeWeight": 1,
 377 |                             "strokeAlign": "INSIDE",
 378 |                             "backgroundColor": {
 379 |                               "r": 0,
 380 |                               "g": 0,
 381 |                               "b": 0,
 382 |                               "a": 0
 383 |                             },
 384 |                             "absoluteBoundingBox": {
 385 |                               "x": 1139,
 386 |                               "y": 884,
 387 |                               "width": 115,
 388 |                               "height": 20
 389 |                             },
 390 |                             "absoluteRenderBounds": {
 391 |                               "x": 1139,
 392 |                               "y": 884,
 393 |                               "width": 115,
 394 |                               "height": 20
 395 |                             },
 396 |                             "constraints": {
 397 |                               "vertical": "TOP",
 398 |                               "horizontal": "LEFT"
 399 |                             },
 400 |                             "effects": [],
 401 |                             "interactions": []
 402 |                           }
 403 |                         ],
 404 |                         "blendMode": "PASS_THROUGH",
 405 |                         "clipsContent": false,
 406 |                         "background": [],
 407 |                         "fills": [],
 408 |                         "strokes": [],
 409 |                         "rectangleCornerRadii": [0, 0, 0, 0],
 410 |                         "cornerSmoothing": 0,
 411 |                         "strokeWeight": 1,
 412 |                         "strokeAlign": "INSIDE",
 413 |                         "backgroundColor": {
 414 |                           "r": 0,
 415 |                           "g": 0,
 416 |                           "b": 0,
 417 |                           "a": 0
 418 |                         },
 419 |                         "absoluteBoundingBox": {
 420 |                           "x": 1076,
 421 |                           "y": 872,
 422 |                           "width": 240,
 423 |                           "height": 44
 424 |                         },
 425 |                         "absoluteRenderBounds": {
 426 |                           "x": 1076,
 427 |                           "y": 872,
 428 |                           "width": 240,
 429 |                           "height": 44
 430 |                         },
 431 |                         "constraints": {
 432 |                           "vertical": "TOP",
 433 |                           "horizontal": "CENTER"
 434 |                         },
 435 |                         "effects": [],
 436 |                         "interactions": []
 437 |                       },
 438 |                       {
 439 |                         "id": "2:686",
 440 |                         "name": "Group 1410104451",
 441 |                         "type": "GROUP",
 442 |                         "scrollBehavior": "SCROLLS",
 443 |                         "children": [
 444 |                           {
 445 |                             "id": "2:687",
 446 |                             "name": "Rectangle 34625783",
 447 |                             "type": "RECTANGLE",
 448 |                             "scrollBehavior": "SCROLLS",
 449 |                             "blendMode": "PASS_THROUGH",
 450 |                             "fills": [
 451 |                               {
 452 |                                 "blendMode": "NORMAL",
 453 |                                 "type": "SOLID",
 454 |                                 "color": {
 455 |                                   "r": 1,
 456 |                                   "g": 1,
 457 |                                   "b": 1,
 458 |                                   "a": 1
 459 |                                 }
 460 |                               }
 461 |                             ],
 462 |                             "strokes": [
 463 |                               {
 464 |                                 "blendMode": "NORMAL",
 465 |                                 "type": "SOLID",
 466 |                                 "color": {
 467 |                                   "r": 0.7680059671401978,
 468 |                                   "g": 0.7680059671401978,
 469 |                                   "b": 0.7680059671401978,
 470 |                                   "a": 1
 471 |                                 }
 472 |                               }
 473 |                             ],
 474 |                             "strokeWeight": 0,
 475 |                             "strokeAlign": "INSIDE",
 476 |                             "cornerRadius": 10,
 477 |                             "cornerSmoothing": 0,
 478 |                             "absoluteBoundingBox": {
 479 |                               "x": 1076,
 480 |                               "y": 926,
 481 |                               "width": 240,
 482 |                               "height": 44
 483 |                             },
 484 |                             "absoluteRenderBounds": {
 485 |                               "x": 1076,
 486 |                               "y": 926,
 487 |                               "width": 240,
 488 |                               "height": 44
 489 |                             },
 490 |                             "constraints": {
 491 |                               "vertical": "TOP",
 492 |                               "horizontal": "LEFT"
 493 |                             },
 494 |                             "effects": [],
 495 |                             "interactions": []
 496 |                           },
 497 |                           {
 498 |                             "id": "2:688",
 499 |                             "name": "添加自定义关键词",
 500 |                             "type": "TEXT",
 501 |                             "scrollBehavior": "SCROLLS",
 502 |                             "blendMode": "PASS_THROUGH",
 503 |                             "fills": [
 504 |                               {
 505 |                                 "blendMode": "NORMAL",
 506 |                                 "type": "SOLID",
 507 |                                 "color": {
 508 |                                   "r": 0.20000000298023224,
 509 |                                   "g": 0.20000000298023224,
 510 |                                   "b": 0.20000000298023224,
 511 |                                   "a": 1
 512 |                                 }
 513 |                               }
 514 |                             ],
 515 |                             "strokes": [],
 516 |                             "strokeWeight": 1,
 517 |                             "strokeAlign": "OUTSIDE",
 518 |                             "absoluteBoundingBox": {
 519 |                               "x": 1123.594970703125,
 520 |                               "y": 940,
 521 |                               "width": 145.82278442382812,
 522 |                               "height": 16
 523 |                             },
 524 |                             "absoluteRenderBounds": {
 525 |                               "x": 1140.8983154296875,
 526 |                               "y": 941.0579833984375,
 527 |                               "width": 110.64208984375,
 528 |                               "height": 13.342041015625
 529 |                             },
 530 |                             "constraints": {
 531 |                               "vertical": "TOP",
 532 |                               "horizontal": "LEFT"
 533 |                             },
 534 |                             "characters": "添加自定义关键词",
 535 |                             "characterStyleOverrides": [],
 536 |                             "styleOverrideTable": {},
 537 |                             "lineTypes": ["NONE"],
 538 |                             "lineIndentations": [0],
 539 |                             "style": {
 540 |                               "fontFamily": "Roboto",
 541 |                               "fontPostScriptName": "Roboto-Bold",
 542 |                               "fontStyle": "Bold",
 543 |                               "fontWeight": 700,
 544 |                               "textCase": "UPPER",
 545 |                               "textAutoResize": "HEIGHT",
 546 |                               "fontSize": 14,
 547 |                               "textAlignHorizontal": "CENTER",
 548 |                               "textAlignVertical": "CENTER",
 549 |                               "letterSpacing": 0,
 550 |                               "lineHeightPx": 16.40625,
 551 |                               "lineHeightPercent": 100,
 552 |                               "lineHeightUnit": "INTRINSIC_%"
 553 |                             },
 554 |                             "layoutVersion": 4,
 555 |                             "effects": [],
 556 |                             "interactions": []
 557 |                           }
 558 |                         ],
 559 |                         "blendMode": "PASS_THROUGH",
 560 |                         "clipsContent": false,
 561 |                         "background": [],
 562 |                         "fills": [],
 563 |                         "strokes": [],
 564 |                         "cornerRadius": 10,
 565 |                         "cornerSmoothing": 0,
 566 |                         "strokeWeight": 1,
 567 |                         "strokeAlign": "INSIDE",
 568 |                         "backgroundColor": {
 569 |                           "r": 0,
 570 |                           "g": 0,
 571 |                           "b": 0,
 572 |                           "a": 0
 573 |                         },
 574 |                         "absoluteBoundingBox": {
 575 |                           "x": 1076,
 576 |                           "y": 926,
 577 |                           "width": 240,
 578 |                           "height": 44
 579 |                         },
 580 |                         "absoluteRenderBounds": {
 581 |                           "x": 1076,
 582 |                           "y": 926,
 583 |                           "width": 240,
 584 |                           "height": 44
 585 |                         },
 586 |                         "constraints": {
 587 |                           "vertical": "TOP",
 588 |                           "horizontal": "LEFT"
 589 |                         },
 590 |                         "exportSettings": [
 591 |                           {
 592 |                             "suffix": "",
 593 |                             "format": "PNG",
 594 |                             "constraint": {
 595 |                               "type": "SCALE",
 596 |                               "value": 1
 597 |                             }
 598 |                           }
 599 |                         ],
 600 |                         "effects": [],
 601 |                         "interactions": []
 602 |                       }
 603 |                     ],
 604 |                     "blendMode": "PASS_THROUGH",
 605 |                     "clipsContent": false,
 606 |                     "background": [],
 607 |                     "fills": [],
 608 |                     "strokes": [],
 609 |                     "rectangleCornerRadii": [0, 0, 0, 0],
 610 |                     "cornerSmoothing": 0,
 611 |                     "strokeWeight": 1,
 612 |                     "strokeAlign": "INSIDE",
 613 |                     "backgroundColor": {
 614 |                       "r": 0,
 615 |                       "g": 0,
 616 |                       "b": 0,
 617 |                       "a": 0
 618 |                     },
 619 |                     "absoluteBoundingBox": {
 620 |                       "x": 1076,
 621 |                       "y": 872,
 622 |                       "width": 240,
 623 |                       "height": 98
 624 |                     },
 625 |                     "absoluteRenderBounds": {
 626 |                       "x": 1076,
 627 |                       "y": 872,
 628 |                       "width": 240,
 629 |                       "height": 98
 630 |                     },
 631 |                     "constraints": {
 632 |                       "vertical": "TOP",
 633 |                       "horizontal": "LEFT"
 634 |                     },
 635 |                     "effects": [],
 636 |                     "interactions": []
 637 |                   }
 638 |                 ],
 639 |                 "blendMode": "PASS_THROUGH",
 640 |                 "clipsContent": false,
 641 |                 "background": [],
 642 |                 "fills": [],
 643 |                 "strokes": [],
 644 |                 "rectangleCornerRadii": [0, 0, 0, 0],
 645 |                 "cornerSmoothing": 0,
 646 |                 "strokeWeight": 1,
 647 |                 "strokeAlign": "INSIDE",
 648 |                 "backgroundColor": {
 649 |                   "r": 0,
 650 |                   "g": 0,
 651 |                   "b": 0,
 652 |                   "a": 0
 653 |                 },
 654 |                 "absoluteBoundingBox": {
 655 |                   "x": 1021,
 656 |                   "y": 822,
 657 |                   "width": 350,
 658 |                   "height": 148
 659 |                 },
 660 |                 "absoluteRenderBounds": {
 661 |                   "x": 1021,
 662 |                   "y": 822,
 663 |                   "width": 350,
 664 |                   "height": 148
 665 |                 },
 666 |                 "constraints": {
 667 |                   "vertical": "TOP",
 668 |                   "horizontal": "LEFT"
 669 |                 },
 670 |                 "effects": [],
 671 |                 "interactions": []
 672 |               },
 673 |               {
 674 |                 "id": "2:689",
 675 |                 "name": "Group 1410104849",
 676 |                 "type": "GROUP",
 677 |                 "scrollBehavior": "SCROLLS",
 678 |                 "children": [
 679 |                   {
 680 |                     "id": "2:690",
 681 |                     "name": "Rectangle 34626114",
 682 |                     "type": "RECTANGLE",
 683 |                     "scrollBehavior": "SCROLLS",
 684 |                     "blendMode": "PASS_THROUGH",
 685 |                     "fills": [
 686 |                       {
 687 |                         "opacity": 0,
 688 |                         "blendMode": "NORMAL",
 689 |                         "type": "SOLID",
 690 |                         "color": {
 691 |                           "r": 1,
 692 |                           "g": 1,
 693 |                           "b": 1,
 694 |                           "a": 1
 695 |                         }
 696 |                       }
 697 |                     ],
 698 |                     "strokes": [],
 699 |                     "strokeWeight": 1.100000023841858,
 700 |                     "strokeAlign": "INSIDE",
 701 |                     "absoluteBoundingBox": {
 702 |                       "x": 1086,
 703 |                       "y": 654,
 704 |                       "width": 220,
 705 |                       "height": 138
 706 |                     },
 707 |                     "absoluteRenderBounds": null,
 708 |                     "constraints": {
 709 |                       "vertical": "TOP",
 710 |                       "horizontal": "LEFT"
 711 |                     },
 712 |                     "effects": [],
 713 |                     "interactions": []
 714 |                   },
 715 |                   {
 716 |                     "id": "2:691",
 717 |                     "name": "Group 1410104848",
 718 |                     "type": "GROUP",
 719 |                     "scrollBehavior": "SCROLLS",
 720 |                     "children": [
 721 |                       {
 722 |                         "id": "2:692",
 723 |                         "name": "Ellipse 2571",
 724 |                         "type": "ELLIPSE",
 725 |                         "scrollBehavior": "SCROLLS",
 726 |                         "blendMode": "PASS_THROUGH",
 727 |                         "fills": [],
 728 |                         "strokes": [
 729 |                           {
 730 |                             "opacity": 0.10000000149011612,
 731 |                             "blendMode": "NORMAL",
 732 |                             "type": "SOLID",
 733 |                             "color": {
 734 |                               "r": 0.1411764770746231,
 735 |                               "g": 0.7803921699523926,
 736 |                               "b": 0.5647059082984924,
 737 |                               "a": 1
 738 |                             }
 739 |                           }
 740 |                         ],
 741 |                         "strokeWeight": 1.9917323589324951,
 742 |                         "strokeAlign": "INSIDE",
 743 |                         "absoluteBoundingBox": {
 744 |                           "x": 1113.2984619140625,
 745 |                           "y": 765.6846313476562,
 746 |                           "width": 10.954526901245117,
 747 |                           "height": 10.9071044921875
 748 |                         },
 749 |                         "absoluteRenderBounds": {
 750 |                           "x": 1113.2984619140625,
 751 |                           "y": 765.6846313476562,
 752 |                           "width": 10.9544677734375,
 753 |                           "height": 10.9071044921875
 754 |                         },
 755 |                         "constraints": {
 756 |                           "vertical": "TOP",
 757 |                           "horizontal": "LEFT"
 758 |                         },
 759 |                         "effects": [],
 760 |                         "arcData": {
 761 |                           "startingAngle": 0,
 762 |                           "endingAngle": 6.2831854820251465,
 763 |                           "innerRadius": 0
 764 |                         },
 765 |                         "interactions": []
 766 |                       },
 767 |                       {
 768 |                         "id": "2:693",
 769 |                         "name": "Rectangle 34626109",
 770 |                         "type": "VECTOR",
 771 |                         "scrollBehavior": "SCROLLS",
 772 |                         "blendMode": "PASS_THROUGH",
 773 |                         "fills": [
 774 |                           {
 775 |                             "blendMode": "NORMAL",
 776 |                             "type": "GRADIENT_LINEAR",
 777 |                             "gradientHandlePositions": [
 778 |                               {
 779 |                                 "x": 0.0657894648366455,
 780 |                                 "y": 7.999539142211631e-9
 781 |                               },
 782 |                               {
 783 |                                 "x": 0.7894736608539367,
 784 |                                 "y": 0.999999973948599
 785 |                               },
 786 |                               {
 787 |                                 "x": -0.4342105609220982,
 788 |                                 "y": 0.3009599985319522
 789 |                               }
 790 |                             ],
 791 |                             "gradientStops": [
 792 |                               {
 793 |                                 "color": {
 794 |                                   "r": 0.29140377044677734,
 795 |                                   "g": 0.942671537399292,
 796 |                                   "b": 0.5844742059707642,
 797 |                                   "a": 1
 798 |                                 },
 799 |                                 "position": 0
 800 |                               },
 801 |                               {
 802 |                                 "color": {
 803 |                                   "r": 0,
 804 |                                   "g": 0.8191801309585571,
 805 |                                   "b": 0.5427696704864502,
 806 |                                   "a": 1
 807 |                                 },
 808 |                                 "position": 1
 809 |                               }
 810 |                             ]
 811 |                           }
 812 |                         ],
 813 |                         "strokes": [],
 814 |                         "strokeWeight": 0.3095206916332245,
 815 |                         "strokeAlign": "CENTER",
 816 |                         "cornerRadius": 18.921457290649414,
 817 |                         "cornerSmoothing": 0,
 818 |                         "absoluteBoundingBox": {
 819 |                           "x": 1138.195068359375,
 820 |                           "y": 661.5670166015625,
 821 |                           "width": 113.52873992919922,
 822 |                           "height": 123.94437408447266
 823 |                         },
 824 |                         "absoluteRenderBounds": {
 825 |                           "x": 1129.1700439453125,
 826 |                           "y": 651.6083374023438,
 827 |                           "width": 131.5787353515625,
 828 |                           "height": 143.8616943359375
 829 |                         },
 830 |                         "constraints": {
 831 |                           "vertical": "TOP",
 832 |                           "horizontal": "LEFT"
 833 |                         },
 834 |                         "effects": [
 835 |                           {
 836 |                             "type": "DROP_SHADOW",
 837 |                             "visible": true,
 838 |                             "color": {
 839 |                               "r": 0,
 840 |                               "g": 0.269947350025177,
 841 |                               "b": 0.17840000987052917,
 842 |                               "a": 0.09000000357627869
 843 |                             },
 844 |                             "blendMode": "NORMAL",
 845 |                             "offset": {
 846 |                               "x": 0,
 847 |                               "y": 0
 848 |                             },
 849 |                             "radius": 9.958662033081055,
 850 |                             "showShadowBehindNode": false
 851 |                           }
 852 |                         ],
 853 |                         "interactions": []
 854 |                       },
 855 |                       {
 856 |                         "id": "2:694",
 857 |                         "name": "Rectangle 34626110",
 858 |                         "type": "RECTANGLE",
 859 |                         "scrollBehavior": "SCROLLS",
 860 |                         "blendMode": "PASS_THROUGH",
 861 |                         "fills": [
 862 |                           {
 863 |                             "blendMode": "NORMAL",
 864 |                             "type": "GRADIENT_LINEAR",
 865 |                             "gradientHandlePositions": [
 866 |                               {
 867 |                                 "x": 0.5221519248834344,
 868 |                                 "y": 4.985427063369147e-14
 869 |                               },
 870 |                               {
 871 |                                 "x": 0.5221519248834481,
 872 |                                 "y": 1.00000000000005
 873 |                               },
 874 |                               {
 875 |                                 "x": 0.4947324413228972,
 876 |                                 "y": -8.056334654634439e-10
 877 |                               }
 878 |                             ],
 879 |                             "gradientStops": [
 880 |                               {
 881 |                                 "color": {
 882 |                                   "r": 0.0117647061124444,
 883 |                                   "g": 0.7882353067398071,
 884 |                                   "b": 0.4745098054409027,
 885 |                                   "a": 1
 886 |                                 },
 887 |                                 "position": 0
 888 |                               },
 889 |                               {
 890 |                                 "color": {
 891 |                                   "r": 0,
 892 |                                   "g": 0.7350029349327087,
 893 |                                   "b": 0.447717547416687,
 894 |                                   "a": 1
 895 |                                 },
 896 |                                 "position": 1
 897 |                               }
 898 |                             ]
 899 |                           }
 900 |                         ],
 901 |                         "strokes": [],
 902 |                         "strokeWeight": 0.9958661794662476,
 903 |                         "strokeAlign": "INSIDE",
 904 |                         "cornerRadius": 18.42352294921875,
 905 |                         "cornerSmoothing": 0,
 906 |                         "absoluteBoundingBox": {
 907 |                           "x": 1116.2860107421875,
 908 |                           "y": 728.0046997070312,
 909 |                           "width": 157.3468475341797,
 910 |                           "height": 36.68753433227539
 911 |                         },
 912 |                         "absoluteRenderBounds": {
 913 |                           "x": 1116.2860107421875,
 914 |                           "y": 728.0046997070312,
 915 |                           "width": 157.3468017578125,
 916 |                           "height": 36.68756103515625
 917 |                         },
 918 |                         "constraints": {
 919 |                           "vertical": "TOP",
 920 |                           "horizontal": "LEFT"
 921 |                         },
 922 |                         "effects": [],
 923 |                         "interactions": []
 924 |                       },
 925 |                       {
 926 |                         "id": "2:695",
 927 |                         "name": "Vector 601",
 928 |                         "type": "VECTOR",
 929 |                         "scrollBehavior": "SCROLLS",
 930 |                         "blendMode": "PASS_THROUGH",
 931 |                         "fills": [],
 932 |                         "fillOverrideTable": {
 933 |                           "1": null,
 934 |                           "2": null,
 935 |                           "3": null
 936 |                         },
 937 |                         "strokes": [
 938 |                           {
 939 |                             "blendMode": "NORMAL",
 940 |                             "type": "SOLID",
 941 |                             "color": {
 942 |                               "r": 0.6509804129600525,
 943 |                               "g": 0.9764705896377563,
 944 |                               "b": 0.7607843279838562,
 945 |                               "a": 1
 946 |                             }
 947 |                           }
 948 |                         ],
 949 |                         "strokeWeight": 2.9875986576080322,
 950 |                         "strokeAlign": "CENTER",
 951 |                         "strokeJoin": "ROUND",
 952 |                         "strokeCap": "ROUND",
 953 |                         "absoluteBoundingBox": {
 954 |                           "x": 1158.15234375,
 955 |                           "y": 737.9147338867188,
 956 |                           "width": 79.68075561523438,
 957 |                           "height": 14.171746253967285
 958 |                         },
 959 |                         "absoluteRenderBounds": {
 960 |                           "x": 1156.6585693359375,
 961 |                           "y": 736.4205932617188,
 962 |                           "width": 82.66845703125,
 963 |                           "height": 17.15972900390625
 964 |                         },
 965 |                         "constraints": {
 966 |                           "vertical": "TOP",
 967 |                           "horizontal": "LEFT"
 968 |                         },
 969 |                         "effects": [],
 970 |                         "interactions": []
 971 |                       },
 972 |                       {
 973 |                         "id": "2:696",
 974 |                         "name": "Frame",
 975 |                         "type": "FRAME",
 976 |                         "scrollBehavior": "SCROLLS",
 977 |                         "children": [
 978 |                           {
 979 |                             "id": "2:697",
 980 |                             "name": "Vector",
 981 |                             "type": "VECTOR",
 982 |                             "scrollBehavior": "SCROLLS",
 983 |                             "blendMode": "PASS_THROUGH",
 984 |                             "fills": [
 985 |                               {
 986 |                                 "blendMode": "NORMAL",
 987 |                                 "type": "SOLID",
 988 |                                 "color": {
 989 |                                   "r": 0.02860725112259388,
 990 |                                   "g": 0.5845416188240051,
 991 |                                   "b": 0.3645462095737457,
 992 |                                   "a": 1
 993 |                                 }
 994 |                               }
 995 |                             ],
 996 |                             "strokes": [],
 997 |                             "strokeWeight": 0.19450511038303375,
 998 |                             "strokeAlign": "INSIDE",
 999 |                             "absoluteBoundingBox": {
1000 |                               "x": 1224.025146484375,
1001 |                               "y": 728.1633911132812,
1002 |                               "width": 37.4700813293457,
1003 |                               "height": 37.30668258666992
1004 |                             },
1005 |                             "absoluteRenderBounds": {
1006 |                               "x": 1224.025146484375,
1007 |                               "y": 728.1633911132812,
1008 |                               "width": 37.4700927734375,
1009 |                               "height": 37.30670166015625
1010 |                             },
1011 |                             "constraints": {
1012 |                               "vertical": "SCALE",
1013 |                               "horizontal": "SCALE"
1014 |                             },
1015 |                             "effects": [],
1016 |                             "interactions": []
1017 |                           },
1018 |                           {
1019 |                             "id": "2:698",
1020 |                             "name": "Vector",
1021 |                             "type": "VECTOR",
1022 |                             "scrollBehavior": "SCROLLS",
1023 |                             "blendMode": "PASS_THROUGH",
1024 |                             "fills": [
1025 |                               {
1026 |                                 "blendMode": "NORMAL",
1027 |                                 "type": "SOLID",
1028 |                                 "color": {
1029 |                                   "r": 0.02860725112259388,
1030 |                                   "g": 0.5845416188240051,
1031 |                                   "b": 0.3645462095737457,
1032 |                                   "a": 1
1033 |                                 }
1034 |                               }
1035 |                             ],
1036 |                             "strokes": [],
1037 |                             "strokeWeight": 0.19450511038303375,
1038 |                             "strokeAlign": "INSIDE",
1039 |                             "absoluteBoundingBox": {
1040 |                               "x": 1252.0159912109375,
1041 |                               "y": 756.0406494140625,
1042 |                               "width": 15.463837623596191,
1043 |                               "height": 15.395621299743652
1044 |                             },
1045 |                             "absoluteRenderBounds": {
1046 |                               "x": 1252.0159912109375,
1047 |                               "y": 756.0406494140625,
1048 |                               "width": 15.4638671875,
1049 |                               "height": 15.3956298828125
1050 |                             },
1051 |                             "constraints": {
1052 |                               "vertical": "SCALE",
1053 |                               "horizontal": "SCALE"
1054 |                             },
1055 |                             "effects": [],
1056 |                             "interactions": []
1057 |                           },
1058 |                           {
1059 |                             "id": "2:699",
1060 |                             "name": "Ellipse 2587",
1061 |                             "type": "ELLIPSE",
1062 |                             "scrollBehavior": "SCROLLS",
1063 |                             "blendMode": "PASS_THROUGH",
1064 |                             "fills": [
1065 |                               {
1066 |                                 "opacity": 0.28999999165534973,
1067 |                                 "blendMode": "NORMAL",
1068 |                                 "type": "SOLID",
1069 |                                 "color": {
1070 |                                   "r": 1,
1071 |                                   "g": 1,
1072 |                                   "b": 1,
1073 |                                   "a": 1
1074 |                                 }
1075 |                               }
1076 |                             ],
1077 |                             "strokes": [],
1078 |                             "strokeWeight": 0.9958661794662476,
1079 |                             "strokeAlign": "INSIDE",
1080 |                             "absoluteBoundingBox": {
1081 |                               "x": 1227.822998046875,
1082 |                               "y": 731.9710693359375,
1083 |                               "width": 29.875986099243164,
1084 |                               "height": 29.746652603149414
1085 |                             },
1086 |                             "absoluteRenderBounds": {
1087 |                               "x": 1227.822998046875,
1088 |                               "y": 731.9710693359375,
1089 |                               "width": 29.8759765625,
1090 |                               "height": 29.74664306640625
1091 |                             },
1092 |                             "constraints": {
1093 |                               "vertical": "SCALE",
1094 |                               "horizontal": "SCALE"
1095 |                             },
1096 |                             "effects": [
1097 |                               {
1098 |                                 "type": "BACKGROUND_BLUR",
1099 |                                 "visible": true,
1100 |                                 "radius": 4.0830512046813965
1101 |                               },
1102 |                               {
1103 |                                 "type": "INNER_SHADOW",
1104 |                                 "visible": true,
1105 |                                 "color": {
1106 |                                   "r": 1,
1107 |                                   "g": 1,
1108 |                                   "b": 1,
1109 |                                   "a": 1
1110 |                                 },
1111 |                                 "blendMode": "NORMAL",
1112 |                                 "offset": {
1113 |                                   "x": 0,
1114 |                                   "y": 0
1115 |                                 },
1116 |                                 "radius": 11.751220703125
1117 |                               }
1118 |                             ],
1119 |                             "arcData": {
1120 |                               "startingAngle": 0,
1121 |                               "endingAngle": 6.2831854820251465,
1122 |                               "innerRadius": 0
1123 |                             },
1124 |                             "interactions": []
1125 |                           }
1126 |                         ],
1127 |                         "blendMode": "PASS_THROUGH",
1128 |                         "clipsContent": true,
1129 |                         "background": [
1130 |                           {
1131 |                             "blendMode": "NORMAL",
1132 |                             "visible": false,
1133 |                             "type": "SOLID",
1134 |                             "color": {
1135 |                               "r": 1,
1136 |                               "g": 1,
1137 |                               "b": 1,
1138 |                               "a": 1
1139 |                             }
1140 |                           }
1141 |                         ],
1142 |                         "fills": [
1143 |                           {
1144 |                             "blendMode": "NORMAL",
1145 |                             "visible": false,
1146 |                             "type": "SOLID",
1147 |                             "color": {
1148 |                               "r": 1,
1149 |                               "g": 1,
1150 |                               "b": 1,
1151 |                               "a": 1
1152 |                             }
1153 |                           }
1154 |                         ],
1155 |                         "strokes": [],
1156 |                         "strokeWeight": 0.9958661794662476,
1157 |                         "strokeAlign": "INSIDE",
1158 |                         "backgroundColor": {
1159 |                           "r": 0,
1160 |                           "g": 0,
1161 |                           "b": 0,
1162 |                           "a": 0
1163 |                         },
1164 |                         "absoluteBoundingBox": {
1165 |                           "x": 1220.85205078125,
1166 |                           "y": 725.0332641601562,
1167 |                           "width": 49.79330825805664,
1168 |                           "height": 49.57775115966797
1169 |                         },
1170 |                         "absoluteRenderBounds": {
1171 |                           "x": 1220.85205078125,
1172 |                           "y": 725.0332641601562,
1173 |                           "width": 49.7933349609375,
1174 |                           "height": 49.5777587890625
1175 |                         },
1176 |                         "constraints": {
1177 |                           "vertical": "TOP",
1178 |                           "horizontal": "LEFT"
1179 |                         },
1180 |                         "effects": [],
1181 |                         "interactions": []
1182 |                       },
1183 |                       {
1184 |                         "id": "2:700",
1185 |                         "name": "Group 1410104846",
1186 |                         "type": "GROUP",
1187 |                         "scrollBehavior": "SCROLLS",
1188 |                         "children": [
1189 |                           {
1190 |                             "id": "2:701",
1191 |                             "name": "Ellipse 2583",
1192 |                             "type": "ELLIPSE",
1193 |                             "scrollBehavior": "SCROLLS",
1194 |                             "blendMode": "PASS_THROUGH",
1195 |                             "fills": [
1196 |                               {
1197 |                                 "blendMode": "NORMAL",
1198 |                                 "type": "SOLID",
1199 |                                 "color": {
1200 |                                   "r": 0.9934962391853333,
1201 |                                   "g": 0.7477474212646484,
1202 |                                   "b": 0.2174474149942398,
1203 |                                   "a": 1
1204 |                                 }
1205 |                               }
1206 |                             ],
1207 |                             "strokes": [],
1208 |                             "strokeWeight": 0.9958661794662476,
1209 |                             "strokeAlign": "INSIDE",
1210 |                             "absoluteBoundingBox": {
1211 |                               "x": 1124.2529296875,
1212 |                               "y": 734.9506225585938,
1213 |                               "width": 22.904922485351562,
1214 |                               "height": 22.80576515197754
1215 |                             },
1216 |                             "absoluteRenderBounds": {
1217 |                               "x": 1124.2529296875,
1218 |                               "y": 734.9506225585938,
1219 |                               "width": 22.9049072265625,
1220 |                               "height": 22.8057861328125
1221 |                             },
1222 |                             "constraints": {
1223 |                               "vertical": "TOP",
1224 |                               "horizontal": "LEFT"
1225 |                             },
1226 |                             "effects": [],
1227 |                             "arcData": {
1228 |                               "startingAngle": 0,
1229 |                               "endingAngle": 6.2831854820251465,
1230 |                               "innerRadius": 0
1231 |                             },
1232 |                             "interactions": []
1233 |                           },
1234 |                           {
1235 |                             "id": "2:702",
1236 |                             "name": "Ellipse 2584",
1237 |                             "type": "ELLIPSE",
1238 |                             "scrollBehavior": "SCROLLS",
1239 |                             "blendMode": "PASS_THROUGH",
1240 |                             "fills": [
1241 |                               {
1242 |                                 "blendMode": "NORMAL",
1243 |                                 "type": "SOLID",
1244 |                                 "color": {
1245 |                                   "r": 1,
1246 |                                   "g": 1,
1247 |                                   "b": 1,
1248 |                                   "a": 1
1249 |                                 }
1250 |                               }
1251 |                             ],
1252 |                             "strokes": [],
1253 |                             "strokeWeight": 0.9958661794662476,
1254 |                             "strokeAlign": "INSIDE",
1255 |                             "absoluteBoundingBox": {
1256 |                               "x": 1134.211669921875,
1257 |                               "y": 750.8113403320312,
1258 |                               "width": 2.987598419189453,
1259 |                               "height": 2.9746649265289307
1260 |                             },
1261 |                             "absoluteRenderBounds": {
1262 |                               "x": 1134.211669921875,
1263 |                               "y": 750.8113403320312,
1264 |                               "width": 2.987548828125,
1265 |                               "height": 2.97467041015625
1266 |                             },
1267 |                             "constraints": {
1268 |                               "vertical": "TOP",
1269 |                               "horizontal": "LEFT"
1270 |                             },
1271 |                             "effects": [],
1272 |                             "arcData": {
1273 |                               "startingAngle": 0,
1274 |                               "endingAngle": 6.2831854820251465,
1275 |                               "innerRadius": 0
1276 |                             },
1277 |                             "interactions": []
1278 |                           },
1279 |                           {
1280 |                             "id": "2:703",
1281 |                             "name": "Rectangle 34626111",
1282 |                             "type": "RECTANGLE",
1283 |                             "scrollBehavior": "SCROLLS",
1284 |                             "blendMode": "PASS_THROUGH",
1285 |                             "fills": [
1286 |                               {
1287 |                                 "blendMode": "NORMAL",
1288 |                                 "type": "SOLID",
1289 |                                 "color": {
1290 |                                   "r": 1,
1291 |                                   "g": 1,
1292 |                                   "b": 1,
1293 |                                   "a": 1
1294 |                                 }
1295 |                               }
1296 |                             ],
1297 |                             "strokes": [],
1298 |                             "strokeWeight": 0.9958661794662476,
1299 |                             "strokeAlign": "INSIDE",
1300 |                             "cornerRadius": 2.9875986576080322,
1301 |                             "cornerSmoothing": 0,
1302 |                             "absoluteBoundingBox": {
1303 |                               "x": 1134.211669921875,
1304 |                               "y": 738.9099731445312,
1305 |                               "width": 2.987598419189453,
1306 |                               "height": 9.915549278259277
1307 |                             },
1308 |                             "absoluteRenderBounds": {
1309 |                               "x": 1134.211669921875,
1310 |                               "y": 738.9099731445312,
1311 |                               "width": 2.987548828125,
1312 |                               "height": 9.91552734375
1313 |                             },
1314 |                             "constraints": {
1315 |                               "vertical": "TOP",
1316 |                               "horizontal": "LEFT"
1317 |                             },
1318 |                             "effects": [],
1319 |                             "interactions": []
1320 |                           }
1321 |                         ],
1322 |                         "blendMode": "PASS_THROUGH",
1323 |                         "clipsContent": false,
1324 |                         "background": [],
1325 |                         "fills": [],
1326 |                         "strokes": [],
1327 |                         "rectangleCornerRadii": [0, 0, 0, 0],
1328 |                         "cornerSmoothing": 0,
1329 |                         "strokeWeight": 0.9958661794662476,
1330 |                         "strokeAlign": "INSIDE",
1331 |                         "backgroundColor": {
1332 |                           "r": 0,
1333 |                           "g": 0,
1334 |                           "b": 0,
1335 |                           "a": 0
1336 |                         },
1337 |                         "absoluteBoundingBox": {
1338 |                           "x": 1124.2529296875,
1339 |                           "y": 734.9506225585938,
1340 |                           "width": 22.904922485351562,
1341 |                           "height": 22.80576515197754
1342 |                         },
1343 |                         "absoluteRenderBounds": {
1344 |                           "x": 1124.2529296875,
1345 |                           "y": 734.9506225585938,
1346 |                           "width": 22.904922485351562,
1347 |                           "height": 22.8057861328125
1348 |                         },
1349 |                         "constraints": {
1350 |                           "vertical": "TOP",
1351 |                           "horizontal": "LEFT"
1352 |                         },
1353 |                         "effects": [],
1354 |                         "interactions": []
1355 |                       },
1356 |                       {
1357 |                         "id": "2:704",
1358 |                         "name": "Ellipse 2585",
1359 |                         "type": "ELLIPSE",
1360 |                         "scrollBehavior": "SCROLLS",
1361 |                         "blendMode": "PASS_THROUGH",
1362 |                         "fills": [
1363 |                           {
1364 |                             "blendMode": "NORMAL",
1365 |                             "type": "SOLID",
1366 |                             "color": {
1367 |                               "r": 0.6039215922355652,
1368 |                               "g": 0.9490196108818054,
1369 |                               "b": 0.7333333492279053,
1370 |                               "a": 1
1371 |                             }
1372 |                           }
1373 |                         ],
1374 |                         "strokes": [],
1375 |                         "strokeWeight": 0.9958661794662476,
1376 |                         "strokeAlign": "INSIDE",
1377 |                         "absoluteBoundingBox": {
1378 |                           "x": 1160.1041259765625,
1379 |                           "y": 700.2426147460938,
1380 |                           "width": 7.9669294357299805,
1381 |                           "height": 7.932440280914307
1382 |                         },
1383 |                         "absoluteRenderBounds": {
1384 |                           "x": 1160.1041259765625,
1385 |                           "y": 700.2426147460938,
1386 |                           "width": 7.9669189453125,
1387 |                           "height": 7.93243408203125
1388 |                         },
1389 |                         "constraints": {
1390 |                           "vertical": "TOP",
1391 |                           "horizontal": "LEFT"
1392 |                         },
1393 |                         "effects": [],
1394 |                         "arcData": {
1395 |                           "startingAngle": 0,
1396 |                           "endingAngle": 6.2831854820251465,
1397 |                           "innerRadius": 0
1398 |                         },
1399 |                         "interactions": []
1400 |                       },
1401 |                       {
1402 |                         "id": "2:705",
1403 |                         "name": "Ellipse 2586",
1404 |                         "type": "ELLIPSE",
1405 |                         "scrollBehavior": "SCROLLS",
1406 |                         "blendMode": "PASS_THROUGH",
1407 |                         "fills": [
1408 |                           {
1409 |                             "blendMode": "NORMAL",
1410 |                             "type": "SOLID",
1411 |                             "color": {
1412 |                               "r": 0.6039215922355652,
1413 |                               "g": 0.9490196108818054,
1414 |                               "b": 0.7333333492279053,
1415 |                               "a": 1
1416 |                             }
1417 |                           }
1418 |                         ],
1419 |                         "strokes": [],
1420 |                         "strokeWeight": 0.9958661794662476,
1421 |                         "strokeAlign": "INSIDE",
1422 |                         "absoluteBoundingBox": {
1423 |                           "x": 1160.1041259765625,
1424 |                           "y": 712.13623046875,
1425 |                           "width": 7.9669294357299805,
1426 |                           "height": 7.932440280914307
1427 |                         },
1428 |                         "absoluteRenderBounds": {
1429 |                           "x": 1160.1041259765625,
1430 |                           "y": 712.13623046875,
1431 |                           "width": 7.9669189453125,
1432 |                           "height": 7.93243408203125
1433 |                         },
1434 |                         "constraints": {
1435 |                           "vertical": "TOP",
1436 |                           "horizontal": "LEFT"
1437 |                         },
1438 |                         "effects": [],
1439 |                         "arcData": {
1440 |                           "startingAngle": 0,
1441 |                           "endingAngle": 6.2831854820251465,
1442 |                           "innerRadius": 0
1443 |                         },
1444 |                         "interactions": []
1445 |                       },
1446 |                       {
1447 |                         "id": "2:706",
1448 |                         "name": "Rectangle 34626112",
1449 |                         "type": "RECTANGLE",
1450 |                         "scrollBehavior": "SCROLLS",
1451 |                         "blendMode": "PASS_THROUGH",
1452 |                         "fills": [
1453 |                           {
1454 |                             "blendMode": "NORMAL",
1455 |                             "type": "SOLID",
1456 |                             "color": {
1457 |                               "r": 0.6039215922355652,
1458 |                               "g": 0.9490196108818054,
1459 |                               "b": 0.7333333492279053,
1460 |                               "a": 1
1461 |                             }
1462 |                           }
1463 |                         ],
1464 |                         "strokes": [],
1465 |                         "strokeWeight": 0.9958661794662476,
1466 |                         "strokeAlign": "INSIDE",
1467 |                         "cornerRadius": 2.9875986576080322,
1468 |                         "cornerSmoothing": 0,
1469 |                         "absoluteBoundingBox": {
1470 |                           "x": 1172.0545654296875,
1471 |                           "y": 700.2426147460938,
1472 |                           "width": 61.74370193481445,
1473 |                           "height": 7.932440280914307
1474 |                         },
1475 |                         "absoluteRenderBounds": {
1476 |                           "x": 1172.0545654296875,
1477 |                           "y": 700.2426147460938,
1478 |                           "width": 61.74365234375,
1479 |                           "height": 7.93243408203125
1480 |                         },
1481 |                         "constraints": {
1482 |                           "vertical": "TOP",
1483 |                           "horizontal": "LEFT"
1484 |                         },
1485 |                         "effects": [],
1486 |                         "interactions": []
1487 |                       },
1488 |                       {
1489 |                         "id": "2:707",
1490 |                         "name": "Rectangle 34626113",
1491 |                         "type": "RECTANGLE",
1492 |                         "scrollBehavior": "SCROLLS",
1493 |                         "blendMode": "PASS_THROUGH",
1494 |                         "fills": [
1495 |                           {
1496 |                             "blendMode": "NORMAL",
1497 |                             "type": "SOLID",
1498 |                             "color": {
1499 |                               "r": 0.6039215922355652,
1500 |                               "g": 0.9490196108818054,
1501 |                               "b": 0.7333333492279053,
1502 |                               "a": 1
1503 |                             }
1504 |                           }
1505 |                         ],
1506 |                         "strokes": [],
1507 |                         "strokeWeight": 0.9958661794662476,
1508 |                         "strokeAlign": "INSIDE",
1509 |                         "cornerRadius": 2.9875986576080322,
1510 |                         "cornerSmoothing": 0,
1511 |                         "absoluteBoundingBox": {
1512 |                           "x": 1172.0545654296875,
1513 |                           "y": 712.13623046875,
1514 |                           "width": 48.79743957519531,
1515 |                           "height": 7.932440280914307
1516 |                         },
1517 |                         "absoluteRenderBounds": {
1518 |                           "x": 1172.0545654296875,
1519 |                           "y": 712.13623046875,
1520 |                           "width": 48.7974853515625,
1521 |                           "height": 7.93243408203125
1522 |                         },
1523 |                         "constraints": {
1524 |                           "vertical": "TOP",
1525 |                           "horizontal": "LEFT"
1526 |                         },
1527 |                         "effects": [],
1528 |                         "interactions": []
1529 |                       },
1530 |                       {
1531 |                         "id": "2:708",
1532 |                         "name": "Vector 600",
1533 |                         "type": "VECTOR",
1534 |                         "scrollBehavior": "SCROLLS",
1535 |                         "blendMode": "PASS_THROUGH",
1536 |                         "fills": [],
1537 |                         "fillOverrideTable": {
1538 |                           "1": null,
1539 |                           "2": null,
1540 |                           "3": null
1541 |                         },
1542 |                         "strokes": [
1543 |                           {
1544 |                             "blendMode": "NORMAL",
1545 |                             "type": "SOLID",
1546 |                             "color": {
1547 |                               "r": 0.6509804129600525,
1548 |                               "g": 0.9764705896377563,
1549 |                               "b": 0.7607843279838562,
1550 |                               "a": 1
1551 |                             }
1552 |                           }
1553 |                         ],
1554 |                         "strokeWeight": 2.9875986576080322,
1555 |                         "strokeAlign": "CENTER",
1556 |                         "strokeJoin": "ROUND",
1557 |                         "strokeCap": "ROUND",
1558 |                         "absoluteBoundingBox": {
1559 |                           "x": 1156.1207275390625,
1560 |                           "y": 677.427734375,
1561 |                           "width": 79.72064208984375,
1562 |                           "height": 11.296095848083496
1563 |                         },
1564 |                         "absoluteRenderBounds": {
1565 |                           "x": 1154.626953125,
1566 |                           "y": 675.93359375,
1567 |                           "width": 82.7083740234375,
1568 |                           "height": 14.2840576171875
1569 |                         },
1570 |                         "constraints": {
1571 |                           "vertical": "TOP",
1572 |                           "horizontal": "LEFT"
1573 |                         },
1574 |                         "effects": [],
1575 |                         "interactions": []
1576 |                       },
1577 |                       {
1578 |                         "id": "2:709",
1579 |                         "name": "Star 4",
1580 |                         "type": "STAR",
1581 |                         "scrollBehavior": "SCROLLS",
1582 |                         "blendMode": "PASS_THROUGH",
1583 |                         "fills": [
1584 |                           {
1585 |                             "opacity": 0.44999998807907104,
1586 |                             "blendMode": "NORMAL",
1587 |                             "type": "SOLID",
1588 |                             "color": {
1589 |                               "r": 0.26274511218070984,
1590 |                               "g": 0.929411768913269,
1591 |                               "b": 0.5803921818733215,
1592 |                               "a": 1
1593 |                             }
1594 |                           }
1595 |                         ],
1596 |                         "strokes": [],
1597 |                         "strokeWeight": 0.9958661794662476,
1598 |                         "strokeAlign": "INSIDE",
1599 |                         "cornerRadius": 0.9958661794662476,
1600 |                         "cornerSmoothing": 0,
1601 |                         "absoluteBoundingBox": {
1602 |                           "x": 1099.3563232421875,
1603 |                           "y": 677.427734375,
1604 |                           "width": 21.909053802490234,
1605 |                           "height": 21.814208984375
1606 |                         },
1607 |                         "absoluteRenderBounds": {
1608 |                           "x": 1101.794189453125,
1609 |                           "y": 679.8384399414062,
1610 |                           "width": 17.0333251953125,
1611 |                           "height": 16.9927978515625
1612 |                         },
1613 |                         "constraints": {
1614 |                           "vertical": "TOP",
1615 |                           "horizontal": "LEFT"
1616 |                         },
1617 |                         "effects": [],
1618 |                         "interactions": []
1619 |                       },
1620 |                       {
1621 |                         "id": "2:710",
1622 |                         "name": "Group 1410104431",
1623 |                         "type": "GROUP",
1624 |                         "scrollBehavior": "SCROLLS",
1625 |                         "rotation": 5.527084202842137e-17,
1626 |                         "children": [
1627 |                           {
1628 |                             "id": "2:711",
1629 |                             "name": "Union",
1630 |                             "type": "BOOLEAN_OPERATION",
1631 |                             "scrollBehavior": "SCROLLS",
1632 |                             "rotation": -1.2451086677321256e-24,
1633 |                             "children": [
1634 |                               {
1635 |                                 "id": "2:712",
1636 |                                 "name": "Vector 561 (Stroke)",
1637 |                                 "type": "VECTOR",
1638 |                                 "scrollBehavior": "SCROLLS",
1639 |                                 "rotation": -1.2451086677321256e-24,
1640 |                                 "blendMode": "PASS_THROUGH",
1641 |                                 "fills": [
1642 |                                   {
1643 |                                     "opacity": 0.10000000149011612,
1644 |                                     "blendMode": "NORMAL",
1645 |                                     "type": "SOLID",
1646 |                                     "color": {
1647 |                                       "r": 0.1411764770746231,
1648 |                                       "g": 0.7803921699523926,
1649 |                                       "b": 0.5647059082984924,
1650 |                                       "a": 1
1651 |                                     }
1652 |                                   }
1653 |                                 ],
1654 |                                 "strokes": [],
1655 |                                 "strokeWeight": 1.9917323589324951,
1656 |                                 "strokeAlign": "CENTER",
1657 |                                 "strokeCap": "ROUND",
1658 |                                 "absoluteBoundingBox": {
1659 |                                   "x": 1252.719482421875,
1660 |                                   "y": 779.5591430664062,
1661 |                                   "width": 9.958661079406738,
1662 |                                   "height": 1.9831100702285767
1663 |                                 },
1664 |                                 "absoluteRenderBounds": null,
1665 |                                 "constraints": {
1666 |                                   "vertical": "TOP",
1667 |                                   "horizontal": "LEFT"
1668 |                                 },
1669 |                                 "effects": [],
1670 |                                 "interactions": []
1671 |                               },
1672 |                               {
1673 |                                 "id": "2:713",
1674 |                                 "name": "Vector 562 (Stroke)",
1675 |                                 "type": "VECTOR",
1676 |                                 "scrollBehavior": "SCROLLS",
1677 |                                 "rotation": 1.57079641145509,
1678 |                                 "blendMode": "PASS_THROUGH",
1679 |                                 "fills": [
1680 |                                   {
1681 |                                     "opacity": 0.10000000149011612,
1682 |                                     "blendMode": "NORMAL",
1683 |                                     "type": "SOLID",
1684 |                                     "color": {
1685 |                                       "r": 0.1411764770746231,
1686 |                                       "g": 0.7803921699523926,
1687 |                                       "b": 0.5647059082984924,
1688 |                                       "a": 1
1689 |                                     }
1690 |                                   }
1691 |                                 ],
1692 |                                 "strokes": [],
1693 |                                 "strokeWeight": 1.9917323589324951,
1694 |                                 "strokeAlign": "CENTER",
1695 |                                 "strokeCap": "ROUND",
1696 |                                 "absoluteBoundingBox": {
1697 |                                   "x": 1256.7027248094278,
1698 |                                   "y": 775.5997923133051,
1699 |                                   "width": 1.9917331983847362,
1700 |                                   "height": 9.915549445422926
1701 |                                 },
1702 |                                 "absoluteRenderBounds": null,
1703 |                                 "constraints": {
1704 |                                   "vertical": "TOP",
1705 |                                   "horizontal": "LEFT"
1706 |                                 },
1707 |                                 "effects": [],
1708 |                                 "interactions": []
1709 |                               }
1710 |                             ],
1711 |                             "blendMode": "PASS_THROUGH",
1712 |                             "fills": [
1713 |                               {
1714 |                                 "opacity": 0.20000000298023224,
1715 |                                 "blendMode": "NORMAL",
1716 |                                 "type": "SOLID",
1717 |                                 "color": {
1718 |                                   "r": 0.1411764770746231,
1719 |                                   "g": 0.7803921699523926,
1720 |                                   "b": 0.5647059082984924,
1721 |                                   "a": 1
1722 |                                 }
1723 |                               }
1724 |                             ],
1725 |                             "strokes": [],
1726 |                             "strokeWeight": 0,
1727 |                             "strokeAlign": "CENTER",
1728 |                             "strokeCap": "ROUND",
1729 |                             "booleanOperation": "UNION",
1730 |                             "absoluteBoundingBox": {
1731 |                               "x": 1252.7197265625,
1732 |                               "y": 775.6001586914062,
1733 |                               "width": 9.9580078125,
1734 |                               "height": 9.9150390625
1735 |                             },
1736 |                             "absoluteRenderBounds": {
1737 |                               "x": 1252.7197265625,
1738 |                               "y": 775.6001586914062,
1739 |                               "width": 9.9580078125,
1740 |                               "height": 9.9150390625
1741 |                             },
1742 |                             "constraints": {
1743 |                               "vertical": "TOP",
1744 |                               "horizontal": "LEFT"
1745 |                             },
1746 |                             "effects": [],
1747 |                             "interactions": []
1748 |                           }
1749 |                         ],
1750 |                         "blendMode": "PASS_THROUGH",
1751 |                         "clipsContent": false,
1752 |                         "background": [],
1753 |                         "fills": [],
1754 |                         "strokes": [],
1755 |                         "strokeWeight": 0.4979330897331238,
1756 |                         "strokeAlign": "INSIDE",
1757 |                         "backgroundColor": {
1758 |                           "r": 0,
1759 |                           "g": 0,
1760 |                           "b": 0,
1761 |                           "a": 0
1762 |                         },
1763 |                         "absoluteBoundingBox": {
1764 |                           "x": 1252.7197265625,
1765 |                           "y": 775.6001586914062,
1766 |                           "width": 9.9580078125,
1767 |                           "height": 9.9150390625
1768 |                         },
1769 |                         "absoluteRenderBounds": {
1770 |                           "x": 1252.7197265625,
1771 |                           "y": 775.6001586914062,
1772 |                           "width": 9.9580078125,
1773 |                           "height": 9.9150390625
1774 |                         },
1775 |                         "constraints": {
1776 |                           "vertical": "TOP",
1777 |                           "horizontal": "LEFT"
1778 |                         },
1779 |                         "effects": [],
1780 |                         "interactions": []
1781 |                       },
1782 |                       {
1783 |                         "id": "2:714",
1784 |                         "name": "Group 1410104847",
1785 |                         "type": "GROUP",
1786 |                         "scrollBehavior": "SCROLLS",
1787 |                         "rotation": 0.7832289476437421,
1788 |                         "children": [
1789 |                           {
1790 |                             "id": "2:715",
1791 |                             "name": "Union",
1792 |                             "type": "BOOLEAN_OPERATION",
1793 |                             "scrollBehavior": "SCROLLS",
1794 |                             "rotation": -2.591895864600957e-10,
1795 |                             "children": [
1796 |                               {
1797 |                                 "id": "2:716",
1798 |                                 "name": "Vector 561 (Stroke)",
1799 |                                 "type": "VECTOR",
1800 |                                 "scrollBehavior": "SCROLLS",
1801 |                                 "rotation": -2.591895864600957e-10,
1802 |                                 "blendMode": "PASS_THROUGH",
1803 |                                 "fills": [
1804 |                                   {
1805 |                                     "opacity": 0.10000000149011612,
1806 |                                     "blendMode": "NORMAL",
1807 |                                     "type": "SOLID",
1808 |                                     "color": {
1809 |                                       "r": 0.1411764770746231,
1810 |                                       "g": 0.7803921699523926,
1811 |                                       "b": 0.5647059082984924,
1812 |                                       "a": 1
1813 |                                     }
1814 |                                   }
1815 |                                 ],
1816 |                                 "strokes": [],
1817 |                                 "strokeWeight": 1.9917323589324951,
1818 |                                 "strokeAlign": "CENTER",
1819 |                                 "strokeCap": "ROUND",
1820 |                                 "absoluteBoundingBox": {
1821 |                                   "x": 1280.4326970016364,
1822 |                                   "y": 716.9205932617188,
1823 |                                   "width": 8.450204286549706,
1824 |                                   "height": 8.413622949463615
1825 |                                 },
1826 |                                 "absoluteRenderBounds": null,
1827 |                                 "constraints": {
1828 |                                   "vertical": "SCALE",
1829 |                                   "horizontal": "SCALE"
1830 |                                 },
1831 |                                 "effects": [],
1832 |                                 "interactions": []
1833 |                               },
1834 |                               {
1835 |                                 "id": "2:717",
1836 |                                 "name": "Vector 562 (Stroke)",
1837 |                                 "type": "VECTOR",
1838 |                                 "scrollBehavior": "SCROLLS",
1839 |                                 "rotation": 1.570796411347784,
1840 |                                 "blendMode": "PASS_THROUGH",
1841 |                                 "fills": [
1842 |                                   {
1843 |                                     "opacity": 0.10000000149011612,
1844 |                                     "blendMode": "NORMAL",
1845 |                                     "type": "SOLID",
1846 |                                     "color": {
1847 |                                       "r": 0.1411764770746231,
1848 |                                       "g": 0.7803921699523926,
1849 |                                       "b": 0.5647059082984924,
1850 |                                       "a": 1
1851 |                                     }
1852 |                                   }
1853 |                                 ],
1854 |                                 "strokes": [],
1855 |                                 "strokeWeight": 1.9917323589324951,
1856 |                                 "strokeAlign": "CENTER",
1857 |                                 "strokeCap": "ROUND",
1858 |                                 "absoluteBoundingBox": {
1859 |                                   "x": 1280.4326077396113,
1860 |                                   "y": 716.9262815659645,
1861 |                                   "width": 8.450204760388715,
1862 |                                   "height": 8.41362247562438
1863 |                                 },
1864 |                                 "absoluteRenderBounds": null,
1865 |                                 "constraints": {
1866 |                                   "vertical": "SCALE",
1867 |                                   "horizontal": "SCALE"
1868 |                                 },
1869 |                                 "effects": [],
1870 |                                 "interactions": []
1871 |                               }
1872 |                             ],
1873 |                             "blendMode": "PASS_THROUGH",
1874 |                             "fills": [
1875 |                               {
1876 |                                 "opacity": 0.20000000298023224,
1877 |                                 "blendMode": "NORMAL",
1878 |                                 "type": "SOLID",
1879 |                                 "color": {
1880 |                                   "r": 0.1411764770746231,
1881 |                                   "g": 0.7803921699523926,
1882 |                                   "b": 0.5647059082984924,
1883 |                                   "a": 1
1884 |                                 }
1885 |                               }
1886 |                             ],
1887 |                             "strokes": [],
1888 |                             "strokeWeight": 0,
1889 |                             "strokeAlign": "CENTER",
1890 |                             "strokeCap": "ROUND",
1891 |                             "booleanOperation": "UNION",
1892 |                             "absoluteBoundingBox": {
1893 |                               "x": 1277.613173712045,
1894 |                               "y": 714.1190795898438,
1895 |                               "width": 14.083507420669775,
1896 |                               "height": 14.022539245837834
1897 |                             },
1898 |                             "absoluteRenderBounds": {
1899 |                               "x": 1280.8450927734375,
1900 |                               "y": 717.3314208984375,
1901 |                               "width": 7.625244140625,
1902 |                               "height": 7.59759521484375
1903 |                             },
1904 |                             "constraints": {
1905 |                               "vertical": "SCALE",
1906 |                               "horizontal": "SCALE"
1907 |                             },
1908 |                             "effects": [],
1909 |                             "interactions": []
1910 |                           }
1911 |                         ],
1912 |                         "blendMode": "PASS_THROUGH",
1913 |                         "clipsContent": false,
1914 |                         "background": [],
1915 |                         "fills": [],
1916 |                         "strokes": [],
1917 |                         "strokeWeight": 0.4979330897331238,
1918 |                         "strokeAlign": "INSIDE",
1919 |                         "backgroundColor": {
1920 |                           "r": 0,
1921 |                           "g": 0,
1922 |                           "b": 0,
1923 |                           "a": 0
1924 |                         },
1925 |                         "absoluteBoundingBox": {
1926 |                           "x": 1277.613173712045,
1927 |                           "y": 714.1190795898438,
1928 |                           "width": 14.083507420669775,
1929 |                           "height": 14.022539245837834
1930 |                         },
1931 |                         "absoluteRenderBounds": {
1932 |                           "x": 1277.613173712045,
1933 |                           "y": 714.1190795898438,
1934 |                           "width": 14.083507420669775,
1935 |                           "height": 14.022539245837834
1936 |                         },
1937 |                         "constraints": {
1938 |                           "vertical": "TOP",
1939 |                           "horizontal": "LEFT"
1940 |                         },
1941 |                         "effects": [],
1942 |                         "interactions": []
1943 |                       },
1944 |                       {
1945 |                         "id": "2:718",
1946 |                         "name": "Ellipse 2568",
1947 |                         "type": "ELLIPSE",
1948 |                         "scrollBehavior": "SCROLLS",
1949 |                         "blendMode": "PASS_THROUGH",
1950 |                         "fills": [],
1951 |                         "strokes": [
1952 |                           {
1953 |                             "opacity": 0.20000000298023224,
1954 |                             "blendMode": "NORMAL",
1955 |                             "type": "SOLID",
1956 |                             "color": {
1957 |                               "r": 0.1411764770746231,
1958 |                               "g": 0.7803921699523926,
1959 |                               "b": 0.5647059082984924,
1960 |                               "a": 1
1961 |                             }
1962 |                           }
1963 |                         ],
1964 |                         "strokeWeight": 1.9917323589324951,
1965 |                         "strokeAlign": "INSIDE",
1966 |                         "absoluteBoundingBox": {
1967 |                           "x": 1268.653564453125,
1968 |                           "y": 677.427734375,
1969 |                           "width": 10.954526901245117,
1970 |                           "height": 10.9071044921875
1971 |                         },
1972 |                         "absoluteRenderBounds": {
1973 |                           "x": 1268.653564453125,
1974 |                           "y": 677.427734375,
1975 |                           "width": 10.9544677734375,
1976 |                           "height": 10.9071044921875
1977 |                         },
1978 |                         "constraints": {
1979 |                           "vertical": "TOP",
1980 |                           "horizontal": "LEFT"
1981 |                         },
1982 |                         "effects": [],
1983 |                         "arcData": {
1984 |                           "startingAngle": 0,
1985 |                           "endingAngle": 6.2831854820251465,
1986 |                           "innerRadius": 0
1987 |                         },
1988 |                         "interactions": []
1989 |                       }
1990 |                     ],
1991 |                     "blendMode": "PASS_THROUGH",
1992 |                     "clipsContent": false,
1993 |                     "background": [],
1994 |                     "fills": [],
1995 |                     "strokes": [],
1996 |                     "rectangleCornerRadii": [0, 0, 0, 0],
1997 |                     "cornerSmoothing": 0,
1998 |                     "strokeWeight": 0.9958661794662476,
1999 |                     "strokeAlign": "INSIDE",
2000 |                     "backgroundColor": {
2001 |                       "r": 0,
2002 |                       "g": 0,
2003 |                       "b": 0,
2004 |                       "a": 0
2005 |                     },
2006 |                     "absoluteBoundingBox": {
2007 |                       "x": 1099.3563232421875,
2008 |                       "y": 661.5670166015625,
2009 |                       "width": 192.34036254882812,
2010 |                       "height": 123.94818115234375
2011 |                     },
2012 |                     "absoluteRenderBounds": {
2013 |                       "x": 1099.3563232421875,
2014 |                       "y": 651.6083374023438,
2015 |                       "width": 192.34036254882812,
2016 |                       "height": 143.8616943359375
2017 |                     },
2018 |                     "constraints": {
2019 |                       "vertical": "TOP",
2020 |                       "horizontal": "LEFT"
2021 |                     },
2022 |                     "effects": [],
2023 |                     "interactions": []
2024 |                   }
2025 |                 ],
2026 |                 "blendMode": "PASS_THROUGH",
2027 |                 "clipsContent": false,
2028 |                 "background": [],
2029 |                 "fills": [],
2030 |                 "strokes": [],
2031 |                 "rectangleCornerRadii": [0, 0, 0, 0],
2032 |                 "cornerSmoothing": 0,
2033 |                 "strokeWeight": 1.100000023841858,
2034 |                 "strokeAlign": "INSIDE",
2035 |                 "backgroundColor": {
2036 |                   "r": 0,
2037 |                   "g": 0,
2038 |                   "b": 0,
2039 |                   "a": 0
2040 |                 },
2041 |                 "absoluteBoundingBox": {
2042 |                   "x": 1086,
2043 |                   "y": 654,
2044 |                   "width": 220,
2045 |                   "height": 138
2046 |                 },
2047 |                 "absoluteRenderBounds": {
2048 |                   "x": 1086,
2049 |                   "y": 651.6083374023438,
2050 |                   "width": 220,
2051 |                   "height": 143.8616943359375
2052 |                 },
2053 |                 "constraints": {
2054 |                   "vertical": "TOP",
2055 |                   "horizontal": "LEFT"
2056 |                 },
2057 |                 "exportSettings": [
2058 |                   {
2059 |                     "suffix": "",
2060 |                     "format": "PNG",
2061 |                     "constraint": {
2062 |                       "type": "SCALE",
2063 |                       "value": 2
2064 |                     }
2065 |                   }
2066 |                 ],
2067 |                 "effects": [],
2068 |                 "interactions": []
2069 |               }
2070 |             ],
2071 |             "blendMode": "PASS_THROUGH",
2072 |             "clipsContent": false,
2073 |             "background": [],
2074 |             "fills": [],
2075 |             "strokes": [],
2076 |             "rectangleCornerRadii": [0, 0, 0, 0],
2077 |             "cornerSmoothing": 0,
2078 |             "strokeWeight": 1,
2079 |             "strokeAlign": "INSIDE",
2080 |             "backgroundColor": {
2081 |               "r": 0,
2082 |               "g": 0,
2083 |               "b": 0,
2084 |               "a": 0
2085 |             },
2086 |             "absoluteBoundingBox": {
2087 |               "x": 1021,
2088 |               "y": 654,
2089 |               "width": 350,
2090 |               "height": 316
2091 |             },
2092 |             "absoluteRenderBounds": {
2093 |               "x": 1021,
2094 |               "y": 651.6083374023438,
2095 |               "width": 350,
2096 |               "height": 318.39166259765625
2097 |             },
2098 |             "constraints": {
2099 |               "vertical": "TOP",
2100 |               "horizontal": "LEFT"
2101 |             },
2102 |             "exportSettings": [
2103 |               {
2104 |                 "suffix": "",
2105 |                 "format": "PNG",
2106 |                 "constraint": {
2107 |                   "type": "SCALE",
2108 |                   "value": 1
2109 |                 }
2110 |               }
2111 |             ],
2112 |             "effects": [],
2113 |             "interactions": []
2114 |           }
2115 |         ],
2116 |         "blendMode": "PASS_THROUGH",
2117 |         "clipsContent": false,
2118 |         "background": [],
2119 |         "fills": [],
2120 |         "strokes": [],
2121 |         "rectangleCornerRadii": [0, 0, 0, 0],
2122 |         "cornerSmoothing": 0,
2123 |         "strokeWeight": 1,
2124 |         "strokeAlign": "INSIDE",
2125 |         "backgroundColor": {
2126 |           "r": 0,
2127 |           "g": 0,
2128 |           "b": 0,
2129 |           "a": 0
2130 |         },
2131 |         "absoluteBoundingBox": {
2132 |           "x": 406,
2133 |           "y": 422,
2134 |           "width": 1580,
2135 |           "height": 895
2136 |         },
2137 |         "absoluteRenderBounds": {
2138 |           "x": 406,
2139 |           "y": 422,
2140 |           "width": 1580,
2141 |           "height": 895
2142 |         },
2143 |         "constraints": {
2144 |           "vertical": "TOP",
2145 |           "horizontal": "LEFT"
2146 |         },
2147 |         "exportSettings": [
2148 |           {
2149 |             "suffix": "",
2150 |             "format": "PNG",
2151 |             "constraint": {
2152 |               "type": "SCALE",
2153 |               "value": 1
2154 |             }
2155 |           }
2156 |         ],
2157 |         "effects": [],
2158 |         "interactions": []
2159 |       },
2160 |       "components": {},
2161 |       "componentSets": {},
2162 |       "schemaVersion": 0,
2163 |       "styles": {}
2164 |     }
2165 |   }
2166 | }
2167 | 
```
Page 6/8FirstPrevNextLast