{"version":3,"sources":["../src/store/themeStore.ts","../src/utils/cookieUtils.ts","../src/store/themeStorage.ts"],"sourcesContent":["import { create } from 'zustand';\nimport { createJSONStorage, devtools, persist } from 'zustand/middleware';\nimport { themeCookieStorage } from './themeStorage';\n\nexport type ThemeMode = 'light' | 'dark' | 'system';\n\n/**\n * Theme store state interface\n */\nexport interface ThemeState {\n  /**\n   * Current theme mode\n   */\n  themeMode: ThemeMode;\n  /**\n   * Whether the current theme is dark\n   */\n  isDark: boolean;\n}\n\n/**\n * Theme store actions interface\n */\nexport interface ThemeActions {\n  /**\n   * Apply theme based on the mode selected\n   */\n  applyTheme: (mode: ThemeMode) => void;\n  /**\n   * Toggle between themes\n   */\n  toggleTheme: () => void;\n  /**\n   * Set a specific theme mode\n   */\n  setTheme: (mode: ThemeMode) => void;\n  /**\n   * Set the white-label theme from institution branding\n   */\n  setWhiteLabelTheme: (theme: string | null) => void;\n  /**\n   * Clear the white-label theme and revert to default\n   */\n  clearWhiteLabelTheme: () => void;\n  /**\n   * Initialize theme on app start\n   */\n  initializeTheme: () => void;\n  /**\n   * Handle system theme change\n   */\n  handleSystemThemeChange: () => void;\n}\n\nexport type ThemeStore = ThemeState & ThemeActions;\n\n/**\n * Mapa de tema light institucional → tema dark correspondente.\n * Adicionar entradas aqui ao introduzir novas instituições.\n */\nconst DARK_THEME_MAP: Record<string, string> = {\n  'base-light': 'base-dark',\n  'enem-parana-light': 'enem-parana-dark',\n  'enem-paraiba-light': 'enem-paraiba-dark',\n  'analytica-light': 'analytica-dark',\n  'papole-light': 'papole-dark',\n};\n\n/**\n * Resolve o seletor CSS dark concreto com base no tema institucional (light)\n * salvo em `data-original-theme`. Fallback: 'base-dark', que também responde\n * ao seletor legado [data-theme='dark'] preservando compat com apps antigos.\n */\nconst resolveDarkTheme = (originalTheme: string | undefined): string => {\n  if (!originalTheme) return 'base-dark';\n  return DARK_THEME_MAP[originalTheme] ?? 'base-dark';\n};\n\n/**\n * Apply theme to DOM based on mode\n */\nconst applyThemeToDOM = (mode: ThemeMode): boolean => {\n  const htmlElement = document.documentElement;\n  const originalTheme = htmlElement.dataset.originalTheme;\n\n  if (mode === 'dark') {\n    htmlElement.dataset.theme = resolveDarkTheme(originalTheme);\n    return true;\n  } else if (mode === 'light') {\n    if (originalTheme) {\n      htmlElement.dataset.theme = originalTheme;\n    }\n    return false;\n  } else if (mode === 'system') {\n    const isSystemDark = window.matchMedia(\n      '(prefers-color-scheme: dark)'\n    ).matches;\n    if (isSystemDark) {\n      htmlElement.dataset.theme = resolveDarkTheme(originalTheme);\n      return true;\n    } else if (originalTheme) {\n      htmlElement.dataset.theme = originalTheme;\n      return false;\n    }\n  }\n  return false;\n};\n\n/**\n * Save original theme from white label (reads from HTML meta tag or data-theme attribute)\n */\nconst saveOriginalTheme = () => {\n  const htmlElement = document.documentElement;\n  const currentTheme =\n    htmlElement.dataset.theme ||\n    document.querySelector('meta[name=\"theme\"]')?.getAttribute('content');\n\n  if (currentTheme && !htmlElement.dataset.originalTheme) {\n    htmlElement.dataset.originalTheme = currentTheme;\n  }\n};\n\n/**\n * Theme store using Zustand with persistence\n */\nexport const useThemeStore = create<ThemeStore>()(\n  devtools(\n    persist(\n      (set, get) => ({\n        // Initial state\n        themeMode: 'system',\n        isDark: false,\n\n        // Actions\n        applyTheme: (mode: ThemeMode) => {\n          const isDark = applyThemeToDOM(mode);\n          set({ isDark });\n        },\n\n        toggleTheme: () => {\n          const { themeMode, applyTheme } = get();\n          let newMode: ThemeMode;\n\n          if (themeMode === 'light') {\n            newMode = 'dark';\n          } else if (themeMode === 'dark') {\n            newMode = 'light';\n          } else {\n            // Se estiver em 'system', vai para 'dark'\n            newMode = 'dark';\n          }\n\n          set({ themeMode: newMode });\n          applyTheme(newMode);\n        },\n\n        setTheme: (mode: ThemeMode) => {\n          const { applyTheme } = get();\n          set({ themeMode: mode });\n          applyTheme(mode);\n        },\n\n        setWhiteLabelTheme: (theme: string | null) => {\n          const htmlElement = document.documentElement;\n\n          if (theme) {\n            // Set the white-label theme as the original theme\n            htmlElement.dataset.originalTheme = theme;\n\n            // Apply theme based on current mode\n            const { themeMode, applyTheme } = get();\n            if (themeMode === 'light' || themeMode === 'system') {\n              htmlElement.dataset.theme = theme;\n            }\n\n            applyTheme(themeMode);\n          }\n        },\n\n        clearWhiteLabelTheme: () => {\n          const htmlElement = document.documentElement;\n\n          // Remove white-label theme attributes\n          delete htmlElement.dataset.originalTheme;\n          delete htmlElement.dataset.theme;\n\n          // Restore original theme from page metadata\n          saveOriginalTheme();\n\n          // Re-apply current theme mode to use defaults\n          const { themeMode, applyTheme } = get();\n          applyTheme(themeMode);\n        },\n\n        initializeTheme: () => {\n          const { themeMode, applyTheme } = get();\n\n          // Save original theme from white label\n          saveOriginalTheme();\n\n          // Apply the current theme mode\n          applyTheme(themeMode);\n        },\n\n        handleSystemThemeChange: () => {\n          const { themeMode, applyTheme } = get();\n          // Only respond to system changes when in system mode\n          if (themeMode === 'system') {\n            applyTheme('system');\n          }\n        },\n      }),\n      {\n        name: 'theme-store', // Storage key (cookie + localStorage)\n        // Root-domain cookie so login and app subdomains share the theme\n        storage: createJSONStorage(() => themeCookieStorage),\n        partialize: (state) => ({\n          themeMode: state.themeMode,\n        }), // Só persiste o themeMode, não o isDark\n      }\n    ),\n    {\n      name: 'theme-store',\n    }\n  )\n);\n","/**\n * Options for writing a cookie\n */\nexport interface CookieOptions {\n  /**\n   * Cookie Domain attribute; omitted when undefined (host-only cookie)\n   */\n  domain?: string;\n  /**\n   * Cookie Path attribute (default '/')\n   */\n  path?: string;\n  /**\n   * Max-Age in seconds\n   */\n  maxAge?: number;\n  /**\n   * SameSite attribute (default 'Lax')\n   */\n  sameSite?: 'Lax' | 'Strict' | 'None';\n  /**\n   * Adds the Secure attribute when true\n   */\n  secure?: boolean;\n}\n\n/**\n * Reads a cookie value by name\n *\n * @param name - The cookie name\n * @returns The decoded cookie value, or null when absent or malformed\n */\nexport const getCookie = (name: string): string | null => {\n  if (typeof document === 'undefined') {\n    return null;\n  }\n  const prefix = `${encodeURIComponent(name)}=`;\n  const match = document.cookie\n    .split('; ')\n    .find((entry) => entry.startsWith(prefix));\n  if (!match) {\n    return null;\n  }\n  try {\n    return decodeURIComponent(match.slice(prefix.length));\n  } catch {\n    return null; // malformed percent-encoding\n  }\n};\n\n/**\n * Writes a cookie with the given options\n *\n * @param name - The cookie name\n * @param value - The cookie value (URL-encoded before writing)\n * @param options - Cookie attributes (domain, path, maxAge, sameSite, secure)\n */\nexport const setCookie = (\n  name: string,\n  value: string,\n  options: CookieOptions = {}\n): void => {\n  if (typeof document === 'undefined') {\n    return;\n  }\n  const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];\n  parts.push(`Path=${options.path ?? '/'}`);\n  if (options.maxAge !== undefined) {\n    parts.push(`Max-Age=${options.maxAge}`);\n  }\n  if (options.domain) {\n    parts.push(`Domain=${options.domain}`);\n  }\n  parts.push(`SameSite=${options.sameSite ?? 'Lax'}`);\n  if (options.secure) {\n    parts.push('Secure');\n  }\n  document.cookie = parts.join('; ');\n};\n\n/**\n * Removes a cookie by expiring it immediately\n *\n * @param name - The cookie name\n * @param options - Domain and path must match the cookie being removed\n */\nexport const removeCookie = (\n  name: string,\n  options: Pick<CookieOptions, 'domain' | 'path'> = {}\n): void => {\n  setCookie(name, '', { ...options, maxAge: 0 });\n};\n","import type { StateStorage } from 'zustand/middleware';\nimport { getCookie, setCookie, removeCookie } from '../utils/cookieUtils';\nimport { resolveRootHostname } from '../utils/domainUtils';\n\nconst ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365;\n\n/**\n * Resolves the cookie Domain attribute for the current location.\n * Returns undefined on localhost / IP literals so the cookie stays host-only.\n *\n * @returns The root domain for the cookie, or undefined\n */\nconst cookieDomain = (): string | undefined =>\n  resolveRootHostname(globalThis.location.hostname) ?? undefined;\n\n/**\n * Reads a value from localStorage without throwing\n * (storage can be disabled in privacy mode or sandboxed iframes)\n *\n * @param name - The storage key\n * @returns The stored value, or null when absent or unavailable\n */\nconst readLocalStorage = (name: string): string | null => {\n  try {\n    return globalThis.localStorage.getItem(name);\n  } catch {\n    return null;\n  }\n};\n\n/**\n * Returns the value only when it contains valid JSON, null otherwise\n * (guards against corrupted persisted values)\n *\n * @param raw - The raw persisted value, or null when absent\n * @returns The same value when it is valid JSON, or null\n */\nconst validJsonOrNull = (raw: string | null): string | null => {\n  if (raw === null) {\n    return null;\n  }\n  try {\n    JSON.parse(raw);\n    return raw;\n  } catch {\n    return null;\n  }\n};\n\n/**\n * Zustand StateStorage that persists theme state in a cookie scoped to the\n * root domain, so the login app (root domain) and the profile apps\n * (subdomains) all share the chosen theme. Reads fall back to localStorage\n * for users that persisted the theme before the cookie storage existed, and\n * writes go to both stores for backward compatibility with older library\n * versions that read only localStorage.\n */\nexport const themeCookieStorage: StateStorage = {\n  getItem: (name: string): string | null => {\n    if (typeof document === 'undefined') {\n      return null;\n    }\n    // A corrupted cookie must not block the localStorage fallback\n    return (\n      validJsonOrNull(getCookie(name)) ??\n      validJsonOrNull(readLocalStorage(name))\n    );\n  },\n  setItem: (name: string, value: string): void => {\n    if (typeof document === 'undefined') {\n      return;\n    }\n    setCookie(name, value, {\n      domain: cookieDomain(),\n      path: '/',\n      maxAge: ONE_YEAR_IN_SECONDS,\n      sameSite: 'Lax',\n      secure: globalThis.location.protocol === 'https:',\n    });\n    try {\n      globalThis.localStorage.setItem(name, value);\n    } catch {\n      // localStorage unavailable; the cookie is the source of truth\n    }\n  },\n  removeItem: (name: string): void => {\n    if (typeof document === 'undefined') {\n      return;\n    }\n    removeCookie(name, { domain: cookieDomain(), path: '/' });\n    try {\n      globalThis.localStorage.removeItem(name);\n    } catch {\n      // ignore: nothing to clean up when storage is unavailable\n    }\n  },\n};\n"],"mappings":";;;;;AAAA,SAAS,cAAc;AACvB,SAAS,mBAAmB,UAAU,eAAe;;;AC+B9C,IAAM,YAAY,CAAC,SAAgC;AACxD,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO;AAAA,EACT;AACA,QAAM,SAAS,GAAG,mBAAmB,IAAI,CAAC;AAC1C,QAAM,QAAQ,SAAS,OACpB,MAAM,IAAI,EACV,KAAK,CAAC,UAAU,MAAM,WAAW,MAAM,CAAC;AAC3C,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,mBAAmB,MAAM,MAAM,OAAO,MAAM,CAAC;AAAA,EACtD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASO,IAAM,YAAY,CACvB,MACA,OACA,UAAyB,CAAC,MACjB;AACT,MAAI,OAAO,aAAa,aAAa;AACnC;AAAA,EACF;AACA,QAAM,QAAQ,CAAC,GAAG,mBAAmB,IAAI,CAAC,IAAI,mBAAmB,KAAK,CAAC,EAAE;AACzE,QAAM,KAAK,QAAQ,QAAQ,QAAQ,GAAG,EAAE;AACxC,MAAI,QAAQ,WAAW,QAAW;AAChC,UAAM,KAAK,WAAW,QAAQ,MAAM,EAAE;AAAA,EACxC;AACA,MAAI,QAAQ,QAAQ;AAClB,UAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;AAAA,EACvC;AACA,QAAM,KAAK,YAAY,QAAQ,YAAY,KAAK,EAAE;AAClD,MAAI,QAAQ,QAAQ;AAClB,UAAM,KAAK,QAAQ;AAAA,EACrB;AACA,WAAS,SAAS,MAAM,KAAK,IAAI;AACnC;AAQO,IAAM,eAAe,CAC1B,MACA,UAAkD,CAAC,MAC1C;AACT,YAAU,MAAM,IAAI,EAAE,GAAG,SAAS,QAAQ,EAAE,CAAC;AAC/C;;;ACvFA,IAAM,sBAAsB,KAAK,KAAK,KAAK;AAQ3C,IAAM,eAAe,MACnB,oBAAoB,WAAW,SAAS,QAAQ,KAAK;AASvD,IAAM,mBAAmB,CAAC,SAAgC;AACxD,MAAI;AACF,WAAO,WAAW,aAAa,QAAQ,IAAI;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,IAAM,kBAAkB,CAAC,QAAsC;AAC7D,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI;AACF,SAAK,MAAM,GAAG;AACd,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUO,IAAM,qBAAmC;AAAA,EAC9C,SAAS,CAAC,SAAgC;AACxC,QAAI,OAAO,aAAa,aAAa;AACnC,aAAO;AAAA,IACT;AAEA,WACE,gBAAgB,UAAU,IAAI,CAAC,KAC/B,gBAAgB,iBAAiB,IAAI,CAAC;AAAA,EAE1C;AAAA,EACA,SAAS,CAAC,MAAc,UAAwB;AAC9C,QAAI,OAAO,aAAa,aAAa;AACnC;AAAA,IACF;AACA,cAAU,MAAM,OAAO;AAAA,MACrB,QAAQ,aAAa;AAAA,MACrB,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ,WAAW,SAAS,aAAa;AAAA,IAC3C,CAAC;AACD,QAAI;AACF,iBAAW,aAAa,QAAQ,MAAM,KAAK;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EACA,YAAY,CAAC,SAAuB;AAClC,QAAI,OAAO,aAAa,aAAa;AACnC;AAAA,IACF;AACA,iBAAa,MAAM,EAAE,QAAQ,aAAa,GAAG,MAAM,IAAI,CAAC;AACxD,QAAI;AACF,iBAAW,aAAa,WAAW,IAAI;AAAA,IACzC,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AFpCA,IAAM,iBAAyC;AAAA,EAC7C,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,gBAAgB;AAClB;AAOA,IAAM,mBAAmB,CAAC,kBAA8C;AACtE,MAAI,CAAC,cAAe,QAAO;AAC3B,SAAO,eAAe,aAAa,KAAK;AAC1C;AAKA,IAAM,kBAAkB,CAAC,SAA6B;AACpD,QAAM,cAAc,SAAS;AAC7B,QAAM,gBAAgB,YAAY,QAAQ;AAE1C,MAAI,SAAS,QAAQ;AACnB,gBAAY,QAAQ,QAAQ,iBAAiB,aAAa;AAC1D,WAAO;AAAA,EACT,WAAW,SAAS,SAAS;AAC3B,QAAI,eAAe;AACjB,kBAAY,QAAQ,QAAQ;AAAA,IAC9B;AACA,WAAO;AAAA,EACT,WAAW,SAAS,UAAU;AAC5B,UAAM,eAAe,OAAO;AAAA,MAC1B;AAAA,IACF,EAAE;AACF,QAAI,cAAc;AAChB,kBAAY,QAAQ,QAAQ,iBAAiB,aAAa;AAC1D,aAAO;AAAA,IACT,WAAW,eAAe;AACxB,kBAAY,QAAQ,QAAQ;AAC5B,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAKA,IAAM,oBAAoB,MAAM;AAC9B,QAAM,cAAc,SAAS;AAC7B,QAAM,eACJ,YAAY,QAAQ,SACpB,SAAS,cAAc,oBAAoB,GAAG,aAAa,SAAS;AAEtE,MAAI,gBAAgB,CAAC,YAAY,QAAQ,eAAe;AACtD,gBAAY,QAAQ,gBAAgB;AAAA,EACtC;AACF;AAKO,IAAM,gBAAgB,OAAmB;AAAA,EAC9C;AAAA,IACE;AAAA,MACE,CAAC,KAAK,SAAS;AAAA;AAAA,QAEb,WAAW;AAAA,QACX,QAAQ;AAAA;AAAA,QAGR,YAAY,CAAC,SAAoB;AAC/B,gBAAM,SAAS,gBAAgB,IAAI;AACnC,cAAI,EAAE,OAAO,CAAC;AAAA,QAChB;AAAA,QAEA,aAAa,MAAM;AACjB,gBAAM,EAAE,WAAW,WAAW,IAAI,IAAI;AACtC,cAAI;AAEJ,cAAI,cAAc,SAAS;AACzB,sBAAU;AAAA,UACZ,WAAW,cAAc,QAAQ;AAC/B,sBAAU;AAAA,UACZ,OAAO;AAEL,sBAAU;AAAA,UACZ;AAEA,cAAI,EAAE,WAAW,QAAQ,CAAC;AAC1B,qBAAW,OAAO;AAAA,QACpB;AAAA,QAEA,UAAU,CAAC,SAAoB;AAC7B,gBAAM,EAAE,WAAW,IAAI,IAAI;AAC3B,cAAI,EAAE,WAAW,KAAK,CAAC;AACvB,qBAAW,IAAI;AAAA,QACjB;AAAA,QAEA,oBAAoB,CAAC,UAAyB;AAC5C,gBAAM,cAAc,SAAS;AAE7B,cAAI,OAAO;AAET,wBAAY,QAAQ,gBAAgB;AAGpC,kBAAM,EAAE,WAAW,WAAW,IAAI,IAAI;AACtC,gBAAI,cAAc,WAAW,cAAc,UAAU;AACnD,0BAAY,QAAQ,QAAQ;AAAA,YAC9B;AAEA,uBAAW,SAAS;AAAA,UACtB;AAAA,QACF;AAAA,QAEA,sBAAsB,MAAM;AAC1B,gBAAM,cAAc,SAAS;AAG7B,iBAAO,YAAY,QAAQ;AAC3B,iBAAO,YAAY,QAAQ;AAG3B,4BAAkB;AAGlB,gBAAM,EAAE,WAAW,WAAW,IAAI,IAAI;AACtC,qBAAW,SAAS;AAAA,QACtB;AAAA,QAEA,iBAAiB,MAAM;AACrB,gBAAM,EAAE,WAAW,WAAW,IAAI,IAAI;AAGtC,4BAAkB;AAGlB,qBAAW,SAAS;AAAA,QACtB;AAAA,QAEA,yBAAyB,MAAM;AAC7B,gBAAM,EAAE,WAAW,WAAW,IAAI,IAAI;AAEtC,cAAI,cAAc,UAAU;AAC1B,uBAAW,QAAQ;AAAA,UACrB;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,MAAM;AAAA;AAAA;AAAA,QAEN,SAAS,kBAAkB,MAAM,kBAAkB;AAAA,QACnD,YAAY,CAAC,WAAW;AAAA,UACtB,WAAW,MAAM;AAAA,QACnB;AAAA;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,IACR;AAAA,EACF;AACF;","names":[]}