# Jag publicerade ett NPM-paket!!! Publishing Mermaid förbättringar som en npm-paket

<!--category-- JavaScript, TypeScript, Mermaid, npm -->
<datetime class="hidden">2025-11-07T16:00</datetime>

# Inledning

Jag, en nätkille, tog äntligen mod till sig för att doppa mina tår i npm-paketens värld!
Mermaid.js är obskyr nog och udda nog att jag faktiskt kunde leverera något användbart! Så njut.

Efter att ha byggt några riktigt användbara förbättringar för Mermaid.js diagram (interaktiv pan/zoom, helskärmsljusbox, export till PNG/SVG, och automatisk temabyte), bestämde jag mig för att det var dags att paketera dem ordentligt och dela dem med samhället. Detta inlägg går igenom hur jag skapade `@mostlylucid/mermaid-enhancements` som ett produktionsklart npm-paket.

> OBS: Jobbar fortfarande på utgivningen. Håll ögonen öppna (mitt första npm paket så tar lite)

[TOC]

[![npm version](https://img.shields.io/npm/v/@mostlylucid/mermaid-enhancements.svg)](https://www.npmjs.com/package/@mostlylucid/mermaid-enhancements)
[![npm- nerladdningar](https://img.shields.io/npm/dm/@mostlylucid/mermaid-enhancements.svg)](https://www.npmjs.com/package/@mostlylucid/mermaid-enhancements)
[![Licens: Olicensierad](https://img.shields.io/badge/License-Unlicense-green.svg)](https://opensource.org/licenses/MIT)
[![Typskript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
[![Bundelstorlek](https://img.shields.io/bundlephobia/minzip/@mostlylucid/mermaid-enhancements)](https://bundlephobia.com/package/@mostlylucid/mermaid-enhancements)

# Varför paketera den?

Jag har använt dessa förbättringar över min blogg ett tag nu, och de har blivit nödvändiga för att arbeta med komplexa Mermaid diagram. Funktionerna inkluderar:

- **Interaktiv panorering och zoom** - Navigera stora diagram smidigt
- **Fullskärmsljusbox** - Fördjupande visningsupplevelse
- **Export till PNG/SVG** - Högkvalitativa diagramnedladdningar
- **Automatisk temabyte** - Sömlös ljus-/mörklägesstöd
- **Responsiv design** - Fungerar på mobil och skrivbord

Eftersom jag kopierade samma kod mellan projekt, var det vettigt att skapa ett riktigt npm-paket som vem som helst kunde använda.

# Projektstruktur

Jag satte upp en professionell paketstruktur med TypeScript-stöd:

```
mostlylucid-mermaid/
├── src/
│   ├── index.ts              # Main entry point
│   ├── enhancements.ts       # Pan/zoom/export functionality
│   ├── theme-switcher.ts     # Theme switching logic
│   ├── types.ts              # TypeScript type definitions
│   └── styles.css            # Complete styling
├── examples/
│   └── demo.html             # Full-featured demo
├── dist/                     # Built output (generated)
├── package.json
├── tsconfig.json
├── README.md
├── QUICKSTART.md
├── PUBLISHING.md
└── LICENSE
```

Paketet använder TypeScript för typsäkerhet och bättre utvecklarupplevelse, men kompilerar ner till JavaScript för maximal kompatibilitet.

# Arkitekturen

Så här passar komponenterna ihop:

## Så här ser det ut!

Så du ser att det är ganska kompakt och har några användbara funktioner utöver bara statiska diagram. Det irriterade mig alltid hur MASSAIV de var på sidan så detta verkade en förnuftig strategi för att minska storlek samtidigt behålla användbarhet.

```mermaid
graph TD
    A[User Initializes] --> B[init Function]
    B --> C[initMermaid]
    B --> D[enhanceMermaidDiagrams]

    C --> E[Theme Detection]
    C --> F[Event Listeners]
    C --> G[Mermaid Rendering]

    E --> E1[Global State]
    E --> E2[LocalStorage]
    E --> E3[DOM Class]
    E --> E4[OS Preference]

    F --> F1[Custom Events]
    F --> F2[Media Query]

    G --> H[Apply Enhancements]
    D --> H

    H --> I[Wrap Diagrams]
    H --> J[Init Pan/Zoom]
    H --> K[Add Controls]

    I --> L[Interactive Diagram]
    J --> L
    K --> L

    L --> M[User Interactions]
    M --> M1[Zoom In/Out]
    M --> M2[Pan]
    M --> M3[Fullscreen]
    M --> M4[Export PNG/SVG]

    style A stroke:#059669,stroke-width:3px,color:#10b981
    style L stroke:#2563eb,stroke-width:3px,color:#3b82f6
    style M stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
```

# Kärnan i genomförandet

## Typdefinitioner

Först definierade jag omfattande typskripttyper:

```typescript
// src/types.ts
export interface PanZoomInstance {
    zoom(scale: number): void;
    zoomIn(): void;
    zoomOut(): void;
    reset(): void;
    fit(): void;
    center(): void;
    resize(): void;
    destroy(): void;
    isPanEnabled(): boolean;
    enablePan(enabled: boolean): void;
}

export type ExportFormat = 'png' | 'svg';
export type Theme = 'dark' | 'default';
export type ControlAction = 'fullscreen' | 'zoomIn' | 'zoomOut' |
                           'reset' | 'pan' | 'exportPng' | 'exportSvg';

export interface EnhancementConfig {
    icons?: IconConfig;
    controls?: {
        fullscreen?: boolean;
        zoom?: boolean;
        pan?: boolean;
        export?: boolean;
    };
}
```

## Huvudinmatningspunkt

Huvudingången är helt enkel:

```typescript
// src/index.ts
export {
    enhanceMermaidDiagrams,
    cleanupMermaidEnhancements
} from './enhancements.js';

export {
    initMermaid
} from './theme-switcher.js';

export async function init() {
    await initMermaid();
}

export default {
    init,
    initMermaid,
    enhanceMermaidDiagrams,
};
```

## Genomförande av pan/zoom

Förbättringslogiken sveper in varje diagram med kontroller och initierar svg-pan-zoom:

```typescript
// src/enhancements.ts
import svgPanZoom from 'svg-pan-zoom';
import { toPng, toSvg } from 'html-to-image';

const panZoomInstances = new Map();

function initPanZoom(svgElement: SVGElement, diagramId: string) {
    // Clean up existing instance if present
    if (panZoomInstances.has(diagramId)) {
        try {
            panZoomInstances.get(diagramId).destroy();
        } catch (e) {
            console.warn('Failed to destroy existing pan-zoom instance:', e);
        }
        panZoomInstances.delete(diagramId);
    }

    try {
        const panZoomInstance = svgPanZoom(svgElement, {
            zoomEnabled: true,
            controlIconsEnabled: false,
            fit: true,
            center: true,
            minZoom: 0.1,
            maxZoom: 10,
            zoomScaleSensitivity: 0.3,
            dblClickZoomEnabled: true,
            mouseWheelZoomEnabled: true,
            preventMouseEventsDefault: true,
            contain: false
        });

        panZoomInstances.set(diagramId, panZoomInstance);
        return panZoomInstance;
    } catch (error) {
        console.error('Failed to initialize pan-zoom:', error);
        return null;
    }
}
```

Styrknapparna skapas dynamiskt:

```typescript
function createControlButtons(container: HTMLElement, diagramId: string) {
    if (container.querySelector('.mermaid-controls')) {
        return;
    }

    const controlsDiv = document.createElement('div');
    controlsDiv.className = 'mermaid-controls';

    const buttons = [
        { icon: 'bx-fullscreen', title: 'Fullscreen', action: 'fullscreen' },
        { icon: 'bx-zoom-in', title: 'Zoom In', action: 'zoomIn' },
        { icon: 'bx-zoom-out', title: 'Zoom Out', action: 'zoomOut' },
        { icon: 'bx-reset', title: 'Reset View', action: 'reset' },
        { icon: 'bx-move', title: 'Pan', action: 'pan' },
        { icon: 'bx-image', title: 'Export as PNG', action: 'exportPng' },
        { icon: 'bx-code-alt', title: 'Export as SVG', action: 'exportSvg' }
    ];

    buttons.forEach(btn => {
        const button = document.createElement('button');
        button.className = `mermaid-control-btn bx ${btn.icon}`;
        button.setAttribute('title', btn.title);
        button.setAttribute('aria-label', btn.title);
        button.setAttribute('data-action', btn.action);
        button.setAttribute('data-diagram-id', diagramId);
        controlsDiv.appendChild(button);
    });

    container.appendChild(controlsDiv);
}
```

## Exportfunktionalitet

Exportgenomförandet klonar SVG, bevarar visningsrutan och använder html-to-image:

```typescript
async function exportDiagram(
    container: HTMLElement,
    format: ExportFormat,
    diagramId: string
) {
    try {
        const svgElement = container.querySelector('svg');
        if (!svgElement) {
            console.warn('No diagram found to export');
            return;
        }

        // Clone to avoid modifying the original
        const clonedSvg = svgElement.cloneNode(true) as SVGElement;

        // Get or calculate viewBox
        let viewBox = clonedSvg.getAttribute('viewBox');
        if (!viewBox) {
            const bbox = svgElement.getBBox();
            viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
            clonedSvg.setAttribute('viewBox', viewBox);
        }

        // Set explicit dimensions for proper export
        const [, , vbWidth, vbHeight] = viewBox.split(' ').map(Number);
        clonedSvg.setAttribute('width', vbWidth.toString());
        clonedSvg.setAttribute('height', vbHeight.toString());

        // Remove pan-zoom transforms
        clonedSvg.removeAttribute('style');
        clonedSvg.style.backgroundColor = 'transparent';
        clonedSvg.style.maxWidth = 'none';

        // Create temporary container
        const tempDiv = document.createElement('div');
        tempDiv.style.position = 'absolute';
        tempDiv.style.left = '-9999px';
        tempDiv.appendChild(clonedSvg);
        document.body.appendChild(tempDiv);

        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const filename = `mermaid-diagram-${timestamp}`;

        if (format === 'png') {
            const dataUrl = await toPng(clonedSvg, {
                backgroundColor: 'white',
                pixelRatio: 2 // Higher quality
            });
            downloadFile(dataUrl, `${filename}.png`);
        } else {
            const dataUrl = await toSvg(clonedSvg, {
                backgroundColor: 'transparent'
            });
            downloadFile(dataUrl, `${filename}.svg`);
        }

        document.body.removeChild(tempDiv);
        console.log(`Diagram exported as ${format.toUpperCase()}`);
    } catch (error) {
        console.error('Failed to export diagram:', error);
    }
}
```

## Temabyte

Temaomkopplaren hanterar flera detektionsmetoder:

```typescript
// src/theme-switcher.ts
export async function initMermaid() {
    // Normalize code fences
    normalizeMermaidCodeFences();

    const mermaidElements = document.querySelectorAll(elementSelector);
    if (mermaidElements.length === 0) return;

    await saveOriginalData();

    // Set up theme change handlers
    const handleDarkThemeSet = async () => {
        await resetProcessed();
        await loadMermaid('dark');
    };

    const handleLightThemeSet = async () => {
        await resetProcessed();
        await loadMermaid('default');
    };

    // Listen for custom theme events
    document.body.addEventListener('dark-theme-set', handleDarkThemeSet);
    document.body.addEventListener('light-theme-set', handleLightThemeSet);

    // OS theme change listener
    if (typeof window.matchMedia === 'function') {
        const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
        mediaQuery.addEventListener('change', async (e) => {
            await resetProcessed();
            await loadMermaid(e.matches ? 'dark' : 'default');
        });
    }

    // Detect current theme with fallbacks
    let isDarkMode = false;
    if (typeof window.__themeState !== 'undefined') {
        isDarkMode = window.__themeState === 'dark';
    } else if (localStorage.theme) {
        isDarkMode = localStorage.theme === 'dark';
    } else if (document.documentElement.classList.contains('dark')) {
        isDarkMode = true;
    } else if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
        isDarkMode = true;
    }

    await loadMermaid(isDarkMode ? 'dark' : 'default');
}
```

# Paketinställning

## paket.json

Paketet.json definierar flera ingångar för olika användningsfall:

```json
{
  "name": "@mostlylucid/mermaid-enhancements",
  "version": "1.0.0",
  "description": "Enhance Mermaid.js diagrams with interactive pan/zoom, fullscreen lightbox, export to PNG/SVG, and automatic theme switching",
  "main": "dist/index.js",
  "module": "src/index.ts",
  "types": "src/types.ts",
  "exports": {
    ".": {
      "types": "./src/types.ts",
      "import": "./src/index.ts",
      "require": "./dist/index.js"
    },
    "./min": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.min.js",
      "require": "./dist/index.min.js"
    },
    "./styles.css": "./src/styles.css"
  },
  "unpkg": "dist/index.min.js",
  "jsdelivr": "dist/index.min.js",
  "scripts": {
    "build": "tsc",
    "minify": "node scripts/minify.js",
    "build:all": "npm run build && npm run minify",
    "prepublishOnly": "npm run build:all",
    "dev": "cd examples && npx http-server -p 3000 -o"
  },
  "peerDependencies": {
    "mermaid": "^10.0.0 || ^11.0.0"
  },
  "dependencies": {
    "html-to-image": "^1.11.11",
    "svg-pan-zoom": "^3.6.1"
  }
}
```

## Inställning av typskript

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "node"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "examples"]
}
```

# Exempel på användning

## Grundläggande användning

Det enklaste sättet att använda förpackningen:

```typescript
import mermaid from 'mermaid';
import { init } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';

await init();
```

### Med temabyte

För platser med ljus/mörkt läge:

```typescript
import { init } from '@mostlylucid/mermaid-enhancements';

// Initialize
await init();

// When theme changes
function toggleTheme() {
    const isDark = document.body.classList.toggle('dark');
    document.documentElement.classList.toggle('dark', isDark);

    // Notify the enhancements
    const event = new Event(isDark ? 'dark-theme-set' : 'light-theme-set');
    document.body.dispatchEvent(event);
}
```

### Reagera integration

```tsx
import { useEffect } from 'react';
import { init, cleanupMermaidEnhancements } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';

function MermaidDiagram({ chart }: { chart: string }) {
    useEffect(() => {
        init();
        return () => cleanupMermaidEnhancements();
    }, [chart]);

    return (
        <div className="mermaid">
            {chart}
        </div>
    );
}
```

## Integrering av meningar

```html
<template>
    <div class="mermaid">{{ chart }}</div>
</template>

<script setup>
import { onMounted, onUnmounted } from 'vue';
import { init, cleanupMermaidEnhancements } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';

const props = defineProps(['chart']);

onMounted(async () => {
    await init();
});

onUnmounted(() => {
    cleanupMermaidEnhancements();
});
</script>
```

## Demosidan

Jag skapade en omfattande demosida som visar alla funktioner:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mermaid Enhancements Demo</title>

    <!-- Boxicons for control button icons -->
    <link href="https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css" rel="stylesheet">

    <!-- Mermaid Enhancements CSS -->
    <link rel="stylesheet" href="../src/styles.css">
</head>
<body>
    <!-- Your diagrams -->
    <div class="mermaid">
graph TD
    A[Start] --> B{Is it working?}
    B -->|Yes| C[Great!]
    B -->|No| D[Check setup]
    C --> E[Zoom & Pan]
    D --> F[Read docs]
    E --> G[Export to PNG/SVG]
    </div>

    <!-- Load Mermaid -->
    <script type="module">
        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
        window.mermaid = mermaid;
    </script>

    <!-- Initialize enhancements -->
    <script type="module">
        import { init } from '../dist/index.js';
        await init();
    </script>
</body>
</html>
```

# Utgivningsprocess

Här är publiceringsarbetsflödet:

```mermaid
sequenceDiagram
    participant Dev as Developer
    participant Git as Git Repo
    participant NPM as npm Registry
    participant CDN as unpkg/jsdelivr
    participant User as End User

    Dev->>Dev: Write code
    Dev->>Dev: npm run build:all
    Dev->>Dev: Test locally

    Dev->>Git: git commit & push
    Dev->>Git: Create version tag

    Dev->>NPM: npm login
    Dev->>NPM: npm publish --access public

    NPM-->>CDN: Sync package

    User->>NPM: npm install
    User->>CDN: Import from CDN

    NPM-->>User: Deliver package
    CDN-->>User: Serve files
```

## Steg för steg

1. **Bygg paketet:**

```bash
npm run build:all  # Compiles TypeScript and minifies
```

2. **Provning lokalt:**

```bash
npm run dev  # Opens demo at localhost:3000
```

3. **Versionsgupp:**

```bash
npm version patch  # or minor, or major
```

4. **Publicera:**

```bash
npm login
npm publish --access public
```

Paketet finns nu tillgängligt via:

- Nu är det dags. `npm install @mostlylucid/mermaid-enhancements`
- CDN för icke-pkg: `https://unpkg.com/@mostlylucid/mermaid-enhancements`
- Det här är Jsdelivr CDN: `https://cdn.jsdelivr.net/npm/@mostlylucid/mermaid-enhancements`

## Strategi för minigrering

Jag lade till ett minifikationsskript för att minska buntstorleken:

```javascript
// scripts/minify.js
const { minify } = require('terser');
const fs = require('fs');
const path = require('path');

async function minifyFile(inputPath, outputPath) {
    const code = fs.readFileSync(inputPath, 'utf8');

    const result = await minify(code, {
        compress: {
            dead_code: true,
            drop_console: false,
            drop_debugger: true,
            keep_classnames: true,
            keep_fnames: true,
        },
        mangle: {
            keep_classnames: true,
            keep_fnames: true,
        },
        format: {
            comments: false,
        },
    });

    fs.writeFileSync(outputPath, result.code);

    const originalSize = fs.statSync(inputPath).size;
    const minifiedSize = fs.statSync(outputPath).size;
    const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(1);

    console.log(`✓ ${path.basename(outputPath)}: ${originalSize} → ${minifiedSize} bytes (${reduction}% smaller)`);
}

// Minify main bundle
minifyFile(
    path.join(__dirname, '../dist/index.js'),
    path.join(__dirname, '../dist/index.min.js')
);
```

Resultat:

- Regelbunden: 23.46 KB
- Minifierad: 9,78 KB (58,3% minskning!)

# Dokumentation

Jag skapade omfattande dokumentation:

- **README.MUND** - Fullständig API-referens, exempel, felsökning
- **KVICKSTART. M.D.** - 5 minuters start guide
- **FÖRKUNNANDE. md** - npm publiceringsanvisningar
- **FÖRÄNDRINGSLOGG. md** - Versionshistorik

# Styling

CSS är fullt responsiv och stöder mörkt läge:

```css
/* Diagram wrapper */
.mermaid-wrapper {
    position: relative;
    border-radius: 0.5rem;
    overflow: hidden;
    width: 100%;
    margin: 1rem 0;
}

/* Control buttons */
.mermaid-controls {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    display: flex;
    gap: 0.25rem;
    z-index: 10;
    background: rgba(255, 255, 255, 0.9);
    border-radius: 0.5rem;
    padding: 0.25rem;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.dark .mermaid-controls {
    background: rgba(31, 41, 55, 0.95);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

/* Individual buttons */
.mermaid-control-btn {
    padding: 0.5rem;
    border-radius: 0.25rem;
    cursor: pointer;
    transition: all 0.2s;
    background: transparent;
    border: none;
    color: #4b5563;
    font-size: 1.25rem;
}

.mermaid-control-btn:hover {
    background: rgba(37, 99, 235, 0.1);
    color: #2563eb;
    transform: scale(1.1);
}
```

# Viktiga lärdomar

## 1. TypeScript är värt det

Även för ett litet bibliotek fångade TypeScript flera fel under utvecklingen och ger utmärkt IDE-stöd för användare.

## 2. Flera ingångspunkter materia

Stöd till båda `import` och `require`, plus att erbjuda en minified version, gör paketet mer mångsidig:

```json
"exports": {
    ".": {
      "types": "./src/types.ts",
      "import": "./src/index.ts",
      "require": "./dist/index.js"
    },
    "./min": {
      "import": "./dist/index.min.js"
    }
}
```

## 3. Demo är viktigt

Demosidan hjälpte mig att fånga buggar och fungerar som levande dokumentation. Användare kan se exakt hur det fungerar.

## 4. Rengöring är viktigt

Ge alltid städfunktioner för minneshantering:

```typescript
export function cleanupMermaidEnhancements() {
    panZoomInstances.forEach((instance, id) => {
        try {
            instance.destroy();
        } catch (e) {
            console.warn(`Failed to destroy pan-zoom instance ${id}:`, e);
        }
    });
    panZoomInstances.clear();
}
```

## 5. Detektering av temabehoven

Olika platser hanterar teman på olika sätt, så jag implementerade flera detektionsmetoder:

1. Globalt tillstånd (`window.__themeState`)
2. Lokal lagring (`localStorage.theme`)
3. DOM-klass (`document.documentElement.classList`)
4. OS- preferens (`prefers-color-scheme`)

# Prestandaöverväganden

Paketet är optimerat för prestanda:

- **Delegering av evenemang** - En enda händelse lyssnare för alla kontrollknappar
- **Lazy initialisering** - Pan/zoom initieras endast vid behov
- **Rensning av instanser** - Rätt minneshantering
- **Minimala utbyten** - Diagram bara re-rendera om temaförändring
- **Effektiva DOM-operationer** - Använder `requestAnimationFrame` för smidiga animeringar

# Webbläsarstöd

Testad och arbeta på:

- Chrome/Edge (senast)
- Firefox (senaste)
- Safari (senaste)
- Mobilwebbläsare (iOS Safari, Chrome Mobile)

# Framtida förbättringar

Idéer för framtida versioner:

- [ ] Konfigurerbara styrknappsikoner (stöd för teckensnitt Awesome, materialikoner)
- [ ] Tangentbordsgenvägar (t.ex. Ctrl+Plus för zoom)
- [ ] Rör gester på mobilen (rosa till zoom)
- [ ] Kopiera diagram källkodsknapp
- [ ] Dela diagram URL-funktion
- [ ] Diagramanteckningar
- [ ] Flera exportformat (PDF, JPEG)

# Slutsatser

Packa Mermaid förbättringar som en npm modul var en stor lärande upplevelse. Paketet är nu:

- Typsäkert med typskript
- Väldokumenterad
- Lätt att använda
- Produktionsfärdig
- Ramar-agnostiker
- Fullt testad

Om du använder Mermaid.js i dina projekt, ge det ett försök! Interaktiva pan / Zoom och export funktioner gör arbete med komplexa diagram så mycket bättre.

# Resurser

- **GitHub-arkivet**: [Mest lucidweb/mestadlylucid-mammaid](https://github.com/scottgal/mostlylucidweb/tree/main/mostlylucid-mermaid)
- **npm- paket**: [@mestlylucid/mermaid-enhancements](https://www.npmjs.com/package/@mostlylucid/mermaid-enhancements) (kommer snart)
- **Demografi**: [Levande demo](https://mostlylucid.net/mermaid-demo) (kommer snart)