{"version":3,"sources":["jsdelivr-header.js","/npm/@dotrino/topbar@0.8.3/src/index.js"],"names":[],"mappings":"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","file":"/npm/@dotrino/topbar@0.8.3/src/index.js/+esm","sourceRoot":"","sourcesContent":["/**\n * Bundled by jsDelivr using Rollup v4.62.2 and esbuild v0.28.1.\n * Original file: /npm/@dotrino/topbar@0.8.3/src/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","/**\n * @dotrino/topbar — la BARRA SUPERIOR estándar del ecosistema Dotrino, en UN\n * solo Web Component. Deja de reimplementar (y desviar) el header en cada app.\n *\n * Empaqueta, en el orden y comportamiento normados (CONVENCIONES §5/§6/§9):\n *   [◀ volver] [marca: icono + nombre]  … <slot acciones> …  [ES|EN] [perfil] [🪙 support]\n * con la **segunda fila de acciones en móvil**, `env(safe-area-inset-*)`, Shadow\n * DOM (sin JS de terceros ni cookies) y estética oscura por defecto tematizable.\n *\n * Reúne las tres piezas compartidas para que la app importe UNA sola cosa:\n *   - `@dotrino/nav`     → chevron de volver + captura del botón físico Android /\n *                          gesto iOS / atrás del navegador (`<dotrino-back>`).\n *   - `@dotrino/support` → moneda de soporte/donación (`<dotrino-support>`).\n *   El botón de perfil va en TODA app (§6.1: no existen las apps sin identidad):\n *   con `.identity` + `.reputation` el topbar abre el modal solo; si no, emite\n *   `dotrino-profile` y la app decide.\n *\n * Uso vanilla (por CDN va con `+esm`, NO con /src/index.js: los imports desnudos\n * de abajo no resuelven en el navegador; `+esm` los reescribe):\n *   <script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@dotrino/topbar@0.5/+esm\"></script>\n *   <dotrino-topbar brand=\"Mi App\" icon=\"/icon.svg\"\n *     support-repo=\"imdotrino/mi-app\" profile></dotrino-topbar>\n *\n * Uso Vue 3: `import '@dotrino/topbar'` y usar el tag; los eventos son nativos.\n *\n * Atributos:\n *   brand            nombre de la marca (texto a la derecha del icono)\n *   icon             URL del icono de marca (p. ej. /icon.svg)\n *   brand-href       destino al pulsar la marca (default \"./\")\n *   lang             'es' | 'en' | 'auto' (default 'auto'); persiste y refleja\n *   home             fallback de \"volver\" si no hay historial (default dotrino.com)\n *   no-back          oculta el chevron de volver\n *   no-lang          oculta el toggle de idioma (apps de un solo idioma)\n *   profile          muestra el botón de perfil (§6.1)\n *   avatar           data-URI del avatar del perfil activo (si falta: silueta o,\n *                    si se pasó `identity`, el identicon derivado del perfil activo)\n *   support-href     URL de support (default https://ko-fi.com/dotrino)\n *   support-repo     repo para el botón \"reportar\" del support\n *   support-discord  invitación de Discord del support\n *   support-contact  si está, pasa `contact` a <dotrino-support>\n *   support-share-url / support-share-text / support-app / support-x-handle\n *                    se propagan tal cual a la moneda, para las apps que enchufan\n *                    su propio enlace al compartir (p. ej. el de invitación de los\n *                    referidos, §12.3). La moneda vive en NUESTRO shadow DOM y la\n *                    recreamos en cada render: sin este passthrough la app no\n *                    tiene forma de llegar a ella.\n *   no-support       oculta la moneda de support\n *\n * Perfil (§6.1) — el topbar es DUEÑO del modal \"Mi perfil\" para que la app NO fije\n * la versión de @dotrino/profile (viaja dentro de @dotrino/topbar). Propiedades JS:\n *   .identity     instancia de @dotrino/identity (Identity.connect())\n *   .reputation   instancia de @dotrino/reputation (createVaultReputation(id))\n *   .profileTheme objeto de CSS vars --ccp-* para tematizar el modal (opcional)\n * Con `identity` + `reputation`, al pulsar el botón el topbar abre el modal solo.\n * El botón de perfil LLEVA a profile.dotrino.com (no abre modal: se quitó porque duplicaba\n * esa página). Al pasar el ratón —o al tocarlo en móvil— ofrece cambiar de perfil.\n * (antes: openMyProfile({ editable }) abría un modal editable para el onboarding\n * \"ponte un apodo\"). Si NO se setea identity, el botón solo emite 'dotrino-profile'\n * (clásico) y la app renderiza su propio <dotrino-profile>.\n *\n * Eventos (bubbles, composed):\n *   dotrino-lang          { lang }  al cambiar de idioma\n *   dotrino-profile       al pulsar el botón (cancelable: preventDefault para que\n *                         la app maneje el perfil en vez del modal propio del topbar)\n *   dotrino-profile-name  { pubkey, name }  al guardar el nombre en el modal\n *   dotrino-profile-close  al cerrar el modal\n *\n * PERSONALIZACIÓN por app (todo opcional, para que cada app tenga su variación):\n *   - **Slots con nombre**:\n *       <span slot=\"brand\">…</span>  → reemplaza icono+nombre por tu marca a medida\n *                                       (logo inline, badges, lo que sea).\n *       (slot por defecto)           → tus botones de acción, al medio.\n *       <button slot=\"end\">…</button>→ controles extra en el cluster derecho\n *                                       (p. ej. <dotrino-install>), antes de idioma.\n *       <button slot=\"trailing\">…</button>→ al FINAL de todo (después de la moneda\n *                                       de support), p. ej. la hamburguesa de menú móvil.\n *   - **CSS `::part()`** para reestilar cualquier pieza desde el CSS de la app:\n *       dotrino-topbar::part(bar) { … }      part(brand) part(brand-name)\n *       part(actions) part(lang) part(lang-btn) part(profile) part(coin) part(back)\n *   - **Variables de tema** en el host (con default oscuro):\n *       --dotrino-topbar-bg / -border / -text / -muted / -accent / -pad / -gap / -font\n *   - Los elementos que pasás por slot viven en el light DOM → los estiliza tu\n *     propio CSS directamente (sin `::part`).\n */\nimport { createBackNav, getBackNav } from '@dotrino/nav' // registra <dotrino-back> + controlador\nimport '@dotrino/support'\n// El topbar YA NO abre un modal de perfil (se quitó: duplicaba profile.dotrino.com). Una\n// app que quiera mostrar la tarjeta de OTRA persona importa `@dotrino/profile` ella misma.\nimport { avatarDataUri } from '@dotrino/identity/avatar' // identicon del perfil activo (subpath barato: no arrastra core.js)\n\n/** Tu perfil vive en una sola página del ecosistema; el topbar solo te lleva. */\nconst PROFILE_URL = 'https://profile.dotrino.com/'\n/** Dónde se crea un perfil: una página común a todo el ecosistema (no un botón al vuelo). */\nconst CREATE_URL = 'https://profile.dotrino.com/create'\n\n/** Escape mínimo para el HTML que arma el menú. */\nconst esc = (v) => String(v == null ? '' : v).replace(/[&<>\"']/g, (c) => (\n  { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c]))\n\nconst T = {\n  es: { profile: 'Mi perfil', back: 'Volver', profiles: 'Tus perfiles', newProfile: 'Crear perfil', openProfile: 'Abrir mi perfil', unnamedProfile: 'Perfil sin nombre' },\n  en: { profile: 'My profile', back: 'Back', profiles: 'Your profiles', newProfile: 'Create profile', openProfile: 'Open my profile', unnamedProfile: 'Unnamed profile' }\n}\n\nconst PROFILE_SVG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"\n  stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n  <circle cx=\"12\" cy=\"8\" r=\"4\" /><path d=\"M4 20c0-4 3.6-6 8-6s8 2 8 6\" /></svg>`\n\nclass DotrinoTopbar extends HTMLElement {\n  static get observedAttributes () { return ['brand', 'icon', 'brand-href', 'lang', 'avatar', 'profile', 'support-share-url', 'support-share-text', 'support-app', 'support-x-handle'] }\n\n  constructor () {\n    super()\n    this.attachShadow({ mode: 'open' })\n    this._lang = 'es'\n  }\n\n  connectedCallback () {\n    this._lang = this._resolveLang()\n    document.documentElement.lang = this._lang\n    // Instala el controlador de \"volver\" (si la app no lo hizo ya). Sin esto,\n    // <dotrino-back> cae a un heurístico débil (history.length) que NO lleva a\n    // dotrino.com cuando no hay página anterior. El controlador decide por\n    // referrer: sin página previa real → va a `home`.\n    if (!this.hasAttribute('no-back') && !getBackNav()) {\n      try { createBackNav({ home: this.getAttribute('home') || 'https://dotrino.com' }) } catch (_) {}\n    }\n    this.render()\n    if (this._identity) this._refreshButtonAvatar()\n  }\n\n  disconnectedCallback () {\n    this._cerrarMenu()\n    if (this._fueraListener) { document.removeEventListener('click', this._fueraListener); this._fueraListener = null }\n  }\n\n  attributeChangedCallback (name, oldV, newV) {\n    // `lang` hay que RE-RESOLVERLO: antes solo se leía en connectedCallback, así\n    // que un cambio de atributo posterior al montaje re-renderizaba con el idioma\n    // viejo (this._lang) y el atributo no servía de nada salvo en el mount.\n    if (name === 'lang' && oldV !== newV) {\n      const l = this._resolveLang()\n      if (l !== this._lang) { this.setLang(l); return }   // setLang ya renderiza y avisa\n    }\n    if (this.shadowRoot.childElementCount) this.render()\n  }\n\n  /* ----- Perfil \"Mi perfil\": el topbar es DUEÑO del modal (§6.1) -----\n   * La app le pasa su `identity` y `reputation` (los pilares que ya maneja) por\n   * propiedad JS; el topbar crea el provider, deriva pubkey/nombre/avatar y abre\n   * <dotrino-profile modal mode=\"self\"> él mismo. Así la app NO fija la versión de\n   * @dotrino/profile: viaja dentro de @dotrino/topbar. Si la app NO setea identity,\n   * el botón solo emite `dotrino-profile` (comportamiento clásico) y la app decide. */\n  get identity () { return this._identity || null }\n  set identity (v) {\n    v = v || null\n    if (v === this._identity) return // evita re-render redundante (la app re-setea el mismo objeto)\n    this._identity = v; this._provider = null\n    if (this.shadowRoot) this._refreshButtonAvatar()\n  }\n  get reputation () { return this._reputation || null }\n  set reputation (v) { v = v || null; if (v === this._reputation) return; this._reputation = v; this._provider = null }\n  // Compat: algunas apps setean `profileTheme` para el modal, que ya no existe. Se acepta\n  // y se ignora, para no romperlas por un atributo que ahora no pinta nada.\n  get profileTheme () { return this._profileTheme || null }\n  set profileTheme (v) { this._profileTheme = v || null }\n\n  // Compat: algunas apps setean `profileTheme` para el modal, que ya no existe. Se acepta\n  // y se ignora, para no romperlas por un atributo que ahora no pinta nada.\n  get profileTheme () { return this._profileTheme || null }\n  set profileTheme (v) { this._profileTheme = v || null }\n\n  /**\n   * Compat: alguna app (el home, para el onboarding «ponte un apodo») llama a esto para\n   * abrir el perfil. El modal ya no existe, así que lleva a la página, que es donde de\n   * verdad se edita. Se mantiene el nombre para no romperlas.\n   */\n  openMyProfile () { this._irAMiPerfil() }\n\n  async _refreshButtonAvatar () {\n    const id = this._identity\n    if (!id) return\n    // Respeta un avatar explícito que haya puesto la app; solo derivamos el nuestro.\n    if (this.hasAttribute('avatar') && !this._avatarAuto) return\n    try {\n      let avatar = null\n      try { const me = id.getMe ? await id.getMe() : null; if (me && me.avatar) avatar = me.avatar } catch (_) {}\n      if (!avatar) {\n        let pk = null\n        try { const cur = id.currentProfile ? await id.currentProfile() : null; pk = cur && cur.pubkey } catch (_) {}\n        if (!pk) pk = id.me && id.me.publickey\n        if (pk) avatar = avatarDataUri(pk, { size: 72 })\n      }\n      // Solo si cambió: setAttribute dispara attributeChangedCallback → render\n      // aunque el valor sea igual; el guard evita re-renders redundantes (y el\n      // parpadeo del botón que desmonta el modal a medio abrir).\n      if (avatar && this.getAttribute('avatar') !== avatar) { this._avatarAuto = true; this.setAttribute('avatar', avatar) }\n    } catch (_) { /* deja el ícono genérico */ }\n  }\n\n  /**\n   * CAMBIO RÁPIDO de perfil al pasar el ratón por el botón. Se carga la lista la primera\n   * vez que hace falta (no en cada render) y se cierra al salir, al pulsar Escape o al\n   * tocar fuera. Con teclado se abre al enfocar el botón, para que no sea solo de ratón.\n   */\n  _wireProfileMenu () {\n    const wrap = this.shadowRoot.querySelector('.profile-wrap')\n    const menu = this.shadowRoot.querySelector('.prof-menu')\n    const btn = this.shadowRoot.querySelector('.profile')\n    if (!wrap || !menu || !btn) return\n\n    // El menú se abre al PULSAR, no al pasar por encima: un menú que aparece solo porque\n    // el ratón pasa cerca es incómodo, y además así el gesto es el mismo en el escritorio\n    // y en el móvil, donde no existe el hover.\n    if (!this._fueraListener) {\n      this._fueraListener = (e) => { if (!this.contains(e.target)) this._cerrarMenu() }\n      document.addEventListener('click', this._fueraListener)\n    }\n    wrap.addEventListener('keydown', (e) => { if (e.key === 'Escape') { this._cerrarMenu(); btn.focus() } })\n  }\n\n  async _abrirMenu () {\n    if (!this._identity) return\n    const menu = this.shadowRoot.querySelector('.prof-menu')\n    const btn = this.shadowRoot.querySelector('.profile')\n    if (!menu || !btn) return\n    await this._loadProfilesMenu()\n    if (!menu.innerHTML) return\n    menu.hidden = false\n    btn.setAttribute('aria-expanded', 'true')\n  }\n\n  _cerrarMenu () {\n    const menu = this.shadowRoot?.querySelector('.prof-menu')\n    const btn = this.shadowRoot?.querySelector('.profile')\n    if (menu) menu.hidden = true\n    if (btn) btn.setAttribute('aria-expanded', 'false')\n  }\n\n  /** Pinta la lista de perfiles del dispositivo (avatar + nombre + cuál está activo). */\n  async _loadProfilesMenu () {\n    const id = this._identity\n    const menu = this.shadowRoot.querySelector('.prof-menu')\n    if (!id || !menu || typeof id.listProfiles !== 'function') return\n    const t = T[this._lang] || T.es\n    try {\n      const lista = await id.listProfiles()\n      if (!Array.isArray(lista) || !lista.length) return\n      const filas = lista.map((p) => {\n        const img = p.avatar || avatarDataUri(p.pubkey || p.id || '', { size: 44 })\n        const nombre = p.name || t.unnamedProfile\n        return p.current\n          ? `<div class=\"item\" aria-current=\"true\"><img src=\"${esc(img)}\" alt=\"\" /><span>${esc(nombre)}</span><span class=\"marca\" aria-hidden=\"true\">✓</span></div>`\n          : `<button class=\"item\" type=\"button\" data-switch=\"${esc(p.id)}\"><img src=\"${esc(img)}\" alt=\"\" /><span>${esc(nombre)}</span></button>`\n      }).join('')\n      menu.innerHTML = `<div class=\"head\">${esc(t.profiles)}</div>${filas}<div class=\"sep\"></div>` +\n        `<a class=\"item\" href=\"${esc(PROFILE_URL)}\">${esc(t.openProfile)}</a>` +\n        `<a class=\"item\" href=\"${esc(CREATE_URL)}?return=${encodeURIComponent(location.href)}\">＋ ${esc(t.newProfile)}</a>`\n      menu.querySelectorAll('[data-switch]').forEach((b) => b.addEventListener('click', async () => {\n        b.disabled = true\n        // Cambiar de perfil NO es reactivo por diseño: se recarga para que toda la app\n        // arranque con el nuevo (las pestañas abiertas conservan el suyo).\n        try { await id.switchProfile(b.getAttribute('data-switch')); location.reload() } catch (_) { b.disabled = false }\n      }))\n    } catch (_) { /* sin perfiles que ofrecer: el botón sigue funcionando igual */ }\n  }\n\n  /**\n   * Pulsar el avatar abre (o cierra) el menú: tus perfiles para cambiar, «Abrir mi perfil»\n   * y «Crear perfil». Un solo gesto, el mismo en escritorio y en móvil.\n   *\n   * No abre ningún modal: el de TU perfil se quitó porque duplicaba\n   * `profile.dotrino.com`. El modal sigue vivo para ver el perfil de OTRA persona, pero\n   * eso lo monta cada app.\n   */\n  _onProfileClick (ev) {\n    const evento = new CustomEvent('dotrino-profile', { bubbles: true, composed: true, cancelable: true })\n    if (!this.dispatchEvent(evento)) return // la app lo maneja a su manera\n    ev?.stopPropagation?.() // que no lo cierre el listener de «clic fuera»\n    const menu = this.shadowRoot.querySelector('.prof-menu')\n    if (menu && !menu.hidden) this._cerrarMenu()\n    else this._abrirMenu()\n  }\n\n  _irAMiPerfil () {\n    try { location.href = PROFILE_URL } catch (_) {}\n  }\n\n  _resolveLang () {\n    const a = (this.getAttribute('lang') || 'auto').toLowerCase()\n    if (a === 'es' || a === 'en') return a\n    let saved = null\n    try { saved = localStorage.getItem('dotrino.lang') } catch (_) {}\n    if (saved === 'es' || saved === 'en') return saved\n    return (navigator.language || 'es').toLowerCase().startsWith('en') ? 'en' : 'es'\n  }\n\n  get lang () { return this._lang }\n\n  /**\n   * OJO: este setter no es opcional. `lang` es una propiedad NATIVA de\n   * HTMLElement, así que el getter de arriba la sombrea; sin setter, asignar la\n   * propiedad (`el.lang = 'es'`) NO reflejaba al atributo y se perdía EN\n   * SILENCIO — sin error, ni siquiera en modo estricto. Y Vue, cuando el\n   * elemento tiene la propiedad (`'lang' in el` → siempre, es nativa), prefiere\n   * asignarla antes que el atributo: o sea que `:lang=\"lang\"` era un no-op y el\n   * topbar se quedaba con el idioma del NAVEGADOR. Síntoma: una app en español\n   * abierta en un navegador en inglés servía `<html lang=\"en\">` y el header en\n   * inglés, rompiendo la coherencia lang/og:locale que pide §7.\n   */\n  set lang (v) {\n    const l = String(v || '').toLowerCase()\n    if (l !== 'es' && l !== 'en' && l !== 'auto') return\n    this.setAttribute('lang', l)   // → attributeChangedCallback re-resuelve el idioma\n  }\n\n  setLang (l) {\n    if (l !== 'es' && l !== 'en') return\n    this._lang = l\n    try { localStorage.setItem('dotrino.lang', l) } catch (_) {}\n    document.documentElement.lang = l\n    // propagar a los sub-componentes que aceptan lang\n    for (const sel of ['dotrino-support', 'dotrino-back']) {\n      this.shadowRoot.querySelector(sel)?.setAttribute('lang', l)\n    }\n    this.render()\n    this.dispatchEvent(new CustomEvent('dotrino-lang', { detail: { lang: l }, bubbles: true, composed: true }))\n  }\n\n  render () {\n    const lang = this._lang\n    const t = T[lang]\n    const has = (a) => this.hasAttribute(a)\n    const brand = this.getAttribute('brand') || ''\n    const icon = this.getAttribute('icon') || ''\n    const brandHref = this.getAttribute('brand-href') || './'\n    const avatar = this.getAttribute('avatar') || ''\n    const home = this.getAttribute('home') || 'https://dotrino.com'\n\n    // `support-no-count` apaga el conteo de aperturas de la moneda, que se registra en\n    // el store COMPARTIDO (store.dotrino.com, un iframe). Una app que no puede hablarle a\n    // ningún dominio de Dotrino —el Inspector corre en tu máquina y esa es su promesa—\n    // no tenía forma de evitarlo desde el topbar y le salía un iframe bloqueado en la\n    // consola. El conteo es opt-out, no opt-in: las apps del ecosistema lo quieren.\n    //\n    // `support-*` se propaga TAL CUAL a la moneda. Incluye lo de compartir\n    // (share-url/share-text/app/x-handle): la moneda vive en NUESTRO shadow DOM\n    // y la recreamos en cada render, así que una app que quisiera enchufar su\n    // enlace de invitación (los referidos de Critters/Diamonds, §12.3) no tenía\n    // forma de llegar y terminaba metiendo un MutationObserver dentro de este\n    // shadowRoot para re-aplicar el atributo. Eso es culpa nuestra, no de la app.\n    const support = has('no-support') ? '' : `<dotrino-support\n      class=\"coin\" part=\"coin\"\n      href=\"${this._attr('support-href') || 'https://ko-fi.com/dotrino'}\"\n      ${this._attr('support-repo') ? `repo=\"${this._attr('support-repo')}\"` : ''}\n      ${this._attr('support-discord') ? `discord=\"${this._attr('support-discord')}\"` : ''}\n      ${this._attr('support-share-url') ? `share-url=\"${this._attr('support-share-url')}\"` : ''}\n      ${this._attr('support-share-text') ? `share-text=\"${this._attr('support-share-text')}\"` : ''}\n      ${this._attr('support-app') ? `app=\"${this._attr('support-app')}\"` : ''}\n      ${this._attr('support-x-handle') ? `x-handle=\"${this._attr('support-x-handle')}\"` : ''}\n      ${has('support-contact') ? 'contact' : ''}\n      ${has('support-no-count') ? 'no-count' : ''}\n      lang=\"${lang}\"></dotrino-support>`\n\n    // El botón va envuelto para poder colgarle el CAMBIO RÁPIDO de perfil: al pasar el\n    // ratón (o al enfocarlo con el teclado) aparece la lista de perfiles de este\n    // dispositivo. Un clic sigue abriendo tu perfil, como siempre.\n    const profile = has('profile') ? `<div class=\"profile-wrap\" part=\"profile-wrap\">\n      <button class=\"profile\" part=\"profile\" type=\"button\"\n        title=\"${t.profile}\" aria-label=\"${t.profile}\" data-testid=\"my-profile\"\n        aria-haspopup=\"true\" aria-expanded=\"false\">\n        ${avatar ? `<img src=\"${avatar}\" alt=\"\" />` : PROFILE_SVG}</button>\n      <div class=\"prof-menu\" part=\"profile-menu\" data-testid=\"profile-menu\" hidden></div>\n    </div>` : ''\n\n    const back = has('no-back') ? '' : `<dotrino-back class=\"back\" part=\"back\" lang=\"${lang}\" home=\"${home}\"></dotrino-back>`\n\n    const langToggle = has('no-lang') ? '' : `<div class=\"lang\" part=\"lang\" role=\"group\" aria-label=\"es / en\">\n      <button type=\"button\" part=\"lang-btn\" class=\"${lang === 'es' ? 'on' : ''}\" data-lang=\"es\">ES</button>\n      <button type=\"button\" part=\"lang-btn\" class=\"${lang === 'en' ? 'on' : ''}\" data-lang=\"en\">EN</button></div>`\n\n    this.shadowRoot.innerHTML = `\n      <style>\n        :host {\n          display: block;\n          --dt-bg: var(--dotrino-topbar-bg, rgba(20, 16, 40, .82));\n          --dt-line: var(--dotrino-topbar-border, #2a2350);\n          --dt-text: var(--dotrino-topbar-text, #e7e3ff);\n          --dt-muted: var(--dotrino-topbar-muted, #9a92c4);\n          --dt-accent: var(--dotrino-topbar-accent, #7c3aed);\n          --dt-pad: var(--dotrino-topbar-pad, 10px clamp(12px, 4vw, 20px));\n          --dt-gap: var(--dotrino-topbar-gap, 12px);\n          --dt-font: var(--dotrino-topbar-font, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif);\n        }\n        .bar {\n          display: flex; align-items: center; gap: var(--dt-gap); flex-wrap: wrap;\n          padding: var(--dt-pad);\n          padding-top: calc(env(safe-area-inset-top) + 10px);\n          padding-left: max(clamp(12px, 4vw, 20px), env(safe-area-inset-left));\n          padding-right: max(clamp(12px, 4vw, 20px), env(safe-area-inset-right));\n          background: var(--dt-bg); color: var(--dt-text);\n          border-bottom: 1px solid var(--dt-line);\n          backdrop-filter: blur(8px); box-sizing: border-box;\n          font-family: var(--dt-font);\n        }\n        /* La marca y el volver se anclan ARRIBA: si las acciones envuelven a varias\n           líneas (móvil angosto), la marca no debe flotar al centro vertical. */\n        .back, .brand { align-self: flex-start; }\n        .brand { display: flex; align-items: center; gap: 8px; text-decoration: none; color: inherit; font-weight: 700; min-width: 0; min-height: 38px; }\n        .brand img { width: 28px; height: 28px; border-radius: 8px; flex: 0 0 auto; }\n        .brand span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n        .mid { display: flex; align-items: center; gap: 8px; min-width: 0; }\n        /* Las acciones se envuelven UNA A UNA (no todo el grupo de golpe): al\n           faltar espacio, solo el/los iconos que no caben bajan a otra línea,\n           empezando por el de MÁS A LA IZQUIERDA (el primero visual). Truco:\n           el DOM va en orden inverso + flex-direction:row-reverse → el orden\n           visual es el correcto (end … trailing), pero el que \"sobra\" y se\n           envuelve es el ÚLTIMO del DOM = el primero visual (izquierda).\n           flex:1 + min-width:0 obliga a que envuelvan los ITEMS (no el grupo). */\n        .actions {\n          display: flex; align-items: center; gap: 10px;\n          /* flex-basis:0 (no auto): así el corte de línea NO envuelve la caja de\n             acciones entera junto a la marca; se queda en la fila y son sus ITEMS\n             los que envuelven uno a uno cuando falta ancho. */\n          flex: 1 1 0; min-width: 0;\n          flex-wrap: wrap; flex-direction: row-reverse; justify-content: flex-start;\n          row-gap: 8px;\n        }\n        .back { --cc-back-color: var(--dt-text); }\n        /* Toggle de idioma = UN control segmentado (una sola pieza con borde\n           único), no dos botones sueltos. Ambas opciones visibles (§9), la\n           activa rellena con el color de acento. */\n        .lang { display: inline-flex; border: 1px solid var(--dt-line); border-radius: 999px; overflow: hidden; }\n        .lang button {\n          background: transparent; border: none; color: var(--dt-muted);\n          padding: 5px 11px; cursor: pointer; font: inherit; font-size: 13px; font-weight: 700;\n        }\n        .lang button + button { border-left: 1px solid var(--dt-line); }\n        .lang button:hover { color: var(--dt-text); }\n        .lang button.on { background: var(--dt-accent); color: var(--dotrino-topbar-accent-text, #fff); }\n        .profile {\n          width: 36px; height: 36px; padding: 0; border-radius: 50%;\n          display: inline-flex; align-items: center; justify-content: center;\n          background: transparent; border: 1px solid var(--dt-line); color: var(--dt-muted);\n          cursor: pointer; overflow: hidden;\n        }\n        .profile:hover { color: var(--dt-text); border-color: var(--dt-accent); }\n        .profile img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }\n        .profile-wrap { position: relative; display: inline-flex; }\n        .prof-menu {\n          position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;\n          min-width: 208px; max-width: 280px; padding: 6px;\n          background: var(--dt-bg, #0f1725); border: 1px solid var(--dt-border, #1e2a3d);\n          border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.35);\n          display: flex; flex-direction: column; gap: 2px;\n        }\n        .prof-menu[hidden] { display: none; }\n        .prof-menu .item {\n          /* box-sizing es lo que arregla que el resaltado del ratón se saliera de la caja:\n             con width:100% MÁS el relleno, cada fila medía 16px de más y pintaba su fondo\n             por encima del borde del menú (y de paso empujaba el visto contra él). */\n          display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box;\n          padding: 7px 8px; border: 0; border-radius: 9px; cursor: pointer;\n          background: transparent; color: var(--dt-text, #dbe7f7); font: inherit; font-size: 13px; text-align: left;\n          text-decoration: none;\n        }\n        .prof-menu .item:hover, .prof-menu .item:focus-visible { background: var(--dt-bg-2, #17263c); }\n        .prof-menu .item[aria-current=\"true\"] { color: var(--dt-accent, #9cc4ff); font-weight: 600; }\n        .prof-menu .item img { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }\n        .prof-menu .item span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n        /* El ✓ del perfil activo, con su propio hueco: antes iba pegado al borde. */\n        .prof-menu .item .marca { flex: 0 0 auto; margin-left: 2px; opacity: .9; }\n        .prof-menu .sep { height: 1px; margin: 4px 2px; background: var(--dt-border, #1e2a3d); }\n        .prof-menu .head { padding: 4px 8px 2px; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dt-muted, #8ea0b8); }\n        .profile svg { width: 20px; height: 20px; }\n        /* La barra hace flex-wrap: las acciones bajan a otra fila SOLO si no caben\n           (overflow real), no siempre. Al envolver, margin-left:auto las mantiene\n           a la derecha y la marca se queda arriba-izquierda. */\n      </style>\n      <header class=\"bar\" part=\"bar\">\n        ${back}\n        <a class=\"brand\" part=\"brand\" href=\"${brandHref}\">\n          <slot name=\"brand\">\n            ${icon ? `<img part=\"brand-icon\" src=\"${icon}\" alt=\"\" width=\"28\" height=\"28\" onerror=\"this.style.display='none'\" />` : ''}\n            ${brand ? `<span part=\"brand-name\">${brand}</span>` : ''}\n          </slot>\n        </a>\n        <div class=\"mid\" part=\"mid\"><slot></slot></div>\n        <div class=\"actions\" part=\"actions\">\n          <!-- DOM en orden INVERSO (row-reverse lo vuelve al orden visual:\n               end … idioma … perfil … support … trailing). El último del DOM\n               = el primero visual (izquierda) es el primero en envolverse. -->\n          <slot name=\"trailing\"></slot>\n          ${support}\n          ${profile}\n          ${langToggle}\n          <slot name=\"end\"></slot>\n        </div>\n      </header>`\n\n    this.shadowRoot.querySelectorAll('.lang button').forEach((b) =>\n      b.addEventListener('click', () => this.setLang(b.dataset.lang)))\n    this.shadowRoot.querySelector('.profile')?.addEventListener('click', (e) => this._onProfileClick(e))\n    this._wireProfileMenu()\n  }\n\n  _attr (n) { return this.getAttribute(n) }\n}\n\nif (!customElements.get('dotrino-topbar')) customElements.define('dotrino-topbar', DotrinoTopbar)\nexport { DotrinoTopbar }\n"]}