@lifecorner/angular-ui-kit 3.6.1 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +1 -0
  2. package/fesm2022/lifecorner-angular-ui-kit-character-svg.mjs +91 -0
  3. package/fesm2022/lifecorner-angular-ui-kit-character-svg.mjs.map +1 -0
  4. package/fesm2022/lifecorner-angular-ui-kit-character.mjs +138 -0
  5. package/fesm2022/lifecorner-angular-ui-kit-character.mjs.map +1 -0
  6. package/fesm2022/lifecorner-angular-ui-kit-code-block.mjs +40 -5
  7. package/fesm2022/lifecorner-angular-ui-kit-code-block.mjs.map +1 -1
  8. package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs +2 -0
  9. package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs.map +1 -1
  10. package/fesm2022/lifecorner-angular-ui-kit-markdown.mjs +2 -0
  11. package/fesm2022/lifecorner-angular-ui-kit-markdown.mjs.map +1 -1
  12. package/fesm2022/lifecorner-angular-ui-kit.mjs +2 -0
  13. package/package.json +12 -1
  14. package/src/assets/README.md +4 -0
  15. package/src/assets/character/all-done-dark.svg +1 -0
  16. package/src/assets/character/all-done-light.svg +1 -0
  17. package/src/assets/character/calm-dark.svg +1 -0
  18. package/src/assets/character/calm-light.svg +1 -0
  19. package/src/assets/character/comforts-dark.svg +1 -0
  20. package/src/assets/character/comforts-light.svg +1 -0
  21. package/src/assets/character/connection-paused-dark.svg +1 -0
  22. package/src/assets/character/connection-paused-light.svg +1 -0
  23. package/src/assets/character/connects-dark.svg +1 -0
  24. package/src/assets/character/connects-light.svg +1 -0
  25. package/src/assets/character/discovered-dark.svg +1 -0
  26. package/src/assets/character/discovered-light.svg +1 -0
  27. package/src/assets/character/happy-dark.svg +1 -0
  28. package/src/assets/character/happy-light.svg +1 -0
  29. package/src/assets/character/hello-dark.svg +1 -0
  30. package/src/assets/character/hello-light.svg +1 -0
  31. package/src/assets/character/in-his-corner-dark.svg +1 -0
  32. package/src/assets/character/in-his-corner-light.svg +1 -0
  33. package/src/assets/character/resting-dark.svg +1 -0
  34. package/src/assets/character/resting-light.svg +1 -0
  35. package/src/assets/character/see-you-dark.svg +1 -0
  36. package/src/assets/character/see-you-light.svg +1 -0
  37. package/src/assets/character/shows-the-way-dark.svg +1 -0
  38. package/src/assets/character/shows-the-way-light.svg +1 -0
  39. package/src/assets/character/thinking-dark.svg +1 -0
  40. package/src/assets/character/thinking-light.svg +1 -0
  41. package/src/assets/character/welcomes-member-dark.svg +1 -0
  42. package/src/assets/character/welcomes-member-light.svg +1 -0
  43. package/src/styles/_horizon.scss +52 -0
  44. package/src/styles/horizon.figure.spec.ts +79 -0
  45. package/types/lifecorner-angular-ui-kit-character-svg.d.ts +44 -0
  46. package/types/lifecorner-angular-ui-kit-character.d.ts +104 -0
  47. package/types/lifecorner-angular-ui-kit-code-block.d.ts +27 -0
  48. package/types/lifecorner-angular-ui-kit.d.ts +2 -0
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-markdown.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/markdown/markdown.component.ts","../../../../libs/angular-ui-kit/src/lib/markdown/markdown.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n signal,\n effect,\n untracked,\n inject,\n ElementRef,\n OnDestroy,\n SecurityContext,\n ViewEncapsulation,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { HttpClient } from '@angular/common/http';\nimport { Subscription } from 'rxjs';\nimport { CodeBlockComponent, CodeBlockLanguage } from '@lifecorner/angular-ui-kit/code-block';\nimport { isSafeImageUrl, isSafeLinkUrl } from '@lifecorner/angular-ui-kit/internal/url';\n\nexport interface MarkdownHeading {\n level: number;\n text: string;\n id: string;\n}\n\nexport interface MarkdownLinkClick {\n href: string;\n event: MouseEvent;\n}\n\nexport interface MarkdownRendered {\n headings: MarkdownHeading[];\n}\n\nexport interface MarkdownChangesHighlighted {\n /** Number of changed/added blocks highlighted in the current render. */\n changedBlocks: number;\n}\n\nexport interface RenderPart {\n type: 'html' | 'code' | 'mermaid';\n index: number;\n safeHtml?: SafeHtml;\n code?: string;\n lang?: CodeBlockLanguage;\n}\n\n/**\n * Stand-in for a fenced code block while the rest of the markdown is processed.\n * Bracketed in NUL characters, which the source is stripped of first — so no\n * text a user writes can ever spell a placeholder and be mistaken for a block.\n * (The earlier `<!--CODE_BLOCK_N-->` form was: an escaped literal comment in\n * the text matched it and dereferenced a block that wasn't there.)\n */\nconst BLOCK_MARK = '\\u0000';\nconst blockPlaceholder = (idx: number): string => `${BLOCK_MARK}LCBLOCK${idx}${BLOCK_MARK}`;\n// eslint-disable-next-line no-control-regex -- the NUL bracket is the point: unforgeable\nconst BLOCK_PLACEHOLDER = /\\u0000LCBLOCK(\\d+)\\u0000/g;\n\n/** Instances numbered so their mermaid render ids never collide on one page. */\nlet markdownInstances = 0;\n\n/**\n * Markdown renderer component.\n *\n * Renders GitHub-Flavored Markdown (GFM) to sanitized HTML with\n * optional syntax highlighting via `<lc-code-block>`.\n *\n * Supports headings (+ anchors), paragraphs, ordered/unordered lists, task lists\n * (`- [ ]` / `- [x]` → accessible checkbox), blockquotes, horizontal rules,\n * images, GFM tables (column alignment + horizontal scroll), bold/italic/\n * strikethrough, inline code, fenced code (incl. `mermaid`), explicit links and\n * autolinks (bare URLs / `www.` / emails, never inside code).\n *\n * Optionally highlights *changed* blocks in place: pass the pre-edit markdown as\n * `previousContent` and set `highlightChanges` — added/edited blocks (diffed at\n * block / list-item level) gain a left accent bar + subtle tint, can auto-fade\n * (`changeHighlightFadeMs`) and scroll into view (`scrollToFirstChange`).\n *\n * @example\n * ```html\n * <lc-markdown [content]=\"'# Hello World'\" />\n * <lc-markdown [src]=\"'/docs/readme.md'\" />\n * <lc-markdown\n * [content]=\"current\" [previousContent]=\"prev\"\n * [highlightChanges]=\"true\" [changeHighlightFadeMs]=\"3000\" />\n * ```\n */\n@Component({\n selector: 'lc-markdown',\n standalone: true,\n imports: [CodeBlockComponent],\n templateUrl: './markdown.component.html',\n styleUrls: ['./markdown.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class MarkdownComponent implements OnDestroy {\n private readonly sanitizer = inject(DomSanitizer);\n private readonly instanceId = ++markdownInstances;\n private readonly http = inject(HttpClient);\n private readonly host = inject(ElementRef<HTMLElement>);\n private httpSub?: Subscription;\n private fadeTimer?: ReturnType<typeof setTimeout>;\n private scrollTimer?: ReturnType<typeof setTimeout>;\n private mermaidApiPromise?: Promise<any>;\n private mermaidInitialized = false;\n private mermaidRenderRun = 0;\n\n /** URL or path to load markdown from */\n readonly src = input<string>();\n\n /** Raw markdown string */\n readonly content = input<string>();\n\n /** Display variant */\n readonly variant = input<'default' | 'compact' | 'chat'>('default');\n\n /** Target for links */\n readonly linkTarget = input<'_self' | '_blank'>('_self');\n\n /** Whether to sanitize HTML output */\n readonly sanitize = input<boolean>(true);\n\n /** Whether to use code-block for fenced code */\n readonly syntaxHighlight = input<boolean>(true);\n\n /** Whether to show anchor links on headings */\n readonly showHeadingAnchors = input<boolean>(false);\n\n /** Base URL for resolving relative links/images */\n readonly baseUrl = input<string>();\n\n // -- Change highlighting --\n /** Enable change highlighting (requires `previousContent` to compute a diff). */\n readonly highlightChanges = input<boolean>(false);\n\n /**\n * The prior Markdown. When it differs from `content`, the changed/added blocks\n * in `content` are highlighted. The caller passes the pre-edit markdown.\n */\n readonly previousContent = input<string>();\n\n /** Auto-fade the highlight after N ms. 0 / undefined ⇒ persist until content changes. */\n readonly changeHighlightFadeMs = input<number>();\n\n /** Scroll the first changed block into view when highlights appear. */\n readonly scrollToFirstChange = input<boolean>(false);\n\n /** Screen-reader marker put in front of each highlighted block (`highlightChanges`). */\n readonly changedMarkerLabel = input('(geändert)');\n\n /** Announced after the count when one block changed: `1 Abschnitt geändert`. */\n readonly changedSummaryOneLabel = input('Abschnitt geändert');\n\n /** Announced after the count when several blocks changed: `3 Abschnitte geändert`. */\n readonly changedSummaryManyLabel = input('Abschnitte geändert');\n\n /** Shown while a Mermaid diagram renders. */\n readonly mermaidLoadingLabel = input('Rendering Mermaid diagram...');\n\n /** Shown above the source when a Mermaid diagram fails. */\n readonly mermaidErrorLabel = input('Mermaid diagram could not be rendered.');\n\n /** Shown above the source when the optional `mermaid` package cannot be loaded. */\n readonly mermaidUnavailableLabel = input('Mermaid runtime is not available.');\n\n /** Emitted when a link is clicked */\n readonly linkClick = output<MarkdownLinkClick>();\n\n /** Emitted after rendering with heading TOC */\n readonly rendered = output<MarkdownRendered>();\n\n /** Emitted after a render that produced change highlights. */\n readonly changesHighlighted = output<MarkdownChangesHighlighted>();\n\n /** Internal resolved markdown source */\n protected resolvedMarkdown = signal<string>('');\n\n /** Parsed result (computed once from resolvedMarkdown) */\n private parsed = computed(() => {\n const md = this.resolvedMarkdown();\n if (!md) {\n return {\n html: '',\n blocks: [] as { kind: 'code' | 'mermaid'; lang: string; code: string }[],\n headings: [] as MarkdownHeading[],\n };\n }\n return this.parseMarkdown(md);\n });\n\n /**\n * Rendered HTML after applying change highlights. When highlighting is off (or\n * there is no differing `previousContent`) this returns the parsed HTML\n * unchanged, so the non-highlight path is byte-for-byte identical to before.\n */\n private readonly highlightResult = computed<{ html: string; count: number }>(() => {\n const { html } = this.parsed();\n const prev = this.previousContent();\n if (\n !html ||\n !this.highlightChanges() ||\n prev == null ||\n prev === this.resolvedMarkdown()\n ) {\n return { html, count: 0 };\n }\n return this.applyChangeHighlights(html, prev);\n });\n\n /** Number of changed/added blocks highlighted in the current render. */\n protected readonly changedCount = computed(() => this.highlightResult().count);\n\n /** Visually-hidden polite summary announced when highlights appear. */\n protected readonly changeSummary = computed(() => {\n const n = this.changedCount();\n if (n <= 0) return '';\n return `${n} ${n === 1 ? this.changedSummaryOneLabel() : this.changedSummaryManyLabel()}`;\n });\n\n /** Whether the current highlights have faded out (after `changeHighlightFadeMs`). */\n protected readonly highlightsFaded = signal(false);\n\n /** Computed render parts (HTML chunks + code blocks interleaved) */\n protected renderParts = computed<RenderPart[]>(() => {\n const html = this.highlightResult().html;\n const { blocks } = this.parsed();\n if (!html) return [];\n\n if (this.syntaxHighlight() && blocks.length > 0) {\n return this.splitIntoParts(html, blocks);\n }\n\n // No code blocks — single HTML part\n return [{\n type: 'html',\n index: 0,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(html, { next: 0 })),\n }];\n });\n\n /**\n * The HTML as it may go into the DOM. With `sanitize` on (the default) that is\n * Angular's HTML sanitizer's output — which drops every `id`, since `id` isn't\n * on its attribute allow-list. The heading ids are put back afterwards: they\n * are what anchors and the `rendered` TOC point at, and each is a `[\\w-]`\n * slug this parser produced itself, so re-adding them reopens nothing. The\n * cursor walks the parsed headings in document order across the parts.\n */\n private sanitizeHtml(html: string, cursor: { next: number }): string {\n const headings = this.headings();\n if (!this.sanitize()) {\n cursor.next += (html.match(/<h[1-6]\\b/g) ?? []).length;\n return html;\n }\n const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, html) || '';\n return sanitized.replace(/<h([1-6])(\\s[^>]*)?>/g, (tag, level: string, attrs = '') => {\n const heading = headings[cursor.next++];\n if (!heading || /\\sid=/.test(attrs)) return tag;\n return `<h${level} id=\"${heading.id}\"${attrs}>`;\n });\n }\n\n protected containerClasses = computed(() => {\n let classes = `lc-markdown lc-markdown--${this.variant()}`;\n if (this.highlightsFaded()) classes += ' lc-markdown--faded';\n return classes;\n });\n\n protected mermaidSvgs = signal<Record<number, SafeHtml>>({});\n protected mermaidErrors = signal<Record<number, string>>({});\n\n private headings = computed(() => this.parsed().headings);\n\n constructor() {\n // Load content from src when src changes\n effect(() => {\n const src = this.src();\n if (src) {\n this.loadFromUrl(src);\n }\n });\n\n // Use raw content when provided\n effect(() => {\n const content = this.content();\n if (content !== undefined) {\n this.resolvedMarkdown.set(content);\n }\n });\n\n // Emit rendered event whenever a render produced a (possibly empty) TOC\n effect(() => {\n const h = this.headings();\n untracked(() => this.rendered.emit({ headings: h }));\n });\n\n // Render Mermaid code fences as inline SVG diagrams.\n effect(() => {\n void this.renderMermaidParts(this.renderParts());\n });\n\n // React to a render that produced change highlights: emit, schedule fade,\n // and optionally scroll the first changed block into view.\n effect(() => {\n const count = this.highlightResult().count;\n untracked(() => this.onHighlightResult(count));\n });\n }\n\n ngOnDestroy(): void {\n this.httpSub?.unsubscribe();\n clearTimeout(this.fadeTimer);\n clearTimeout(this.scrollTimer);\n }\n\n protected onLinkClick(event: MouseEvent): void {\n const target = event.target as HTMLElement;\n const anchor = target.closest('a');\n if (anchor) {\n this.linkClick.emit({ href: anchor.href, event });\n }\n }\n\n // -- Change highlighting -----------------------------------------------------\n\n /** Side-effects for a render: emit, (re)arm the fade timer, optional scroll. */\n private onHighlightResult(count: number): void {\n clearTimeout(this.fadeTimer);\n clearTimeout(this.scrollTimer);\n // A fresh render starts un-faded (re-entrant: never reuse stale fade state).\n this.highlightsFaded.set(false);\n if (count <= 0) return;\n\n this.changesHighlighted.emit({ changedBlocks: count });\n\n const fadeMs = this.changeHighlightFadeMs();\n if (fadeMs && fadeMs > 0) {\n this.fadeTimer = setTimeout(() => this.highlightsFaded.set(true), fadeMs);\n }\n if (this.scrollToFirstChange()) {\n // Defer until the highlighted HTML has been written to the DOM.\n this.scrollTimer = setTimeout(() => this.scrollToFirstChanged(), 0);\n }\n }\n\n private scrollToFirstChanged(): void {\n if (typeof document === 'undefined') return;\n const el = this.host.nativeElement.querySelector('.lc-markdown__block--changed');\n el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }\n\n /**\n * Wraps the changed/added top-level blocks of `html` with\n * `.lc-markdown__block--changed`. A block is \"changed\" when its normalized\n * text is not present among the blocks of `prevMarkdown`. Lists are diffed\n * per `<li>` and tables per `<tr>` so a single edited item highlights alone.\n */\n private applyChangeHighlights(\n html: string,\n prevMarkdown: string\n ): { html: string; count: number } {\n if (typeof document === 'undefined') return { html, count: 0 };\n\n const prevKeys = this.collectBlockKeys(this.parseMarkdown(prevMarkdown).html);\n const root = this.htmlToElement(html);\n if (!root) return { html, count: 0 };\n\n let count = 0;\n const markIfChanged = (el: Element): void => {\n const key = this.blockTextKey(el.textContent ?? '');\n if (!key || prevKeys.has(key)) return;\n el.classList.add('lc-markdown__block--changed');\n const sr = document.createElement('span');\n sr.className = 'lc-markdown__sr-only';\n sr.textContent = `${this.changedMarkerLabel()} `;\n el.insertBefore(sr, el.firstChild);\n count++;\n };\n\n this.eachLogicalBlock(root, markIfChanged);\n return { html: root.innerHTML, count };\n }\n\n /** Normalized text set of the logical blocks within an HTML fragment. */\n private collectBlockKeys(html: string): Set<string> {\n const keys = new Set<string>();\n const root = this.htmlToElement(html);\n if (!root) return keys;\n this.eachLogicalBlock(root, (el) => {\n const key = this.blockTextKey(el.textContent ?? '');\n if (key) keys.add(key);\n });\n return keys;\n }\n\n /**\n * Visits each diffable block under `root`: top-level elements, but descending\n * into `<ul>`/`<ol>` (per `<li>`) and `<table>` (per `<tr>`).\n */\n private eachLogicalBlock(root: Element, visit: (el: Element) => void): void {\n for (const node of Array.from(root.childNodes)) {\n if (node.nodeType !== 1) continue; // elements only (skips code placeholders)\n const el = node as Element;\n const tag = el.tagName;\n if (tag === 'UL' || tag === 'OL') {\n for (const li of Array.from(el.children)) {\n if (li.tagName === 'LI') visit(li);\n }\n } else if (tag === 'TABLE' || el.querySelector('table')) {\n // Tables (incl. the scroll wrapper div) diff per row.\n for (const tr of Array.from(el.querySelectorAll('tr'))) {\n visit(tr);\n }\n } else {\n visit(el);\n }\n }\n }\n\n private blockTextKey(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().toLowerCase();\n }\n\n private htmlToElement(html: string): HTMLElement | null {\n if (typeof document === 'undefined') return null;\n const div = document.createElement('div');\n div.innerHTML = html;\n return div;\n }\n\n private loadFromUrl(url: string): void {\n this.httpSub?.unsubscribe();\n this.httpSub = this.http.get(url, { responseType: 'text' }).subscribe({\n next: (text) => this.resolvedMarkdown.set(text),\n error: () => this.resolvedMarkdown.set(`*Failed to load ${url}*`),\n });\n }\n\n private splitIntoParts(\n html: string,\n blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[]\n ): RenderPart[] {\n const parts: RenderPart[] = [];\n const regex = new RegExp(BLOCK_PLACEHOLDER.source, 'g');\n const cursor = { next: 0 };\n let lastIndex = 0;\n let partIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(html)) !== null) {\n const before = html.slice(lastIndex, match.index);\n if (before.trim()) {\n parts.push({\n type: 'html',\n index: partIndex++,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(before, cursor)),\n });\n }\n const block = blocks[parseInt(match[1], 10)];\n lastIndex = match.index + match[0].length;\n // Unforgeable placeholder, so this can only be a bug — but never a crash.\n if (!block) continue;\n if (block.kind === 'mermaid') {\n parts.push({\n type: 'mermaid',\n index: partIndex++,\n code: block.code,\n });\n } else {\n parts.push({\n type: 'code',\n index: partIndex++,\n code: block.code,\n lang: block.lang as CodeBlockLanguage,\n });\n }\n }\n\n const remaining = html.slice(lastIndex);\n if (remaining.trim()) {\n parts.push({\n type: 'html',\n index: partIndex++,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(remaining, cursor)),\n });\n }\n\n return parts;\n }\n\n private parseMarkdown(md: string): {\n html: string;\n blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[];\n headings: MarkdownHeading[];\n } {\n const blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[] = [];\n const headings: MarkdownHeading[] = [];\n const baseUrl = this.baseUrl();\n const linkTarget = this.linkTarget();\n const showAnchors = this.showHeadingAnchors();\n\n // 1. Extract fenced code blocks → placeholders (see BLOCK_MARK: the source\n // is stripped of the marker character first, so it can't forge one)\n // eslint-disable-next-line no-control-regex -- strip the placeholder marker (see BLOCK_MARK)\n let processed = md.replace(/\\u0000/g, '').replace(\n /```(\\w*)\\n([\\s\\S]*?)```/g,\n (_match, lang: string, code: string) => {\n const idx = blocks.length;\n const normalizedLang = (lang || 'text').toLowerCase();\n blocks.push({\n kind: normalizedLang === 'mermaid' ? 'mermaid' : 'code',\n lang: normalizedLang,\n code: code.trimEnd(),\n });\n return blockPlaceholder(idx);\n }\n );\n\n // 2. Escape HTML entities — quotes included, so nothing the parser later\n // interpolates into an attribute (link/image URLs, alt text) can close it.\n processed = this.escapeHtml(processed);\n\n // 3. Headings (# to ######)\n processed = processed.replace(\n /^(#{1,6})\\s+(.+)$/gm,\n (_match, hashes: string, text: string) => {\n const level = hashes.length;\n const id = text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-');\n headings.push({ level, text, id });\n const anchor = showAnchors\n ? `<a href=\"#${id}\" class=\"lc-markdown__anchor\" aria-hidden=\"true\">#</a>`\n : '';\n return `<h${level} id=\"${id}\">${anchor}${text}</h${level}>`;\n }\n );\n\n // 4. Horizontal rules\n processed = processed.replace(/^---+$/gm, '<hr>');\n\n // 5. Blockquotes\n processed = processed.replace(\n /^(?:&gt;)\\s?(.*)$/gm,\n '<blockquote>$1</blockquote>'\n );\n // Merge consecutive blockquotes\n processed = processed.replace(\n /<\\/blockquote>\\n<blockquote>/g,\n '\\n'\n );\n\n // 6. Tables (GFM)\n processed = this.parseTables(processed);\n\n // 7. Task lists. Angular's HTML sanitizer strips <input>, so the checkbox is\n // rendered as a styled <span role=\"checkbox\"> (which survives sanitization and\n // carries accessible checked/disabled state) rather than a form control.\n processed = processed.replace(\n /^[-*]\\s+\\[[xX]\\]\\s+(.*)$/gm,\n '<li class=\"lc-markdown__task-item\">' +\n '<span class=\"lc-markdown__checkbox lc-markdown__checkbox--checked\" role=\"checkbox\" aria-checked=\"true\" aria-disabled=\"true\"></span>' +\n '<span class=\"lc-markdown__task-label\">$1</span></li>'\n );\n processed = processed.replace(\n /^[-*]\\s+\\[ \\]\\s+(.*)$/gm,\n '<li class=\"lc-markdown__task-item\">' +\n '<span class=\"lc-markdown__checkbox\" role=\"checkbox\" aria-checked=\"false\" aria-disabled=\"true\"></span>' +\n '<span class=\"lc-markdown__task-label\">$1</span></li>'\n );\n\n // 8. Unordered lists\n processed = processed.replace(\n /^[-*]\\s+(.*)$/gm,\n '<li>$1</li>'\n );\n // Wrap consecutive <li> in <ul>\n processed = processed.replace(\n /((?:<li[^>]*>.*<\\/li>\\n?)+)/g,\n '<ul>$1</ul>'\n );\n\n // 9. Ordered lists — marked while wrapping, so a run of them gets its <ol>\n // and isn't confused with the <li>s already inside <ul>s above.\n processed = processed.replace(\n /^\\d+\\.\\s+(.*)$/gm,\n '<li data-ol>$1</li>'\n );\n processed = processed.replace(\n /((?:<li data-ol>.*<\\/li>\\n?)+)/g,\n (run) => `<ol>${run.replace(/ data-ol/g, '')}</ol>`\n );\n\n // 10. Inline code\n processed = processed.replace(\n /`([^`]+)`/g,\n '<code>$1</code>'\n );\n\n // 11. Bold + italic combos\n processed = processed.replace(\n /\\*\\*\\*(.+?)\\*\\*\\*/g,\n '<strong><em>$1</em></strong>'\n );\n processed = processed.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n processed = processed.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n\n // 12. Strikethrough (GFM)\n processed = processed.replace(/~~(.+?)~~/g, '<del>$1</del>');\n\n // 13. Images. Only allow-listed URL schemes become a `src` (see\n // safe-url.ts); anything else — `javascript:`, unknown schemes — is\n // rendered as its alt text. This holds regardless of `sanitize`.\n processed = processed.replace(\n /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g,\n (_match, alt: string, src: string) => {\n if (!isSafeImageUrl(src)) return alt;\n const resolvedSrc = baseUrl && !src.startsWith('http') ? `${baseUrl}/${src}` : src;\n return `<img src=\"${resolvedSrc}\" alt=\"${alt}\" class=\"lc-markdown__img\">`;\n }\n );\n\n // 14. Links — same rule: an unsafe URL leaves plain text, not a link.\n processed = processed.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match, text: string, href: string) => {\n if (!isSafeLinkUrl(href)) return text;\n const resolvedHref = baseUrl && !href.startsWith('http') && !href.startsWith('#')\n ? `${baseUrl}/${href}`\n : href;\n return `<a href=\"${resolvedHref}\" target=\"${linkTarget}\" rel=\"noopener\">${text}</a>`;\n }\n );\n\n // 14b. Autolinks — linkify bare URLs / www / emails in plain text. Runs after\n // explicit links and inline code so it can skip anything already inside an\n // <a> or <code> (and never touches tag attributes).\n processed = this.autolink(processed, linkTarget);\n\n // 15. Paragraphs — wrap standalone lines\n processed = processed\n .split('\\n\\n')\n .map((block) => {\n const trimmed = block.trim();\n if (!trimmed) return '';\n if (\n trimmed.startsWith('<h') ||\n trimmed.startsWith('<ul') ||\n trimmed.startsWith('<ol') ||\n trimmed.startsWith('<blockquote') ||\n trimmed.startsWith('<hr') ||\n trimmed.startsWith('<table') ||\n trimmed.startsWith('<div') ||\n trimmed.startsWith(BLOCK_MARK)\n ) {\n return trimmed;\n }\n return `<p>${trimmed.replace(/\\n/g, '<br>')}</p>`;\n })\n .join('\\n');\n\n // 16. Restore code block placeholders\n if (this.syntaxHighlight()) {\n // Leave placeholders — the template will render <lc-code-block> for each\n // No replacement needed here\n } else {\n processed = processed.replace(\n new RegExp(BLOCK_PLACEHOLDER.source, 'g'),\n (_match, idx: string) => {\n const block = blocks[parseInt(idx, 10)];\n if (!block) return '';\n return `<pre><code class=\"language-${block.lang}\">${this.escapeHtml(block.code)}</code></pre>`;\n }\n );\n }\n\n return { html: processed, blocks, headings };\n }\n\n private parseTables(text: string): string {\n return text.replace(\n /^(\\|.+\\|)\\n(\\|[-| :]+\\|)\\n((?:\\|.+\\|\\n?)*)/gm,\n (_match, header: string, separator: string, body: string) => {\n // Per-column alignment from the delimiter row: :--- left, :--: center,\n // ---: right. Surfaced as classes (the sanitizer strips inline `style`).\n const aligns = this.splitTableRow(separator).map((cell) => {\n const left = cell.startsWith(':');\n const right = cell.endsWith(':');\n if (left && right) return 'center';\n if (right) return 'right';\n if (left) return 'left';\n return '';\n });\n const alignClass = (col: number): string =>\n aligns[col] ? ` class=\"lc-markdown__cell--${aligns[col]}\"` : '';\n\n const headers = this.splitTableRow(header)\n .map((cell, i) => `<th scope=\"col\"${alignClass(i)}>${cell.trim()}</th>`)\n .join('');\n\n const rows = body\n .trim()\n .split('\\n')\n .map((row: string) => {\n const cells = this.splitTableRow(row)\n .map((cell, i) => `<td${alignClass(i)}>${cell.trim()}</td>`)\n .join('');\n return `<tr>${cells}</tr>`;\n })\n .join('');\n\n // Wrapped so wide tables scroll horizontally instead of breaking layout.\n return (\n `<div class=\"lc-markdown__table-wrap\">` +\n `<table class=\"lc-markdown__table\"><thead><tr>${headers}</tr></thead><tbody>${rows}</tbody></table>` +\n `</div>`\n );\n }\n );\n }\n\n /** Splits a pipe-table row into trimmed cells, dropping the leading/trailing edges. */\n private splitTableRow(row: string): string[] {\n return row\n .trim()\n .replace(/^\\|/, '')\n .replace(/\\|$/, '')\n .split('|')\n .map((cell) => cell.trim());\n }\n\n private escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n }\n\n /**\n * Linkifies bare URLs (`http(s)://…`, `www.…`) and email addresses in text,\n * leaving anything inside an existing `<a>` or `<code>` (and all tag\n * attributes) untouched. The HTML is tokenized into tags vs. text so only\n * top-level text runs are linkified.\n */\n private autolink(html: string, linkTarget: string): string {\n const url = /(?:https?:\\/\\/|www\\.)[^\\s<]*[^\\s<.,:;!?)\\]}'\"]/;\n const email = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/;\n const pattern = new RegExp(`${url.source}|${email.source}`, 'g');\n\n let depth = 0; // inside <a> or <code>\n return html\n .split(/(<[^>]+>)/g)\n .map((segment) => {\n if (segment.startsWith('<')) {\n if (/^<(a|code)[\\s>]/i.test(segment)) depth++;\n else if (/^<\\/(a|code)>/i.test(segment)) depth = Math.max(0, depth - 1);\n return segment;\n }\n if (depth > 0 || !segment) return segment;\n return segment.replace(pattern, (token) => {\n const isEmail = token.includes('@') && !/^(?:https?:\\/\\/|www\\.)/.test(token);\n const href = isEmail\n ? `mailto:${token}`\n : token.startsWith('www.')\n ? `https://${token}`\n : token;\n return `<a href=\"${href}\" target=\"${linkTarget}\" rel=\"noopener\">${token}</a>`;\n });\n })\n .join('');\n }\n\n protected mermaidSvgFor(index: number): SafeHtml | null {\n return this.mermaidSvgs()[index] ?? null;\n }\n\n protected mermaidErrorFor(index: number): string | null {\n return this.mermaidErrors()[index] ?? null;\n }\n\n private async renderMermaidParts(parts: RenderPart[]): Promise<void> {\n const mermaidParts = parts.filter((part) => part.type === 'mermaid' && !!part.code);\n this.mermaidSvgs.set({});\n this.mermaidErrors.set({});\n\n if (mermaidParts.length === 0) {\n return;\n }\n\n const runId = ++this.mermaidRenderRun;\n\n try {\n const mermaidApi = await this.getMermaidApi();\n if (runId !== this.mermaidRenderRun) return;\n\n const nextSvgs: Record<number, SafeHtml> = {};\n const nextErrors: Record<number, string> = {};\n\n for (const part of mermaidParts) {\n try {\n // Per instance, per run, per part: mermaid's render() removes any\n // element already carrying the id it is given, so an id shared with\n // another <lc-markdown> on the page would delete that one's diagram.\n const renderId = `lc-md-mermaid-${this.instanceId}-${runId}-${part.index}`;\n const { svg } = await mermaidApi.render(renderId, part.code!);\n if (runId !== this.mermaidRenderRun) return;\n // Mermaid emits self-contained SVG with internal <style> rules.\n // Angular HTML sanitization strips critical style declarations,\n // which leaves only labels visible (no edges/shapes). We trust\n // Mermaid output here and rely on Mermaid's strict security mode.\n nextSvgs[part.index] = this.sanitizer.bypassSecurityTrustHtml(svg);\n } catch {\n nextErrors[part.index] = this.mermaidErrorLabel();\n }\n }\n\n if (runId !== this.mermaidRenderRun) return;\n this.mermaidSvgs.set(nextSvgs);\n this.mermaidErrors.set(nextErrors);\n } catch {\n if (runId !== this.mermaidRenderRun) return;\n const fallbackErrors: Record<number, string> = {};\n for (const part of mermaidParts) {\n fallbackErrors[part.index] = this.mermaidUnavailableLabel();\n }\n this.mermaidErrors.set(fallbackErrors);\n }\n }\n\n private async getMermaidApi(): Promise<any> {\n if (!this.mermaidApiPromise) {\n this.mermaidApiPromise = import('mermaid').then((module) => {\n const api = module.default ?? module;\n if (!this.mermaidInitialized) {\n api.initialize({\n startOnLoad: false,\n securityLevel: 'strict',\n });\n this.mermaidInitialized = true;\n }\n return api;\n });\n }\n return this.mermaidApiPromise;\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- delegated handler for clicks on rendered links, which are focusable anchors -->\n<div [class]=\"containerClasses()\" (click)=\"onLinkClick($event)\">\n @if (highlightChanges()) {\n <div class=\"lc-markdown__sr-only\" role=\"status\" aria-live=\"polite\">{{ changeSummary() }}</div>\n }\n @for (part of renderParts(); track part.index) {\n @if (part.type === 'html') {\n <div [innerHTML]=\"part.safeHtml\"></div>\n } @else if (part.type === 'code') {\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"part.lang ?? 'text'\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n } @else {\n @if (mermaidSvgFor(part.index); as svg) {\n <div class=\"lc-markdown__mermaid\" [innerHTML]=\"svg\"></div>\n } @else if (mermaidErrorFor(part.index); as errorMessage) {\n <div class=\"lc-markdown__mermaid-fallback\">\n <p class=\"lc-markdown__mermaid-error\">{{ errorMessage }}</p>\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"'text'\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n </div>\n } @else {\n <div class=\"lc-markdown__mermaid-loading\">{{ mermaidLoadingLabel() }}</div>\n }\n }\n }\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-markdown.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/markdown/markdown.component.ts","../../../../libs/angular-ui-kit/src/lib/markdown/markdown.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n signal,\n effect,\n untracked,\n inject,\n ElementRef,\n OnDestroy,\n SecurityContext,\n ViewEncapsulation,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { HttpClient } from '@angular/common/http';\nimport { Subscription } from 'rxjs';\nimport { CodeBlockComponent, CodeBlockLanguage } from '@lifecorner/angular-ui-kit/code-block';\nimport { isSafeImageUrl, isSafeLinkUrl } from '@lifecorner/angular-ui-kit/internal/url';\n\nexport interface MarkdownHeading {\n level: number;\n text: string;\n id: string;\n}\n\nexport interface MarkdownLinkClick {\n href: string;\n event: MouseEvent;\n}\n\nexport interface MarkdownRendered {\n headings: MarkdownHeading[];\n}\n\nexport interface MarkdownChangesHighlighted {\n /** Number of changed/added blocks highlighted in the current render. */\n changedBlocks: number;\n}\n\nexport interface RenderPart {\n type: 'html' | 'code' | 'mermaid';\n index: number;\n safeHtml?: SafeHtml;\n code?: string;\n lang?: CodeBlockLanguage;\n}\n\n/**\n * Stand-in for a fenced code block while the rest of the markdown is processed.\n * Bracketed in NUL characters, which the source is stripped of first — so no\n * text a user writes can ever spell a placeholder and be mistaken for a block.\n * (The earlier `<!--CODE_BLOCK_N-->` form was: an escaped literal comment in\n * the text matched it and dereferenced a block that wasn't there.)\n */\nconst BLOCK_MARK = '\\u0000';\nconst blockPlaceholder = (idx: number): string => `${BLOCK_MARK}LCBLOCK${idx}${BLOCK_MARK}`;\n// eslint-disable-next-line no-control-regex -- the NUL bracket is the point: unforgeable\nconst BLOCK_PLACEHOLDER = /\\u0000LCBLOCK(\\d+)\\u0000/g;\n\n/** Instances numbered so their mermaid render ids never collide on one page. */\nlet markdownInstances = 0;\n\n/**\n * Markdown renderer component.\n *\n * Renders GitHub-Flavored Markdown (GFM) to sanitized HTML with\n * optional syntax highlighting via `<lc-code-block>`.\n *\n * Supports headings (+ anchors), paragraphs, ordered/unordered lists, task lists\n * (`- [ ]` / `- [x]` → accessible checkbox), blockquotes, horizontal rules,\n * images, GFM tables (column alignment + horizontal scroll), bold/italic/\n * strikethrough, inline code, fenced code (incl. `mermaid`), explicit links and\n * autolinks (bare URLs / `www.` / emails, never inside code).\n *\n * Optionally highlights *changed* blocks in place: pass the pre-edit markdown as\n * `previousContent` and set `highlightChanges` — added/edited blocks (diffed at\n * block / list-item level) gain a left accent bar + subtle tint, can auto-fade\n * (`changeHighlightFadeMs`) and scroll into view (`scrollToFirstChange`).\n *\n * @example\n * ```html\n * <lc-markdown [content]=\"'# Hello World'\" />\n * <lc-markdown [src]=\"'/docs/readme.md'\" />\n * <lc-markdown\n * [content]=\"current\" [previousContent]=\"prev\"\n * [highlightChanges]=\"true\" [changeHighlightFadeMs]=\"3000\" />\n * ```\n */\n@Component({\n selector: 'lc-markdown',\n standalone: true,\n imports: [CodeBlockComponent],\n templateUrl: './markdown.component.html',\n styleUrls: ['./markdown.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class MarkdownComponent implements OnDestroy {\n private readonly sanitizer = inject(DomSanitizer);\n private readonly instanceId = ++markdownInstances;\n private readonly http = inject(HttpClient);\n private readonly host = inject(ElementRef<HTMLElement>);\n private httpSub?: Subscription;\n private fadeTimer?: ReturnType<typeof setTimeout>;\n private scrollTimer?: ReturnType<typeof setTimeout>;\n private mermaidApiPromise?: Promise<any>;\n private mermaidInitialized = false;\n private mermaidRenderRun = 0;\n\n /** URL or path to load markdown from */\n readonly src = input<string>();\n\n /** Raw markdown string */\n readonly content = input<string>();\n\n /** Display variant */\n readonly variant = input<'default' | 'compact' | 'chat'>('default');\n\n /** Target for links */\n readonly linkTarget = input<'_self' | '_blank'>('_self');\n\n /** Whether to sanitize HTML output */\n readonly sanitize = input<boolean>(true);\n\n /** Whether to use code-block for fenced code */\n readonly syntaxHighlight = input<boolean>(true);\n\n /** Whether to show anchor links on headings */\n readonly showHeadingAnchors = input<boolean>(false);\n\n /** Base URL for resolving relative links/images */\n readonly baseUrl = input<string>();\n\n // -- Change highlighting --\n /** Enable change highlighting (requires `previousContent` to compute a diff). */\n readonly highlightChanges = input<boolean>(false);\n\n /**\n * The prior Markdown. When it differs from `content`, the changed/added blocks\n * in `content` are highlighted. The caller passes the pre-edit markdown.\n */\n readonly previousContent = input<string>();\n\n /** Auto-fade the highlight after N ms. 0 / undefined ⇒ persist until content changes. */\n readonly changeHighlightFadeMs = input<number>();\n\n /** Scroll the first changed block into view when highlights appear. */\n readonly scrollToFirstChange = input<boolean>(false);\n\n /** Screen-reader marker put in front of each highlighted block (`highlightChanges`). */\n readonly changedMarkerLabel = input('(geändert)');\n\n /** Announced after the count when one block changed: `1 Abschnitt geändert`. */\n readonly changedSummaryOneLabel = input('Abschnitt geändert');\n\n /** Announced after the count when several blocks changed: `3 Abschnitte geändert`. */\n readonly changedSummaryManyLabel = input('Abschnitte geändert');\n\n /** Shown while a Mermaid diagram renders. */\n readonly mermaidLoadingLabel = input('Rendering Mermaid diagram...');\n\n /** Shown above the source when a Mermaid diagram fails. */\n readonly mermaidErrorLabel = input('Mermaid diagram could not be rendered.');\n\n /** Shown above the source when the optional `mermaid` package cannot be loaded. */\n readonly mermaidUnavailableLabel = input('Mermaid runtime is not available.');\n\n /** Emitted when a link is clicked */\n readonly linkClick = output<MarkdownLinkClick>();\n\n /** Emitted after rendering with heading TOC */\n readonly rendered = output<MarkdownRendered>();\n\n /** Emitted after a render that produced change highlights. */\n readonly changesHighlighted = output<MarkdownChangesHighlighted>();\n\n /** Internal resolved markdown source */\n protected resolvedMarkdown = signal<string>('');\n\n /** Parsed result (computed once from resolvedMarkdown) */\n private parsed = computed(() => {\n const md = this.resolvedMarkdown();\n if (!md) {\n return {\n html: '',\n blocks: [] as { kind: 'code' | 'mermaid'; lang: string; code: string }[],\n headings: [] as MarkdownHeading[],\n };\n }\n return this.parseMarkdown(md);\n });\n\n /**\n * Rendered HTML after applying change highlights. When highlighting is off (or\n * there is no differing `previousContent`) this returns the parsed HTML\n * unchanged, so the non-highlight path is byte-for-byte identical to before.\n */\n private readonly highlightResult = computed<{ html: string; count: number }>(() => {\n const { html } = this.parsed();\n const prev = this.previousContent();\n if (\n !html ||\n !this.highlightChanges() ||\n prev == null ||\n prev === this.resolvedMarkdown()\n ) {\n return { html, count: 0 };\n }\n return this.applyChangeHighlights(html, prev);\n });\n\n /** Number of changed/added blocks highlighted in the current render. */\n protected readonly changedCount = computed(() => this.highlightResult().count);\n\n /** Visually-hidden polite summary announced when highlights appear. */\n protected readonly changeSummary = computed(() => {\n const n = this.changedCount();\n if (n <= 0) return '';\n return `${n} ${n === 1 ? this.changedSummaryOneLabel() : this.changedSummaryManyLabel()}`;\n });\n\n /** Whether the current highlights have faded out (after `changeHighlightFadeMs`). */\n protected readonly highlightsFaded = signal(false);\n\n /** Computed render parts (HTML chunks + code blocks interleaved) */\n protected renderParts = computed<RenderPart[]>(() => {\n const html = this.highlightResult().html;\n const { blocks } = this.parsed();\n if (!html) return [];\n\n if (this.syntaxHighlight() && blocks.length > 0) {\n return this.splitIntoParts(html, blocks);\n }\n\n // No code blocks — single HTML part\n return [{\n type: 'html',\n index: 0,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(html, { next: 0 })),\n }];\n });\n\n /**\n * The HTML as it may go into the DOM. With `sanitize` on (the default) that is\n * Angular's HTML sanitizer's output — which drops every `id`, since `id` isn't\n * on its attribute allow-list. The heading ids are put back afterwards: they\n * are what anchors and the `rendered` TOC point at, and each is a `[\\w-]`\n * slug this parser produced itself, so re-adding them reopens nothing. The\n * cursor walks the parsed headings in document order across the parts.\n */\n private sanitizeHtml(html: string, cursor: { next: number }): string {\n const headings = this.headings();\n if (!this.sanitize()) {\n cursor.next += (html.match(/<h[1-6]\\b/g) ?? []).length;\n return html;\n }\n const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, html) || '';\n return sanitized.replace(/<h([1-6])(\\s[^>]*)?>/g, (tag, level: string, attrs = '') => {\n const heading = headings[cursor.next++];\n if (!heading || /\\sid=/.test(attrs)) return tag;\n return `<h${level} id=\"${heading.id}\"${attrs}>`;\n });\n }\n\n protected containerClasses = computed(() => {\n let classes = `lc-markdown lc-markdown--${this.variant()}`;\n if (this.highlightsFaded()) classes += ' lc-markdown--faded';\n return classes;\n });\n\n protected mermaidSvgs = signal<Record<number, SafeHtml>>({});\n protected mermaidErrors = signal<Record<number, string>>({});\n\n private headings = computed(() => this.parsed().headings);\n\n constructor() {\n // Load content from src when src changes\n effect(() => {\n const src = this.src();\n if (src) {\n this.loadFromUrl(src);\n }\n });\n\n // Use raw content when provided\n effect(() => {\n const content = this.content();\n if (content !== undefined) {\n this.resolvedMarkdown.set(content);\n }\n });\n\n // Emit rendered event whenever a render produced a (possibly empty) TOC\n effect(() => {\n const h = this.headings();\n untracked(() => this.rendered.emit({ headings: h }));\n });\n\n // Render Mermaid code fences as inline SVG diagrams.\n effect(() => {\n void this.renderMermaidParts(this.renderParts());\n });\n\n // React to a render that produced change highlights: emit, schedule fade,\n // and optionally scroll the first changed block into view.\n effect(() => {\n const count = this.highlightResult().count;\n untracked(() => this.onHighlightResult(count));\n });\n }\n\n ngOnDestroy(): void {\n this.httpSub?.unsubscribe();\n clearTimeout(this.fadeTimer);\n clearTimeout(this.scrollTimer);\n }\n\n protected onLinkClick(event: MouseEvent): void {\n const target = event.target as HTMLElement;\n const anchor = target.closest('a');\n if (anchor) {\n this.linkClick.emit({ href: anchor.href, event });\n }\n }\n\n // -- Change highlighting -----------------------------------------------------\n\n /** Side-effects for a render: emit, (re)arm the fade timer, optional scroll. */\n private onHighlightResult(count: number): void {\n clearTimeout(this.fadeTimer);\n clearTimeout(this.scrollTimer);\n // A fresh render starts un-faded (re-entrant: never reuse stale fade state).\n this.highlightsFaded.set(false);\n if (count <= 0) return;\n\n this.changesHighlighted.emit({ changedBlocks: count });\n\n const fadeMs = this.changeHighlightFadeMs();\n if (fadeMs && fadeMs > 0) {\n this.fadeTimer = setTimeout(() => this.highlightsFaded.set(true), fadeMs);\n }\n if (this.scrollToFirstChange()) {\n // Defer until the highlighted HTML has been written to the DOM.\n this.scrollTimer = setTimeout(() => this.scrollToFirstChanged(), 0);\n }\n }\n\n private scrollToFirstChanged(): void {\n if (typeof document === 'undefined') return;\n const el = this.host.nativeElement.querySelector('.lc-markdown__block--changed');\n el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }\n\n /**\n * Wraps the changed/added top-level blocks of `html` with\n * `.lc-markdown__block--changed`. A block is \"changed\" when its normalized\n * text is not present among the blocks of `prevMarkdown`. Lists are diffed\n * per `<li>` and tables per `<tr>` so a single edited item highlights alone.\n */\n private applyChangeHighlights(\n html: string,\n prevMarkdown: string\n ): { html: string; count: number } {\n if (typeof document === 'undefined') return { html, count: 0 };\n\n const prevKeys = this.collectBlockKeys(this.parseMarkdown(prevMarkdown).html);\n const root = this.htmlToElement(html);\n if (!root) return { html, count: 0 };\n\n let count = 0;\n const markIfChanged = (el: Element): void => {\n const key = this.blockTextKey(el.textContent ?? '');\n if (!key || prevKeys.has(key)) return;\n el.classList.add('lc-markdown__block--changed');\n const sr = document.createElement('span');\n sr.className = 'lc-markdown__sr-only';\n sr.textContent = `${this.changedMarkerLabel()} `;\n el.insertBefore(sr, el.firstChild);\n count++;\n };\n\n this.eachLogicalBlock(root, markIfChanged);\n return { html: root.innerHTML, count };\n }\n\n /** Normalized text set of the logical blocks within an HTML fragment. */\n private collectBlockKeys(html: string): Set<string> {\n const keys = new Set<string>();\n const root = this.htmlToElement(html);\n if (!root) return keys;\n this.eachLogicalBlock(root, (el) => {\n const key = this.blockTextKey(el.textContent ?? '');\n if (key) keys.add(key);\n });\n return keys;\n }\n\n /**\n * Visits each diffable block under `root`: top-level elements, but descending\n * into `<ul>`/`<ol>` (per `<li>`) and `<table>` (per `<tr>`).\n */\n private eachLogicalBlock(root: Element, visit: (el: Element) => void): void {\n for (const node of Array.from(root.childNodes)) {\n if (node.nodeType !== 1) continue; // elements only (skips code placeholders)\n const el = node as Element;\n const tag = el.tagName;\n if (tag === 'UL' || tag === 'OL') {\n for (const li of Array.from(el.children)) {\n if (li.tagName === 'LI') visit(li);\n }\n } else if (tag === 'TABLE' || el.querySelector('table')) {\n // Tables (incl. the scroll wrapper div) diff per row.\n for (const tr of Array.from(el.querySelectorAll('tr'))) {\n visit(tr);\n }\n } else {\n visit(el);\n }\n }\n }\n\n private blockTextKey(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().toLowerCase();\n }\n\n private htmlToElement(html: string): HTMLElement | null {\n if (typeof document === 'undefined') return null;\n const div = document.createElement('div');\n div.innerHTML = html;\n return div;\n }\n\n private loadFromUrl(url: string): void {\n this.httpSub?.unsubscribe();\n this.httpSub = this.http.get(url, { responseType: 'text' }).subscribe({\n next: (text) => this.resolvedMarkdown.set(text),\n error: () => this.resolvedMarkdown.set(`*Failed to load ${url}*`),\n });\n }\n\n private splitIntoParts(\n html: string,\n blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[]\n ): RenderPart[] {\n const parts: RenderPart[] = [];\n const regex = new RegExp(BLOCK_PLACEHOLDER.source, 'g');\n const cursor = { next: 0 };\n let lastIndex = 0;\n let partIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(html)) !== null) {\n const before = html.slice(lastIndex, match.index);\n if (before.trim()) {\n parts.push({\n type: 'html',\n index: partIndex++,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(before, cursor)),\n });\n }\n const block = blocks[parseInt(match[1], 10)];\n lastIndex = match.index + match[0].length;\n // Unforgeable placeholder, so this can only be a bug — but never a crash.\n if (!block) continue;\n if (block.kind === 'mermaid') {\n parts.push({\n type: 'mermaid',\n index: partIndex++,\n code: block.code,\n });\n } else {\n parts.push({\n type: 'code',\n index: partIndex++,\n code: block.code,\n lang: block.lang as CodeBlockLanguage,\n });\n }\n }\n\n const remaining = html.slice(lastIndex);\n if (remaining.trim()) {\n parts.push({\n type: 'html',\n index: partIndex++,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(remaining, cursor)),\n });\n }\n\n return parts;\n }\n\n private parseMarkdown(md: string): {\n html: string;\n blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[];\n headings: MarkdownHeading[];\n } {\n const blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[] = [];\n const headings: MarkdownHeading[] = [];\n const baseUrl = this.baseUrl();\n const linkTarget = this.linkTarget();\n const showAnchors = this.showHeadingAnchors();\n\n // 1. Extract fenced code blocks → placeholders (see BLOCK_MARK: the source\n // is stripped of the marker character first, so it can't forge one)\n // eslint-disable-next-line no-control-regex -- strip the placeholder marker (see BLOCK_MARK)\n let processed = md.replace(/\\u0000/g, '').replace(\n /```(\\w*)\\n([\\s\\S]*?)```/g,\n (_match, lang: string, code: string) => {\n const idx = blocks.length;\n const normalizedLang = (lang || 'text').toLowerCase();\n blocks.push({\n kind: normalizedLang === 'mermaid' ? 'mermaid' : 'code',\n lang: normalizedLang,\n code: code.trimEnd(),\n });\n return blockPlaceholder(idx);\n }\n );\n\n // 2. Escape HTML entities — quotes included, so nothing the parser later\n // interpolates into an attribute (link/image URLs, alt text) can close it.\n processed = this.escapeHtml(processed);\n\n // 3. Headings (# to ######)\n processed = processed.replace(\n /^(#{1,6})\\s+(.+)$/gm,\n (_match, hashes: string, text: string) => {\n const level = hashes.length;\n const id = text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-');\n headings.push({ level, text, id });\n const anchor = showAnchors\n ? `<a href=\"#${id}\" class=\"lc-markdown__anchor\" aria-hidden=\"true\">#</a>`\n : '';\n return `<h${level} id=\"${id}\">${anchor}${text}</h${level}>`;\n }\n );\n\n // 4. Horizontal rules\n processed = processed.replace(/^---+$/gm, '<hr>');\n\n // 5. Blockquotes\n processed = processed.replace(\n /^(?:&gt;)\\s?(.*)$/gm,\n '<blockquote>$1</blockquote>'\n );\n // Merge consecutive blockquotes\n processed = processed.replace(\n /<\\/blockquote>\\n<blockquote>/g,\n '\\n'\n );\n\n // 6. Tables (GFM)\n processed = this.parseTables(processed);\n\n // 7. Task lists. Angular's HTML sanitizer strips <input>, so the checkbox is\n // rendered as a styled <span role=\"checkbox\"> (which survives sanitization and\n // carries accessible checked/disabled state) rather than a form control.\n processed = processed.replace(\n /^[-*]\\s+\\[[xX]\\]\\s+(.*)$/gm,\n '<li class=\"lc-markdown__task-item\">' +\n '<span class=\"lc-markdown__checkbox lc-markdown__checkbox--checked\" role=\"checkbox\" aria-checked=\"true\" aria-disabled=\"true\"></span>' +\n '<span class=\"lc-markdown__task-label\">$1</span></li>'\n );\n processed = processed.replace(\n /^[-*]\\s+\\[ \\]\\s+(.*)$/gm,\n '<li class=\"lc-markdown__task-item\">' +\n '<span class=\"lc-markdown__checkbox\" role=\"checkbox\" aria-checked=\"false\" aria-disabled=\"true\"></span>' +\n '<span class=\"lc-markdown__task-label\">$1</span></li>'\n );\n\n // 8. Unordered lists\n processed = processed.replace(\n /^[-*]\\s+(.*)$/gm,\n '<li>$1</li>'\n );\n // Wrap consecutive <li> in <ul>\n processed = processed.replace(\n /((?:<li[^>]*>.*<\\/li>\\n?)+)/g,\n '<ul>$1</ul>'\n );\n\n // 9. Ordered lists — marked while wrapping, so a run of them gets its <ol>\n // and isn't confused with the <li>s already inside <ul>s above.\n processed = processed.replace(\n /^\\d+\\.\\s+(.*)$/gm,\n '<li data-ol>$1</li>'\n );\n processed = processed.replace(\n /((?:<li data-ol>.*<\\/li>\\n?)+)/g,\n (run) => `<ol>${run.replace(/ data-ol/g, '')}</ol>`\n );\n\n // 10. Inline code\n processed = processed.replace(\n /`([^`]+)`/g,\n '<code>$1</code>'\n );\n\n // 11. Bold + italic combos\n processed = processed.replace(\n /\\*\\*\\*(.+?)\\*\\*\\*/g,\n '<strong><em>$1</em></strong>'\n );\n processed = processed.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n processed = processed.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n\n // 12. Strikethrough (GFM)\n processed = processed.replace(/~~(.+?)~~/g, '<del>$1</del>');\n\n // 13. Images. Only allow-listed URL schemes become a `src` (see\n // safe-url.ts); anything else — `javascript:`, unknown schemes — is\n // rendered as its alt text. This holds regardless of `sanitize`.\n processed = processed.replace(\n /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g,\n (_match, alt: string, src: string) => {\n if (!isSafeImageUrl(src)) return alt;\n const resolvedSrc = baseUrl && !src.startsWith('http') ? `${baseUrl}/${src}` : src;\n return `<img src=\"${resolvedSrc}\" alt=\"${alt}\" class=\"lc-markdown__img\">`;\n }\n );\n\n // 14. Links — same rule: an unsafe URL leaves plain text, not a link.\n processed = processed.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match, text: string, href: string) => {\n if (!isSafeLinkUrl(href)) return text;\n const resolvedHref = baseUrl && !href.startsWith('http') && !href.startsWith('#')\n ? `${baseUrl}/${href}`\n : href;\n return `<a href=\"${resolvedHref}\" target=\"${linkTarget}\" rel=\"noopener\">${text}</a>`;\n }\n );\n\n // 14b. Autolinks — linkify bare URLs / www / emails in plain text. Runs after\n // explicit links and inline code so it can skip anything already inside an\n // <a> or <code> (and never touches tag attributes).\n processed = this.autolink(processed, linkTarget);\n\n // 15. Paragraphs — wrap standalone lines\n processed = processed\n .split('\\n\\n')\n .map((block) => {\n const trimmed = block.trim();\n if (!trimmed) return '';\n if (\n trimmed.startsWith('<h') ||\n trimmed.startsWith('<ul') ||\n trimmed.startsWith('<ol') ||\n trimmed.startsWith('<blockquote') ||\n trimmed.startsWith('<hr') ||\n trimmed.startsWith('<table') ||\n trimmed.startsWith('<div') ||\n trimmed.startsWith(BLOCK_MARK)\n ) {\n return trimmed;\n }\n return `<p>${trimmed.replace(/\\n/g, '<br>')}</p>`;\n })\n .join('\\n');\n\n // 16. Restore code block placeholders\n if (this.syntaxHighlight()) {\n // Leave placeholders — the template will render <lc-code-block> for each\n // No replacement needed here\n } else {\n processed = processed.replace(\n new RegExp(BLOCK_PLACEHOLDER.source, 'g'),\n (_match, idx: string) => {\n const block = blocks[parseInt(idx, 10)];\n if (!block) return '';\n return `<pre><code class=\"language-${block.lang}\">${this.escapeHtml(block.code)}</code></pre>`;\n }\n );\n }\n\n return { html: processed, blocks, headings };\n }\n\n private parseTables(text: string): string {\n return text.replace(\n /^(\\|.+\\|)\\n(\\|[-| :]+\\|)\\n((?:\\|.+\\|\\n?)*)/gm,\n (_match, header: string, separator: string, body: string) => {\n // Per-column alignment from the delimiter row: :--- left, :--: center,\n // ---: right. Surfaced as classes (the sanitizer strips inline `style`).\n const aligns = this.splitTableRow(separator).map((cell) => {\n const left = cell.startsWith(':');\n const right = cell.endsWith(':');\n if (left && right) return 'center';\n if (right) return 'right';\n if (left) return 'left';\n return '';\n });\n const alignClass = (col: number): string =>\n aligns[col] ? ` class=\"lc-markdown__cell--${aligns[col]}\"` : '';\n\n const headers = this.splitTableRow(header)\n .map((cell, i) => `<th scope=\"col\"${alignClass(i)}>${cell.trim()}</th>`)\n .join('');\n\n const rows = body\n .trim()\n .split('\\n')\n .map((row: string) => {\n const cells = this.splitTableRow(row)\n .map((cell, i) => `<td${alignClass(i)}>${cell.trim()}</td>`)\n .join('');\n return `<tr>${cells}</tr>`;\n })\n .join('');\n\n // Wrapped so wide tables scroll horizontally instead of breaking layout.\n return (\n `<div class=\"lc-markdown__table-wrap\">` +\n `<table class=\"lc-markdown__table\"><thead><tr>${headers}</tr></thead><tbody>${rows}</tbody></table>` +\n `</div>`\n );\n }\n );\n }\n\n /** Splits a pipe-table row into trimmed cells, dropping the leading/trailing edges. */\n private splitTableRow(row: string): string[] {\n return row\n .trim()\n .replace(/^\\|/, '')\n .replace(/\\|$/, '')\n .split('|')\n .map((cell) => cell.trim());\n }\n\n private escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n }\n\n /**\n * Linkifies bare URLs (`http(s)://…`, `www.…`) and email addresses in text,\n * leaving anything inside an existing `<a>` or `<code>` (and all tag\n * attributes) untouched. The HTML is tokenized into tags vs. text so only\n * top-level text runs are linkified.\n */\n private autolink(html: string, linkTarget: string): string {\n const url = /(?:https?:\\/\\/|www\\.)[^\\s<]*[^\\s<.,:;!?)\\]}'\"]/;\n const email = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/;\n const pattern = new RegExp(`${url.source}|${email.source}`, 'g');\n\n let depth = 0; // inside <a> or <code>\n return html\n .split(/(<[^>]+>)/g)\n .map((segment) => {\n if (segment.startsWith('<')) {\n if (/^<(a|code)[\\s>]/i.test(segment)) depth++;\n else if (/^<\\/(a|code)>/i.test(segment)) depth = Math.max(0, depth - 1);\n return segment;\n }\n if (depth > 0 || !segment) return segment;\n return segment.replace(pattern, (token) => {\n const isEmail = token.includes('@') && !/^(?:https?:\\/\\/|www\\.)/.test(token);\n const href = isEmail\n ? `mailto:${token}`\n : token.startsWith('www.')\n ? `https://${token}`\n : token;\n return `<a href=\"${href}\" target=\"${linkTarget}\" rel=\"noopener\">${token}</a>`;\n });\n })\n .join('');\n }\n\n protected mermaidSvgFor(index: number): SafeHtml | null {\n return this.mermaidSvgs()[index] ?? null;\n }\n\n protected mermaidErrorFor(index: number): string | null {\n return this.mermaidErrors()[index] ?? null;\n }\n\n private async renderMermaidParts(parts: RenderPart[]): Promise<void> {\n const mermaidParts = parts.filter((part) => part.type === 'mermaid' && !!part.code);\n this.mermaidSvgs.set({});\n this.mermaidErrors.set({});\n\n if (mermaidParts.length === 0) {\n return;\n }\n\n const runId = ++this.mermaidRenderRun;\n\n try {\n const mermaidApi = await this.getMermaidApi();\n if (runId !== this.mermaidRenderRun) return;\n\n const nextSvgs: Record<number, SafeHtml> = {};\n const nextErrors: Record<number, string> = {};\n\n for (const part of mermaidParts) {\n try {\n // Per instance, per run, per part: mermaid's render() removes any\n // element already carrying the id it is given, so an id shared with\n // another <lc-markdown> on the page would delete that one's diagram.\n const renderId = `lc-md-mermaid-${this.instanceId}-${runId}-${part.index}`;\n const { svg } = await mermaidApi.render(renderId, part.code!);\n if (runId !== this.mermaidRenderRun) return;\n // Mermaid emits self-contained SVG with internal <style> rules.\n // Angular HTML sanitization strips critical style declarations,\n // which leaves only labels visible (no edges/shapes). We trust\n // Mermaid output here and rely on Mermaid's strict security mode.\n nextSvgs[part.index] = this.sanitizer.bypassSecurityTrustHtml(svg);\n } catch {\n nextErrors[part.index] = this.mermaidErrorLabel();\n }\n }\n\n if (runId !== this.mermaidRenderRun) return;\n this.mermaidSvgs.set(nextSvgs);\n this.mermaidErrors.set(nextErrors);\n } catch {\n if (runId !== this.mermaidRenderRun) return;\n const fallbackErrors: Record<number, string> = {};\n for (const part of mermaidParts) {\n fallbackErrors[part.index] = this.mermaidUnavailableLabel();\n }\n this.mermaidErrors.set(fallbackErrors);\n }\n }\n\n private async getMermaidApi(): Promise<any> {\n if (!this.mermaidApiPromise) {\n this.mermaidApiPromise = import('mermaid').then((module) => {\n const api = module.default ?? module;\n if (!this.mermaidInitialized) {\n api.initialize({\n startOnLoad: false,\n securityLevel: 'strict',\n });\n this.mermaidInitialized = true;\n }\n return api;\n });\n }\n return this.mermaidApiPromise;\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- delegated handler for clicks on rendered links, which are focusable anchors -->\n<div [class]=\"containerClasses()\" (click)=\"onLinkClick($event)\">\n @if (highlightChanges()) {\n <div class=\"lc-markdown__sr-only\" role=\"status\" aria-live=\"polite\">{{ changeSummary() }}</div>\n }\n @for (part of renderParts(); track part.index) {\n @if (part.type === 'html') {\n <div [innerHTML]=\"part.safeHtml\"></div>\n } @else if (part.type === 'code') {\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"part.lang ?? 'text'\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n } @else {\n @if (mermaidSvgFor(part.index); as svg) {\n <div class=\"lc-markdown__mermaid\" [innerHTML]=\"svg\"></div>\n } @else if (mermaidErrorFor(part.index); as errorMessage) {\n <div class=\"lc-markdown__mermaid-fallback\">\n <p class=\"lc-markdown__mermaid-error\">{{ errorMessage }}</p>\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"'text'\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n </div>\n } @else {\n <div class=\"lc-markdown__mermaid-loading\">{{ mermaidLoadingLabel() }}</div>\n }\n }\n }\n</div>\n"],"mappings":";;;;;;AAwDA,MAAM,aAAa;AACnB,MAAM,oBAAoB,QAAwB,GAAG,WAAU,SAAU,MAAM;AAE/E,MAAM,oBAAoB;AAG1B,IAAI,oBAAoB;AAqCxB,IAAa,oBAAb,MAAa,kBAAiB;CACX,YAAY,OAAO,YAAY;CAC/B,aAAa,EAAE;CACf,OAAO,OAAO,UAAU;CACxB,OAAO,OAAO,UAAuB;CAC9C;CACA;CACA;CACA;CACA,qBAAqB;CACrB,mBAAmB;CAGlB,MAAM,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,MAAA,CAAA,+BAAA,CAAA,CAAA;CAGrB,UAAU,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAGzB,UAAU,MAAsC,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAG1D,aAAa,MAA0B,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG/C,WAAW,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,kBAAkB,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtC,qBAAqB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,qBAAA,CAAA,+BAAA,CAAA,CAAA;CAG1C,UAAU,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAIzB,mBAAmB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAMxC,kBAAkB,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,wBAAwB,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,wBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,sBAAsB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,sBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,qBAAqB,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,qBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxC,yBAAyB,MAAM,sBAAqB,GAAA,YAAA,CAAA,EAAA,WAAA,yBAAA,CAAA,+BAAA,CAAA,CAAA;CAGpD,0BAA0B,MAAM,uBAAsB,GAAA,YAAA,CAAA,EAAA,WAAA,0BAAA,CAAA,+BAAA,CAAA,CAAA;CAGtD,sBAAsB,MAAM,gCAA+B,GAAA,YAAA,CAAA,EAAA,WAAA,sBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3D,oBAAoB,MAAM,0CAAyC,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAGnE,0BAA0B,MAAM,qCAAoC,GAAA,YAAA,CAAA,EAAA,WAAA,0BAAA,CAAA,+BAAA,CAAA,CAAA;CAGpE,YAAY,OAAM;CAGlB,WAAW,OAAM;CAGjB,qBAAqB,OAAM;CAG1B,mBAAmB,OAAe,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,SAAS,eAAc;EAC7B,MAAM,KAAK,KAAK,iBAAgB;EAChC,IAAI,CAAC,IACH,OAAO;GACL,MAAM;GACN,QAAQ,CAAA;GACR,UAAU,CAAA;EACX;EAEH,OAAO,KAAK,cAAc,EAAE;CAC9B,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAOe,kBAAkB,eAA+C;EAChF,MAAM,EAAE,SAAS,KAAK,OAAM;EAC5B,MAAM,OAAO,KAAK,gBAAe;EACjC,IACE,CAAC,QACD,CAAC,KAAK,iBAAgB,KACtB,QAAQ,QACR,SAAS,KAAK,iBAAgB,GAE9B,OAAO;GAAE;GAAM,OAAO;EAAC;EAEzB,OAAO,KAAK,sBAAsB,MAAM,IAAI;CAC9C,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGiB,eAAe,eAAe,KAAK,gBAAe,CAAE,CAAC,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAG3D,gBAAgB,eAAc;EAC/C,MAAM,IAAI,KAAK,aAAY;EAC3B,IAAI,KAAK,GAAG,OAAO;EACnB,OAAO,GAAG,EAAC,GAAI,MAAM,IAAI,KAAK,uBAAsB,IAAK,KAAK,wBAAuB;CACvF,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAGiB,kBAAkB,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxC,cAAc,eAA4B;EAClD,MAAM,OAAO,KAAK,gBAAe,CAAE,CAAC;EACpC,MAAM,EAAE,WAAW,KAAK,OAAM;EAC9B,IAAI,CAAC,MAAM,OAAO,CAAA;EAElB,IAAI,KAAK,gBAAe,KAAM,OAAO,SAAS,GAC5C,OAAO,KAAK,eAAe,MAAM,MAAM;EAIzC,OAAO,CAAC;GACN,MAAM;GACN,OAAO;GACP,UAAU,KAAK,UAAU,wBAAwB,KAAK,aAAa,MAAM,EAAE,MAAM,EAAC,CAAE,CAAC;EACtF,CAAA;CACH,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAUM,aAAa,MAAc,QAAwB;EACzD,MAAM,WAAW,KAAK,SAAQ;EAC9B,IAAI,CAAC,KAAK,SAAQ,GAAI;GACpB,OAAO,SAAS,KAAK,MAAM,YAAY,KAAK,CAAA,EAAA,CAAI;GAChD,OAAO;EACT;EAEA,QADkB,KAAK,UAAU,SAAS,gBAAgB,MAAM,IAAI,KAAK,GAClE,CAAU,QAAQ,0BAA0B,KAAK,OAAe,QAAQ,OAAM;GACnF,MAAM,UAAU,SAAS,OAAO;GAChC,IAAI,CAAC,WAAW,QAAQ,KAAK,KAAK,GAAG,OAAO;GAC5C,OAAO,KAAK,MAAK,OAAQ,QAAQ,GAAE,GAAI,MAAK;EAC9C,CAAC;CACH;CAEU,mBAAmB,eAAc;EACzC,IAAI,UAAU,4BAA4B,KAAK,QAAO;EACtD,IAAI,KAAK,gBAAe,GAAI,WAAW;EACvC,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,cAAc,OAAiC,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAClD,gBAAgB,OAA+B,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAEpD,WAAW,eAAe,KAAK,OAAM,CAAE,CAAC,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAEzD,cAAA;EAEE,aAAY;GACV,MAAM,MAAM,KAAK,IAAG;GACpB,IAAI,KACF,KAAK,YAAY,GAAG;EAExB,CAAC;EAGD,aAAY;GACV,MAAM,UAAU,KAAK,QAAO;GAC5B,IAAI,YAAY,QACd,KAAK,iBAAiB,IAAI,OAAO;EAErC,CAAC;EAGD,aAAY;GACV,MAAM,IAAI,KAAK,SAAQ;GACvB,gBAAgB,KAAK,SAAS,KAAK,EAAE,UAAU,EAAC,CAAE,CAAC;EACrD,CAAC;EAGD,aAAY;GACV,AAAK,KAAK,mBAAmB,KAAK,YAAW,CAAE;EACjD,CAAC;EAID,aAAY;GACV,MAAM,QAAQ,KAAK,gBAAe,CAAE,CAAC;GACrC,gBAAgB,KAAK,kBAAkB,KAAK,CAAC;EAC/C,CAAC;CACH;CAEA,cAAW;EACT,KAAK,SAAS,YAAW;EACzB,aAAa,KAAK,SAAS;EAC3B,aAAa,KAAK,WAAW;CAC/B;CAEU,YAAY,OAAiB;EAErC,MAAM,SADS,MAAM,OACC,QAAQ,GAAG;EACjC,IAAI,QACF,KAAK,UAAU,KAAK;GAAE,MAAM,OAAO;GAAM;EAAK,CAAE;CAEpD;CAKQ,kBAAkB,OAAa;EACrC,aAAa,KAAK,SAAS;EAC3B,aAAa,KAAK,WAAW;EAE7B,KAAK,gBAAgB,IAAI,KAAK;EAC9B,IAAI,SAAS,GAAG;EAEhB,KAAK,mBAAmB,KAAK,EAAE,eAAe,MAAK,CAAE;EAErD,MAAM,SAAS,KAAK,sBAAqB;EACzC,IAAI,UAAU,SAAS,GACrB,KAAK,YAAY,iBAAiB,KAAK,gBAAgB,IAAI,IAAI,GAAG,MAAM;EAE1E,IAAI,KAAK,oBAAmB,GAE1B,KAAK,cAAc,iBAAiB,KAAK,qBAAoB,GAAI,CAAC;CAEtE;CAEQ,uBAAoB;EAC1B,IAAI,OAAO,aAAa,aAAa;EAErC,AADW,KAAK,KAAK,cAAc,cAAc,8BACjD,CAAA,EAAI,eAAe;GAAE,UAAU;GAAU,OAAO;EAAS,CAAE;CAC7D;CAQQ,sBACN,MACA,cAAoB;EAEpB,IAAI,OAAO,aAAa,aAAa,OAAO;GAAE;GAAM,OAAO;EAAC;EAE5D,MAAM,WAAW,KAAK,iBAAiB,KAAK,cAAc,YAAY,CAAC,CAAC,IAAI;EAC5E,MAAM,OAAO,KAAK,cAAc,IAAI;EACpC,IAAI,CAAC,MAAM,OAAO;GAAE;GAAM,OAAO;EAAC;EAElC,IAAI,QAAQ;EACZ,MAAM,iBAAiB,OAAqB;GAC1C,MAAM,MAAM,KAAK,aAAa,GAAG,eAAe,EAAE;GAClD,IAAI,CAAC,OAAO,SAAS,IAAI,GAAG,GAAG;GAC/B,GAAG,UAAU,IAAI,6BAA6B;GAC9C,MAAM,KAAK,SAAS,cAAc,MAAM;GACxC,GAAG,YAAY;GACf,GAAG,cAAc,GAAG,KAAK,mBAAkB,EAAE;GAC7C,GAAG,aAAa,IAAI,GAAG,UAAU;GACjC;EACF;EAEA,KAAK,iBAAiB,MAAM,aAAa;EACzC,OAAO;GAAE,MAAM,KAAK;GAAW;EAAK;CACtC;CAGQ,iBAAiB,MAAY;EACnC,MAAM,uBAAO,IAAI,IAAG;EACpB,MAAM,OAAO,KAAK,cAAc,IAAI;EACpC,IAAI,CAAC,MAAM,OAAO;EAClB,KAAK,iBAAiB,OAAO,OAAM;GACjC,MAAM,MAAM,KAAK,aAAa,GAAG,eAAe,EAAE;GAClD,IAAI,KAAK,KAAK,IAAI,GAAG;EACvB,CAAC;EACD,OAAO;CACT;CAMQ,iBAAiB,MAAe,OAA4B;EAClE,KAAK,MAAM,QAAQ,MAAM,KAAK,KAAK,UAAU,GAAG;GAC9C,IAAI,KAAK,aAAa,GAAG;GACzB,MAAM,KAAK;GACX,MAAM,MAAM,GAAG;GACf,IAAI,QAAQ,QAAQ,QAAQ,MAC1B;SAAK,MAAM,MAAM,MAAM,KAAK,GAAG,QAAQ,GACrC,IAAI,GAAG,YAAY,MAAM,MAAM,EAAE;GACnC,OACK,IAAI,QAAQ,WAAW,GAAG,cAAc,OAAO,GAEpD,KAAK,MAAM,MAAM,MAAM,KAAK,GAAG,iBAAiB,IAAI,CAAC,GACnD,MAAM,EAAE;QAGV,MAAM,EAAE;EAEZ;CACF;CAEQ,aAAa,MAAY;EAC/B,OAAO,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAI,CAAE,CAAC,YAAW;CACrD;CAEQ,cAAc,MAAY;EAChC,IAAI,OAAO,aAAa,aAAa,OAAO;EAC5C,MAAM,MAAM,SAAS,cAAc,KAAK;EACxC,IAAI,YAAY;EAChB,OAAO;CACT;CAEQ,YAAY,KAAW;EAC7B,KAAK,SAAS,YAAW;EACzB,KAAK,UAAU,KAAK,KAAK,IAAI,KAAK,EAAE,cAAc,OAAM,CAAE,CAAC,CAAC,UAAU;GACpE,OAAO,SAAS,KAAK,iBAAiB,IAAI,IAAI;GAC9C,aAAa,KAAK,iBAAiB,IAAI,mBAAmB,IAAG,EAAG;EACjE,CAAA;CACH;CAEQ,eACN,MACA,QAAkE;EAElE,MAAM,QAAsB,CAAA;EAC5B,MAAM,QAAQ,IAAI,OAAO,kBAAkB,QAAQ,GAAG;EACtD,MAAM,SAAS,EAAE,MAAM,EAAC;EACxB,IAAI,YAAY;EAChB,IAAI,YAAY;EAChB,IAAI;EAEJ,QAAQ,QAAQ,MAAM,KAAK,IAAI,OAAO,MAAM;GAC1C,MAAM,SAAS,KAAK,MAAM,WAAW,MAAM,KAAK;GAChD,IAAI,OAAO,KAAI,GACb,MAAM,KAAK;IACT,MAAM;IACN,OAAO;IACP,UAAU,KAAK,UAAU,wBAAwB,KAAK,aAAa,QAAQ,MAAM,CAAC;GACnF,CAAA;GAEH,MAAM,QAAQ,OAAO,SAAS,MAAM,IAAI,EAAE;GAC1C,YAAY,MAAM,QAAQ,MAAM,EAAE,CAAC;GAEnC,IAAI,CAAC,OAAO;GACZ,IAAI,MAAM,SAAS,WACjB,MAAM,KAAK;IACT,MAAM;IACN,OAAO;IACP,MAAM,MAAM;GACb,CAAA;QAED,MAAM,KAAK;IACT,MAAM;IACN,OAAO;IACP,MAAM,MAAM;IACZ,MAAM,MAAM;GACb,CAAA;EAEL;EAEA,MAAM,YAAY,KAAK,MAAM,SAAS;EACtC,IAAI,UAAU,KAAI,GAChB,MAAM,KAAK;GACT,MAAM;GACN,OAAO;GACP,UAAU,KAAK,UAAU,wBAAwB,KAAK,aAAa,WAAW,MAAM,CAAC;EACtF,CAAA;EAGH,OAAO;CACT;CAEQ,cAAc,IAAU;EAK9B,MAAM,SAAqE,CAAA;EAC3E,MAAM,WAA8B,CAAA;EACpC,MAAM,UAAU,KAAK,QAAO;EAC5B,MAAM,aAAa,KAAK,WAAU;EAClC,MAAM,cAAc,KAAK,mBAAkB;EAK3C,IAAI,YAAY,GAAG,QAAQ,WAAW,EAAE,CAAC,CAAC,QACxC,6BACC,QAAQ,MAAc,SAAgB;GACrC,MAAM,MAAM,OAAO;GACnB,MAAM,kBAAkB,QAAQ,OAAA,CAAQ,YAAW;GACnD,OAAO,KAAK;IACV,MAAM,mBAAmB,YAAY,YAAY;IACjD,MAAM;IACN,MAAM,KAAK,QAAO;GACnB,CAAA;GACD,OAAO,iBAAiB,GAAG;EAC7B,CAAC;EAKH,YAAY,KAAK,WAAW,SAAS;EAGrC,YAAY,UAAU,QACpB,wBACC,QAAQ,QAAgB,SAAgB;GACvC,MAAM,QAAQ,OAAO;GACrB,MAAM,KAAK,KACR,YAAW,CAAE,CACb,QAAQ,aAAa,EAAE,CAAC,CACxB,QAAQ,QAAQ,GAAG;GACtB,SAAS,KAAK;IAAE;IAAO;IAAM;GAAE,CAAE;GAIjC,OAAO,KAAK,MAAK,OAAQ,GAAE,IAHZ,cACX,aAAa,GAAE,0DACf,KACqC,KAAI,KAAM,MAAK;EAC1D,CAAC;EAIH,YAAY,UAAU,QAAQ,YAAY,MAAM;EAGhD,YAAY,UAAU,QACpB,uBACA,6BAA6B;EAG/B,YAAY,UAAU,QACpB,iCACA,IAAI;EAIN,YAAY,KAAK,YAAY,SAAS;EAKtC,YAAY,UAAU,QACpB,8BACA,wOAEwD;EAE1D,YAAY,UAAU,QACpB,2BACA,0MAEwD;EAI1D,YAAY,UAAU,QACpB,mBACA,aAAa;EAGf,YAAY,UAAU,QACpB,gCACA,aAAa;EAKf,YAAY,UAAU,QACpB,oBACA,qBAAqB;EAEvB,YAAY,UAAU,QACpB,oCACC,QAAQ,OAAO,IAAI,QAAQ,aAAa,EAAE,EAAC,MAAO;EAIrD,YAAY,UAAU,QACpB,cACA,iBAAiB;EAInB,YAAY,UAAU,QACpB,sBACA,8BAA8B;EAEhC,YAAY,UAAU,QAAQ,kBAAkB,qBAAqB;EACrE,YAAY,UAAU,QAAQ,cAAc,aAAa;EAGzD,YAAY,UAAU,QAAQ,cAAc,eAAe;EAK3D,YAAY,UAAU,QACpB,8BACC,QAAQ,KAAa,QAAe;GACnC,IAAI,CAAC,eAAe,GAAG,GAAG,OAAO;GAEjC,OAAO,aADa,WAAW,CAAC,IAAI,WAAW,MAAM,IAAI,GAAG,QAAO,GAAI,QAAQ,IAChD,SAAU,IAAG;EAC9C,CAAC;EAIH,YAAY,UAAU,QACpB,6BACC,QAAQ,MAAc,SAAgB;GACrC,IAAI,CAAC,cAAc,IAAI,GAAG,OAAO;GAIjC,OAAO,YAHc,WAAW,CAAC,KAAK,WAAW,MAAM,KAAK,CAAC,KAAK,WAAW,GAAG,IAC5E,GAAG,QAAO,GAAI,SACd,KAC2B,YAAa,WAAU,mBAAoB,KAAI;EAChF,CAAC;EAMH,YAAY,KAAK,SAAS,WAAW,UAAU;EAG/C,YAAY,UACT,MAAM,MAAM,CAAC,CACb,KAAK,UAAS;GACb,MAAM,UAAU,MAAM,KAAI;GAC1B,IAAI,CAAC,SAAS,OAAO;GACrB,IACE,QAAQ,WAAW,IAAI,KACvB,QAAQ,WAAW,KAAK,KACxB,QAAQ,WAAW,KAAK,KACxB,QAAQ,WAAW,aAAa,KAChC,QAAQ,WAAW,KAAK,KACxB,QAAQ,WAAW,QAAQ,KAC3B,QAAQ,WAAW,MAAM,KACzB,QAAQ,WAAW,UAAU,GAE7B,OAAO;GAET,OAAO,MAAM,QAAQ,QAAQ,OAAO,MAAM,EAAC;EAC7C,CAAC,CAAC,CACD,KAAK,IAAI;EAGZ,IAAI,KAAK,gBAAe,GAAI,CAG5B,OACE,YAAY,UAAU,QACpB,IAAI,OAAO,kBAAkB,QAAQ,GAAG,IACvC,QAAQ,QAAe;GACtB,MAAM,QAAQ,OAAO,SAAS,KAAK,EAAE;GACrC,IAAI,CAAC,OAAO,OAAO;GACnB,OAAO,8BAA8B,MAAM,KAAI,IAAK,KAAK,WAAW,MAAM,IAAI,EAAC;EACjF,CAAC;EAIL,OAAO;GAAE,MAAM;GAAW;GAAQ;EAAQ;CAC5C;CAEQ,YAAY,MAAY;EAC9B,OAAO,KAAK,QACV,iDACC,QAAQ,QAAgB,WAAmB,SAAgB;GAG1D,MAAM,SAAS,KAAK,cAAc,SAAS,CAAC,CAAC,KAAK,SAAQ;IACxD,MAAM,OAAO,KAAK,WAAW,GAAG;IAChC,MAAM,QAAQ,KAAK,SAAS,GAAG;IAC/B,IAAI,QAAQ,OAAO,OAAO;IAC1B,IAAI,OAAO,OAAO;IAClB,IAAI,MAAM,OAAO;IACjB,OAAO;GACT,CAAC;GACD,MAAM,cAAc,QAClB,OAAO,OAAO,8BAA8B,OAAO,KAAI,KAAM;GAkB/D,OACE,qFAjBc,KAAK,cAAc,MAAM,CAAC,CACvC,KAAK,MAAM,MAAM,kBAAkB,WAAW,CAAC,EAAC,GAAI,KAAK,KAAI,EAAE,MAAO,CAAC,CACvE,KAAK,EAgB0C,EAAO,sBAd5C,KACV,KAAI,CAAE,CACN,MAAM,IAAI,CAAC,CACX,KAAK,QAAe;IAInB,OAAO,OAHO,KAAK,cAAc,GAAG,CAAC,CAClC,KAAK,MAAM,MAAM,MAAM,WAAW,CAAC,EAAC,GAAI,KAAK,KAAI,EAAE,MAAO,CAAC,CAC3D,KAAK,EACM,EAAK;GACrB,CAAC,CAAC,CACD,KAAK,EAKwE,EAAI;EAGtF,CAAC;CAEL;CAGQ,cAAc,KAAW;EAC/B,OAAO,IACJ,KAAI,CAAE,CACN,QAAQ,OAAO,EAAE,CAAC,CAClB,QAAQ,OAAO,EAAE,CAAC,CAClB,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,KAAI,CAAE;CAC9B;CAEQ,WAAW,MAAY;EAC7B,OAAO,KACJ,QAAQ,MAAM,OAAO,CAAC,CACtB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,QAAQ;CAC3B;CAQQ,SAAS,MAAc,YAAkB;EAG/C,MAAM,UAAU,IAAI,OAAO,GAAG,iDAAI,OAAM,GAAI,iDAAM,UAAU,GAAG;EAE/D,IAAI,QAAQ;EACZ,OAAO,KACJ,MAAM,YAAY,CAAC,CACnB,KAAK,YAAW;GACf,IAAI,QAAQ,WAAW,GAAG,GAAG;IAC3B,IAAI,mBAAmB,KAAK,OAAO,GAAG;SACjC,IAAI,iBAAiB,KAAK,OAAO,GAAG,QAAQ,KAAK,IAAI,GAAG,QAAQ,CAAC;IACtE,OAAO;GACT;GACA,IAAI,QAAQ,KAAK,CAAC,SAAS,OAAO;GAClC,OAAO,QAAQ,QAAQ,UAAU,UAAS;IAOxC,OAAO,YANS,MAAM,SAAS,GAAG,KAAK,CAAC,yBAAyB,KAAK,KAAK,IAEvE,UAAU,UACV,MAAM,WAAW,MAAM,IACrB,WAAW,UACX,MACiB,YAAa,WAAU,mBAAoB,MAAK;GACzE,CAAC;EACH,CAAC,CAAC,CACD,KAAK,EAAE;CACZ;CAEU,cAAc,OAAa;EACnC,OAAO,KAAK,YAAW,CAAE,CAAC,UAAU;CACtC;CAEU,gBAAgB,OAAa;EACrC,OAAO,KAAK,cAAa,CAAE,CAAC,UAAU;CACxC;CAEQ,MAAM,mBAAmB,OAAmB;EAClD,MAAM,eAAe,MAAM,QAAQ,SAAS,KAAK,SAAS,aAAa,CAAC,CAAC,KAAK,IAAI;EAClF,KAAK,YAAY,IAAI,CAAA,CAAE;EACvB,KAAK,cAAc,IAAI,CAAA,CAAE;EAEzB,IAAI,aAAa,WAAW,GAC1B;EAGF,MAAM,QAAQ,EAAE,KAAK;EAErB,IAAI;GACF,MAAM,aAAa,MAAM,KAAK,cAAa;GAC3C,IAAI,UAAU,KAAK,kBAAkB;GAErC,MAAM,WAAqC,CAAA;GAC3C,MAAM,aAAqC,CAAA;GAE3C,KAAK,MAAM,QAAQ,cACjB,IAAI;IAIF,MAAM,WAAW,iBAAiB,KAAK,WAAU,GAAI,MAAK,GAAI,KAAK;IACnE,MAAM,EAAE,QAAQ,MAAM,WAAW,OAAO,UAAU,KAAK,IAAK;IAC5D,IAAI,UAAU,KAAK,kBAAkB;IAKrC,SAAS,KAAK,SAAS,KAAK,UAAU,wBAAwB,GAAG;GACnE,QAAQ;IACN,WAAW,KAAK,SAAS,KAAK,kBAAiB;GACjD;GAGF,IAAI,UAAU,KAAK,kBAAkB;GACrC,KAAK,YAAY,IAAI,QAAQ;GAC7B,KAAK,cAAc,IAAI,UAAU;EACnC,QAAQ;GACN,IAAI,UAAU,KAAK,kBAAkB;GACrC,MAAM,iBAAyC,CAAA;GAC/C,KAAK,MAAM,QAAQ,cACjB,eAAe,KAAK,SAAS,KAAK,wBAAuB;GAE3D,KAAK,cAAc,IAAI,cAAc;EACvC;CACF;CAEQ,MAAM,gBAAa;EACzB,IAAI,CAAC,KAAK,mBACR,KAAK,oBAAoB,OAAO,UAAU,CAAC,MAAM,WAAU;GACzD,MAAM,MAAM,OAAO,WAAW;GAC9B,IAAI,CAAC,KAAK,oBAAoB;IAC5B,IAAI,WAAW;KACb,aAAa;KACb,eAAe;IAChB,CAAA;IACD,KAAK,qBAAqB;GAC5B;GACA,OAAO;EACT,CAAC;EAEH,OAAO,KAAK;CACd;CA/uBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAiB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAjB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAiB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,KAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,oBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,kBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,uBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,qBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,oBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,wBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,yBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,qBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,mBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,yBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA;GAAA,WAAA;GAAA,UAAA;GAAA,oBAAA;EAAA;EAAA,UAAA;EAAA,UCnG9B;EAkCA,QAAA,CAAA,whPAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MD2DY;GAAkB,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAMjB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAiB,YAAA,CAAA;EAT7B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAa,YACX;GAAI,SACP,CAAC,kBAAkB;GAAC,iBAGZ,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,UAAA;GAAA,QAAA,CAAA,whPAAA;EAAA,CAAA"}
@@ -9,6 +9,8 @@ export * from "@lifecorner/angular-ui-kit/icon";
9
9
  export * from "@lifecorner/angular-ui-kit/icon-names";
10
10
  export * from "@lifecorner/angular-ui-kit/icons";
11
11
  export * from "@lifecorner/angular-ui-kit/logo";
12
+ export * from "@lifecorner/angular-ui-kit/character";
13
+ export * from "@lifecorner/angular-ui-kit/character-svg";
12
14
  export * from "@lifecorner/angular-ui-kit/menu";
13
15
  export * from "@lifecorner/angular-ui-kit/input";
14
16
  export * from "@lifecorner/angular-ui-kit/radio";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifecorner/angular-ui-kit",
3
- "version": "3.6.1",
3
+ "version": "3.8.0",
4
4
  "description": "LifeCorner Design System – Angular UI component library",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,6 +48,9 @@
48
48
  "./icon-names.json": {
49
49
  "default": "./src/assets/icon-names.json"
50
50
  },
51
+ "./assets/character/*": {
52
+ "default": "./src/assets/character/*"
53
+ },
51
54
  "./package.json": {
52
55
  "default": "./package.json"
53
56
  },
@@ -107,6 +110,14 @@
107
110
  "types": "./types/lifecorner-angular-ui-kit-card.d.ts",
108
111
  "default": "./fesm2022/lifecorner-angular-ui-kit-card.mjs"
109
112
  },
113
+ "./character": {
114
+ "types": "./types/lifecorner-angular-ui-kit-character.d.ts",
115
+ "default": "./fesm2022/lifecorner-angular-ui-kit-character.mjs"
116
+ },
117
+ "./character-svg": {
118
+ "types": "./types/lifecorner-angular-ui-kit-character-svg.d.ts",
119
+ "default": "./fesm2022/lifecorner-angular-ui-kit-character-svg.mjs"
120
+ },
110
121
  "./chat": {
111
122
  "types": "./types/lifecorner-angular-ui-kit-chat.d.ts",
112
123
  "default": "./fesm2022/lifecorner-angular-ui-kit-chat.mjs"
@@ -31,3 +31,7 @@ The words "Life" and "Corner" are **outlined to paths**, so the lockup looks the
31
31
  | Layout | x 118, baseline 74, size 72, letter-spacing −1.4, kerning on, no ligatures (as a browser sets it) |
32
32
 
33
33
  The font file itself is not shipped. Outlined glyphs in a logo are artwork made with the font, which the OFL allows.
34
+
35
+ ## Character (`character/`, 3.8)
36
+
37
+ Edu's 14 poses, `<pose>-light.svg` and `<pose>-dark.svg` (100 × 100, about 1.5 KB each), for `lc-character` (`@lifecorner/angular-ui-kit/character`, loaded from `LC_CHARACTER_BASE_PATH`, default `LC_LOGO_BASE_PATH` + `/character`), `<img>` or `<picture>`. They resolve as `@lifecorner/angular-ui-kit/assets/character/<file>`; the same drawings are strings in `@lifecorner/angular-ui-kit/character-svg`. Source: the approved drawings in life-os `docs/brand/character/`, copied unchanged; the dark files of welcomes-member, resting, in-his-corner, comforts, all-done, connects, discovered, thinking, connection-paused and see-you are made by its dark rule (`scripts/generate-character-assets.mjs`).
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C73 57 78 64 83 68"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><path d="M37.5 38.5 Q42 35 46.5 38.5" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58.6" cy="38.4" r="2.3" fill="#0c1722"></circle><path d="M44 45 Q50 52 56 45" fill="none" stroke="#0c1722" stroke-width="2.2" stroke-linecap="round"></path><g fill="#84c7d4" stroke="#84c7d4" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="85" cy="70" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C73 57 78 64 83 68"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><path d="M37.5 38.5 Q42 35 46.5 38.5" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58.6" cy="38.4" r="2.3" fill="#0c1722"></circle><path d="M44 45 Q50 52 56 45" fill="none" stroke="#0c1722" stroke-width="2.2" stroke-linecap="round"></path><g fill="#84c7d4" stroke="#1a6a79" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="85" cy="70" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C73 57 78 64 83 68"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="42" cy="39.8" r="2.3" fill="#0c1722"></circle><circle cx="58" cy="39.8" r="2.3" fill="#0c1722"></circle><path d="M45 45.5 Q50 49 55 45.5" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="85" cy="70" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C73 57 78 64 83 68"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="42" cy="39.8" r="2.3" fill="#0c1722"></circle><circle cx="58" cy="39.8" r="2.3" fill="#0c1722"></circle><path d="M45 45.5 Q50 49 55 45.5" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="85" cy="70" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C75 54 83 55 89 52"></path></g><g transform="rotate(-8 50 45)"><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="42" cy="39.8" r="2.3" fill="#0c1722"></circle><circle cx="58" cy="39.8" r="2.3" fill="#0c1722"></circle><g fill="none" stroke="#0c1722" stroke-width="1.8" stroke-linecap="round"><path d="M38 31 L45.5 32.5"></path><path d="M54.5 32.5 L62 31"></path><path d="M46 47.5 Q50 46.3 54 47.5"></path></g></g><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="91" cy="51" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C75 54 83 55 89 52"></path></g><g transform="rotate(-8 50 45)"><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="42" cy="39.8" r="2.3" fill="#0c1722"></circle><circle cx="58" cy="39.8" r="2.3" fill="#0c1722"></circle><g fill="none" stroke="#0c1722" stroke-width="1.8" stroke-linecap="round"><path d="M38 31 L45.5 32.5"></path><path d="M54.5 32.5 L62 31"></path><path d="M46 47.5 Q50 46.3 54 47.5"></path></g></g><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="91" cy="51" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M76.8 54.9 A10 10 0 0 1 86.7 72.2" fill="none" stroke="#4f9aaa" stroke-width="2" stroke-linecap="round"></path><path d="M85.2 70.1 L84.1 74.0 L88.2 74.3 Z" fill="#4f9aaa"></path><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C71 56 74 60 77 62"></path></g><g transform="rotate(4 50 45)"><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="44.0" cy="40.6" r="2.3" fill="#0c1722"></circle><circle cx="60.0" cy="40.6" r="2.3" fill="#0c1722"></circle><path d="M37.5 30.5 Q41.5 29.2 45.5 30.5" fill="none" stroke="#0c1722" stroke-width="1.8" stroke-linecap="round"></path><path d="M54.5 28.6 Q58.5 25.2 62.5 27.6" fill="none" stroke="#0c1722" stroke-width="1.8" stroke-linecap="round"></path><path d="M46.5 47 Q50 48.8 53.8 46.4" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path></g><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle></g><circle cx="81" cy="64" r="5.5" fill="#0a121b" stroke="#84c7d4" stroke-width="3" stroke-dasharray="3.2 2.6"></circle></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M76.8 54.9 A10 10 0 0 1 86.7 72.2" fill="none" stroke="#84c7d4" stroke-width="2" stroke-linecap="round"></path><path d="M85.2 70.1 L84.1 74.0 L88.2 74.3 Z" fill="#84c7d4"></path><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C71 56 74 60 77 62"></path></g><g transform="rotate(4 50 45)"><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="44.0" cy="40.6" r="2.3" fill="#0c1722"></circle><circle cx="60.0" cy="40.6" r="2.3" fill="#0c1722"></circle><path d="M37.5 30.5 Q41.5 29.2 45.5 30.5" fill="none" stroke="#0c1722" stroke-width="1.8" stroke-linecap="round"></path><path d="M54.5 28.6 Q58.5 25.2 62.5 27.6" fill="none" stroke="#0c1722" stroke-width="1.8" stroke-linecap="round"></path><path d="M46.5 47 Q50 48.8 53.8 46.4" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path></g><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle></g><circle cx="81" cy="64" r="5.5" fill="#ffffff" stroke="#1a6a79" stroke-width="3" stroke-dasharray="3.2 2.6"></circle></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M8 56 Q20 94 50 94 Q80 94 92 56" fill="none" stroke="#4f9aaa" stroke-width="1.6" stroke-dasharray="3 3"></path><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M33 48 C24 52 16 56 9 56"></path><path d="M42 56 C38 66 32 74 24 80"></path><path d="M58 56 C62 66 68 74 76 80"></path><path d="M67 48 C76 52 84 56 91 56"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="42" cy="39.8" r="2.3" fill="#0c1722"></circle><circle cx="58" cy="39.8" r="2.3" fill="#0c1722"></circle><path d="M47 46.5 L53 46.5" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path><g fill="#84c7d4" stroke="#84c7d4" stroke-width="4"><circle cx="8" cy="56" r="5.5"></circle><circle cx="23" cy="81" r="5.5"></circle><circle cx="77" cy="81" r="5.5"></circle><circle cx="92" cy="56" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M8 56 Q20 94 50 94 Q80 94 92 56" fill="none" stroke="#84c7d4" stroke-width="1.6" stroke-dasharray="3 3"></path><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M33 48 C24 52 16 56 9 56"></path><path d="M42 56 C38 66 32 74 24 80"></path><path d="M58 56 C62 66 68 74 76 80"></path><path d="M67 48 C76 52 84 56 91 56"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="42" cy="39.8" r="2.3" fill="#0c1722"></circle><circle cx="58" cy="39.8" r="2.3" fill="#0c1722"></circle><path d="M47 46.5 L53 46.5" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path><g fill="#84c7d4" stroke="#1a6a79" stroke-width="4"><circle cx="8" cy="56" r="5.5"></circle><circle cx="23" cy="81" r="5.5"></circle><circle cx="77" cy="81" r="5.5"></circle><circle cx="92" cy="56" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M88 3 L89.7 8.3 L95 10 L89.7 11.7 L88 17 L86.3 11.7 L81 10 L86.3 8.3 Z" fill="#e8b04a"></path><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 46 C73 39 77 31 78 24"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="42" cy="38" r="5" fill="#ffffff"></circle><circle cx="58" cy="38" r="5" fill="#ffffff"></circle><circle cx="43.4" cy="36.6" r="2.3" fill="#0c1722"></circle><circle cx="59.4" cy="36.6" r="2.3" fill="#0c1722"></circle><g fill="none" stroke="#0c1722" stroke-width="1.8" stroke-linecap="round"><path d="M37 30 Q41 27.5 45 29.5"></path><path d="M55 29.5 Q59 27.5 63 30"></path></g><circle cx="50" cy="47" r="1.9" fill="#0c1722"></circle><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle></g><circle cx="78" cy="21" r="5.5" fill="#e8b04a" stroke="#84c7d4" stroke-width="4"></circle></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M88 3 L89.7 8.3 L95 10 L89.7 11.7 L88 17 L86.3 11.7 L81 10 L86.3 8.3 Z" fill="#e8b04a"></path><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 46 C73 39 77 31 78 24"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="42" cy="38" r="5" fill="#ffffff"></circle><circle cx="58" cy="38" r="5" fill="#ffffff"></circle><circle cx="43.4" cy="36.6" r="2.3" fill="#0c1722"></circle><circle cx="59.4" cy="36.6" r="2.3" fill="#0c1722"></circle><g fill="none" stroke="#0c1722" stroke-width="1.8" stroke-linecap="round"><path d="M37 30 Q41 27.5 45 29.5"></path><path d="M55 29.5 Q59 27.5 63 30"></path></g><circle cx="50" cy="47" r="1.9" fill="#0c1722"></circle><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle></g><circle cx="78" cy="21" r="5.5" fill="#e8b04a" stroke="#1a6a79" stroke-width="4"></circle></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M9 29 L10.5 32.5 L14 34 L10.5 35.5 L9 39 L7.5 35.5 L4 34 L7.5 32.5 Z" fill="#e8b04a"></path><path d="M91 29 L92.5 32.5 L96 34 L92.5 35.5 L91 39 L89.5 35.5 L86 34 L89.5 32.5 Z" fill="#e8b04a"></path><path d="M50 2.5 L51.4 5.7 L54.5 7 L51.4 8.3 L50 11.5 L48.6 8.3 L45.5 7 L48.6 5.7 Z" fill="#e8b04a"></path><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 46 C26 38 21 29 19 19"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 46 C74 38 79 29 81 19"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><path d="M37.5 38 Q42 33.5 46.5 38" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M53.5 38 Q58 33.5 62.5 38" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M44.5 45 Q50 53 55.5 45 Q50 47 44.5 45 Z" fill="#0c1722"></path><g fill="#84c7d4" stroke="#84c7d4" stroke-width="4"><circle cx="18" cy="16" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="82" cy="16" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M9 29 L10.5 32.5 L14 34 L10.5 35.5 L9 39 L7.5 35.5 L4 34 L7.5 32.5 Z" fill="#e8b04a"></path><path d="M91 29 L92.5 32.5 L96 34 L92.5 35.5 L91 39 L89.5 35.5 L86 34 L89.5 32.5 Z" fill="#e8b04a"></path><path d="M50 2.5 L51.4 5.7 L54.5 7 L51.4 8.3 L50 11.5 L48.6 8.3 L45.5 7 L48.6 5.7 Z" fill="#e8b04a"></path><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 46 C26 38 21 29 19 19"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 46 C74 38 79 29 81 19"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><path d="M37.5 38 Q42 33.5 46.5 38" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M53.5 38 Q58 33.5 62.5 38" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M44.5 45 Q50 53 55.5 45 Q50 47 44.5 45 Z" fill="#0c1722"></path><g fill="#84c7d4" stroke="#1a6a79" stroke-width="4"><circle cx="18" cy="16" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="82" cy="16" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 46 C74 40 79 31 81 22"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><g fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"><path d="M37.5 38 Q42 33.5 46.5 38"></path><path d="M53.5 38 Q58 33.5 62.5 38"></path><path d="M45 45.5 Q50 50 55 45.5"></path></g><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="82" cy="19" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 46 C74 40 79 31 81 22"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><g fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"><path d="M37.5 38 Q42 33.5 46.5 38"></path><path d="M53.5 38 Q58 33.5 62.5 38"></path><path d="M45 45.5 Q50 50 55 45.5"></path></g><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="82" cy="19" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M6 6 L6 92 L98 92" fill="none" stroke="#2a4250" stroke-width="2" stroke-linecap="round"></path><g transform="translate(3 6)"><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 58 21 70 14 80"></path><path d="M44 56 C42 66 38 74 33 80"></path><path d="M56 56 C59 66 63 74 68 80"></path><path d="M66 50 C74 58 82 72 90 80"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="43.6" cy="38.8" r="2.3" fill="#0c1722"></circle><circle cx="59.6" cy="38.8" r="2.3" fill="#0c1722"></circle><path d="M45 45.5 Q50 49 55 45.5" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="14" cy="80" r="5.5"></circle><circle cx="33" cy="80" r="5.5"></circle><circle cx="68" cy="80" r="5.5"></circle><circle cx="90" cy="80" r="5.5"></circle></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M6 6 L6 92 L98 92" fill="none" stroke="#cfe0e4" stroke-width="2" stroke-linecap="round"></path><g transform="translate(3 6)"><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 58 21 70 14 80"></path><path d="M44 56 C42 66 38 74 33 80"></path><path d="M56 56 C59 66 63 74 68 80"></path><path d="M66 50 C74 58 82 72 90 80"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="43.6" cy="38.8" r="2.3" fill="#0c1722"></circle><circle cx="59.6" cy="38.8" r="2.3" fill="#0c1722"></circle><path d="M45 45.5 Q50 49 55 45.5" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="14" cy="80" r="5.5"></circle><circle cx="33" cy="80" r="5.5"></circle><circle cx="68" cy="80" r="5.5"></circle><circle cx="90" cy="80" r="5.5"></circle></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 58 24 66 22 74"></path><path d="M44 56 C42 65 40 72 38 79"></path><path d="M56 56 C58 65 60 72 62 79"></path><path d="M66 50 C73 58 76 66 78 74"></path></g><g transform="rotate(-5 50 45)"><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><path d="M37.5 37.5 Q42 41.5 46.5 37.5" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M53.5 37.5 Q58 41.5 62.5 37.5" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M46 46 Q50 48.6 54 46" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path></g><g fill="#84c7d4" stroke="#84c7d4" stroke-width="4"><circle cx="22" cy="76" r="5.5"></circle><circle cx="38" cy="81" r="5.5"></circle><circle cx="62" cy="81" r="5.5"></circle><circle cx="78" cy="76" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 58 24 66 22 74"></path><path d="M44 56 C42 65 40 72 38 79"></path><path d="M56 56 C58 65 60 72 62 79"></path><path d="M66 50 C73 58 76 66 78 74"></path></g><g transform="rotate(-5 50 45)"><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><path d="M37.5 37.5 Q42 41.5 46.5 37.5" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M53.5 37.5 Q58 41.5 62.5 37.5" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M46 46 Q50 48.6 54 46" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path></g><g fill="#84c7d4" stroke="#1a6a79" stroke-width="4"><circle cx="22" cy="76" r="5.5"></circle><circle cx="38" cy="81" r="5.5"></circle><circle cx="62" cy="81" r="5.5"></circle><circle cx="78" cy="76" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#4f9aaa" stroke-width="1.8" stroke-linecap="round"><path d="M7 13 Q4.5 19 7 25"></path><path d="M30 12 Q32.5 18 30 24"></path></g><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 46 C26 40 21 31 19 22"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C73 57 78 64 83 68"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><path d="M37.5 38 Q42 33.5 46.5 38" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M53.5 38 Q58 33.5 62.5 38" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M45 45.5 Q50 50 55 45.5" fill="none" stroke="#0c1722" stroke-width="2.2" stroke-linecap="round"></path><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="18" cy="19" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="85" cy="70" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="none" stroke="#84c7d4" stroke-width="1.8" stroke-linecap="round"><path d="M7 13 Q4.5 19 7 25"></path><path d="M30 12 Q32.5 18 30 24"></path></g><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 46 C26 40 21 31 19 22"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C73 57 78 64 83 68"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><path d="M37.5 38 Q42 33.5 46.5 38" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M53.5 38 Q58 33.5 62.5 38" fill="none" stroke="#0c1722" stroke-width="2.4" stroke-linecap="round"></path><path d="M45 45.5 Q50 50 55 45.5" fill="none" stroke="#0c1722" stroke-width="2.2" stroke-linecap="round"></path><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="18" cy="19" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="85" cy="70" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M78 30 Q86 27 94 29" fill="none" stroke="#4f9aaa" stroke-width="1.6" stroke-dasharray="3 3" stroke-linecap="round"></path><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M67 45 C76 43 84 41 91 39"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="44.2" cy="38.6" r="2.3" fill="#0c1722"></circle><circle cx="60.2" cy="38.6" r="2.3" fill="#0c1722"></circle><path d="M45 45 Q50.5 49.5 56 45" fill="none" stroke="#0c1722" stroke-width="2.2" stroke-linecap="round"></path><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle></g><g fill="#84c7d4" stroke="#84c7d4" stroke-width="4"><circle cx="92" cy="39" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M78 30 Q86 27 94 29" fill="none" stroke="#84c7d4" stroke-width="1.6" stroke-dasharray="3 3" stroke-linecap="round"></path><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 50 C27 57 22 64 17 68"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M67 45 C76 43 84 41 91 39"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="44.2" cy="38.6" r="2.3" fill="#0c1722"></circle><circle cx="60.2" cy="38.6" r="2.3" fill="#0c1722"></circle><path d="M45 45 Q50.5 49.5 56 45" fill="none" stroke="#0c1722" stroke-width="2.2" stroke-linecap="round"></path><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="15" cy="70" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle></g><g fill="#84c7d4" stroke="#1a6a79" stroke-width="4"><circle cx="92" cy="39" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="#4f9aaa"><circle cx="74" cy="20" r="2"></circle><circle cx="80.5" cy="15" r="2.4"></circle><circle cx="87.5" cy="10.5" r="2.8"></circle></g><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C73 57 78 64 83 68"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="40.8" cy="36.6" r="2.3" fill="#0c1722"></circle><circle cx="56.8" cy="36.6" r="2.3" fill="#0c1722"></circle><path d="M46 47 Q50.5 48.5 54 46" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path><path d="M33 50 C29 57 32 63 38 60" fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"></path><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="40" cy="58" r="4.6"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="85" cy="70" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill="#84c7d4"><circle cx="74" cy="20" r="2"></circle><circle cx="80.5" cy="15" r="2.4"></circle><circle cx="87.5" cy="10.5" r="2.8"></circle></g><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 50 C73 57 78 64 83 68"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="40.8" cy="36.6" r="2.3" fill="#0c1722"></circle><circle cx="56.8" cy="36.6" r="2.3" fill="#0c1722"></circle><path d="M46 47 Q50.5 48.5 54 46" fill="none" stroke="#0c1722" stroke-width="2" stroke-linecap="round"></path><path d="M33 50 C29 57 32 63 38 60" fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"></path><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="40" cy="58" r="4.6"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="85" cy="70" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M50 15.5 C44 11.5 41 8.5 41 6 C41 3.4 43 2.2 45.2 2.2 C47.2 2.2 49 3.4 50 5.2 C51 3.4 52.8 2.2 54.8 2.2 C57 2.2 59 3.4 59 6 C59 8.5 56 11.5 50 15.5 Z" fill="#e8b04a"></path><g fill="none" stroke="#84c7d4" stroke-width="5.5" stroke-linecap="round"><path d="M34 46 C26 44 18 40 11 35"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 46 C74 44 82 40 89 35"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#84c7d4"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.22"></ellipse><circle cx="37" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#1a6a79" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="42.0" cy="38.6" r="2.3" fill="#0c1722"></circle><circle cx="58.0" cy="38.6" r="2.3" fill="#0c1722"></circle><path d="M44 45 Q50 51.5 56 45" fill="none" stroke="#0c1722" stroke-width="2.2" stroke-linecap="round"></path><g fill="#0a121b" stroke="#84c7d4" stroke-width="4"><circle cx="10" cy="34" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="90" cy="34" r="5.5"></circle></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M50 15.5 C44 11.5 41 8.5 41 6 C41 3.4 43 2.2 45.2 2.2 C47.2 2.2 49 3.4 50 5.2 C51 3.4 52.8 2.2 54.8 2.2 C57 2.2 59 3.4 59 6 C59 8.5 56 11.5 50 15.5 Z" fill="#e8b04a"></path><g fill="none" stroke="#1a6a79" stroke-width="5.5" stroke-linecap="round"><path d="M34 46 C26 44 18 40 11 35"></path><path d="M44 56 C42 65 39 73 36 79"></path><path d="M56 56 C58 65 61 73 64 79"></path><path d="M66 46 C74 44 82 40 89 35"></path></g><ellipse cx="50" cy="38" rx="22" ry="20" fill="#1a6a79"></ellipse><ellipse cx="43" cy="29" rx="8" ry="4.5" fill="#ffffff" opacity="0.14"></ellipse><circle cx="37" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="63" cy="46" r="3.2" fill="#84c7d4" opacity="0.55"></circle><circle cx="42" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="58" cy="38" r="4.6" fill="#ffffff"></circle><circle cx="42.0" cy="38.6" r="2.3" fill="#0c1722"></circle><circle cx="58.0" cy="38.6" r="2.3" fill="#0c1722"></circle><path d="M44 45 Q50 51.5 56 45" fill="none" stroke="#0c1722" stroke-width="2.2" stroke-linecap="round"></path><g fill="#ffffff" stroke="#1a6a79" stroke-width="4"><circle cx="10" cy="34" r="5.5"></circle><circle cx="35" cy="82" r="5.5"></circle><circle cx="65" cy="82" r="5.5"></circle><circle cx="90" cy="34" r="5.5"></circle></g></svg>