{"version":3,"file":"nuclo.ssr.mjs","names":["globalScope","camelToKebab","firstUpper","str","code","result","i","ATTR_ESCAPE_RE","TEXT_ESCAPE_RE","escapeHtml","first","text","last","escaped","escapeText","propertyToAttribute","name","c","SERIALIZING_KEY","globalScope$1","shared","runSerializing","previous","fn","isFunction","value","isZeroArityFunction","all","scopeRootsById","set","id","ref","HTML_BOOLEAN_ATTRIBUTES","UNSAFE_ATTRIBUTE_NAME","htmlAttributeNames","htmlAttributeName","mapped","serializeAttribute","styleStr","key","val","serializeAttributes","isPolyfill","el","element","attrs","styleObj","css","svg","valueIsContent","attribute","attr","TAG_CATEGORY","RAW_TEXT_CLOSE_RE","escapeRawText","tagName","formStateAttributes","option","owner","select","input","textOf","children","serializeNode","node","data","isPolyfillShape","category","textarea","polyfillAttrs","attributes","selectValue","own","childNodes","rawContent","rawChildren","child","tc","innerHTML","childrenHtml","renderToString","container","built","error","renderManyToString","inputs","renderToStringWithContainer","content","containerTag","containerAttrs","createState","ENGINE_STATE_KEY","globalScope","cachedState","getState","getCssText","state","out","query","rules"],"sources":["../../src/shared/strings.ts","../../src/shared/serializing.ts","../../src/shared/type-guards.ts","../../src/update/scope.ts","../../src/update/registry.ts","../../src/shared/dom.ts","../../src/ssr/render-to-string.ts","../../src/style/engine.ts"],"sourcesContent":["/**\n * camelCase → kebab-case for CSS property (and attribute) names. A leading\n * capital becomes a leading dash, as vendor prefixes need\n * (WebkitTransition → -webkit-transition). Char-code loop with a\n * no-uppercase fast path: SSR runs this per style declaration.\n */\nexport function camelToKebab(str: string) {\n  let firstUpper = 0;\n  for (; firstUpper < str.length; firstUpper++) {\n    const code = str.charCodeAt(firstUpper);\n    if (code >= 65 && code <= 90) break;\n  }\n  if (firstUpper === str.length) return str;\n\n  let result = str.slice(0, firstUpper);\n  for (let i = firstUpper; i < str.length; i++) {\n    const code = str.charCodeAt(i);\n    // A-Z is 65-90, a-z is 97-122 (difference of 32)\n    result += code >= 65 && code <= 90 ? '-' + String.fromCharCode(code + 32) : str[i];\n  }\n  return result;\n}\n\nconst ATTR_ESCAPE_RE = /[&<>\"']/;\nconst TEXT_ESCAPE_RE = /[&<>]/;\n\n/**\n * Escapes HTML special characters in attribute values (includes \" and ')\n */\nexport function escapeHtml(text: string) {\n  // Fast path: most strings contain no special characters at all.\n  const first = ATTR_ESCAPE_RE.exec(text);\n  if (!first) return text;\n\n  let result = '';\n  let last = 0;\n  for (let i = first.index; i < text.length; i++) {\n    let escaped: string;\n    switch (text.charCodeAt(i)) {\n      case 38: escaped = '&amp;'; break;   // &\n      case 60: escaped = '&lt;'; break;    // <\n      case 62: escaped = '&gt;'; break;    // >\n      case 34: escaped = '&quot;'; break;  // \"\n      case 39: escaped = '&#039;'; break;  // '\n      default: continue;\n    }\n    result += text.slice(last, i) + escaped;\n    last = i + 1;\n  }\n  return result + text.slice(last);\n}\n\n/**\n * Escapes HTML special characters in text node content.\n * Only &, < and > need escaping — quotes are safe inside text nodes.\n */\nexport function escapeText(text: string) {\n  const first = TEXT_ESCAPE_RE.exec(text);\n  if (!first) return text;\n\n  let result = '';\n  let last = 0;\n  for (let i = first.index; i < text.length; i++) {\n    let escaped: string;\n    switch (text.charCodeAt(i)) {\n      case 38: escaped = '&amp;'; break;  // &\n      case 60: escaped = '&lt;'; break;   // <\n      case 62: escaped = '&gt;'; break;   // >\n      default: continue;\n    }\n    result += text.slice(last, i) + escaped;\n    last = i + 1;\n  }\n  return result + text.slice(last);\n}\n\n/**\n * IDL property name → HTML content attribute name, for the camelCase keys a\n * real element reflects (tabIndex → tabindex, htmlFor → for,\n * ariaDescribedBy → aria-describedby, httpEquiv → http-equiv). Lowercase and\n * kebab-case names pass through unchanged. SSR runs this per polyfill attribute.\n */\nexport function propertyToAttribute(name: string) {\n  switch (name) {\n    case 'htmlFor': return 'for';\n    case 'httpEquiv': return 'http-equiv';\n    case 'acceptCharset': return 'accept-charset';\n    case 'defaultValue': return 'value';\n    case 'defaultChecked': return 'checked';\n    case 'defaultSelected': return 'selected';\n  }\n  // ARIA reflection: one dash after \"aria\", the rest lowercased\n  // (ariaDescribedBy → aria-describedby, not aria-described-by).\n  const c = name.charCodeAt(4);\n  if (c >= 65 && c <= 90 && name.startsWith('aria')) return 'aria-' + name.slice(4).toLowerCase();\n  return name.toLowerCase();\n}\n","/**\n * \"A tree is being built for renderToString()\" flag.\n *\n * True while renderToString() runs. Text children must keep their\n * `<!-- text-N -->` markers in this mode so the emitted HTML stays hydratable\n * — even when `isBrowser` is true (SSR under jsdom/happy-dom globals, or an\n * isomorphic worker that provides a DOM) — and nothing built for a throwaway\n * server tree may register itself anywhere (lifecycle hooks, reactive nodes,\n * list/when runtimes, scope roots). A pure client render leaves this false.\n *\n * Kept on globalThis under a Symbol.for key (like style/engine.ts's registry)\n * rather than in a module-level `let`: `nuclo` and `nuclo/ssr` ship as\n * separate bundles, each with its own module scope. renderToString() (in\n * nuclo/ssr) sets the flag and the tag builders (in nuclo) read it — as a\n * module variable those would be two unrelated booleans.\n */\nconst SERIALIZING_KEY = Symbol.for(\"nuclo.serializing.v1\");\ninterface SerializingState { on: boolean; scratch: Map<string, unknown> }\nconst globalScope = globalThis as unknown as Record<symbol, SerializingState | undefined>;\nconst shared = globalScope[SERIALIZING_KEY]\n  ?? (globalScope[SERIALIZING_KEY] = { on: false, scratch: new Map<string, unknown>() });\n\nexport function isSerializing() {\n  return shared.on;\n}\n\n/**\n * Scratch space for the current renderToString() call, emptied when that call\n * ends.\n *\n * A server renders every request from one process, so any registry a feature\n * keeps in module scope is shared by every request and every user at once.\n * Anything a server tree needs to look up mid-build goes here instead: it is\n * reachable for exactly as long as the tree is being built, and nothing from\n * one request is reachable from the next. Keys are namespaced by the feature\n * that owns them.\n */\nexport function serializingScratch(): Map<string, unknown> {\n  return shared.scratch;\n}\n\n/**\n * Runs `fn` with serialization mode enabled, restoring the previous state\n * afterwards (so nested/re-entrant renderToString calls behave correctly).\n */\nexport function runSerializing<T>(fn: () => T): T {\n  const previous = shared.on;\n  shared.on = true;\n  try {\n    return fn();\n  } finally {\n    shared.on = previous;\n    // Only the outermost call clears: a nested renderToString() is still part\n    // of the same request and the tree around it is still being built.\n    if (!shared.on) shared.scratch.clear();\n  }\n}\n","export function isPrimitive(value: unknown): value is Primitive {\n\treturn value === null || (typeof value !== \"object\" && typeof value !== \"function\");\n}\n\nexport function isNode<T>(value: T): value is T & Node {\n\tif (value instanceof Node) return true;\n\tif (typeof value !== 'object' || value === null) return false;\n\t// Recognize polyfill DOM objects (plain objects with a numeric nodeType)\n\treturn typeof (value as { nodeType?: unknown }).nodeType === 'number';\n}\n\nexport function isObject(value: unknown): value is object {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/**\n * Narrows `value` to a callable. The generic `<T extends Function>` is intentionally\n * removed: it allowed callers to assert any specific function signature via the type\n * parameter without a runtime check, which is unsafe. Use explicit casts at call sites\n * when a more specific signature is needed.\n */\nexport function isFunction(value: unknown): value is (...args: unknown[]) => unknown {\n\treturn typeof value === \"function\";\n}\n\nexport function isZeroArityFunction(value: unknown): value is () => unknown {\n\treturn isFunction(value) && value.length === 0;\n}\n","/**\n * scope() – tags an element as the root of a named update scope so\n * `update(\"name\")` can refresh just that part of the page.\n *\n * ── Membership index ─────────────────────────────────────────────────────\n * update(\"id\") must not visit every reactive node on the page and ask\n * `root.contains(node)`: with N scoped components each refreshing itself\n * per frame that is O(N²) per frame. Instead every registry (list markers,\n * when markers, reactive elements, reactive text nodes) is a *kind* here:\n * the global iteration set of each kind lives in `all`, and every member\n * also goes through `pending`. The next update() drains `pending`, walking\n * each new node's ancestors once and adding it to the member set of every\n * scope root above it — nested roots included, so update(\"outer\") reaches\n * what update(\"inner\") does. A scoped update then iterates only the member\n * sets of its roots.\n *\n * Membership is computed from the DOM when the node is first seen by an\n * update(), not at registration: nuclo builds subtrees detached and attaches\n * them in one shot, so at registration time a node's ancestors are not in\n * place yet. A node still detached at that point is left out — the passes\n * prune disconnected nodes from the registries anyway.\n *\n * Member sets hold the registries' own WeakRefs, so they pin nothing: a\n * collected member is swept amortized O(1) on insert, and the passes drop\n * disconnected members as they go.\n */\nimport { isBrowser } from \"../shared/environment\";\nimport { isSerializing } from \"../shared/serializing\";\n\n/** The roots update(...ids) resolved; the passes iterate their members. */\nexport interface UpdateScope {\n  roots: readonly Element[];\n}\n\n/** Member kinds, in the order update() runs them. */\nexport const LIST_KIND = 0;\nexport const WHEN_KIND = 1;\nexport const ELEMENT_KIND = 2;\nexport const TEXT_KIND = 3;\nconst KINDS = 4;\n\ntype Members = Set<WeakRef<Node>>[];\n\n/** Global iteration set per kind — the registries' sets. */\nexport const all: Members = [new Set(), new Set(), new Set(), new Set()];\n/** Members registered since the last update(), per kind, not yet indexed. */\nconst pending: WeakRef<Node>[][] = [[], [], [], []];\nlet membersByRoot = new WeakMap<Element, Members>();\n/** No scope() ever ran: pending is dropped instead of indexed. */\nlet hasRoots = false;\n/** A scope() ran on an element already in the document, whose descendants may already be indexed. */\nlet reindex = false;\n\nconst EMPTY: Set<WeakRef<Node>> = new Set();\n\n/** Registers a member: in its kind's global set now, indexed by the next update(). */\nexport function track(kind: number, ref: WeakRef<Node>) {\n  all[kind].add(ref);\n  pending[kind].push(ref);\n}\n\n/** The members of `kind` under `root` (empty if none were indexed). */\nexport function membersOf(root: Element, kind: number): Set<WeakRef<Node>> {\n  return membersByRoot.get(root)?.[kind] ?? EMPTY;\n}\n\nconst NO_ROOTS: readonly Element[] = [];\n/**\n * Scope roots at or above each node, memoized for one indexPending() call:\n * the members of a list row share every ancestor above the row, so after the\n * first one the others stop at their parent. The DOM does not change during\n * a drain, so the memo is never stale.\n */\nlet rootsMemo: Map<Node, readonly Element[]> | null = null;\nconst unmemoized: Node[] = [];\n\nfunction rootsAtOrAbove(start: Node): readonly Element[] {\n  let node: Node | null = start;\n  let roots: readonly Element[] | undefined;\n  while (node && (roots = rootsMemo!.get(node)) === undefined) {\n    unmemoized.push(node);\n    node = node.parentNode;\n  }\n  if (roots === undefined) roots = NO_ROOTS;\n  for (let i = unmemoized.length - 1; i >= 0; i--) {\n    const n = unmemoized[i];\n    if (n.nodeType === 1 && idsByRoot.has(n as Element)) roots = [n as Element, ...roots];\n    rootsMemo!.set(n, roots);\n  }\n  unmemoized.length = 0;\n  return roots;\n}\n\nfunction indexMember(kind: number, ref: WeakRef<Node>) {\n  const node = ref.deref();\n  if (!node || !node.isConnected) return;\n  // A text node or marker is never a root: start from its parent.\n  const roots = rootsAtOrAbove(node.nodeType === 1 ? node : node.parentNode!);\n  for (let i = 0; i < roots.length; i++) {\n    const root = roots[i];\n    let members = membersByRoot.get(root);\n    if (!members) membersByRoot.set(root, members = [new Set(), new Set(), new Set(), new Set()]);\n    const set = members[kind];\n    set.add(ref);\n    if (set.size >= (sweepAt.get(set) ?? MIN_SWEEP)) sweepCollected(set);\n  }\n}\n\n/** Indexes everything registered since the last update(). Called by update() before its passes. */\nexport function indexPending() {\n  if (reindex) {\n    reindex = false;\n    membersByRoot = new WeakMap();\n    rootsMemo = new Map();\n    for (let k = 0; k < KINDS; k++) {\n      pending[k].length = 0;\n      for (const ref of all[k]) indexMember(k, ref);\n    }\n    rootsMemo = null;\n    return;\n  }\n  for (let k = 0; k < KINDS; k++) {\n    const queue = pending[k];\n    if (queue.length === 0) continue;\n    if (hasRoots) {\n      rootsMemo ??= new Map();\n      for (let i = 0; i < queue.length; i++) indexMember(k, queue[i]);\n    }\n    queue.length = 0;\n  }\n  rootsMemo = null;\n}\n\ntype ScopeId = string;\n\n/**\n * Stores weak references to scope root elements.\n * Using WeakRef prevents memory leaks - elements can be garbage collected when removed from DOM.\n */\nexport const scopeRootsById = new Map<ScopeId, Set<WeakRef<Element>>>();\n\n/**\n * Drops a collected root's entry — and its id, once no root is left under it.\n * Without this, per-row ids (scope(`row-${id}`)) that are never passed to\n * update() again would stay in the map forever: one id, one Set and one dead\n * WeakRef per row the app ever rendered. The sweeps below only bound a single\n * id's set, not the number of ids.\n */\nconst scopeRootFinalizer = typeof FinalizationRegistry !== \"undefined\"\n  ? new FinalizationRegistry<{ id: ScopeId; ref: WeakRef<Element> }>(({ id, ref }) => {\n      const set = scopeRootsById.get(id);\n      if (set?.delete(ref) && set.size === 0) scopeRootsById.delete(id);\n    })\n  : null;\n\nfunction normalizeScopeIds(ids: readonly string[]): ScopeId[] {\n  const normalized: ScopeId[] = [];\n  const seen = new Set<ScopeId>();\n\n  for (const raw of ids) {\n    if (typeof raw !== \"string\") continue;\n    const id = raw.trim();\n    if (!id) continue;\n    if (seen.has(id)) continue;\n    seen.add(id);\n    normalized.push(id);\n  }\n\n  return normalized;\n}\n\n/** Ids each root is already registered under: O(1) dedup instead of a set scan. */\nconst idsByRoot = new WeakMap<Element, Set<ScopeId>>();\n/** Sweep a set of dead refs once it grows past this; doubles with the live size. */\nconst sweepAt = new WeakMap<Set<WeakRef<object>>, number>();\nconst MIN_SWEEP = 64;\n\n/** Drops collected refs at amortized O(1) per add. Detached ones are kept. */\nfunction sweepCollected(set: Set<WeakRef<object>>) {\n  for (const ref of set) if (ref.deref() === undefined) set.delete(ref);\n  sweepAt.set(set, Math.max(MIN_SWEEP, set.size * 2));\n}\n\nfunction addScopeRoot(id: ScopeId, el: Element) {\n  let ids = idsByRoot.get(el);\n  if (!ids) idsByRoot.set(el, ids = new Set());\n  // Re-registration (forceUpdate() reclaim) must not grow the set.\n  if (ids.has(id)) return;\n  ids.add(id);\n  hasRoots = true;\n  // A root that is already in the document (hydration, or scope() applied by\n  // hand) may have descendants the index already filed elsewhere.\n  if (el.isConnected) reindex = true;\n\n  let set = scopeRootsById.get(id);\n  if (!set) scopeRootsById.set(id, set = new Set());\n  const ref = new WeakRef(el);\n  set.add(ref);\n  scopeRootFinalizer?.register(el, { id, ref });\n\n  // Ids that are never passed to update(id) are never pruned by getScopeRoots(),\n  // so list rows scoped with scope(\"row\") would grow the set forever. Detached\n  // roots are kept: they are legitimately unattached while their tree is still\n  // being built.\n  if (set.size >= (sweepAt.get(set) ?? MIN_SWEEP)) sweepCollected(set);\n}\n\nexport function getScopeRoots(ids: readonly string[]): Element[] {\n  const scopeIds = normalizeScopeIds(ids);\n  if (scopeIds.length === 0) return [];\n\n  const roots = new Set<Element>();\n\n  for (const id of scopeIds) {\n    const set = scopeRootsById.get(id);\n    if (!set) continue;\n\n    for (const ref of set) {\n      const el = ref.deref();\n      // Collected or disconnected: drop it (deleting while iterating a Set is safe).\n      if (el === undefined) set.delete(ref);\n      else if (!el.isConnected) {\n        set.delete(ref);\n        idsByRoot.get(el)?.delete(id); // let scope() re-register it if it comes back\n      } else roots.add(el);\n    }\n\n    if (set.size === 0) scopeRootsById.delete(id);\n  }\n\n  // A root inside another root in the set is covered by it: its members are\n  // a subset of the outer root's, so visiting both would run them twice.\n  if (roots.size > 1) {\n    for (const root of roots) {\n      for (let a = root.parentNode; a; a = a.parentNode) {\n        if (roots.has(a as Element)) { roots.delete(root); break; }\n      }\n    }\n  }\n\n  return Array.from(roots);\n}\n\nexport function scope<TTagName extends ElementTagName = ElementTagName>(\n  ...ids: string[]\n): NodeModFn<TTagName> {\n  const scopeIds = normalizeScopeIds(ids);\n\n  return function(parent: ExpandedElement<TTagName>) {\n    if (!isBrowser || isSerializing()) return;\n    if (!(parent instanceof Element)) return;\n    for (const id of scopeIds) addScopeRoot(id, parent);\n  };\n}\n","/**\n * Shared types and registries for reactive nodes.\n * Single source of truth — imported by reactive-text.ts and reactive-attributes.ts.\n *\n * Registry structure: the iteration sets only hold WeakRefs; the entries live\n * in WeakMaps keyed by the node, one flat object per node (its WeakRef plus\n * its reactive state). Storing entries in a strongly-held map would pin the\n * node in memory forever; with WeakMap storage the whole entry is collectible\n * as soon as the node is unreachable.\n *\n * Dead WeakRefs left in the iteration sets are pruned by the notify passes as\n * they iterate (deref() === undefined, or node disconnected). Registration is\n * the hottest path in list-heavy renders (one text node + often one element\n * per row), so no per-node FinalizationRegistry is used here: its register()\n * cost per row outweighs keeping the sets tidy between updates, and a stale\n * husk is just an empty WeakRef until the next update() sweeps it.\n */\n\nimport { all, track, ELEMENT_KIND, TEXT_KIND } from \"./scope\";\n\nexport type TextResolver = () => unknown;\n\n/**\n * A reactive text node. Resolver results render with the DSL's text\n * semantics: nullish and non-primitive values render as \"\".\n */\nexport interface ReactiveTextEntry {\n  ref: WeakRef<Text>;\n  resolver: TextResolver;\n  lastValue: string;\n}\n\nexport type AttributeResolver = () => unknown;\n\n/**\n * Writes a resolved value to the element. A shared module-level function\n * (not a per-registration closure), so a reactive attribute costs no closure.\n * Returning `false` reports that the write did not take (a <select> value\n * whose <option> does not exist yet): the value is then not cached, so the\n * next update() applies it again.\n */\nexport type AttributeApplier = (element: Element, key: string, value: unknown) => boolean | void;\n\nexport interface AttributeResolverRecord {\n  key: string;\n  resolver: AttributeResolver;\n  apply: AttributeApplier;\n  lastValue: unknown;\n}\n\nexport interface ReactiveElementEntry {\n  ref: WeakRef<Element>;\n  /**\n   * Flat keyed array rather than a Map: elements almost always carry one or\n   * two reactive attributes, so linear key lookup on registration beats a Map\n   * allocation per element, and the per-update flush iterates with a plain\n   * index loop (no iterator/destructuring allocations).\n   */\n  attributeResolvers: AttributeResolverRecord[];\n}\n\n/** Iteration set of weak references to reactive text nodes (owned by the scope index). */\nexport const reactiveTextNodes = all[TEXT_KIND] as Set<WeakRef<Text>>;\n\n/** O(1) lookup of a reactive text node's entry. */\nexport const reactiveTextNodesByNode = new WeakMap<Text, ReactiveTextEntry>();\n\n/** Iteration set of weak references to reactive elements (owned by the scope index). */\nexport const reactiveElements = all[ELEMENT_KIND] as Set<WeakRef<Element>>;\n\n/** O(1) lookup of a reactive element's entry. */\nexport const reactiveElementsByNode = new WeakMap<Element, ReactiveElementEntry>();\n\n/**\n * Registers a reactive text node. Re-registration (e.g. repeated hydration)\n * replaces the resolver and last value in place.\n */\nexport function registerReactiveTextNode(node: Text, resolver: TextResolver, lastValue: string) {\n  const existing = reactiveTextNodesByNode.get(node);\n  if (existing) {\n    existing.resolver = resolver;\n    existing.lastValue = lastValue;\n    return;\n  }\n  const ref = new WeakRef(node);\n  track(TEXT_KIND, ref);\n  reactiveTextNodesByNode.set(node, { ref, resolver, lastValue });\n}\n\n/** Returns the element's reactive entry, creating and registering it on first use. */\nexport function registerReactiveElement(element: Element): ReactiveElementEntry {\n  let entry = reactiveElementsByNode.get(element);\n  if (!entry) {\n    const ref = new WeakRef(element);\n    entry = { ref, attributeResolvers: [] };\n    track(ELEMENT_KIND, ref);\n    reactiveElementsByNode.set(element, entry);\n  }\n  return entry;\n}\n\n/** Unregisters a reactive text node (called when nuclo removes it). */\nexport function cleanupReactiveTextNode(node: Text) {\n  const entry = reactiveTextNodesByNode.get(node);\n  if (entry) {\n    reactiveTextNodes.delete(entry.ref);\n    reactiveTextNodesByNode.delete(node);\n  }\n}\n\n/** Unregisters a reactive element (called when nuclo removes it). */\nexport function cleanupReactiveElement(element: Element) {\n  const entry = reactiveElementsByNode.get(element);\n  if (entry) {\n    reactiveElements.delete(entry.ref);\n    reactiveElementsByNode.delete(element);\n  }\n}\n","import { logError } from \"./errors\";\nimport { isBrowser } from \"./environment\";\nimport { removeAllListeners } from \"../element/events\";\nimport { cleanupReactiveTextNode, cleanupReactiveElement } from \"../update/registry\";\nimport { disposeElementLifecycle, hasActiveLifecycleRegistrations } from \"../element/lifecycle\";\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\n/**\n * Recursively removes all event listeners and reactive subscriptions from a node and its descendants\n * to prevent memory leaks when elements are removed from the DOM.\n */\nexport function cleanupNodeTree(node: Node) {\n  // Clean up the node itself based on its type. Order-independent bookkeeping\n  // (listeners, reactive registrations) runs up front; onDestroy is fired\n  // below, after descendants — see the comment there.\n  const type = node.nodeType;\n  if (type === 1 /* ELEMENT_NODE */) {\n    const element = node as HTMLElement;\n    removeAllListeners(element);\n    cleanupReactiveElement(element);\n  } else if (type === 3 /* TEXT_NODE */) {\n    cleanupReactiveTextNode(node as Text);\n    return;\n  } else if (type === 8) {\n    // A region's start marker or a view's anchor carries a disposer.\n    disposeElementLifecycle(node);\n    return;\n  }\n\n  // Recursively clean up all child nodes\n  // Destroy callbacks can mutate the tree: snapshot only when hooks exist. In\n  // a real DOM, ordinary cleanup walks siblings — reading `childNodes` makes\n  // the engine allocate and cache a live NodeList on every visited node, which\n  // dominated bulk list clears. The SSR polyfill keeps children in a plain\n  // array (its sibling getters are O(n)), so it iterates that directly.\n  if (isBrowser && !hasActiveLifecycleRegistrations()) {\n    for (let child = node.firstChild; child !== null; child = child.nextSibling) cleanupNodeTree(child);\n  } else {\n    const children = hasActiveLifecycleRegistrations() ? Array.from(node.childNodes) : node.childNodes;\n    for (let i = 0; i < children.length; i++) cleanupNodeTree(children[i]);\n  }\n\n  // Fire onDestroy last — after every descendant's onDestroy has already run\n  // (post-order) — so a parent's teardown can rely on its children having\n  // already released whatever they were using (e.g. a subscription or timer\n  // the parent owns and children only read from). This mirrors onMount's own\n  // order, which is naturally parent-before-child (registration order is\n  // construction order: a parent's own onMount/{onMount} modifier is applied\n  // before its child factories are even invoked) — so mount is top-down,\n  // destroy is bottom-up, the same LIFO discipline nested resource scopes\n  // always use. No-op unless this exact element registered a callback.\n  if (type === 1) {\n    disposeElementLifecycle(node as HTMLElement);\n  }\n}\n\nexport function safeRemoveChild(child: Node) {\n  if (!child?.parentNode) return false;\n  try {\n    // Clean up all event listeners before removing the element\n    cleanupNodeTree(child);\n    // A destroy callback may already have removed the node.\n    child.parentNode?.removeChild(child);\n    return true;\n  } catch (error) {\n    logError('Failed to remove child node', error);\n    return false;\n  }\n}\n\nfunction safeInsertBefore(parent: Node, newNode: Node, referenceNode: Node | null) {\n  try {\n    parent.insertBefore(newNode, referenceNode);\n    return true;\n  } catch (error) {\n    logError('Failed to insert node before reference', error);\n    return false;\n  }\n}\n\n/**\n * Creates one of nuclo's own marker comments (text / list / when). The extra\n * argument is ignored by a real DOM; the SSR polyfill records it so\n * renderToString() can emit markers without inspecting their text.\n */\nexport function createMarker(text: string): Comment {\n  return (document as unknown as { createComment(data: string, marker: boolean): Comment }).createComment(text, true);\n}\n\nexport function createMarkerPair(prefix: string, id: number): { start: Comment; end: Comment } {\n  return { start: createMarker(`${prefix}-start-${id}`), end: createMarker(`${prefix}-end`) };\n}\n\nexport function clearBetweenMarkers(startMarker: Comment, endMarker: Comment) {\n  let current = startMarker.nextSibling;\n  while (current && current !== endMarker) {\n    const next = current.nextSibling;\n    safeRemoveChild(current);\n    current = next;\n  }\n}\n\nexport function insertNodesBefore(nodes: Node[], referenceNode: Node) {\n  const parent = referenceNode.parentNode;\n  if (parent) {\n    for (let i = 0; i < nodes.length; i++) {\n      safeInsertBefore(parent, nodes[i], referenceNode);\n    }\n  }\n}\n\n/**\n * Creates a scoped DOM insertion context that temporarily redirects appendChild\n * to insertBefore at a specific reference node. This is useful for inserting\n * content at specific positions in the DOM tree.\n *\n * @param host - The host element\n * @param referenceNode - The node before which new nodes should be inserted\n * @param callback - Function to execute with the scoped context\n * @returns The result of the callback\n */\nexport function withScopedInsertion<T, THost extends Element | object>(\n  host: THost,\n  referenceNode: Node,\n  callback: () => T\n): T {\n  const parent = host as unknown as Node & ParentNode;\n  const originalAppend = Object.getOwnPropertyDescriptor(parent, \"appendChild\");\n  const originalInsert = parent.insertBefore;\n\n  // Temporarily override appendChild to insert before the reference node\n  // TypeScript doesn't like this override but it's safe at runtime\n  (parent as unknown as Record<string, unknown>).appendChild = function(node: Node): Node {\n    return originalInsert.call(parent, node, referenceNode);\n  };\n\n  try {\n    return callback();\n  } finally {\n    // Restore original method\n    if (originalAppend) Object.defineProperty(parent, \"appendChild\", originalAppend);\n    else Reflect.deleteProperty(parent, \"appendChild\");\n  }\n}\n","/**\n * Server-Side Rendering (SSR) utilities for Nuclo\n * Renders Nuclo components to HTML strings in Node.js environment\n */\n\nimport { escapeHtml, escapeText, camelToKebab, propertyToAttribute } from '../shared/strings';\nimport { runSerializing } from '../shared/serializing';\nimport { isZeroArityFunction } from '../shared/type-guards';\nimport { SVG_NAMESPACE } from '../shared/dom';\n\ntype RenderableInput =\n  | NodeModFn<ElementTagName>\n  | (() => NodeModFn<ElementTagName>)\n  | Element\n  | Node\n  | null\n  | undefined;\n\n/**\n * HTML boolean attributes — presence means true, absence means false.\n * When the stored value is the string \"true\" or \"false\" (from setAttribute),\n * we must re-apply boolean semantics instead of outputting the raw string.\n */\nconst HTML_BOOLEAN_ATTRIBUTES = new Set([\n  'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls',\n  'default', 'defer', 'disabled', 'disablepictureinpicture',\n  'disableremoteplayback', 'formnovalidate', 'hidden', 'inert', 'ismap',\n  'itemscope', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open',\n  'playsinline', 'readonly', 'required', 'reversed', 'selected',\n]);\n\n/** The fields of the SSR polyfill's NucloElement that the serializer reads. */\ninterface PolyfillElementShape {\n  id?: string;\n  className?: string;\n  namespaceURI?: string;\n  children?: ArrayLike<Node>;\n  _attributes?: Map<string, string>;\n  _style?: Record<string, unknown>;\n}\n\n/**\n * Characters that cannot appear in an HTML attribute name. A name containing\n * one would end the attribute — or the tag — early and let the rest be parsed\n * as markup (`{ 'x><script>…': '' }`), so such attributes are dropped. The\n * browser refuses them too: setAttribute() throws on these names. Only names\n * that come from the app's own objects are checked — a real element's\n * attributes were validated by the DOM, and id/class/style are fixed.\n */\nconst UNSAFE_ATTRIBUTE_NAME = /[\\s\"'<>\\/=\\u0000-\\u001f\\u007f]/;\n\n/**\n * IDL property name → HTML attribute name, or '' for a name that must not be\n * emitted. The same few names repeat on every element, so both the mapping\n * and the safety check are paid once per distinct name. Bounded, so\n * attacker-chosen names cannot grow it without limit.\n */\nconst htmlAttributeNames = new Map<string, string>();\nfunction htmlAttributeName(name: string) {\n  let mapped = htmlAttributeNames.get(name);\n  if (mapped === undefined) {\n    mapped = UNSAFE_ATTRIBUTE_NAME.test(name) ? '' : propertyToAttribute(name);\n    if (htmlAttributeNames.size < 1024) htmlAttributeNames.set(name, mapped);\n  }\n  return mapped;\n}\n\n/**\n * Serializes a DOM attribute value\n */\nfunction serializeAttribute(name: string, value: unknown) {\n  if (value === null || value === undefined || value === false) {\n    return '';\n  }\n\n  if (value === true) {\n    return ` ${name}`;\n  }\n\n  // Boolean attributes stored as strings via setAttribute() need special handling\n  if (HTML_BOOLEAN_ATTRIBUTES.has(name)) {\n    if (value === 'false') return '';\n    if (value === 'true' || value === '' || value === name) return ` ${name}`;\n  }\n\n  // A style object (e.g. renderToStringWithContainer's containerAttrs from\n  // untyped JS): serialize its declarations, skipping nullish/empty values.\n  if (name === 'style' && typeof value === 'object') {\n    let styleStr = '';\n    for (const key in value as Record<string, unknown>) {\n      const val = (value as Record<string, unknown>)[key];\n      if (val == null || val === '') continue;\n      styleStr += (styleStr ? ' ' : '') + camelToKebab(key) + ': ' + String(val) + ';';\n    }\n    return styleStr ? ` style=\"${escapeHtml(styleStr)}\"` : '';\n  }\n\n  return ` ${name}=\"${escapeHtml(String(value))}\"`;\n}\n\n/**\n * Serializes DOM element attributes to HTML string\n *\n * `isPolyfill` is passed in by the caller (serializeNode already computed it\n * to decide whether tagName needs lowercasing) so this doesn't repeat the\n * same `Array.isArray` discriminator check per element.\n */\nfunction serializeAttributes(element: Element, isPolyfill: boolean, valueIsContent: boolean) {\n  let result = '';\n\n  // Handle polyfill elements. NucloElement keeps every child in a plain Array\n  // (`children`), which a real DOM element never does (HTMLCollection) — this is\n  // the same allocation-free discriminator serializeNode() uses. Reading\n  // `el.attributes` directly would lazily allocate an empty Map for every\n  // element being serialized, so the backing `_attributes` field is read instead.\n  if (isPolyfill) {\n    const el = element as unknown as PolyfillElementShape;\n    const attrs = el._attributes;\n\n    // id — may live on the property rather than in the Map\n    if (el.id && !attrs?.has('id')) {\n      result += serializeAttribute('id', el.id);\n    }\n\n    // class — kept on .className, mirrored to Map only when setAttribute is used\n    if (el.className && !attrs?.has('class')) {\n      result += serializeAttribute('class', el.className);\n    }\n\n    // style — lives on the backing _style object (its own properties are the\n    // declarations), not in the attributes Map. Read the field directly (never\n    // `el.style`) so elements that never set a style are not forced to lazily\n    // allocate an empty SSRStyle just to serialize. Empty values (e.g. reactive\n    // styles that resolved to undefined) are skipped.\n    const styleObj = el._style;\n    if (styleObj && !attrs?.has('style')) {\n      let css = '';\n      for (const key in styleObj) {\n        const val = styleObj[key];\n        if (val == null || val === '') continue;\n        css += (css ? ' ' : '') + camelToKebab(key) + ': ' + String(val) + ';';\n      }\n      if (css) result += ` style=\"${escapeHtml(css)}\"`;\n    }\n\n    // All remaining attributes from the Map. HTML elements: a camelCase key\n    // (tabIndex, htmlFor, ariaDescribedBy) is an IDL property name that a real\n    // element would have reflected to its content attribute (tabindex, for,\n    // aria-describedby) — NucloElement has no such properties, so the key was\n    // stored verbatim and is mapped here. SVG elements: browsers keep\n    // setAttribute() names as-is (viewBox, preserveAspectRatio), so do we.\n    if (attrs) {\n      const svg = el.namespaceURI === SVG_NAMESPACE;\n      for (const [name, value] of attrs) {\n        // Rendered as the element's content, not as attributes — see serializeNode().\n        if (name === 'innerHTML' || (valueIsContent && name === 'value')) continue;\n        const attribute = htmlAttributeName(name);\n        if (attribute === '') continue;\n        result += serializeAttribute(svg ? name : attribute, value);\n      }\n    }\n    return result;\n  }\n\n  // Handle browser elements with NamedNodeMap attributes\n  if (element.attributes && element.attributes.length) {\n    for (let i = 0; i < element.attributes.length; i++) {\n      const attr = element.attributes[i];\n      if (attr && attr.name) {\n        result += serializeAttribute(attr.name, attr.value);\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Tag-category lookup, keyed by lowercase tag name. Every element in a tree\n * hits this once, so void + raw-text elements are folded into a single\n * dictionary lookup instead of two separate `Set.has()` probes (one of which\n * would always miss for the other category).\n */\nconst TAG_VOID = 1;\nconst TAG_RAW_TEXT = 2;\nconst TAG_RCDATA = 3;\nconst TAG_SELECT = 4;\nconst TAG_OPTION = 5;\nconst TAG_OPTGROUP = 6;\ntype TagCategory = 1 | 2 | 3 | 4 | 5 | 6;\nconst TAG_CATEGORY: Record<string, TagCategory> = Object.assign(Object.create(null), {\n  // Self-closing HTML tags that don't have closing tags\n  area: TAG_VOID, base: TAG_VOID, br: TAG_VOID, col: TAG_VOID, embed: TAG_VOID,\n  hr: TAG_VOID, img: TAG_VOID, input: TAG_VOID, link: TAG_VOID, meta: TAG_VOID,\n  param: TAG_VOID, source: TAG_VOID, track: TAG_VOID, wbr: TAG_VOID,\n  // Raw-text elements — browsers never decode entities inside them, so their\n  // text content must be emitted verbatim (escaping would corrupt inline\n  // JS/CSS, e.g. `a < b` becoming `a &lt; b` inside a script). A closing-tag\n  // sequence in the content would terminate the element early, so it is\n  // neutralized (see escapeRawText).\n  script: TAG_RAW_TEXT, style: TAG_RAW_TEXT,\n  // Escapable raw-text elements — entities are decoded but markup is not\n  // parsed, so a `<!-- text-N -->` marker would show up literally (in the\n  // page title, in a textarea's value). Text only, escaped.\n  title: TAG_RCDATA, textarea: TAG_RCDATA,\n  // A select's `{ value }` is rendered as its matching option's `selected`.\n  select: TAG_SELECT, option: TAG_OPTION, optgroup: TAG_OPTGROUP,\n});\n\n// One precompiled pattern per raw-text tag. Sharing a /g RegExp across calls\n// (and requests) is safe: String.prototype.replace resets lastIndex first.\nconst RAW_TEXT_CLOSE_RE: Record<string, RegExp> = { script: /<\\/(script)/gi, style: /<\\/(style)/gi };\n\nfunction escapeRawText(tagName: string, text: string) {\n  // \"</script\" (any case) inside a script would close it — break the sequence\n  // the same way JSON serializers do (\"<\\/script\").\n  return text.replace(RAW_TEXT_CLOSE_RE[tagName], '<\\\\/$1');\n}\n\n/**\n * Serializes a DOM node to HTML string\n */\n/**\n * SSR on a real DOM (jsdom, happy-dom): a form control keeps what it shows in\n * properties — `input({ checked })`, `input({ value })` and a select's\n * selected option never become attributes there, so the markup would render\n * an unchecked box, an empty field and the first option. Emit the attributes\n * that make the parsed page show the same state. (The polyfill stores these\n * as attributes already.)\n */\nfunction formStateAttributes(element: Element) {\n  if ((element as HTMLOptionElement).selected !== undefined && element.localName === 'option') {\n    const option = element as HTMLOptionElement;\n    if (!option.selected || option.hasAttribute('selected')) return '';\n    // The first option of a single-choice select is selected by default.\n    // (Parent walk rather than closest(): jsdom caches selector matches on\n    // the document, which would keep the serialized tree alive.)\n    let owner = option.parentNode;\n    if (owner && owner.nodeName === 'OPTGROUP') owner = owner.parentNode;\n    const select = owner && owner.nodeName === 'SELECT' ? owner as HTMLSelectElement : null;\n    return select && !select.multiple && select.options[0] === option ? '' : ' selected';\n  }\n  const input = element as HTMLInputElement;\n  let result = '';\n  if (input.checked === true && !input.hasAttribute('checked')) result += ' checked';\n  const value = input.value;\n  if (typeof value === 'string' && value !== '' && !input.hasAttribute('value')) {\n    // An unset checkbox/radio reports the default value \"on\".\n    const toggle = input.type === 'checkbox' || input.type === 'radio';\n    if (!(toggle && value === 'on') && input.type !== 'file') result += serializeAttribute('value', value);\n  }\n  return result;\n}\n\n/** Concatenated data of an element's direct text children. */\nfunction textOf(element: Element) {\n  const children = (element as unknown as PolyfillElementShape).children;\n  let text = '';\n  if (children) {\n    for (let i = 0; i < children.length; i++) {\n      if (children[i].nodeType === 3) text += children[i].textContent || '';\n    }\n  }\n  return text;\n}\n\n/**\n * `selectValue` is the `{ value }` of the enclosing <select> (polyfill trees\n * only): HTML has no value attribute for a select — the matching <option> is\n * marked `selected` instead.\n */\nfunction serializeNode(node: Node, selectValue?: string) {\n  // Text node — only & < > need escaping; quotes are safe in text content\n  if (node.nodeType === 3) { // Node.TEXT_NODE\n    return escapeText(node.textContent || '');\n  }\n\n  // Comment node. nuclo's own markers (flagged by the polyfill) are emitted\n  // as they are. In a comment the app supplied, \"-->\" (or a leading \">\") would\n  // end it early and expose the rest as markup, so \">\" is written as an\n  // entity — comments do not decode entities.\n  if (node.nodeType === 8) { // Node.COMMENT_NODE\n    const data = node.textContent || '';\n    return (node as { marker?: boolean }).marker === true || !data.includes('>')\n      ? `<!--${data}-->`\n      : `<!--${data.replace(/>/g, '&gt;')}-->`;\n  }\n\n  // Element node\n  if (node.nodeType === 1) { // Node.ELEMENT_NODE\n    const element = node as Element;\n    // Duck-typed polyfill discriminator (also used by serializeAttributes): a\n    // plain Array `children` field never occurs on a real DOM element\n    // (HTMLCollection), only on NucloElement and polyfill-shaped test doubles.\n    const isPolyfillShape = Array.isArray((element as unknown as PolyfillElementShape).children);\n    // Always lowercased: NucloElement already is (toLowerCase() then returns\n    // the same string), browser elements report \"DIV\". An `instanceof\n    // NucloElement` shortcut only ever hit in-repo — the published nuclo/ssr\n    // and nuclo/polyfill bundles hold different copies of the class.\n    const tagName = element.tagName.toLowerCase();\n    let category: TagCategory | undefined = TAG_CATEGORY[tagName];\n    // <title> inside <svg> is an ordinary element — only HTML's is RCDATA.\n    if (category === TAG_RCDATA && element.namespaceURI === SVG_NAMESPACE) category = undefined;\n    const textarea = category === TAG_RCDATA && tagName === 'textarea';\n    const polyfillAttrs = isPolyfillShape ? (element as unknown as PolyfillElementShape)._attributes : undefined;\n    let attributes = serializeAttributes(element, isPolyfillShape, textarea || category === TAG_SELECT);\n    if (category === TAG_SELECT) {\n      const value = polyfillAttrs?.get('value');\n      selectValue = value == null ? undefined : String(value);\n    } else if (selectValue !== undefined) {\n      if (category === TAG_OPTION) {\n        // An option's value defaults to its text.\n        const own = polyfillAttrs?.get('value') ?? textOf(element);\n        if (String(own) === selectValue && !polyfillAttrs?.has('selected')) attributes += ' selected';\n      }\n      if (category !== TAG_OPTGROUP) selectValue = undefined;\n    }\n    if (!isPolyfillShape && (category === TAG_OPTION || tagName === 'input')) {\n      attributes += formStateAttributes(element);\n    }\n\n    // Self-closing tags\n    if (category === TAG_VOID) {\n      return `<${tagName}${attributes} />`;\n    }\n\n    const childNodes: ArrayLike<Node> = isPolyfillShape ? (element as unknown as PolyfillElementShape).children! : element.childNodes;\n\n    // Raw-text elements: emit text verbatim (no entity escaping, no Nuclo\n    // text markers — `<!--` would act as a line comment inside a script).\n    if (category === TAG_RAW_TEXT || category === TAG_RCDATA) {\n      let rawContent = '';\n      const rawChildren = childNodes;\n      if (rawChildren && rawChildren.length > 0) {\n        for (let i = 0; i < rawChildren.length; i++) {\n          const child = rawChildren[i];\n          if (child && child.nodeType === 3) {\n            rawContent += child.textContent || '';\n          }\n        }\n      } else {\n        const tc = (node as { textContent?: unknown }).textContent;\n        if (typeof tc === 'string') rawContent = tc;\n      }\n      if (category === TAG_RAW_TEXT) {\n        return `<${tagName}${attributes}>${escapeRawText(tagName, rawContent)}</${tagName}>`;\n      }\n      if (textarea) {\n        // A textarea's value is its content: `{ value }` (a property on a real\n        // element, a stored attribute on the polyfill) wins over text children.\n        const value = isPolyfillShape\n          ? (element as unknown as PolyfillElementShape)._attributes?.get('value')\n          : (element as HTMLTextAreaElement).value;\n        if (typeof value === 'string' && value) rawContent = value;\n        // The parser drops one newline right after the start tag.\n        if (rawContent.charCodeAt(0) === 10) rawContent = '\\n' + rawContent;\n      }\n      return `<${tagName}${attributes}>${escapeText(rawContent)}</${tagName}>`;\n    }\n\n    // Regular elements with children. `{ innerHTML }` is a property on a real\n    // element (its parsed children are serialized below); the polyfill stores\n    // it as an attribute, emitted here verbatim like the browser would parse it.\n    const innerHTML = polyfillAttrs?.get('innerHTML');\n    let childrenHtml = innerHTML === undefined ? '' : String(innerHTML);\n    if (childNodes && childNodes.length > 0) {\n      for (let i = 0; i < childNodes.length; i++) {\n        const child = childNodes[i];\n        if (child) {\n          childrenHtml += serializeNode(child, selectValue);\n        }\n      }\n    } else if (innerHTML === undefined) {\n      // Fallback: textContent set directly on the element (e.g. el.textContent = \"...\")\n      const tc = (node as { textContent?: unknown }).textContent;\n      if (typeof tc === 'string' && tc) {\n        childrenHtml = escapeText(tc);\n      }\n    }\n\n    return `<${tagName}${attributes}>${childrenHtml}</${tagName}>`;\n  }\n\n  // Document fragment\n  if (node.nodeType === 11) { // Node.DOCUMENT_FRAGMENT_NODE\n    let result = '';\n    const childNodes: ArrayLike<Node> = node.childNodes ?? [];\n    if (childNodes.length > 0) {\n      for (let i = 0; i < childNodes.length; i++) {\n        const child = childNodes[i];\n        if (child) {\n          result += serializeNode(child);\n        }\n      }\n    }\n    return result;\n  }\n\n  return '';\n}\n\n/**\n * Renders a Nuclo component to an HTML string for server-side rendering\n *\n * @param input - A Nuclo component function, DOM element, or node\n * @returns HTML string representation of the component\n *\n * @example\n * ```ts\n * import 'nuclo/polyfill';\n * import 'nuclo'; // tag builders such as div() are globals only\n * import { renderToString } from 'nuclo/ssr';\n *\n * const html = renderToString(\n *   div(\"Hello, World!\")\n * );\n * // Returns: '<div><!-- text-0 -->Hello, World!</div>'\n * ```\n */\nexport function renderToString(input: RenderableInput) {\n  if (!input) return '';\n\n  if (typeof input === 'function') {\n    try {\n      // Build the tree in serialization mode so text children keep their\n      // <!-- text-N --> markers (needed for hydration) even when isBrowser is\n      // true, e.g. SSR running under jsdom.\n      const element = runSerializing(() => {\n        if (typeof document === 'undefined') throw new Error('Document is not available. Make sure polyfills are loaded.');\n        const container = document.createElement('div') as unknown as ExpandedElement<ElementTagName>;\n        // render(App)-style component function: call it for the tree's\n        // builder (mirrors render()/hydrate()), run the root hooks, then build.\n        let built: unknown = isZeroArityFunction(input) ? (input as () => unknown)() : input;\n        if (typeof built === 'function') built = (built as NodeModFn<ElementTagName>)(container, 0);\n        return built;\n      });\n      return element && typeof element === 'object' && 'nodeType' in element ? serializeNode(element as Node) : '';\n    } catch (error) {\n      console.error('Error rendering component to string:', error);\n      return '';\n    }\n  }\n\n  if ('nodeType' in input) {\n    return serializeNode(input as Node);\n  }\n\n  return '';\n}\n\n/**\n * Renders multiple Nuclo components to HTML strings\n *\n * @param inputs - Array of Nuclo components\n * @returns Array of HTML strings\n */\nexport function renderManyToString(inputs: RenderableInput[]): string[] {\n  return inputs.map(input => renderToString(input));\n}\n\n/**\n * Renders a Nuclo component and wraps it in a container element\n *\n * @param input - A Nuclo component\n * @param containerTag - The tag name for the container (default: 'div')\n * @param containerAttrs - Attributes for the container element\n * @returns HTML string with container wrapper\n */\nexport function renderToStringWithContainer(\n  input: RenderableInput,\n  containerTag = 'div',\n  containerAttrs: Record<string, string> = {}\n) {\n  const content = renderToString(input);\n  const attrs = Object.entries(containerAttrs)\n    .map(([key, value]) => (key === '' || UNSAFE_ATTRIBUTE_NAME.test(key) ? '' : serializeAttribute(key, value)))\n    .join('');\n\n  return `<${containerTag}${attrs}>${content}</${containerTag}>`;\n}\n","/**\n * Shared generated-CSS engine state.\n *\n * One stylesheet, one rule registry, shared by every `createCss` instance so\n * identical declarations dedupe globally and cascade order stays deterministic:\n * base rules first (insertion order), then one grouping rule per at-rule query\n * in registration order (theme `screens` order).\n *\n * Browser: rules are injected through CSSOM with O(1) appends — no rescans.\n * SSR: rules accumulate in the registry; `getCssText()` serializes them.\n *\n * The state below lives on `globalThis`, keyed by a `Symbol.for(...)` (the\n * global symbol registry, shared by every realm-local copy of this module).\n * `nuclo` publishes `.`, `./ssr`, and `./polyfill` as independently built\n * bundle files (see tsdown.config.ts) — a consumer that resolves them\n * through normal package resolution (any bundler/runtime that doesn't\n * alias every subpath to this same source file) loads two or three\n * separate copies of this module, each with its own module-scope\n * variables. Without a realm-wide singleton, `css()` (from `nuclo`) would\n * mint rules into one copy's registry while `getCssText()` (from\n * `nuclo/ssr`) reads an entirely different, always-empty one — SSR output\n * would render the right classes with no matching CSS at all. Storing the registry on `globalThis` instead makes\n * every copy of this module share the same state regardless of how many\n * times it ends up bundled.\n *\n * The registry never shrinks on its own: atomCache/atomMeta/rawKeys/\n * baseRules/queryRules retain every rule for the life of the process —\n * content-addressed dedup needs a durable record of what each class means,\n * and the engine has no way to know whether a minted class still backs a\n * live DOM node (it's deliberately decoupled from element/list lifecycle),\n * so it can't safely evict. Only resetStyles() (a manual test helper)\n * clears it. This is fine for the intended use — a bounded design-token\n * vocabulary — but a caller piping truly per-instance dynamic values\n * through css({ raw: {...} }) (e.g. per-row data in a list) mints one\n * permanent class per unique value; prefer the `style` prop\n * (element/inline-style.ts) for values that vary per instance rather than\n * per design token. See test/style/registry-growth.test.ts.\n */\n\nexport interface BlockMeta {\n\treadonly query: string | undefined;\n\treadonly suffix: string;\n\treadonly decls: ReadonlyArray<readonly [string, string]>;\n\t/** Author-supplied identity from `css(name, …)`; inherited by cx() merges. */\n\treadonly name: string | undefined;\n}\n\ninterface EngineState {\n\t// Registry — the source of truth for all generated CSS.\n\t// atomCache: declaration-block key -> class name (dedup)\n\t// atomMeta: class name -> what the class means (its selector context and its\n\t// declarations). This is what lets cx() merge two same-context classes at the\n\t// declaration level instead of guessing: the engine can always recover the\n\t// exact content of any class it minted.\n\tatomCache: Map<string, string>;\n\tatomMeta: Map<string, BlockMeta>;\n\trawKeys: Set<string>;\n\tbaseRules: string[];\n\tqueryRules: Map<string, string[]>;\n\tstyleEpoch: number;\n\n\t// Memoized getCssText() output. SSR calls getCssText() once per request, but the\n\t// rule set is stable after warmup — so the serialized sheet is recomputed only\n\t// when a new rule is actually recorded. Invalidated in record() and resetStyles().\n\tcssTextCache: string | null;\n\n\t// Browser CSSOM state. Base rules occupy [0, baseRuleCount); grouping rules\n\t// (one per query, created in registration order) live after them. CSSRule\n\t// references stay live when indices shift, so insertion never rescans.\n\tsheet: CSSStyleSheet | null;\n\tsheetDocument: Document | null;\n\tbaseRuleCount: number;\n\tgroupRules: Map<string, CSSGroupingRule>;\n\n\t// Normalized rules already sitting in a freshly-adopted stylesheet that this\n\t// module didn't insert itself — e.g. server-rendered CSS being hydrated.\n\t// Rebuilt on every fresh bind in ensureSheet(); null when there is nothing\n\t// external to guard against.\n\texternalRules: Map<string, Set<string>> | null;\n\thasExternalGroups: boolean;\n}\n\nfunction createState(): EngineState {\n\treturn {\n\t\tatomCache: new Map(),\n\t\tatomMeta: new Map(),\n\t\trawKeys: new Set(),\n\t\tbaseRules: [],\n\t\tqueryRules: new Map(),\n\t\tstyleEpoch: 0,\n\t\tcssTextCache: null,\n\t\tsheet: null,\n\t\tsheetDocument: null,\n\t\tbaseRuleCount: 0,\n\t\tgroupRules: new Map(),\n\t\texternalRules: null,\n\t\thasExternalGroups: false,\n\t};\n}\n\nconst ENGINE_STATE_KEY = Symbol.for(\"nuclo.style.engine.v1\");\nconst globalScope = globalThis as unknown as Record<symbol, EngineState>;\n// Importing the runtime does not allocate the registry until styling is used.\nlet cachedState: EngineState | undefined;\nfunction getState(): EngineState {\n\treturn cachedState ??= globalScope[ENGINE_STATE_KEY] ?? (globalScope[ENGINE_STATE_KEY] = createState());\n}\n\n/** Pre-register at-rule queries so their cascade order follows theme order. */\nexport function registerQueries(queries: Iterable<string>) {\n\tfor (const query of queries) {\n\t\tconst state = getState();\n\t\tif (!state.queryRules.has(query)) state.queryRules.set(query, []);\n\t}\n}\n\nfunction createGroup(s: CSSStyleSheet, query: string): CSSGroupingRule | null {\n\tconst state = getState();\n\ttry {\n\t\tconst idx = s.cssRules.length;\n\t\ts.insertRule(query + \"{}\", idx);\n\t\tconst group = s.cssRules[idx] as CSSGroupingRule;\n\t\tstate.groupRules.set(query, group);\n\t\tstate.groupRules.set(groupQueryOf(group), group);\n\t\treturn group;\n\t} catch {\n\t\treturn null; // unsupported at-rule in this environment\n\t}\n}\n\n// A group prelude is stable while its children grow. Reading cssText on each\n// insertion would serialize all previous children and make loading quadratic.\n// Weak keys allow detached stylesheets to be collected after reset/rebinding.\nconst groupQueries = new WeakMap<CSSRule, string>();\nfunction groupQueryOf(rule: CSSRule) {\n\tconst cached = groupQueries.get(rule);\n\tif (cached !== undefined) return cached;\n\tconst text = rule.cssText;\n\tconst brace = text.indexOf(\"{\");\n\tconst query = (brace === -1 ? text : text.slice(0, brace)).trim();\n\tgroupQueries.set(rule, query);\n\treturn query;\n}\n\nfunction ruleIdentity(rule: CSSRule) {\n\treturn rule instanceof CSSStyleRule ? rule.selectorText : groupQueryOf(rule);\n}\n\nfunction rememberExternalRule(rule: CSSRule, query: string | undefined) {\n\tconst state = getState();\n\tif (!state.externalRules) return;\n\tconst key = contextKeyOf(query, ruleIdentity(rule));\n\tlet texts = state.externalRules.get(key);\n\tif (!texts) {\n\t\ttexts = new Set();\n\t\tstate.externalRules.set(key, texts);\n\t}\n\ttexts.add(rule.cssText);\n}\n\nfunction consumeExternalRule(rule: CSSRule, query: string | undefined) {\n\tconst state = getState();\n\tif (!state.externalRules) return false;\n\tconst key = contextKeyOf(query, ruleIdentity(rule));\n\tconst texts = state.externalRules.get(key);\n\tif (!texts?.delete(rule.cssText)) return false;\n\tif (texts.size === 0) state.externalRules.delete(key);\n\tif (state.externalRules.size === 0) state.externalRules = null;\n\treturn true;\n}\n\n/**\n * Index an already-populated stylesheet found on adopt (SSR output, most\n * commonly): every top-level conditional grouping rule (@media/@container/…)\n * is registered into groupRules (by its reconstructed at-rule text) so later\n * record() calls append into it instead of creating a sibling duplicate. The\n * other rules are indexed by scope, selector/prelude, and normalized cssText\n * so exact server/client matches can be skipped without hiding a changed\n * named or global rule that happens to reuse the same selector.\n */\nfunction indexExternalRules(rules: CSSRuleList) {\n\tconst state = getState();\n\tfor (let i = 0; i < rules.length; i++) {\n\t\tconst rule = rules[i];\n\t\tif (rule instanceof CSSStyleRule) {\n\t\t\trememberExternalRule(rule, undefined);\n\t\t\tcontinue;\n\t\t}\n\t\tconst prelude = groupQueryOf(rule);\n\t\tif (\"cssRules\" in rule && !prelude.startsWith(\"@keyframes\")) {\n\t\t\tconst group = rule as CSSGroupingRule;\n\t\t\tstate.hasExternalGroups = true;\n\t\t\tstate.groupRules.set(prelude, group);\n\t\t\tfor (let j = 0; j < group.cssRules.length; j++) {\n\t\t\t\trememberExternalRule(group.cssRules[j], prelude);\n\t\t\t}\n\t\t} else {\n\t\t\t// @keyframes, @font-face, … — one-shot rules matched by prelude.\n\t\t\trememberExternalRule(rule, undefined);\n\t\t}\n\t}\n}\n\nfunction insertBase(s: CSSStyleSheet, rule: string) {\n\tconst state = getState();\n\ttry {\n\t\ts.insertRule(rule, state.baseRuleCount);\n\t\tconst inserted = s.cssRules[state.baseRuleCount];\n\t\tif (consumeExternalRule(inserted, undefined)) {\n\t\t\ts.deleteRule(state.baseRuleCount);\n\t\t\treturn;\n\t\t}\n\t\tstate.baseRuleCount++;\n\t} catch {\n\t\t// Invalid value/selector: drop the declaration, like browsers do for bad CSS.\n\t}\n}\n\nfunction insertGrouped(group: CSSGroupingRule, rule: string) {\n\tconst state = getState();\n\ttry {\n\t\tconst index = group.cssRules.length;\n\t\tgroup.insertRule(rule, index);\n\t\tconst inserted = group.cssRules[index];\n\t\tif (state.externalRules && consumeExternalRule(inserted, groupQueryOf(group))) group.deleteRule(index);\n\t} catch {\n\t\t// Invalid value/selector: drop the declaration, like browsers do for bad CSS.\n\t}\n}\n\nfunction getOrCreateGroup(s: CSSStyleSheet, query: string): CSSGroupingRule | null {\n\tconst state = getState();\n\tconst direct = state.groupRules.get(query);\n\tif (direct) return direct;\n\tif (!state.hasExternalGroups) return createGroup(s, query);\n\n\t// CSSOM may normalize whitespace in an at-rule prelude. Normalize the\n\t// authored query through a temporary empty group before deciding that an\n\t// adopted SSR group is missing.\n\ttry {\n\t\tconst index = s.cssRules.length;\n\t\ts.insertRule(query + \"{}\", index);\n\t\tconst normalized = groupQueryOf(s.cssRules[index]);\n\t\ts.deleteRule(index);\n\t\tconst existing = state.groupRules.get(normalized);\n\t\tif (existing) {\n\t\t\tstate.groupRules.set(query, existing);\n\t\t\treturn existing;\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n\treturn createGroup(s, query);\n}\n\n/**\n * Returns the live stylesheet, (re)binding when the document changes — e.g.\n * fresh jsdom documents between tests. Rebinding replays the whole registry,\n * which replaces v1's per-call classExistsInDOM() DOM scans with a single\n * replay per document.\n */\nexport function ensureSheet(): CSSStyleSheet | null {\n\tconst state = getState();\n\tif (typeof document === \"undefined\") return null;\n\t// The nuclo SSR polyfill provides a document without getElementById — in\n\t// that context rules accumulate in the registry for getCssText() only.\n\tif (typeof document.getElementById !== \"function\") return null;\n\tif (\n\t\tstate.sheet &&\n\t\tstate.sheetDocument === document &&\n\t\tstate.sheet.ownerNode &&\n\t\t(state.sheet.ownerNode as Element).isConnected\n\t) {\n\t\treturn state.sheet;\n\t}\n\tconst existing = document.getElementById(\"nuclo-styles\");\n\tlet el: HTMLStyleElement;\n\tif (existing?.tagName === \"STYLE\") {\n\t\tel = existing as HTMLStyleElement;\n\t} else {\n\t\tel = document.createElement(\"style\");\n\t\tel.id = \"nuclo-styles\";\n\t\tif (existing) existing.replaceWith(el);\n\t\telse document.head.appendChild(el);\n\t}\n\tstate.sheet = el.sheet;\n\tstate.sheetDocument = document;\n\tstate.groupRules.clear();\n\tstate.baseRuleCount = 0;\n\tstate.externalRules = null;\n\tstate.hasExternalGroups = false;\n\tif (!state.sheet) return null;\n\tif (state.sheet.cssRules.length > 0) {\n\t\t// Leading top-level non-group rules (style rules, @keyframes — before\n\t\t// the first @media/@container group) are this element's existing\n\t\t// \"base\" region — count them so insertBase() keeps inserting new base\n\t\t// rules right after them, still ahead of every group.\n\t\tlet i = 0;\n\t\twhile (i < state.sheet.cssRules.length) {\n\t\t\tconst r = state.sheet.cssRules[i];\n\t\t\tif (!(r instanceof CSSStyleRule) && \"cssRules\" in r && !groupQueryOf(r).startsWith(\"@keyframes\")) break;\n\t\t\ti++;\n\t\t}\n\t\tstate.baseRuleCount = i;\n\t\tstate.externalRules = new Map();\n\t\tindexExternalRules(state.sheet.cssRules);\n\t}\n\tfor (const rule of state.baseRules) insertBase(state.sheet, rule);\n\tfor (const [query, rules] of state.queryRules) {\n\t\tconst group = getOrCreateGroup(state.sheet, query);\n\t\tif (group) for (const rule of rules) insertGrouped(group, rule);\n\t}\n\treturn state.sheet;\n}\n\nfunction record(rule: string, query: string | undefined) {\n\tconst state = getState();\n\t// Bind/replay *before* this rule joins the registry, so a fresh bind's\n\t// replay only ever covers previously-known rules — never this one twice.\n\tconst s = ensureSheet();\n\n\tif (query === undefined) {\n\t\tstate.baseRules.push(rule);\n\t} else {\n\t\tlet bucket = state.queryRules.get(query);\n\t\tif (!bucket) {\n\t\t\tbucket = [];\n\t\t\tstate.queryRules.set(query, bucket);\n\t\t}\n\t\tbucket.push(rule);\n\t}\n\tstate.cssTextCache = null; // a new rule changes the serialized sheet\n\n\tif (s) {\n\t\tif (query === undefined) {\n\t\t\tinsertBase(s, rule);\n\t\t} else {\n\t\t\tconst group = getOrCreateGroup(s, query);\n\t\t\tif (group) insertGrouped(group, rule);\n\t\t}\n\t}\n}\n\n/**\n * Content-addressed ~42-bit hash (two interleaved FNV-1a style passes). Class\n * names must match between server and client for SSR hydration, so a counter\n * is not an option; 42 bits keeps collision odds low (~1e-5 at 10k\n * unique declaration blocks).\n */\nexport function hash(input: string) {\n\tlet a = 0x811c9dc5;\n\tlet b = 0x7ee3623b;\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst c = input.charCodeAt(i);\n\t\ta = Math.imul(a ^ c, 0x01000193);\n\t\tb = Math.imul(b ^ c, 0x85ebca6b);\n\t}\n\treturn (a >>> 0).toString(36) + ((b >>> 9) % 1296).toString(36);\n}\n\n/** Length-prefixed (query, selector-suffix) key — unambiguous for any strings. */\nexport function contextKeyOf(query: string | undefined, suffix: string) {\n\tconst queryPart = query === undefined ? \"-\" : \"+\" + query.length + \":\" + query;\n\treturn queryPart + suffix.length + \":\" + suffix;\n}\n\nfunction appendKeyPart(key: string, value: string) {\n\treturn key + value.length + \":\" + value;\n}\n\nexport function expandAmpersands(input: string, replacement: string) {\n\tlet expanded = \"\";\n\tlet quote = 0;\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst code = input.charCodeAt(i);\n\t\tif (code === 92 /* \\\\ */ && i + 1 < input.length) {\n\t\t\texpanded += input[i] + input[++i];\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote !== 0) {\n\t\t\texpanded += input[i];\n\t\t\tif (code === quote) quote = 0;\n\t\t\tcontinue;\n\t\t}\n\t\tif (code === 34 /* \" */ || code === 39 /* ' */) {\n\t\t\tquote = code;\n\t\t\texpanded += input[i];\n\t\t} else {\n\t\t\texpanded += code === 38 /* & */ ? replacement : input[i];\n\t\t}\n\t}\n\treturn expanded;\n}\n\nfunction selectorFor(className: string, suffix: string) {\n\tconst selector = \".\" + className;\n\treturn selector + expandAmpersands(suffix, selector);\n}\n\n/**\n * True for strings usable verbatim as an unescaped CSS class selector:\n * letters/digits/`-`/`_`, not starting with a digit or `-`+digit.\n * Char-code loop rather than a RegExp — this runs on the cold mint path but\n * stays allocation-free either way.\n */\nfunction isValidClassName(name: string) {\n\tif (name.length === 0 || name === \"-\") return false;\n\tfor (let i = 0; i < name.length; i++) {\n\t\tconst c = name.charCodeAt(i);\n\t\tconst ok =\n\t\t\t(c >= 97 && c <= 122) || // a-z\n\t\t\t(c >= 65 && c <= 90) || // A-Z\n\t\t\tc === 45 || c === 95 || // - _\n\t\t\t(i > 0 && c >= 48 && c <= 57); // 0-9, never leading\n\t\tif (!ok) return false;\n\t}\n\t// \"-9foo\" is not a valid identifier start either.\n\tif (name.charCodeAt(0) === 45 && name.length > 1) {\n\t\tconst second = name.charCodeAt(1);\n\t\tif (second >= 48 && second <= 57) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Mint (or reuse) the class for a declaration block in a selector context.\n *\n * Declarations keep their authored order — CSS resolves shorthand/longhand\n * and repeated-property conflicts by declaration order, so reordering (e.g.\n * alphabetical sorting) would silently change what `{ pt: 20, p: 16 }` means.\n * The cost is that the same declarations authored in a different order mint a\n * second, content-identical class — harmless, and rare in practice.\n *\n * `name` is the author's identity for the style (`css(\"app-root\", …)`). It\n * becomes part of the cache key, so two different names never share a class\n * even with identical declarations. With `exactName`, this block *is* the\n * named one and takes the name verbatim; otherwise the name is only a\n * readable prefix on a content-addressed prefix (`app-root-1a2b3c`), which is\n * how a named style's non-base contexts and its cx() merges stay identifiable\n * in devtools without ever colliding with the base class.\n */\nexport function atomBlock(\n\tquery: string | undefined,\n\tsuffix: string,\n\tdecls: ReadonlyArray<readonly [string, string]>,\n\tname?: string,\n\texactName = false,\n) {\n\tconst state = getState();\n\tlet contextKey = contextKeyOf(query, suffix);\n\tfor (const [prop, value] of decls) {\n\t\tcontextKey = appendKeyPart(appendKeyPart(contextKey, prop), value);\n\t}\n\tif (name !== undefined && !isValidClassName(name)) {\n\t\tconsole.warn(\n\t\t\t`[nuclo] css() name ${JSON.stringify(name)} is not a valid CSS class name ` +\n\t\t\t\t\"(letters, digits, - and _ only, not starting with a digit); falling back to a generated name.\",\n\t\t);\n\t\tname = undefined;\n\t}\n\tconst declKey = name === undefined ? \"a\" + contextKey : \"n\" + appendKeyPart(\"\", name) + contextKey;\n\tlet className = state.atomCache.get(declKey);\n\tif (className !== undefined) {\n\t\t// Touch the sheet so a swapped document (tests) gets the replayed rules.\n\t\tensureSheet();\n\t\treturn className;\n\t}\n\n\tif (name === undefined) {\n\t\tclassName = \"n\" + hash(declKey);\n\t} else {\n\t\tclassName = exactName ? name : name + \"-\" + hash(declKey);\n\t\tif (state.atomMeta.has(className)) {\n\t\t\tconsole.warn(\n\t\t\t\t`[nuclo] css() name ${JSON.stringify(className)} is already used by a different style. ` +\n\t\t\t\t\t\"Both rules are emitted under the same class, so they will override each other — rename one.\",\n\t\t\t);\n\t\t}\n\t}\n\n\tlet body = \"\";\n\tfor (const [prop, value] of decls) body += (body ? \";\" : \"\") + prop + \":\" + value;\n\tstate.atomCache.set(declKey, className);\n\tstate.atomMeta.set(className, { query, suffix, decls, name });\n\trecord(selectorFor(className, suffix) + \"{\" + body + \"}\", query);\n\treturn className;\n}\n\n/**\n * Merge two same-context generated classes into one: `second`'s declarations\n * override `first`'s per exact property (shorthand/longhand interplay is left\n * to CSS order), and the combined block mints its own content-addressed class\n * — so cx(base, override) keeps base's untouched properties instead of\n * dropping the whole base class. Deterministic: the same pair always yields\n * the same class name, on server and client alike. A named base passes its\n * name down as a prefix, so `cx(appRoot, active)` reads as `app-root-1a2b3c`.\n */\nexport function mergeBlocks(first: string, second: string) {\n\tconst state = getState();\n\tconst a = state.atomMeta.get(first);\n\tconst b = state.atomMeta.get(second);\n\tif (!a || !b) return second; // unknown input — nothing to merge with\n\tconst concat = [...a.decls, ...b.decls];\n\t// Keep only the last occurrence of each exact property, at its position —\n\t// so a later `padding` still fully resets an earlier `padding-top`.\n\tconst lastIndex = new Map<string, number>();\n\tfor (let i = 0; i < concat.length; i++) lastIndex.set(concat[i][0], i);\n\tconst merged = concat.filter((decl, i) => lastIndex.get(decl[0]) === i);\n\treturn atomBlock(a.query, a.suffix, merged, a.name ?? b.name);\n}\n\n/** Conflict key (query|suffix) for a generated class — lets cx() group classes by selector context. */\nexport function conflictKeyOf(className: string): string | undefined {\n\tconst state = getState();\n\tconst meta = state.atomMeta.get(className);\n\treturn meta === undefined ? undefined : contextKeyOf(meta.query, meta.suffix);\n}\n\n/** Current registry generation, used to invalidate per-instance WeakMap hits after resetStyles(). */\nexport function getStyleEpoch() {\n\treturn (cachedState ?? globalScope[ENGINE_STATE_KEY])?.styleEpoch ?? 0;\n}\n\n/** Register a non-atomic rule (keyframes, global styles) once per dedupe key. */\nexport function addRawRule(dedupeKey: string, rule: string) {\n\tconst state = getState();\n\tif (state.rawKeys.has(dedupeKey)) {\n\t\tensureSheet();\n\t\treturn;\n\t}\n\tstate.rawKeys.add(dedupeKey);\n\trecord(rule, undefined);\n}\n\n/** All generated CSS — base rules first, then at-rule groups in registration order. */\nexport function getCssText() {\n\tconst state = getState();\n\tif (state.cssTextCache !== null) return state.cssTextCache;\n\tlet out = state.baseRules.join(\"\");\n\tfor (const [query, rules] of state.queryRules) {\n\t\tif (rules.length > 0) out += query + \"{\" + rules.join(\"\") + \"}\";\n\t}\n\tstate.cssTextCache = out;\n\treturn out;\n}\n\n/** Clear all engine state (test helper). Removes the injected style element. */\nexport function resetStyles() {\n\tconst state = getState();\n\tstate.atomCache.clear();\n\tstate.atomMeta.clear();\n\tstate.rawKeys.clear();\n\tstate.baseRules.length = 0;\n\tstate.queryRules.clear();\n\tstate.groupRules.clear();\n\tstate.cssTextCache = null;\n\tstate.baseRuleCount = 0;\n\tstate.sheet = null;\n\tstate.sheetDocument = null;\n\tstate.externalRules = null;\n\tstate.hasExternalGroups = false;\n\tstate.styleEpoch++;\n\tif (typeof document !== \"undefined\") {\n\t\tdocument.getElementById(\"nuclo-styles\")?.remove();\n\t}\n}\n"],"mappings":"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"}