{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-KI4EGKNV.cjs","../src/platform-domains.ts","../src/utils/cn.ts"],"names":[],"mappings":"AAAA,6rBAAY;AACZ;AACE;AACF,wDAA6B;AAC7B;AACA;AC4EO,SAAS,KAAA,CAAM,GAAA,EAA8C;AAClE,EAAA,OAAO,gBAAA,CAAiB,IAAA,CAAK,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,IAAA,IAAQ,GAAG,CAAA;AACnD;AAGA,SAAS,cAAA,CAAe,GAAA,EAA4B;AAClD,EAAA,MAAM,OAAA,kBAAS,KAAA,iBAAM,GAAG,CAAA,6BAAG,QAAA;AAC3B,EAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,OAAO,IAAA;AACpB,EAAA,OAAQ,aAAA,CAAqD,MAAM,EAAA,GAAK,IAAA;AAC1E;AAiBO,SAAS,YAAA,CAAa,GAAA,EAAqB;AAChD,EAAA,MAAM,QAAA,EAAU,GAAA,CAAI,IAAA,CAAK,CAAA;AACzB,EAAA,GAAA,CAAI,0BAAA,CAA2B,IAAA,CAAK,OAAO,CAAA,EAAG,OAAO,OAAA;AACrD,EAAA,OAAO,CAAA,QAAA,EAAW,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAA;AAC/C;AASmE;AAG/D,EAAA;AAG0B,EAAA;AAC9B;AAKwE;AACnD,EAAA;AACf,EAAA;AACyC,IAAA;AACrC,EAAA;AACC,IAAA;AACT,EAAA;AACF;AAGsD;AACT,EAAA;AACD,EAAA;AAC9B,EAAA;AACd;AAG0E;AAC5C,EAAA;AACkB,EAAA;AACvC,EAAA;AACT;AAGoD;AACZ,EAAA;AACxC;AAGgG;AACtE,EAAA;AACe,EAAA;AACA,EAAA;AAChC,EAAA;AACT;AAOkF;AAC9C,EAAA;AACI,EAAA;AAClB,IAAA;AACyB,IAAA;AAC7C,EAAA;AACO,EAAA;AACT;AAWyD;AACjB,EAAA;AACxC;AAgBsD;AACT,EAAA;AAEV,EAAA;AAGY,EAAA;AACnC,IAAA;AACV,EAAA;AAI6B,EAAA;AAGb,EAAA;AACqB,IAAA;AACrC,EAAA;AAGoC,EAAA;AACE,EAAA;AAClB,IAAA;AACE,IAAA;AACT,IAAA;AAC8B,IAAA;AAC/B,IAAA;AACkB,MAAA;AACN,MAAA;AACtB,IAAA;AACF,EAAA;AAC6B,EAAA;AAC/B;AA6CM;AA9RN;AAAA,EAAA;AAAA,IAAA;AAkCgE,IAAA;AAC1B,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAuE;AAAA;AAAA;AAAA;AAKpE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACG,MAAA;AACH,MAAA;AACzC,IAAA;AAgBsB,IAAA;AACmB,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACD,MAAA;AACH,MAAA;AACC,MAAA;AACJ,MAAA;AACK,MAAA;AACF,MAAA;AACvC,IAAA;AAgN6D,IAAA;AAEzB,IAAA;AAM1B,MAAA;AACN,QAAA;AAEF,MAAA;AACF,IAAA;AAAA,EAAA;AAAA;AD5KgD;AACA;AE/HV;AACF;AA4CQ;AACf,EAAA;AAC7B;AAsBsD;AAET,EAAA;AACC,IAAA;AAC5C,EAAA;AAGc,EAAA;AAC4B,IAAA;AAC1C,EAAA;AAGgB,EAAA;AACgB,IAAA;AAChC,EAAA;AAK4C,EAAA;AAC9C;AAhEM;AAzBN;AAAA,EAAA;AAAA,IAAA;AAMA,IAAA;AAmBsD,IAAA;AAC5C,MAAA;AACO,QAAA;AACO,UAAA;AAChB,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACF,UAAA;AACF,QAAA;AACF,MAAA;AACD,IAAA;AAAA,EAAA;AAAA;AFgI+C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-KI4EGKNV.cjs","sourcesContent":[null,"/**\n * Platform-Domain SSOT (single source of truth) + derivations.\n *\n * ONE registry maps each platform → its canonical production URL (`defaultUrl`,\n * the load-bearing source) with an optional per-deploy `NEXT_PUBLIC_*_URL`\n * OVERRIDE. Everything else — the reverse host→platform resolver, the cookie\n * base-domain set (the cross-subdomain SSO mechanism), www/apex expansion, the\n * URL→host parse, preview detection — derives from this one table.\n *\n * EDGE-SAFE + PURE: no React/clsx/tailwind, no `server-only`, no `node:`\n * builtins, no `'use client'`. So it is legal in the Edge middleware\n * (`proxy.ts`), in `'use client'` providers, AND in `server-only` modules\n * (e.g. cookie-domain-server.ts) simultaneously. The ONLY non-pure export is\n * `getAllPlatformBaseDomains`, which reads `typeof window`/`process.env` to\n * preserve byte-identical cookie behavior.\n */\n\nimport type { PlatformName } from './types/platform'\n\nexport type PlatformDomainKey = PlatformName | 'openframe-dashboard'\n\nexport interface PlatformDomainEntry {\n  /** Platform key (matches `PlatformName`, plus the forward-only `openframe-dashboard`). */\n  key: PlatformDomainKey\n  /** Canonical production URL — the LOAD-BEARING source of truth (today's hardcoded fallbacks). */\n  defaultUrl: string\n  /** `NEXT_PUBLIC_*_URL` per-deploy OVERRIDE. The `defaultUrl` covers it when unset. */\n  envVar: string\n  /** Legacy/secondary hosts that REVERSE-map to this key (no env var exists — NOT canonical). */\n  aliasHostnames?: string[]\n  /** Forward-only: no DB row, excluded from the reverse index + cookie set (e.g. the product-CTA dashboard). */\n  pseudo?: boolean\n}\n\nexport const PLATFORM_DOMAINS: readonly PlatformDomainEntry[] = [\n  { key: 'marketing-hub', defaultUrl: 'https://marketing-hub.flamingo.so', envVar: 'NEXT_PUBLIC_MARKETING_HUB_URL' },\n  { key: 'company-hub',   defaultUrl: 'https://company-hub.flamingo.so',   envVar: 'NEXT_PUBLIC_COMPANY_HUB_URL' },\n  { key: 'product-hub',   defaultUrl: 'https://product-hub.flamingo.so',   envVar: 'NEXT_PUBLIC_PRODUCT_HUB_URL' },\n  { key: 'revenue-hub',   defaultUrl: 'https://revenue-hub.flamingo.so',   envVar: 'NEXT_PUBLIC_REVENUE_HUB_URL' },\n  { key: 'people-hub',    defaultUrl: 'https://people-hub.flamingo.so',    envVar: 'NEXT_PUBLIC_PEOPLE_HUB_URL' },\n  { key: 'openmsp',       defaultUrl: 'https://www.openmsp.ai',            envVar: 'NEXT_PUBLIC_OPENMSP_URL' },\n  // ORDERING INVARIANT (first-wins, load-bearing): `flamingo` MUST precede `flamingo-teaser` + `universal`.\n  // All three resolve to www.flamingo.run; the reverse index is first-wins → flamingo claims the shared host,\n  // teaser keeps only its unique flamingo.cx aliases, universal contributes no unique host.\n  // ⚠️ DO NOT REORDER — enforced by the module-load self-check at the bottom of this file.\n  { key: 'flamingo',         defaultUrl: 'https://www.flamingo.run',  envVar: 'NEXT_PUBLIC_FLAMINGO_URL' },\n  { key: 'tmcg',             defaultUrl: 'https://www.tmcg.miami',    envVar: 'NEXT_PUBLIC_TMCG_URL' },\n  { key: 'flamingo-teaser',  defaultUrl: 'https://www.flamingo.run',  envVar: 'NEXT_PUBLIC_FLAMINGO_URL', aliasHostnames: ['flamingo.cx', 'www.flamingo.cx'] },\n  { key: 'openframe',        defaultUrl: 'https://openframe.ai',      envVar: 'NEXT_PUBLIC_OPENFRAME_URL', aliasHostnames: ['openframe.ai', 'www.openframe.ai', 'hub.openframe.ai'] },\n  { key: 'openframe-dashboard', defaultUrl: 'https://openframe.ai',   envVar: 'NEXT_PUBLIC_OPENFRAME_DASHBOARD_URL', pseudo: true },\n  { key: 'universal',        defaultUrl: 'https://www.flamingo.run',  envVar: 'NEXT_PUBLIC_FLAMINGO_URL' },\n]\n\n// ── Compile-time key guards (anchor the table on PlatformName, both directions) ──\n// (1) Removal/typo guard: every table key must be a valid PlatformDomainKey.\nconst _tableSatisfies = PLATFORM_DOMAINS satisfies readonly PlatformDomainEntry[]\nvoid _tableSatisfies\n// (2) Addition guard: a NEW PlatformName member that lacks a table row fails the build.\ntype _MissingKey = Exclude<PlatformName, (typeof PLATFORM_DOMAINS)[number]['key']>\nconst _exhaustive: [_MissingKey] extends [never] ? true : false = true\nvoid _exhaustive\n\n// ── Env overrides (the ONLY place env URLs enter — literal-key inlined + compile-time-guarded) ──\n// `process.env.NEXT_PUBLIC_X` is build-inlined ONLY with a LITERAL key, so the env-var name is the\n// irreducible two-copy (the registry `envVar` column + the literal access below). The `satisfies` makes\n// `tsc`/`next build` FAIL if this map is missing a registry env var OR carries a stale one (bidirectional).\ntype EnvVarKey = (typeof PLATFORM_DOMAINS)[number]['envVar']\nconst ENV_OVERRIDES = {\n  NEXT_PUBLIC_MARKETING_HUB_URL: process.env.NEXT_PUBLIC_MARKETING_HUB_URL,\n  NEXT_PUBLIC_COMPANY_HUB_URL: process.env.NEXT_PUBLIC_COMPANY_HUB_URL,\n  NEXT_PUBLIC_PRODUCT_HUB_URL: process.env.NEXT_PUBLIC_PRODUCT_HUB_URL,\n  NEXT_PUBLIC_REVENUE_HUB_URL: process.env.NEXT_PUBLIC_REVENUE_HUB_URL,\n  NEXT_PUBLIC_PEOPLE_HUB_URL: process.env.NEXT_PUBLIC_PEOPLE_HUB_URL,\n  NEXT_PUBLIC_OPENMSP_URL: process.env.NEXT_PUBLIC_OPENMSP_URL,\n  NEXT_PUBLIC_FLAMINGO_URL: process.env.NEXT_PUBLIC_FLAMINGO_URL,\n  NEXT_PUBLIC_TMCG_URL: process.env.NEXT_PUBLIC_TMCG_URL,\n  NEXT_PUBLIC_OPENFRAME_URL: process.env.NEXT_PUBLIC_OPENFRAME_URL,\n  NEXT_PUBLIC_OPENFRAME_DASHBOARD_URL: process.env.NEXT_PUBLIC_OPENFRAME_DASHBOARD_URL,\n} satisfies Record<EnvVarKey, string | undefined>\n\n/** The registry entry for a key (undefined for an unknown key). */\nexport function byKey(key: string): PlatformDomainEntry | undefined {\n  return PLATFORM_DOMAINS.find((e) => e.key === key)\n}\n\n/** Read a platform's `NEXT_PUBLIC_*_URL` override (or null). */\nfunction envOverrideFor(key: string): string | null {\n  const envVar = byKey(key)?.envVar\n  if (!envVar) return null\n  return (ENV_OVERRIDES as Record<string, string | undefined>)[envVar] || null\n}\n\n/**\n * Ensure a URL string carries a scheme. Per-deploy `NEXT_PUBLIC_*_URL` overrides are\n * stored SCHEME-LESS (bare host, e.g. `www.openmsp.ai` / `hub.openframe.ai`) — the\n * canonical convention in the Vercel shared-env store. This normalizes them to a full\n * `https://` URL so every downstream consumer (`hostOf`/`new URL`, hrefs, the cookie\n * base-domain derivation, CSP) receives a parseable URL. Full-URL inputs (the registry\n * `defaultUrl`s, any scheme'd override) pass through unchanged.\n *\n * EXPORTED as the single owner of the scheme-normalization rule (next.config.mjs keeps a\n * byte-identical local copy ONLY because Next evaluates its config outside the TS module\n * graph and cannot import this — see the comment there).\n *\n * Handles a (theoretical) protocol-relative `//host` too: strips the leading slashes so it\n * doesn't become `https:////host` (empty-host → hostOf null → silent platform drop).\n */\nexport function ensureScheme(url: string): string {\n  const trimmed = url.trim()\n  if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed)) return trimmed // already has a scheme\n  return `https://${trimmed.replace(/^\\/+/, '')}` // bare host or protocol-relative `//host`\n}\n\n/**\n * Canonical production URL for a platform: env override wins, else the `defaultUrl`.\n * NEVER throws / undefined — the default guarantees a host (this is what keeps the\n * cookie base-domains, the reverse map, and CSP intact even with every override unset).\n * The result ALWAYS carries a scheme (`ensureScheme`), so the scheme-less env overrides\n * resolve to valid URLs. Unknown-key fallback preserves cn.ts's flamingo.run default.\n */\nexport function getPlatformProductionUrl(platform: string): string {\n  const resolved =\n    envOverrideFor(platform) ??\n    byKey(platform)?.defaultUrl ??\n    envOverrideFor('flamingo') ??\n    'https://www.flamingo.run'\n  return ensureScheme(resolved)\n}\n\n// ── Single-owner host primitives ──\n\n/** Canonical URL→host parser: `.hostname` (PORT-STRIPPED, lowercased), null on parse failure. */\nexport function hostOf(value: string | null | undefined): string | null {\n  if (!value) return null\n  try {\n    return new URL(value).hostname.toLowerCase()\n  } catch {\n    return null\n  }\n}\n\n/** Expand a host into its `www.`/apex pair. 3+-label and single-label hosts return `[host]`. */\nexport function expandWwwApex(host: string): string[] {\n  if (host.startsWith('www.')) return [host, host.slice(4)]\n  if (host.split('.').length === 2) return [host, `www.${host}`]\n  return [host]\n}\n\n/** Registrable base domain (`parts.slice(-2).join('.')`), dotless; undefined for <2-label. */\nexport function toRegistrableBaseDomain(host: string): string | undefined {\n  const parts = host.split('.')\n  if (parts.length >= 2) return parts.slice(-2).join('.')\n  return undefined\n}\n\n/** An entry's alias hosts (single-owner reader). */\nexport function aliasHostsOf(key: string): string[] {\n  return byKey(key)?.aliasHostnames ?? []\n}\n\n/** All hosts an entry contributes to the reverse index (resolved host + optional aliases). */\nfunction hostsForEntry(entry: PlatformDomainEntry, opts: { includeAliases: boolean }): string[] {\n  const resolved = hostOf(getPlatformProductionUrl(entry.key))\n  const hosts = resolved ? expandWwwApex(resolved) : []\n  if (opts.includeAliases) hosts.push(...aliasHostsOf(entry.key))\n  return hosts\n}\n\n/**\n * Reverse resolver: hostname → platform key (first-wins over registry order, non-pseudo only).\n * Guarantees openframe.ai / www.openframe.ai / hub.openframe.ai → openframe in every env.\n * Replaces the hub `PLATFORM_DOMAIN_MAP`.\n */\nexport function getPlatformByHostname(hostname: string): PlatformDomainKey | null {\n  const host = hostname.toLowerCase()\n  for (const entry of PLATFORM_DOMAINS) {\n    if (entry.pseudo) continue\n    if (hostsForEntry(entry, { includeAliases: true }).includes(host)) return entry.key\n  }\n  return null\n}\n\n// ── Preview detection (two distinct predicates — env-form vs host-form) ──\n\n/** Env-form preview predicate (Vercel `VERCEL_ENV`). */\nexport function isPreviewEnv(): boolean {\n  return process.env.VERCEL_ENV === 'preview'\n}\n\n/** Host-form preview predicate (a `*.vercel.app` host). Dot-bounded suffix so a\n *  malicious `foo.vercel.app.evil.com` is NOT treated as preview. */\nexport function isPreviewHost(hostname: string): boolean {\n  return hostname.endsWith('.vercel.app')\n}\n\n/**\n * ALL unique cookie base domains (the cross-subdomain SSO mechanism).\n *\n * NON-PURE (the sole such export): reads `typeof window` + `process.env` to\n * preserve byte-identical cookie behavior. Keeps the original three branches:\n *   1. localhost / private IP → [] (host-only cookies)\n *   2. Vercel preview (env OR host) → ['.vercel.app','vercel.app']\n *   3. production → for each non-pseudo platform, registrable base of its resolved\n *      host, emitted as both `.base` and bare `base`.\n *\n * Because `getPlatformProductionUrl` always yields a host (override OR default),\n * `.flamingo.so` / `.flamingo.run` / `.openmsp.ai` / `.tmcg.miami` / `.openframe.ai`\n * are ALWAYS present → cross-hub SSO is byte-identical to today.\n */\nexport function getAllPlatformBaseDomains(): string[] {\n  if (typeof window === 'undefined') return []\n\n  const hostname = window.location.hostname\n\n  // Case 1: localhost / private IP — no domains\n  if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('127.')) {\n    return []\n  }\n\n  // Case 2: Vercel preview — vercel.app domain\n  const previewEnv =\n    process.env.VERCEL_ENV === 'preview' ||\n    process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' ||\n    isPreviewHost(hostname)\n  if (previewEnv) {\n    return ['.vercel.app', 'vercel.app']\n  }\n\n  // Case 3: production — registrable base of every non-pseudo platform's resolved host\n  const baseDomains = new Set<string>()\n  for (const entry of PLATFORM_DOMAINS) {\n    if (entry.pseudo) continue\n    const host = hostOf(getPlatformProductionUrl(entry.key))\n    if (!host) continue\n    const base = toRegistrableBaseDomain(host)\n    if (base) {\n      baseDomains.add(`.${base}`)\n      baseDomains.add(base)\n    }\n  }\n  return Array.from(baseDomains)\n}\n\n// ── Cookie-domain guard + match (single owner; shared by the client cookie-domain.ts +\n//    server-only cookie-domain-server.ts resolvers, which previously hand-rolled both 3×) ──\n\n/**\n * Hosts that must NOT receive a `Domain=` cookie → the caller returns undefined (host-only):\n * localhost, loopback/private IPs, and any `*.vercel.app`. `vercel.app` is on the Public Suffix\n * List, so browsers SILENTLY drop `Set-Cookie: Domain=.vercel.app` — which broke the PKCE verifier\n * + session cookies on preview deploys. Host-only is sufficient there (the same preview host\n * round-trips the OAuth chain); production hosts (`.flamingo.so`/`.openmsp.ai`/…) fall through.\n */\nexport function isNonCookieableHost(hostname: string): boolean {\n  return (\n    hostname === 'localhost' ||\n    hostname === '127.0.0.1' ||\n    hostname.startsWith('127.') ||\n    hostname.startsWith('192.168.') ||\n    hostname.startsWith('10.') ||\n    hostname.includes('.vercel.app')\n  )\n}\n\n/**\n * Match a hostname against a set of registrable base domains → the dotted cookie `Domain`\n * (`.flamingo.so`), or undefined when none contains the host. Accepts bases with or without a\n * leading dot and always returns the dotted form. Single owner for the match loop both resolvers ran.\n */\nexport function matchCookieDomain(hostname: string, baseDomains: string[]): string | undefined {\n  for (const domain of baseDomains) {\n    const bare = domain.startsWith('.') ? domain.slice(1) : domain\n    if (hostname === bare || hostname.endsWith(`.${bare}`)) {\n      return domain.startsWith('.') ? domain : `.${domain}`\n    }\n  }\n  return undefined\n}\n\n// ── Module-load ordering self-check (NON-fatal — this module is imported by cn.ts → ~everything,\n// so a hard throw would be a total outage if the assertion were ever over-strict). Checks the REAL\n// invariant — table order — STRUCTURALLY (findIndex), NOT via getPlatformByHostname on a hardcoded\n// host: the latter false-positives when NEXT_PUBLIC_FLAMINGO_URL is overridden to a non-default host\n// (flamingo's resolved host changes, so `www.flamingo.run` no longer reverse-maps to it though the\n// ordering is fine). The alias checks below ARE env-immune (aliasHostnames are unique per key). The\n// authoritative guard is the reverse-map vitest. ⚠️ keep `flamingo` before `flamingo-teaser`/`universal`.\nconst _orderIdx = (k: PlatformDomainKey) => PLATFORM_DOMAINS.findIndex((e) => e.key === k)\nif (\n  _orderIdx('flamingo') > _orderIdx('flamingo-teaser') ||\n  _orderIdx('flamingo') > _orderIdx('universal') ||\n  getPlatformByHostname('flamingo.cx') !== 'flamingo-teaser' ||\n  getPlatformByHostname('hub.openframe.ai') !== 'openframe'\n) {\n  // eslint-disable-next-line no-console\n  console.error(\n    '[platform-domains] ⚠️ PLATFORM_DOMAINS ordering invariant violated — `flamingo` must precede ' +\n      '`flamingo-teaser`/`universal`, and the openframe/teaser aliases must be intact. Do not reorder the table.',\n  )\n}\n","import { clsx, type ClassValue } from \"clsx\"\nimport { extendTailwindMerge } from \"tailwind-merge\"\n// Platform→domain resolution moved to the SSOT module `src/platform-domains.ts`.\n// `getPlatformProductionUrl` / `getAllPlatformBaseDomains` now live there (re-exported\n// via the utils barrel for existing callers); `getBaseUrl` stays here because it owns the\n// dev-localhost + Vercel-self-origin branches, and delegates its platform branch.\nimport { getPlatformProductionUrl } from \"../platform-domains\"\n\n/**\n * EVERY custom `text-*` utility we add in `tailwind.config.ts` MUST be listed here.\n *\n * tailwind-merge has no knowledge of our utilities, and `text-<word>` is\n * indistinguishable from a text COLOUR to it. So an unlisted `text-badge` gets\n * filed in tailwind-merge's `text-color` group, where it and any real colour\n * class silently annihilate each other — last one wins, the other is dropped\n * from the output entirely:\n *\n *   cn('text-badge', 'text-ods-text-on-accent')  -> 'text-ods-text-on-accent'  (size lost)\n *   cn('text-[--some-colour]', 'text-badge')     -> 'text-badge'               (colour lost)\n *\n * Listing them in their own group makes them non-conflicting with colours, so\n * both survive. This is why `text-h1`…`text-h6` are here; `text-code` and\n * `text-badge` were added to the config later and missing them shipped exactly\n * the two failures above (huge badges, and badge text losing its colour).\n */\nconst twMerge = extendTailwindMerge<'ods-typography'>({\n  extend: {\n    classGroups: {\n      'ods-typography': [\n        'text-h1',\n        'text-h2',\n        'text-h3',\n        'text-h4',\n        'text-h5',\n        'text-h6',\n        'text-code',\n        'text-badge',\n      ],\n    },\n  },\n})\n\n/**\n * Combine class names with Tailwind's merge utility\n */\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n\n/**\n * Get the application base URL for the current environment\n *\n * @param platform - Optional platform name (openmsp, flamingo, tmcg, openframe, etc.)\n * @returns The base URL with protocol (https:// or http://)\n *\n * Priority order:\n * 1. Development (http://localhost:3000)\n * 2. Platform-specific URL via the SSOT (if `platform` provided) — env override ?? default\n * 3. VERCEL_PROJECT_PRODUCTION_URL (Vercel production domain)\n * 4. Production fallback: the DEPLOYING platform's canonical URL (NEXT_PUBLIC_APP_TYPE),\n *    openmsp if unset — sourced from the SSOT, no hardcoded literal.\n *\n * The per-platform canonical URLs + their `NEXT_PUBLIC_*_URL` overrides are the single\n * source of truth in `src/platform-domains.ts` (`PLATFORM_DOMAINS`).\n *\n * @example\n * getBaseUrl() // Current deployment's URL\n * getBaseUrl('flamingo') // https://www.flamingo.run (prod) or http://localhost:3000 (dev)\n */\nexport function getBaseUrl(platform?: string): string {\n  // In development, always use localhost (regardless of platform)\n  if (process.env.NODE_ENV !== 'production') {\n    return process.env.NEXT_PUBLIC_DEV_URL || 'http://localhost:3000'\n  }\n\n  // If platform is specified, return its production URL (env override ?? default)\n  if (platform) {\n    return getPlatformProductionUrl(platform)\n  }\n\n  // Production: Use Vercel domain if available\n  if (process.env.VERCEL_PROJECT_PRODUCTION_URL) {\n    return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`\n  }\n\n  // Production fallback: the deploying platform's canonical www domain (avoids Google\n  // \"Page with redirect\"). Derived from the SSOT for the current app type (openmsp when\n  // unset → 'https://www.openmsp.ai', byte-identical to the old hardcoded fallback).\n  return getPlatformProductionUrl(process.env.NEXT_PUBLIC_APP_TYPE || 'openmsp')\n}\n"]}