From eadb343292494afb59a768f218b113b45b703113 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Wed, 11 Jun 2025 16:55:58 +0200 Subject: [PATCH] Tidy-tree POC --- cypress/platform/knsv2.html | 131 ++++++-- packages/mermaid/package.json | 1 + .../tidy-tree/cytoscape-setup.test.ts | 281 ++++++++++++++++ .../tidy-tree/cytoscape-setup.ts | 207 ++++++++++++ .../layout-algorithms/tidy-tree/index.ts | 25 ++ .../tidy-tree/layout.test.ts | 250 ++++++++++++++ .../layout-algorithms/tidy-tree/layout.ts | 79 +++++ .../layout-algorithms/tidy-tree/render.ts | 183 +++++++++++ .../layout-algorithms/tidy-tree/types.ts | 43 +++ packages/mermaid/src/rendering-util/render.ts | 4 + pnpm-lock.yaml | 309 ++++++++++++------ 11 files changed, 1387 insertions(+), 126 deletions(-) create mode 100644 packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/cytoscape-setup.test.ts create mode 100644 packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/cytoscape-setup.ts create mode 100644 packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/index.ts create mode 100644 packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.test.ts create mode 100644 packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.ts create mode 100644 packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/render.ts create mode 100644 packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/types.ts diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 697be86b4..431e139a5 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -105,6 +105,29 @@ +
+      ---
+      config:
+        layout: tidy-tree
+      ---
+      mindmap
+      root((mindmap))
+        Origins
+        Tools
+          Pen and paper
+          Mermaid
+    
+
+      ---
+      config:
+        layout: elk
+      ---
+      flowchart TB
+          A --> n0["1"]
+          A --> n1["2"]
+          A --> n2["3"]
+          A --> n3["4"]
+    
       ---
       config:
@@ -127,10 +150,8 @@
         Tools
           Pen and paper
           Mermaid
-
-
     
-
+    
       ---
       config:
         layout: cose-bilkent
@@ -138,23 +159,67 @@
       mindmap
       root((mindmap))
         Origins
-          Europe
-          Asia
-            East
-            West
+          Long history
+          ::icon(fa fa-book)
+          Popularisation
+            British popular psychology author Tony Buzan
+        Research
+          On effectiveness<br/>and features
+          On Automatic creation
+            Uses
+                Creative techniques
+                Strategic planning
+                Argument mapping
+        Tools
+          Pen and paper
+          Mermaid
+    
+
+      ---
+      config:
+        layout: elk
+      ---
+      mindmap
+      root((mindmap))
+        Origins
+          Long history
+          ::icon(fa fa-book)
+          Popularisation
+            British popular psychology author Tony Buzan
+        Research
+          On effectiveness<br/>and features
+          On Automatic creation
+            Uses
+                Creative techniques
+                Strategic planning
+                Argument mapping
+        Tools
+          Pen and paper
+          Mermaid
+    
+
+      ---
+      config:
+        layout: cose-bilkent
+      ---
+      flowchart LR
+      root{mindmap} --- Origins --- Europe
+      Origins --> Asia
+      root --- Background --- Rich
+      Background --- Poor
+      subgraph apa
         Background
-          Rich
-          Poor
-
+        Poor
+      end
 
 
 
 
     
-
+    
       ---
       config:
-        layout: cose-bilkent
+        layout: elk
       ---
       flowchart LR
       root{mindmap} --- Origins --- Europe
@@ -269,7 +334,7 @@ config:
       end
       end
     
-
+    
 ---
 config:
   layout: elk
@@ -282,7 +347,7 @@ config:
       D-->I
       D-->I
     
-
+    
 ---
 config:
   layout: elk
@@ -321,7 +386,7 @@ flowchart LR
     n8@{ shape: rect}
 
     
-
+    
 ---
 config:
   layout: elk
@@ -337,7 +402,7 @@ flowchart LR
 
 
     
-
+    
 ---
 config:
   layout: elk
@@ -346,7 +411,7 @@ flowchart LR
     A{A} --> B & C
 
-
+    
 ---
 config:
   layout: elk
@@ -376,7 +441,7 @@ flowchart LR
 
 
     
-
+    
 ---
 config:
   kanban:
@@ -395,81 +460,81 @@ kanban
     task3[💻 Develop login feature]@{ ticket: 103 }
 
     
-
+    
 flowchart LR
 nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }
 
     
-
+    
 flowchart LR
 nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' }
 style A fill:#f9f,stroke:#333,stroke-width:4px
     
-
+    
 flowchart LR
 nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' }
 A:::AClass
 classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
     
-
+    
 flowchart LR
   nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }
 
     
-
+    
 flowchart LR
 nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }
 
     
-
+    
 flowchart LR
 nA[Style] --> A@{ icon: 'fa:bell', form: 'square' }
 style A fill:#f9f,stroke:#333,stroke-width:4px
     
-
+    
 flowchart LR
 nA[Class] --> A@{ icon: 'fa:bell', form: 'square' }
 A:::AClass
 classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
     
-
+    
 flowchart LR
   nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }
 
     
-
+    
 flowchart LR
 nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }
 
     
-
+    
 flowchart LR
 nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' }
 style A fill:#f9f,stroke:#333,stroke-width:4px
     
-
+    
 flowchart LR
 nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' }
 A:::AClass
 classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
     
-
+    
 flowchart LR
   nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' }
   A:::AClass
   classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
     
-
+    
 flowchart LR
   nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' }
   style A fill:#f9f,stroke:#333,stroke-width:4px
     
-
+    
 kanban
   id2[In progress]
     docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }
     
-
+    
 ---
 config:
   kanban:
diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json
index 7f8230229..157f00669 100644
--- a/packages/mermaid/package.json
+++ b/packages/mermaid/package.json
@@ -83,6 +83,7 @@
     "khroma": "^2.1.0",
     "lodash-es": "^4.17.21",
     "marked": "^15.0.7",
+    "non-layered-tidy-tree-layout": "^2.0.2",
     "roughjs": "^4.6.6",
     "stylis": "^4.3.6",
     "ts-dedent": "^2.2.0",
diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/cytoscape-setup.test.ts b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/cytoscape-setup.test.ts
new file mode 100644
index 000000000..2592b57db
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/cytoscape-setup.test.ts
@@ -0,0 +1,281 @@
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import {
+  addNodes,
+  addEdges,
+  extractPositionedNodes,
+  extractPositionedEdges,
+} from './cytoscape-setup.js';
+import type { Node, Edge } from '../../types.js';
+
+// Mock cytoscape
+const mockCy = {
+  add: vi.fn(),
+  nodes: vi.fn(),
+  edges: vi.fn(),
+};
+
+vi.mock('cytoscape', () => {
+  const mockCytoscape = vi.fn(() => mockCy) as any;
+  mockCytoscape.use = vi.fn();
+  return {
+    default: mockCytoscape,
+  };
+});
+
+vi.mock('cytoscape-cose-bilkent', () => ({
+  default: vi.fn(),
+}));
+
+vi.mock('d3', () => ({
+  select: vi.fn(() => ({
+    append: vi.fn(() => ({
+      attr: vi.fn(() => ({
+        attr: vi.fn(() => ({
+          remove: vi.fn(),
+        })),
+      })),
+    })),
+  })),
+}));
+
+describe('Cytoscape Setup', () => {
+  let mockNodes: Node[];
+  let mockEdges: Edge[];
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+
+    mockNodes = [
+      {
+        id: '1',
+        label: 'Root',
+        isGroup: false,
+        shape: 'rect',
+        width: 100,
+        height: 50,
+        padding: 10,
+        x: 100,
+        y: 100,
+        cssClasses: '',
+        cssStyles: [],
+        look: 'default',
+      },
+      {
+        id: '2',
+        label: 'Child 1',
+        isGroup: false,
+        shape: 'rect',
+        width: 80,
+        height: 40,
+        padding: 10,
+        x: 150,
+        y: 150,
+        cssClasses: '',
+        cssStyles: [],
+        look: 'default',
+      },
+    ];
+
+    mockEdges = [
+      {
+        id: '1_2',
+        start: '1',
+        end: '2',
+        type: 'edge',
+        classes: '',
+        style: [],
+        animate: false,
+        arrowTypeEnd: 'arrow_point',
+        arrowTypeStart: 'none',
+      },
+    ];
+  });
+
+  describe('addNodes', () => {
+    it('should add nodes to cytoscape', () => {
+      addNodes([mockNodes[0]], mockCy as unknown as any);
+
+      expect(mockCy.add).toHaveBeenCalledWith({
+        group: 'nodes',
+        data: {
+          id: '1',
+          labelText: 'Root',
+          height: 50,
+          width: 100,
+          padding: 10,
+          isGroup: false,
+          shape: 'rect',
+          cssClasses: '',
+          cssStyles: [],
+          look: 'default',
+        },
+        position: {
+          x: 100,
+          y: 100,
+        },
+      });
+    });
+
+    it('should add multiple nodes to cytoscape', () => {
+      addNodes(mockNodes, mockCy as unknown as any);
+
+      expect(mockCy.add).toHaveBeenCalledTimes(2);
+
+      expect(mockCy.add).toHaveBeenCalledWith({
+        group: 'nodes',
+        data: {
+          id: '1',
+          labelText: 'Root',
+          height: 50,
+          width: 100,
+          padding: 10,
+          isGroup: false,
+          shape: 'rect',
+          cssClasses: '',
+          cssStyles: [],
+          look: 'default',
+        },
+        position: {
+          x: 100,
+          y: 100,
+        },
+      });
+
+      expect(mockCy.add).toHaveBeenCalledWith({
+        group: 'nodes',
+        data: {
+          id: '2',
+          labelText: 'Child 1',
+          height: 40,
+          width: 80,
+          padding: 10,
+          isGroup: false,
+          shape: 'rect',
+          cssClasses: '',
+          cssStyles: [],
+          look: 'default',
+        },
+        position: {
+          x: 150,
+          y: 150,
+        },
+      });
+    });
+  });
+
+  describe('addEdges', () => {
+    it('should add edges to cytoscape', () => {
+      addEdges(mockEdges, mockCy as unknown as any);
+
+      expect(mockCy.add).toHaveBeenCalledWith({
+        group: 'edges',
+        data: {
+          id: '1_2',
+          source: '1',
+          target: '2',
+          type: 'edge',
+          classes: '',
+          style: [],
+          animate: false,
+          arrowTypeEnd: 'arrow_point',
+          arrowTypeStart: 'none',
+        },
+      });
+    });
+  });
+
+  describe('extractPositionedNodes', () => {
+    it('should extract positioned nodes from cytoscape', () => {
+      const mockCytoscapeNodes = [
+        {
+          data: () => ({
+            id: '1',
+            labelText: 'Root',
+            width: 100,
+            height: 50,
+            padding: 10,
+            isGroup: false,
+            shape: 'rect',
+          }),
+          position: () => ({ x: 100, y: 100 }),
+        },
+        {
+          data: () => ({
+            id: '2',
+            labelText: 'Child 1',
+            width: 80,
+            height: 40,
+            padding: 10,
+            isGroup: false,
+            shape: 'rect',
+          }),
+          position: () => ({ x: 150, y: 150 }),
+        },
+      ];
+
+      mockCy.nodes.mockReturnValue({
+        map: (fn: unknown) => mockCytoscapeNodes.map(fn as any),
+      });
+
+      const result = extractPositionedNodes(mockCy as unknown as any);
+
+      expect(result).toHaveLength(2);
+      expect(result[0]).toEqual({
+        id: '1',
+        x: 100,
+        y: 100,
+        labelText: 'Root',
+        width: 100,
+        height: 50,
+        padding: 10,
+        isGroup: false,
+        shape: 'rect',
+      });
+    });
+  });
+
+  describe('extractPositionedEdges', () => {
+    it('should extract positioned edges from cytoscape', () => {
+      const mockCytoscapeEdges = [
+        {
+          data: () => ({
+            id: '1_2',
+            source: '1',
+            target: '2',
+            type: 'edge',
+          }),
+          _private: {
+            rscratch: {
+              startX: 100,
+              startY: 100,
+              midX: 125,
+              midY: 125,
+              endX: 150,
+              endY: 150,
+            },
+          },
+        },
+      ];
+
+      mockCy.edges.mockReturnValue({
+        map: (fn: unknown) => mockCytoscapeEdges.map(fn as any),
+      });
+
+      const result = extractPositionedEdges(mockCy as unknown as any);
+
+      expect(result).toHaveLength(1);
+      expect(result[0]).toEqual({
+        id: '1_2',
+        source: '1',
+        target: '2',
+        type: 'edge',
+        startX: 100,
+        startY: 100,
+        midX: 125,
+        midY: 125,
+        endX: 150,
+        endY: 150,
+      });
+    });
+  });
+});
diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/cytoscape-setup.ts b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/cytoscape-setup.ts
new file mode 100644
index 000000000..8fb9b2599
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/cytoscape-setup.ts
@@ -0,0 +1,207 @@
+import cytoscape from 'cytoscape';
+import coseBilkent from 'cytoscape-cose-bilkent';
+import { select } from 'd3';
+import { log } from '../../../logger.js';
+import type { LayoutData, Node, Edge } from '../../types.js';
+import type { CytoscapeLayoutConfig, PositionedNode, PositionedEdge } from './types.js';
+
+// Inject the layout algorithm into cytoscape
+cytoscape.use(coseBilkent);
+
+/**
+ * Declare module augmentation for cytoscape edge types
+ */
+declare module 'cytoscape' {
+  interface EdgeSingular {
+    _private: {
+      bodyBounds: unknown;
+      rscratch: {
+        startX: number;
+        startY: number;
+        midX: number;
+        midY: number;
+        endX: number;
+        endY: number;
+      };
+    };
+  }
+}
+
+/**
+ * Add nodes to cytoscape instance from provided node array
+ * This function processes only the nodes provided in the data structure
+ * @param nodes - Array of nodes to add
+ * @param cy - The cytoscape instance
+ */
+export function addNodes(nodes: Node[], cy: cytoscape.Core): void {
+  nodes.forEach((node) => {
+    const nodeData: Record = {
+      id: node.id,
+      labelText: node.label,
+      height: node.height,
+      width: node.width,
+      padding: node.padding ?? 0,
+    };
+
+    // Add any additional properties from the node
+    Object.keys(node).forEach((key) => {
+      if (!['id', 'label', 'height', 'width', 'padding', 'x', 'y'].includes(key)) {
+        nodeData[key] = (node as unknown as Record)[key];
+      }
+    });
+
+    cy.add({
+      group: 'nodes',
+      data: nodeData,
+      position: {
+        x: node.x ?? 0,
+        y: node.y ?? 0,
+      },
+    });
+  });
+}
+
+/**
+ * Add edges to cytoscape instance from provided edge array
+ * This function processes only the edges provided in the data structure
+ * @param edges - Array of edges to add
+ * @param cy - The cytoscape instance
+ */
+export function addEdges(edges: Edge[], cy: cytoscape.Core): void {
+  edges.forEach((edge) => {
+    const edgeData: Record = {
+      id: edge.id,
+      source: edge.start,
+      target: edge.end,
+    };
+
+    // Add any additional properties from the edge
+    Object.keys(edge).forEach((key) => {
+      if (!['id', 'start', 'end'].includes(key)) {
+        edgeData[key] = (edge as unknown as Record)[key];
+      }
+    });
+
+    cy.add({
+      group: 'edges',
+      data: edgeData,
+    });
+  });
+}
+
+/**
+ * Create and configure cytoscape instance
+ * @param data - Layout data containing nodes and edges
+ * @returns Promise resolving to configured cytoscape instance
+ */
+export function createCytoscapeInstance(data: LayoutData): Promise {
+  return new Promise((resolve) => {
+    // Add temporary render element
+    const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
+
+    const cy = cytoscape({
+      container: document.getElementById('cy'), // container to render in
+      style: [
+        {
+          selector: 'edge',
+          style: {
+            'curve-style': 'bezier',
+          },
+        },
+      ],
+    });
+
+    // Remove element after layout
+    renderEl.remove();
+
+    // Add all nodes and edges to cytoscape using the generic functions
+    addNodes(data.nodes, cy);
+    addEdges(data.edges, cy);
+
+    // Make cytoscape care about the dimensions of the nodes
+    cy.nodes().forEach(function (n) {
+      n.layoutDimensions = () => {
+        const nodeData = n.data();
+        return { w: nodeData.width, h: nodeData.height };
+      };
+    });
+
+    // Configure and run the cose-bilkent layout
+    const layoutConfig: CytoscapeLayoutConfig = {
+      name: 'cose-bilkent',
+      // @ts-ignore Types for cose-bilkent are not correct?
+      quality: 'proof',
+      styleEnabled: false,
+      animate: false,
+    };
+
+    cy.layout(layoutConfig).run();
+
+    cy.ready((e) => {
+      log.info('Cytoscape ready', e);
+      resolve(cy);
+    });
+  });
+}
+
+/**
+ * Extract positioned nodes from cytoscape instance
+ * @param cy - The cytoscape instance after layout
+ * @returns Array of positioned nodes
+ */
+export function extractPositionedNodes(cy: cytoscape.Core): PositionedNode[] {
+  return cy.nodes().map((node) => {
+    const data = node.data();
+    const position = node.position();
+
+    // Create a positioned node with all original data plus position
+    const positionedNode: PositionedNode = {
+      id: data.id,
+      x: position.x,
+      y: position.y,
+    };
+
+    // Add all other properties from the original data
+    Object.keys(data).forEach((key) => {
+      if (key !== 'id') {
+        positionedNode[key] = data[key];
+      }
+    });
+
+    return positionedNode;
+  });
+}
+
+/**
+ * Extract positioned edges from cytoscape instance
+ * @param cy - The cytoscape instance after layout
+ * @returns Array of positioned edges
+ */
+export function extractPositionedEdges(cy: cytoscape.Core): PositionedEdge[] {
+  return cy.edges().map((edge) => {
+    const data = edge.data();
+    const rscratch = edge._private.rscratch;
+
+    // Create a positioned edge with all original data plus position
+    const positionedEdge: PositionedEdge = {
+      id: data.id,
+      source: data.source,
+      target: data.target,
+      startX: rscratch.startX,
+      startY: rscratch.startY,
+      midX: rscratch.midX,
+      midY: rscratch.midY,
+      endX: rscratch.endX,
+      endY: rscratch.endY,
+    };
+
+    // Add all other properties from the original data
+    Object.keys(data).forEach((key) => {
+      if (!['id', 'source', 'target'].includes(key)) {
+        positionedEdge[key] = data[key];
+      }
+    });
+
+    return positionedEdge;
+  });
+}
diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/index.ts b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/index.ts
new file mode 100644
index 000000000..9e12d38a7
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/index.ts
@@ -0,0 +1,25 @@
+import { render as renderWithCoseBilkent } from './render.js';
+
+/**
+ * Cose-Bilkent Layout Algorithm for Generic Diagrams
+ *
+ * This module provides a layout algorithm implementation using Cytoscape
+ * with the cose-bilkent algorithm for positioning nodes and edges.
+ *
+ * The algorithm follows the unified rendering pattern and can be used
+ * by any diagram type that provides compatible LayoutData.
+ */
+
+/**
+ * Render function for the cose-bilkent layout algorithm
+ *
+ * This function follows the unified rendering pattern used by all layout algorithms.
+ * It takes LayoutData, inserts nodes into DOM, runs the cose-bilkent layout algorithm,
+ * and renders the positioned elements to the SVG.
+ *
+ * @param layoutData - Layout data containing nodes, edges, and configuration
+ * @param svg - SVG element to render to
+ * @param helpers - Internal helper functions for rendering
+ * @param options - Rendering options
+ */
+export const render = renderWithCoseBilkent;
diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.test.ts b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.test.ts
new file mode 100644
index 000000000..1f8416f35
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.test.ts
@@ -0,0 +1,250 @@
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+
+// Mock cytoscape and cytoscape-cose-bilkent before importing the modules
+vi.mock('cytoscape', () => {
+  const mockCy = {
+    add: vi.fn(),
+    nodes: vi.fn(() => ({
+      forEach: vi.fn(),
+      map: vi.fn((fn) => [
+        fn({
+          data: () => ({
+            id: '1',
+            nodeId: '1',
+            labelText: 'Root',
+            level: 0,
+            type: 0,
+            width: 100,
+            height: 50,
+            padding: 10,
+          }),
+          position: () => ({ x: 100, y: 100 }),
+        }),
+      ]),
+    })),
+    edges: vi.fn(() => ({
+      map: vi.fn((fn) => [
+        fn({
+          data: () => ({
+            id: '1_2',
+            source: '1',
+            target: '2',
+            depth: 0,
+          }),
+          _private: {
+            rscratch: {
+              startX: 100,
+              startY: 100,
+              midX: 150,
+              midY: 150,
+              endX: 200,
+              endY: 200,
+            },
+          },
+        }),
+      ]),
+    })),
+    layout: vi.fn(() => ({
+      run: vi.fn(),
+    })),
+    ready: vi.fn((callback) => callback({})),
+  };
+
+  const mockCytoscape = vi.fn(() => mockCy);
+  mockCytoscape.use = vi.fn();
+
+  return {
+    default: mockCytoscape,
+  };
+});
+
+vi.mock('cytoscape-cose-bilkent', () => ({
+  default: vi.fn(),
+}));
+
+vi.mock('d3', () => ({
+  select: vi.fn(() => ({
+    append: vi.fn(() => ({
+      attr: vi.fn(() => ({
+        attr: vi.fn(() => ({
+          remove: vi.fn(),
+        })),
+      })),
+    })),
+  })),
+}));
+
+// Import modules after mocks
+import { layout, validateLayoutData } from './index.js';
+import type { MindmapLayoutData, LayoutResult } from './types.js';
+import type { MindmapNode } from '../../../diagrams/mindmap/mindmapTypes.js';
+import type { MermaidConfig } from '../../../config.type.js';
+
+describe('Cose-Bilkent Layout Algorithm', () => {
+  let mockConfig: MermaidConfig;
+  let mockRootNode: MindmapNode;
+  let mockLayoutData: MindmapLayoutData;
+
+  beforeEach(() => {
+    mockConfig = {
+      mindmap: {
+        layoutAlgorithm: 'cose-bilkent',
+        padding: 10,
+        maxNodeWidth: 200,
+        useMaxWidth: true,
+      },
+    } as MermaidConfig;
+
+    mockRootNode = {
+      id: 1,
+      nodeId: '1',
+      level: 0,
+      descr: 'Root',
+      type: 0,
+      width: 100,
+      height: 50,
+      padding: 10,
+      x: 0,
+      y: 0,
+      children: [
+        {
+          id: 2,
+          nodeId: '2',
+          level: 1,
+          descr: 'Child 1',
+          type: 0,
+          width: 80,
+          height: 40,
+          padding: 10,
+          x: 0,
+          y: 0,
+        },
+      ],
+    } as MindmapNode;
+
+    mockLayoutData = {
+      nodes: [
+        {
+          id: '1',
+          nodeId: '1',
+          level: 0,
+          descr: 'Root',
+          type: 0,
+          width: 100,
+          height: 50,
+          padding: 10,
+        },
+        {
+          id: '2',
+          nodeId: '2',
+          level: 1,
+          descr: 'Child 1',
+          type: 0,
+          width: 80,
+          height: 40,
+          padding: 10,
+        },
+      ],
+      edges: [
+        {
+          id: '1_2',
+          source: '1',
+          target: '2',
+          depth: 0,
+        },
+      ],
+      config: mockConfig,
+      rootNode: mockRootNode,
+    };
+  });
+
+  describe('validateLayoutData', () => {
+    it('should validate correct layout data', () => {
+      expect(() => validateLayoutData(mockLayoutData)).not.toThrow();
+    });
+
+    it('should throw error for missing data', () => {
+      expect(() => validateLayoutData(null as any)).toThrow('Layout data is required');
+    });
+
+    it('should throw error for missing root node', () => {
+      const invalidData = { ...mockLayoutData, rootNode: null as any };
+      expect(() => validateLayoutData(invalidData)).toThrow('Root node is required');
+    });
+
+    it('should throw error for missing config', () => {
+      const invalidData = { ...mockLayoutData, config: null as any };
+      expect(() => validateLayoutData(invalidData)).toThrow('Configuration is required');
+    });
+
+    it('should throw error for invalid nodes array', () => {
+      const invalidData = { ...mockLayoutData, nodes: null as any };
+      expect(() => validateLayoutData(invalidData)).toThrow('Nodes array is required');
+    });
+
+    it('should throw error for invalid edges array', () => {
+      const invalidData = { ...mockLayoutData, edges: null as any };
+      expect(() => validateLayoutData(invalidData)).toThrow('Edges array is required');
+    });
+  });
+
+  describe('layout function', () => {
+    it('should execute layout algorithm successfully', async () => {
+      const result: LayoutResult = await layout(mockLayoutData, mockConfig);
+
+      expect(result).toBeDefined();
+      expect(result.nodes).toBeDefined();
+      expect(result.edges).toBeDefined();
+      expect(Array.isArray(result.nodes)).toBe(true);
+      expect(Array.isArray(result.edges)).toBe(true);
+    });
+
+    it('should return positioned nodes with coordinates', async () => {
+      const result: LayoutResult = await layout(mockLayoutData, mockConfig);
+
+      expect(result.nodes.length).toBeGreaterThan(0);
+      result.nodes.forEach((node) => {
+        expect(node.x).toBeDefined();
+        expect(node.y).toBeDefined();
+        expect(typeof node.x).toBe('number');
+        expect(typeof node.y).toBe('number');
+      });
+    });
+
+    it('should return positioned edges with coordinates', async () => {
+      const result: LayoutResult = await layout(mockLayoutData, mockConfig);
+
+      expect(result.edges.length).toBeGreaterThan(0);
+      result.edges.forEach((edge) => {
+        expect(edge.startX).toBeDefined();
+        expect(edge.startY).toBeDefined();
+        expect(edge.midX).toBeDefined();
+        expect(edge.midY).toBeDefined();
+        expect(edge.endX).toBeDefined();
+        expect(edge.endY).toBeDefined();
+      });
+    });
+
+    it('should handle empty mindmap data gracefully', async () => {
+      const emptyData: MindmapLayoutData = {
+        nodes: [],
+        edges: [],
+        config: mockConfig,
+        rootNode: mockRootNode,
+      };
+
+      const result: LayoutResult = await layout(emptyData, mockConfig);
+      expect(result).toBeDefined();
+      expect(result.nodes).toBeDefined();
+      expect(result.edges).toBeDefined();
+      expect(Array.isArray(result.nodes)).toBe(true);
+      expect(Array.isArray(result.edges)).toBe(true);
+    });
+
+    it('should throw error for invalid data', async () => {
+      const invalidData = { ...mockLayoutData, rootNode: null as any };
+
+      await expect(layout(invalidData, mockConfig)).rejects.toThrow();
+    });
+  });
+});
diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.ts b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.ts
new file mode 100644
index 000000000..ba4ec0e12
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.ts
@@ -0,0 +1,79 @@
+import type { MermaidConfig } from '../../../config.type.js';
+import { log } from '../../../logger.js';
+import type { LayoutData } from '../../types.js';
+import type { LayoutResult } from './types.js';
+import {
+  createCytoscapeInstance,
+  extractPositionedNodes,
+  extractPositionedEdges,
+} from './cytoscape-setup.js';
+
+/**
+ * Execute the cose-bilkent layout algorithm on generic layout data
+ *
+ * This function takes layout data and uses Cytoscape with the cose-bilkent
+ * algorithm to calculate optimal node positions and edge paths.
+ *
+ * @param data - The layout data containing nodes, edges, and configuration
+ * @param config - Mermaid configuration object
+ * @returns Promise resolving to layout result with positioned nodes and edges
+ */
+export async function executeCoseBilkentLayout(
+  data: LayoutData,
+  _config: MermaidConfig
+): Promise {
+  log.debug('Starting cose-bilkent layout algorithm');
+
+  try {
+    // Validate input data
+    if (!data.nodes || !Array.isArray(data.nodes)) {
+      throw new Error('No nodes found in layout data');
+    }
+
+    if (!data.edges || !Array.isArray(data.edges)) {
+      throw new Error('No edges found in layout data');
+    }
+
+    // Create and configure cytoscape instance
+    const cy = await createCytoscapeInstance(data);
+
+    // Extract positioned nodes and edges after layout
+    const positionedNodes = extractPositionedNodes(cy);
+    const positionedEdges = extractPositionedEdges(cy);
+
+    log.debug(`Layout completed: ${positionedNodes.length} nodes, ${positionedEdges.length} edges`);
+
+    return {
+      nodes: positionedNodes,
+      edges: positionedEdges,
+    };
+  } catch (error) {
+    log.error('Error in cose-bilkent layout algorithm:', error);
+    throw error;
+  }
+}
+
+/**
+ * Validate layout data structure
+ * @param data - The data to validate
+ * @returns True if data is valid, throws error otherwise
+ */
+export function validateLayoutData(data: LayoutData): boolean {
+  if (!data) {
+    throw new Error('Layout data is required');
+  }
+
+  if (!data.config) {
+    throw new Error('Configuration is required in layout data');
+  }
+
+  if (!Array.isArray(data.nodes)) {
+    throw new Error('Nodes array is required in layout data');
+  }
+
+  if (!Array.isArray(data.edges)) {
+    throw new Error('Edges array is required in layout data');
+  }
+
+  return true;
+}
diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/render.ts b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/render.ts
new file mode 100644
index 000000000..1616fb781
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/render.ts
@@ -0,0 +1,183 @@
+import type { InternalHelpers, LayoutData, RenderOptions, SVG, SVGGroup } from 'mermaid';
+import { executeCoseBilkentLayout } from './layout.js';
+
+type Node = LayoutData['nodes'][number];
+
+interface NodeWithPosition extends Node {
+  x?: number;
+  y?: number;
+  domId?: SVGGroup;
+}
+
+/**
+ * Render function for cose-bilkent layout algorithm
+ *
+ * This follows the same pattern as ELK and dagre renderers:
+ * 1. Insert nodes into DOM to get their actual dimensions
+ * 2. Run the layout algorithm to calculate positions
+ * 3. Position the nodes and edges based on layout results
+ */
+export const render = async (
+  data4Layout: LayoutData,
+  svg: SVG,
+  {
+    insertCluster,
+    insertEdge,
+    insertEdgeLabel,
+    insertMarkers,
+    insertNode,
+    log,
+    positionEdgeLabel,
+  }: InternalHelpers,
+  { algorithm }: RenderOptions
+) => {
+  const nodeDb: Record = {};
+  const clusterDb: Record = {};
+
+  // Insert markers for edges
+  const element = svg.select('g');
+  insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
+
+  // Create container groups
+  const subGraphsEl = element.insert('g').attr('class', 'subgraphs');
+  const edgePaths = element.insert('g').attr('class', 'edgePaths');
+  const edgeLabels = element.insert('g').attr('class', 'edgeLabels');
+  const nodes = element.insert('g').attr('class', 'nodes');
+
+  // Step 1: Insert nodes into DOM to get their actual dimensions
+  log.debug('Inserting nodes into DOM for dimension calculation');
+
+  await Promise.all(
+    data4Layout.nodes.map(async (node) => {
+      if (node.isGroup) {
+        // Handle subgraphs/clusters
+        const clusterNode: NodeWithPosition = { ...node };
+        clusterDb[node.id] = clusterNode;
+        nodeDb[node.id] = clusterNode;
+
+        // Insert cluster to get dimensions
+        await insertCluster(subGraphsEl, node);
+      } else {
+        // Handle regular nodes
+        const nodeWithPosition: NodeWithPosition = { ...node };
+        nodeDb[node.id] = nodeWithPosition;
+
+        // Insert node to get actual dimensions
+        const nodeEl = await insertNode(nodes, node, {
+          config: data4Layout.config,
+          dir: data4Layout.direction || 'TB',
+        });
+
+        // Get the actual bounding box after insertion
+        const boundingBox = nodeEl.node()!.getBBox();
+        nodeWithPosition.width = boundingBox.width;
+        nodeWithPosition.height = boundingBox.height;
+        nodeWithPosition.domId = nodeEl;
+
+        log.debug(`Node ${node.id} dimensions: ${boundingBox.width}x${boundingBox.height}`);
+      }
+    })
+  );
+
+  // Step 2: Run the cose-bilkent layout algorithm
+  log.debug('Running cose-bilkent layout algorithm');
+
+  // Update the layout data with actual dimensions
+  const updatedLayoutData = {
+    ...data4Layout,
+    nodes: data4Layout.nodes.map((node) => {
+      const nodeWithDimensions = nodeDb[node.id];
+      return {
+        ...node,
+        width: nodeWithDimensions.width,
+        height: nodeWithDimensions.height,
+      };
+    }),
+  };
+
+  const layoutResult = await executeCoseBilkentLayout(updatedLayoutData, data4Layout.config);
+
+  // Step 3: Position the nodes based on layout results
+  log.debug('Positioning nodes based on layout results');
+
+  layoutResult.nodes.forEach((positionedNode) => {
+    const node = nodeDb[positionedNode.id];
+    if (node && node.domId) {
+      // Position the node at the calculated coordinates
+      // The positionedNode.x/y represents the center of the node, so use directly
+      node.domId.attr('transform', `translate(${positionedNode.x}, ${positionedNode.y})`);
+
+      // Store the final position
+      node.x = positionedNode.x;
+      node.y = positionedNode.y;
+
+      log.debug(`Positioned node ${node.id} at center (${positionedNode.x}, ${positionedNode.y})`);
+    }
+  });
+
+  // Step 4: Insert and position edges
+  log.debug('Inserting and positioning edges');
+
+  await Promise.all(
+    data4Layout.edges.map(async (edge) => {
+      // Insert edge label first
+      const edgeLabel = await insertEdgeLabel(edgeLabels, edge);
+
+      // Get start and end nodes
+      const startNode = nodeDb[edge.start];
+      const endNode = nodeDb[edge.end];
+
+      if (startNode && endNode) {
+        // Find the positioned edge data
+        const positionedEdge = layoutResult.edges.find((e) => e.id === edge.id);
+
+        if (positionedEdge) {
+          // Create edge path with positioned coordinates
+          const edgeWithPath = {
+            ...edge,
+            points: [
+              { x: positionedEdge.startX, y: positionedEdge.startY },
+              { x: positionedEdge.endX, y: positionedEdge.endY },
+            ],
+          };
+
+          // Insert the edge path
+          const paths = insertEdge(
+            edgePaths,
+            edgeWithPath,
+            clusterDb,
+            data4Layout.type,
+            startNode,
+            endNode,
+            data4Layout.diagramId
+          );
+
+          // Position the edge label
+          positionEdgeLabel(edgeWithPath, paths);
+        } else {
+          // Fallback: create a simple straight line between nodes
+          const edgeWithPath = {
+            ...edge,
+            points: [
+              { x: startNode.x || 0, y: startNode.y || 0 },
+              { x: endNode.x || 0, y: endNode.y || 0 },
+            ],
+          };
+
+          const paths = insertEdge(
+            edgePaths,
+            edgeWithPath,
+            clusterDb,
+            data4Layout.type,
+            startNode,
+            endNode,
+            data4Layout.diagramId
+          );
+          positionEdgeLabel(edgeWithPath, paths);
+        }
+      }
+    })
+  );
+
+  log.debug('Cose-bilkent rendering completed');
+};
diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/types.ts b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/types.ts
new file mode 100644
index 000000000..fade24682
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/types.ts
@@ -0,0 +1,43 @@
+/**
+ * Positioned node after layout calculation
+ */
+export interface PositionedNode {
+  id: string;
+  x: number;
+  y: number;
+  [key: string]: unknown; // Allow additional properties
+}
+
+/**
+ * Positioned edge after layout calculation
+ */
+export interface PositionedEdge {
+  id: string;
+  source: string;
+  target: string;
+  startX: number;
+  startY: number;
+  midX: number;
+  midY: number;
+  endX: number;
+  endY: number;
+  [key: string]: unknown; // Allow additional properties
+}
+
+/**
+ * Result of layout algorithm execution
+ */
+export interface LayoutResult {
+  nodes: PositionedNode[];
+  edges: PositionedEdge[];
+}
+
+/**
+ * Cytoscape layout configuration
+ */
+export interface CytoscapeLayoutConfig {
+  name: 'cose-bilkent';
+  quality: 'proof';
+  styleEnabled: boolean;
+  animate: boolean;
+}
diff --git a/packages/mermaid/src/rendering-util/render.ts b/packages/mermaid/src/rendering-util/render.ts
index 22a7b96b0..7d4ee84d5 100644
--- a/packages/mermaid/src/rendering-util/render.ts
+++ b/packages/mermaid/src/rendering-util/render.ts
@@ -43,6 +43,10 @@ const registerDefaultLayoutLoaders = () => {
       name: 'cose-bilkent',
       loader: async () => await import('./layout-algorithms/cose-bilkent/index.js'),
     },
+    {
+      name: 'tidy-tree',
+      loader: async () => await import('./layout-algorithms/tidy-tree/index.js'),
+    },
   ]);
 };
 
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 40039466f..3c13b7f92 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -265,6 +265,9 @@ importers:
       marked:
         specifier: ^15.0.7
         version: 15.0.7
+      non-layered-tidy-tree-layout:
+        specifier: ^2.0.2
+        version: 2.0.2
       roughjs:
         specifier: ^4.6.6
         version: 4.6.6(patch_hash=3543d47108cb41b68ec6a671c0e1f9d0cfe2ce524fea5b0992511ae84c3c6b64)
@@ -508,6 +511,67 @@ importers:
         specifier: ^7.3.0
         version: 7.3.0
 
+  packages/mermaid/src/vitepress:
+    dependencies:
+      '@mdi/font':
+        specifier: ^7.4.47
+        version: 7.4.47
+      '@vueuse/core':
+        specifier: ^12.7.0
+        version: 12.7.0(typescript@5.7.3)
+      font-awesome:
+        specifier: ^4.7.0
+        version: 4.7.0
+      jiti:
+        specifier: ^2.4.2
+        version: 2.4.2
+      mermaid:
+        specifier: workspace:^
+        version: link:../..
+      vue:
+        specifier: ^3.4.38
+        version: 3.5.13(typescript@5.7.3)
+    devDependencies:
+      '@iconify-json/carbon':
+        specifier: ^1.1.37
+        version: 1.2.1
+      '@unocss/reset':
+        specifier: ^66.0.0
+        version: 66.0.0
+      '@vite-pwa/vitepress':
+        specifier: ^0.5.3
+        version: 0.5.4(vite-plugin-pwa@0.21.2(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0))
+      '@vitejs/plugin-vue':
+        specifier: ^5.0.5
+        version: 5.2.1(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))
+      fast-glob:
+        specifier: ^3.3.3
+        version: 3.3.3
+      https-localhost:
+        specifier: ^4.7.1
+        version: 4.7.1
+      pathe:
+        specifier: ^2.0.3
+        version: 2.0.3
+      unocss:
+        specifier: ^66.0.0
+        version: 66.0.0(postcss@8.5.3)(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))
+      unplugin-vue-components:
+        specifier: ^28.4.0
+        version: 28.4.0(@babel/parser@7.27.2)(vue@3.5.13(typescript@5.7.3))
+      vite:
+        specifier: ^6.1.1
+        version: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+      vite-plugin-pwa:
+        specifier: ^0.21.1
+        version: 0.21.2(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0)
+      vitepress:
+        specifier: 1.6.3
+        version: 1.6.3(@algolia/client-search@5.20.3)(@types/node@22.13.5)(axios@1.8.4)(postcss@8.5.3)(search-insights@2.17.2)(terser@5.39.0)(typescript@5.7.3)
+      workbox-window:
+        specifier: ^7.3.0
+        version: 7.3.0
+
   packages/parser:
     dependencies:
       langium:
@@ -912,10 +976,6 @@ packages:
     resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-string-parser@7.25.9':
-    resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/helper-string-parser@7.27.1':
     resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
     engines: {node: '>=6.9.0'}
@@ -3627,6 +3687,15 @@ packages:
     peerDependencies:
       vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0
 
+  '@vite-pwa/vitepress@0.5.4':
+    resolution: {integrity: sha512-g57qwG983WTyQNLnOcDVPQEIeN+QDgK/HdqghmygiUFp3a/MzVvmLXC/EVnPAXxWa8W2g9pZ9lE3EiDGs2HjsA==}
+    peerDependencies:
+      '@vite-pwa/assets-generator': ^0.2.6
+      vite-plugin-pwa: '>=0.21.2 <1'
+    peerDependenciesMeta:
+      '@vite-pwa/assets-generator':
+        optional: true
+
   '@vite-pwa/vitepress@1.0.0':
     resolution: {integrity: sha512-i5RFah4urA6tZycYlGyBslVx8cVzbZBcARJLDg5rWMfAkRmyLtpRU6usGfVOwyN9kjJ2Bkm+gBHXF1hhr7HptQ==}
     peerDependencies:
@@ -4372,10 +4441,6 @@ packages:
     resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
     engines: {node: '>= 0.4'}
 
-  call-bound@1.0.3:
-    resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==}
-    engines: {node: '>= 0.4'}
-
   call-bound@1.0.4:
     resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
     engines: {node: '>= 0.4'}
@@ -6082,10 +6147,6 @@ packages:
     resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
     engines: {node: '>=18'}
 
-  get-intrinsic@1.2.7:
-    resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==}
-    engines: {node: '>= 0.4'}
-
   get-intrinsic@1.3.0:
     resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
     engines: {node: '>= 0.4'}
@@ -7517,10 +7578,6 @@ packages:
     resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
     engines: {node: '>= 0.6'}
 
-  mime-db@1.53.0:
-    resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==}
-    engines: {node: '>= 0.6'}
-
   mime-db@1.54.0:
     resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
     engines: {node: '>= 0.6'}
@@ -7714,6 +7771,9 @@ packages:
     resolution: {integrity: sha512-fiVbT7BqxiQqjlR9U3FDGOSERFCKoXVCdxV2FwZuNN7/cmJ42iQx35nUFOAFDcyvemu9Adp+IlsCGlKQYLmBKw==}
     deprecated: Package no longer supported. Contact support@npmjs.com for more info.
 
+  non-layered-tidy-tree-layout@2.0.2:
+    resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==}
+
   normalize-path@3.0.0:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
@@ -9594,6 +9654,18 @@ packages:
     peerDependencies:
       vite: '>=4 <=6'
 
+  vite-plugin-pwa@0.21.2:
+    resolution: {integrity: sha512-vFhH6Waw8itNu37hWUJxL50q+CBbNcMVzsKaYHQVrfxTt3ihk3PeLO22SbiP1UNWzcEPaTQv+YVxe4G0KOjAkg==}
+    engines: {node: '>=16.0.0'}
+    peerDependencies:
+      '@vite-pwa/assets-generator': ^0.2.6
+      vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
+      workbox-build: ^7.3.0
+      workbox-window: ^7.3.0
+    peerDependenciesMeta:
+      '@vite-pwa/assets-generator':
+        optional: true
+
   vite-plugin-pwa@1.0.0:
     resolution: {integrity: sha512-X77jo0AOd5OcxmWj3WnVti8n7Kw2tBgV1c8MCXFclrSlDV23ePzv2eTDIALXI2Qo6nJ5pZJeZAuX0AawvRfoeA==}
     engines: {node: '>=16.0.0'}
@@ -10648,7 +10720,7 @@ snapshots:
 
   '@babel/code-frame@7.26.2':
     dependencies:
-      '@babel/helper-validator-identifier': 7.25.9
+      '@babel/helper-validator-identifier': 7.27.1
       js-tokens: 4.0.0
       picocolors: 1.1.1
 
@@ -10670,10 +10742,10 @@ snapshots:
       '@babel/helper-compilation-targets': 7.26.5
       '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9)
       '@babel/helpers': 7.26.9
-      '@babel/parser': 7.26.9
+      '@babel/parser': 7.27.2
       '@babel/template': 7.26.9
       '@babel/traverse': 7.26.9
-      '@babel/types': 7.26.9
+      '@babel/types': 7.27.1
       convert-source-map: 2.0.0
       debug: 4.4.0(supports-color@8.1.1)
       gensync: 1.0.0-beta.2
@@ -10704,8 +10776,8 @@ snapshots:
 
   '@babel/generator@7.26.9':
     dependencies:
-      '@babel/parser': 7.26.9
-      '@babel/types': 7.26.9
+      '@babel/parser': 7.27.2
+      '@babel/types': 7.27.1
       '@jridgewell/gen-mapping': 0.3.8
       '@jridgewell/trace-mapping': 0.3.25
       jsesc: 3.1.0
@@ -10810,7 +10882,7 @@ snapshots:
   '@babel/helper-module-imports@7.25.9':
     dependencies:
       '@babel/traverse': 7.26.9
-      '@babel/types': 7.26.9
+      '@babel/types': 7.27.1
     transitivePeerDependencies:
       - supports-color
 
@@ -10825,7 +10897,7 @@ snapshots:
     dependencies:
       '@babel/core': 7.26.9
       '@babel/helper-module-imports': 7.25.9
-      '@babel/helper-validator-identifier': 7.25.9
+      '@babel/helper-validator-identifier': 7.27.1
       '@babel/traverse': 7.26.9
     transitivePeerDependencies:
       - supports-color
@@ -10901,8 +10973,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/helper-string-parser@7.25.9': {}
-
   '@babel/helper-string-parser@7.27.1': {}
 
   '@babel/helper-validator-identifier@7.25.9': {}
@@ -10924,7 +10994,7 @@ snapshots:
   '@babel/helpers@7.26.9':
     dependencies:
       '@babel/template': 7.26.9
-      '@babel/types': 7.26.9
+      '@babel/types': 7.27.1
 
   '@babel/helpers@7.27.1':
     dependencies:
@@ -10933,7 +11003,7 @@ snapshots:
 
   '@babel/parser@7.26.9':
     dependencies:
-      '@babel/types': 7.26.9
+      '@babel/types': 7.27.1
 
   '@babel/parser@7.27.2':
     dependencies:
@@ -11945,8 +12015,8 @@ snapshots:
   '@babel/template@7.26.9':
     dependencies:
       '@babel/code-frame': 7.26.2
-      '@babel/parser': 7.26.9
-      '@babel/types': 7.26.9
+      '@babel/parser': 7.27.2
+      '@babel/types': 7.27.1
 
   '@babel/template@7.27.2':
     dependencies:
@@ -11958,9 +12028,9 @@ snapshots:
     dependencies:
       '@babel/code-frame': 7.26.2
       '@babel/generator': 7.26.9
-      '@babel/parser': 7.26.9
+      '@babel/parser': 7.27.2
       '@babel/template': 7.26.9
-      '@babel/types': 7.26.9
+      '@babel/types': 7.27.1
       debug: 4.4.0(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
@@ -11980,8 +12050,8 @@ snapshots:
 
   '@babel/types@7.26.9':
     dependencies:
-      '@babel/helper-string-parser': 7.25.9
-      '@babel/helper-validator-identifier': 7.25.9
+      '@babel/helper-string-parser': 7.27.1
+      '@babel/helper-validator-identifier': 7.27.1
 
   '@babel/types@7.27.1':
     dependencies:
@@ -13634,24 +13704,24 @@ snapshots:
 
   '@types/babel__core@7.20.5':
     dependencies:
-      '@babel/parser': 7.26.9
-      '@babel/types': 7.26.9
+      '@babel/parser': 7.27.2
+      '@babel/types': 7.27.1
       '@types/babel__generator': 7.6.8
       '@types/babel__template': 7.4.4
       '@types/babel__traverse': 7.20.6
 
   '@types/babel__generator@7.6.8':
     dependencies:
-      '@babel/types': 7.26.9
+      '@babel/types': 7.27.1
 
   '@types/babel__template@7.4.4':
     dependencies:
-      '@babel/parser': 7.26.9
-      '@babel/types': 7.26.9
+      '@babel/parser': 7.27.2
+      '@babel/types': 7.27.1
 
   '@types/babel__traverse@7.20.6':
     dependencies:
-      '@babel/types': 7.26.9
+      '@babel/types': 7.27.1
 
   '@types/body-parser@1.19.5':
     dependencies:
@@ -14191,6 +14261,16 @@ snapshots:
     transitivePeerDependencies:
       - vue
 
+  '@unocss/astro@66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))':
+    dependencies:
+      '@unocss/core': 66.0.0
+      '@unocss/reset': 66.0.0
+      '@unocss/vite': 66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))
+    optionalDependencies:
+      vite: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+    transitivePeerDependencies:
+      - vue
+
   '@unocss/cli@66.0.0':
     dependencies:
       '@ampproject/remapping': 2.3.0
@@ -14326,6 +14406,24 @@ snapshots:
     transitivePeerDependencies:
       - vue
 
+  '@unocss/vite@66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))':
+    dependencies:
+      '@ampproject/remapping': 2.3.0
+      '@unocss/config': 66.0.0
+      '@unocss/core': 66.0.0
+      '@unocss/inspector': 66.0.0(vue@3.5.13(typescript@5.7.3))
+      chokidar: 3.6.0
+      magic-string: 0.30.17
+      tinyglobby: 0.2.12
+      unplugin-utils: 0.2.4
+      vite: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+    transitivePeerDependencies:
+      - vue
+
+  '@vite-pwa/vitepress@0.5.4(vite-plugin-pwa@0.21.2(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0))':
+    dependencies:
+      vite-plugin-pwa: 0.21.2(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0)
+
   '@vite-pwa/vitepress@1.0.0(vite-plugin-pwa@1.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0))':
     dependencies:
       vite-plugin-pwa: 1.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0)
@@ -14340,6 +14438,11 @@ snapshots:
       vite: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
       vue: 3.5.13(typescript@5.7.3)
 
+  '@vitejs/plugin-vue@5.2.1(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))':
+    dependencies:
+      vite: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+      vue: 3.5.13(typescript@5.7.3)
+
   '@vitest/coverage-v8@3.0.6(vitest@3.0.6)':
     dependencies:
       '@ampproject/remapping': 2.3.0
@@ -14418,7 +14521,7 @@ snapshots:
 
   '@vue/compiler-core@3.5.13':
     dependencies:
-      '@babel/parser': 7.26.9
+      '@babel/parser': 7.27.2
       '@vue/shared': 3.5.13
       entities: 4.5.0
       estree-walker: 2.0.2
@@ -14431,7 +14534,7 @@ snapshots:
 
   '@vue/compiler-sfc@3.5.13':
     dependencies:
-      '@babel/parser': 7.26.9
+      '@babel/parser': 7.27.2
       '@vue/compiler-core': 3.5.13
       '@vue/compiler-dom': 3.5.13
       '@vue/compiler-ssr': 3.5.13
@@ -14884,7 +14987,7 @@ snapshots:
 
   array-buffer-byte-length@1.0.2:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       is-array-buffer: 3.0.5
 
   array-flatten@1.1.1: {}
@@ -14989,7 +15092,7 @@ snapshots:
   babel-plugin-jest-hoist@29.6.3:
     dependencies:
       '@babel/template': 7.26.9
-      '@babel/types': 7.26.9
+      '@babel/types': 7.27.1
       '@types/babel__core': 7.20.5
       '@types/babel__traverse': 7.20.6
 
@@ -15233,14 +15336,9 @@ snapshots:
     dependencies:
       call-bind-apply-helpers: 1.0.2
       es-define-property: 1.0.1
-      get-intrinsic: 1.2.7
+      get-intrinsic: 1.3.0
       set-function-length: 1.2.2
 
-  call-bound@1.0.3:
-    dependencies:
-      call-bind-apply-helpers: 1.0.2
-      get-intrinsic: 1.2.7
-
   call-bound@1.0.4:
     dependencies:
       call-bind-apply-helpers: 1.0.2
@@ -15508,7 +15606,7 @@ snapshots:
 
   compressible@2.0.18:
     dependencies:
-      mime-db: 1.53.0
+      mime-db: 1.54.0
 
   compression@1.7.4:
     dependencies:
@@ -16175,7 +16273,7 @@ snapshots:
       array-buffer-byte-length: 1.0.2
       call-bind: 1.0.8
       es-get-iterator: 1.1.3
-      get-intrinsic: 1.2.7
+      get-intrinsic: 1.3.0
       is-arguments: 1.1.1
       is-array-buffer: 3.0.5
       is-date-object: 1.1.0
@@ -16498,7 +16596,7 @@ snapshots:
   es-get-iterator@1.1.3:
     dependencies:
       call-bind: 1.0.8
-      get-intrinsic: 1.2.7
+      get-intrinsic: 1.3.0
       has-symbols: 1.1.0
       is-arguments: 1.1.1
       is-map: 2.0.3
@@ -16518,7 +16616,7 @@ snapshots:
   es-set-tostringtag@2.1.0:
     dependencies:
       es-errors: 1.3.0
-      get-intrinsic: 1.2.7
+      get-intrinsic: 1.3.0
       has-tostringtag: 1.0.2
       hasown: 2.0.2
 
@@ -17214,7 +17312,7 @@ snapshots:
 
   find-test-names@1.29.5(@babel/core@7.26.9):
     dependencies:
-      '@babel/parser': 7.26.9
+      '@babel/parser': 7.27.2
       '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.26.9)
       acorn-walk: 8.3.4
       debug: 4.4.0(supports-color@8.1.1)
@@ -17376,19 +17474,6 @@ snapshots:
 
   get-east-asian-width@1.3.0: {}
 
-  get-intrinsic@1.2.7:
-    dependencies:
-      call-bind-apply-helpers: 1.0.2
-      es-define-property: 1.0.1
-      es-errors: 1.3.0
-      es-object-atoms: 1.1.1
-      function-bind: 1.1.2
-      get-proto: 1.0.1
-      gopd: 1.2.0
-      has-symbols: 1.1.0
-      hasown: 2.0.2
-      math-intrinsics: 1.1.0
-
   get-intrinsic@1.3.0:
     dependencies:
       call-bind-apply-helpers: 1.0.2
@@ -17842,8 +17927,8 @@ snapshots:
   is-array-buffer@3.0.5:
     dependencies:
       call-bind: 1.0.8
-      call-bound: 1.0.3
-      get-intrinsic: 1.2.7
+      call-bound: 1.0.4
+      get-intrinsic: 1.3.0
 
   is-arrayish@0.2.1: {}
 
@@ -17867,7 +17952,7 @@ snapshots:
 
   is-boolean-object@1.2.2:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       has-tostringtag: 1.0.2
 
   is-builtin-module@5.0.0:
@@ -17888,7 +17973,7 @@ snapshots:
 
   is-date-object@1.1.0:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       has-tostringtag: 1.0.2
 
   is-decimal@1.0.4: {}
@@ -17937,7 +18022,7 @@ snapshots:
 
   is-number-object@1.1.1:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       has-tostringtag: 1.0.2
 
   is-number@7.0.0: {}
@@ -17960,7 +18045,7 @@ snapshots:
 
   is-regex@1.2.1:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       gopd: 1.2.0
       has-tostringtag: 1.0.2
       hasown: 2.0.2
@@ -17971,7 +18056,7 @@ snapshots:
 
   is-shared-array-buffer@1.0.4:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
 
   is-stream@1.1.0: {}
 
@@ -17981,7 +18066,7 @@ snapshots:
 
   is-string@1.1.1:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       has-tostringtag: 1.0.2
 
   is-subdir@1.2.0:
@@ -17990,7 +18075,7 @@ snapshots:
 
   is-symbol@1.1.1:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       has-symbols: 1.1.0
       safe-regex-test: 1.1.0
 
@@ -18015,7 +18100,7 @@ snapshots:
   is-weakset@2.0.3:
     dependencies:
       call-bind: 1.0.8
-      get-intrinsic: 1.2.7
+      get-intrinsic: 1.3.0
 
   is-what@4.1.16: {}
 
@@ -18053,7 +18138,7 @@ snapshots:
   istanbul-lib-instrument@5.2.1:
     dependencies:
       '@babel/core': 7.26.9
-      '@babel/parser': 7.26.9
+      '@babel/parser': 7.27.2
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.2
       semver: 6.3.1
@@ -18063,7 +18148,7 @@ snapshots:
   istanbul-lib-instrument@6.0.3:
     dependencies:
       '@babel/core': 7.26.9
-      '@babel/parser': 7.26.9
+      '@babel/parser': 7.27.2
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.2
       semver: 7.7.1
@@ -18382,7 +18467,7 @@ snapshots:
       '@babel/generator': 7.26.9
       '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.26.9)
       '@babel/plugin-syntax-typescript': 7.25.7(@babel/core@7.26.9)
-      '@babel/types': 7.26.9
+      '@babel/types': 7.27.1
       '@jest/expect-utils': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
@@ -19246,8 +19331,6 @@ snapshots:
 
   mime-db@1.52.0: {}
 
-  mime-db@1.53.0: {}
-
   mime-db@1.54.0: {}
 
   mime-types@2.1.35:
@@ -19392,13 +19475,15 @@ snapshots:
 
   node-source-walk@7.0.0:
     dependencies:
-      '@babel/parser': 7.26.9
+      '@babel/parser': 7.27.2
 
   nomnom@1.5.2:
     dependencies:
       colors: 0.5.1
       underscore: 1.1.7
 
+  non-layered-tidy-tree-layout@2.0.2: {}
+
   normalize-path@3.0.0: {}
 
   normalize-url@6.1.0: {}
@@ -19497,7 +19582,7 @@ snapshots:
   object.assign@4.1.7:
     dependencies:
       call-bind: 1.0.8
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       define-properties: 1.2.1
       es-object-atoms: 1.1.1
       has-symbols: 1.1.0
@@ -20388,7 +20473,7 @@ snapshots:
 
   safe-regex-test@1.1.0:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       es-errors: 1.3.0
       is-regex: 1.2.1
 
@@ -20524,7 +20609,7 @@ snapshots:
       define-data-property: 1.1.4
       es-errors: 1.3.0
       function-bind: 1.1.2
-      get-intrinsic: 1.2.7
+      get-intrinsic: 1.3.0
       gopd: 1.2.0
       has-property-descriptors: 1.0.2
 
@@ -20613,16 +20698,16 @@ snapshots:
 
   side-channel-map@1.0.1:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       es-errors: 1.3.0
-      get-intrinsic: 1.2.7
+      get-intrinsic: 1.3.0
       object-inspect: 1.13.4
 
   side-channel-weakmap@1.0.2:
     dependencies:
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       es-errors: 1.3.0
-      get-intrinsic: 1.2.7
+      get-intrinsic: 1.3.0
       object-inspect: 1.13.4
       side-channel-map: 1.0.1
 
@@ -21085,7 +21170,7 @@ snapshots:
   terser@5.34.1:
     dependencies:
       '@jridgewell/source-map': 0.3.6
-      acorn: 8.14.0
+      acorn: 8.14.1
       commander: 2.20.3
       source-map-support: 0.5.21
 
@@ -21455,6 +21540,33 @@ snapshots:
       - supports-color
       - vue
 
+  unocss@66.0.0(postcss@8.5.3)(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3)):
+    dependencies:
+      '@unocss/astro': 66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))
+      '@unocss/cli': 66.0.0
+      '@unocss/core': 66.0.0
+      '@unocss/postcss': 66.0.0(postcss@8.5.3)
+      '@unocss/preset-attributify': 66.0.0
+      '@unocss/preset-icons': 66.0.0
+      '@unocss/preset-mini': 66.0.0
+      '@unocss/preset-tagify': 66.0.0
+      '@unocss/preset-typography': 66.0.0
+      '@unocss/preset-uno': 66.0.0
+      '@unocss/preset-web-fonts': 66.0.0
+      '@unocss/preset-wind': 66.0.0
+      '@unocss/preset-wind3': 66.0.0
+      '@unocss/transformer-attributify-jsx': 66.0.0
+      '@unocss/transformer-compile-class': 66.0.0
+      '@unocss/transformer-directives': 66.0.0
+      '@unocss/transformer-variant-group': 66.0.0
+      '@unocss/vite': 66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))
+    optionalDependencies:
+      vite: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+    transitivePeerDependencies:
+      - postcss
+      - supports-color
+      - vue
+
   unpipe@1.0.0: {}
 
   unplugin-utils@0.2.4:
@@ -21480,7 +21592,7 @@ snapshots:
 
   unplugin@2.2.0:
     dependencies:
-      acorn: 8.14.0
+      acorn: 8.14.1
       webpack-virtual-modules: 0.6.2
 
   untildify@4.0.0: {}
@@ -21570,6 +21682,17 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  vite-plugin-pwa@0.21.2(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0):
+    dependencies:
+      debug: 4.4.0(supports-color@8.1.1)
+      pretty-bytes: 6.1.1
+      tinyglobby: 0.2.12
+      vite: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+      workbox-build: 7.1.1(@types/babel__core@7.20.5)
+      workbox-window: 7.3.0
+    transitivePeerDependencies:
+      - supports-color
+
   vite-plugin-pwa@1.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0):
     dependencies:
       debug: 4.4.0(supports-color@8.1.1)
@@ -22035,7 +22158,7 @@ snapshots:
     dependencies:
       available-typed-arrays: 1.0.7
       call-bind: 1.0.8
-      call-bound: 1.0.3
+      call-bound: 1.0.4
       for-each: 0.3.5
       gopd: 1.2.0
       has-tostringtag: 1.0.2