<!DOCTYPE html>
<html lang="en">
  <head>
<!-- Build Info: commit=993ab92 branch=main date="2026-10-30 18:05:43 UTC" -->
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>EasyCompare - Compare Stock Performance</title>
    
    <!--
      ?reset-onboarding wipes everything this site has stored in the browser
      (sessions, comparison lists, onboarding, intro, settings, dev toggles)
      and reloads the app the way a brand-new visitor sees it. Runs before
      every other script so nothing reads stale state. Mirrored in mobile.html.
    -->
    <script>
      (function () {
        try {
          if (!new URLSearchParams(window.location.search).has('reset-onboarding')) return;
          try { localStorage.clear(); } catch (_) {}
          try { sessionStorage.clear(); } catch (_) {}
          window.location.replace('/app');
        } catch (_) {}
      })();
    </script>

    <!-- Sentry Error Monitoring - Full SDK (load first to catch all errors) -->
    <script
      src="https://browser.sentry-cdn.com/8.40.0/bundle.min.js"
      crossorigin="anonymous"
    ></script>
    
    <!--
      Smart device routing.

      Mobile UA / narrow viewport landing on a desktop URL is bounced
      to its path-equivalent mobile URL (preserving session / share /
      raw context). The override flag stops bounces when the user has
      explicitly picked desktop ("Open on desktop" from mobile).

      Inline + pre-paint so we don't flash the desktop SPA before the
      redirect fires. Logic is mirrored in mobile.html, keep them in
      sync. The runtime equivalent lives in js/shared/deviceRouting.js
      for modules that need it.
    -->
    <script>
      (function () {
        try {
          var KEY = 'easycompare-device-pref';

          // Clean up any value persisted by a previous (now-removed)
          // version that wrote the preference to localStorage. Without
          // this, anyone who tapped "Open on desktop" or "Open desktop
          // upgrade page" before is stranded on desktop forever.
          try { localStorage.removeItem(KEY); } catch (_) {}

          // ?desktop=1 / ?mobile=1 lets cross-device share links carry
          // an explicit one-tab intent. Stored in sessionStorage so it
          // expires when the tab closes, then stripped from the URL.
          var url = new URL(window.location.href);
          if (url.searchParams.get('desktop') === '1') {
            try { sessionStorage.setItem(KEY, 'desktop'); } catch (_) {}
            url.searchParams.delete('desktop');
            window.history.replaceState(null, '', url.pathname + (url.search ? url.search : '') + url.hash);
          } else if (url.searchParams.get('mobile') === '1') {
            try { sessionStorage.setItem(KEY, 'mobile'); } catch (_) {}
            url.searchParams.delete('mobile');
            window.history.replaceState(null, '', url.pathname + (url.search ? url.search : '') + url.hash);
          }

          var pref = null;
          try { pref = sessionStorage.getItem(KEY); } catch (_) {}
          // Desktop pref pinned → stay regardless of device.
          if (pref === 'desktop') return;

          var isMobile = window.innerWidth < 768 ||
            /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
          // Not mobile and no override → stay on desktop (the common case).
          if (!isMobile && pref !== 'mobile') return;

          // Translate the current path to its mobile equivalent. Only
          // session / share / raw / app paths have counterparts; admin,
          // upgrade, status, etc. are desktop-only and stay put.
          var p = window.location.pathname;
          var mobilePath = null;
          if (p === '/' || p === '/app' || p === '/app.html' || p === '/index.html' || p === '/mobile.html') {
            mobilePath = '/m';
          } else if (/^\/(c|t|s)\//.test(p)) {
            mobilePath = '/m' + p;
          }
          if (mobilePath && mobilePath !== p) {
            window.location.replace(mobilePath + window.location.search + window.location.hash);
          }
        } catch (_) {
          // Silently fall through: showing desktop is a safer failure
          // mode than a white screen.
        }
      })();
    </script>

    <!-- Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=G-P08F010M5H"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', 'G-P08F010M5H');
    </script>

    <meta name="description" content="Compare stock performance across multiple tickers with interactive charts, advanced metrics, and customizable timeframes." />
    
    <!-- Favicon -->
    <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
    <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
    <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
    
    <!-- Preconnect to external domains for faster loading -->
    <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
    <link rel="preconnect" href="https://cdn.plot.ly" crossorigin />
    <link rel="preconnect" href="https://clerk.quantcommons.com" crossorigin />
    <link rel="preconnect" href="https://clerk.easycompare.io" crossorigin />
    <link rel="preconnect" href="https://go-api.easycompare.io" crossorigin />
    <link rel="preconnect" href="https://simpletrends-session-api.ashishgup.workers.dev" crossorigin />

    <!-- Preload Clerk JS bundle so the browser starts the download during
         HTML parse instead of waiting for app.js to inject the <script> tag.
         Saves ~100-200ms on the auth:init phase, which is on the critical path. -->
    <link rel="preload" as="script" crossorigin
          href="https://clerk.quantcommons.com/npm/@clerk/clerk-js@6/dist/clerk.browser.js" />
    <link rel="preload" as="script" crossorigin
          href="https://clerk.quantcommons.com/npm/@clerk/ui@1/dist/ui.browser.js" />
    
    <link rel="preload" href="/features/easycompare.csv" as="fetch" crossorigin="anonymous" />
    <link rel="stylesheet" href="/css/brand.css" />
    <link rel="stylesheet" href="/css/feature-reel.css" />
    <link rel="stylesheet" href="/css/intro-modal.css" />
    <link rel="stylesheet" href="/css/style.css" />
    <link rel="stylesheet" href="/css/stories.css" />
    <link rel="stylesheet" href="/css/briefing.css" />
    <link rel="stylesheet" href="/css/correlation.css" />
    <link rel="stylesheet" href="/css/tickerSpotlight.css" />
    <!-- Multi-period sparkline modal (shared with discover.html). The JS
         module that owns this modal lives at js/sparkline/tickerSparklineModal.js
         and is loaded on demand by mainAppTable.js when the new sparkline
         column is enabled. Keeping the CSS in a dedicated file (rather than
         folding into style.css) makes the dependency obvious and keeps
         style.css from growing every time we share a component. -->
    <link rel="stylesheet" href="/css/sparkline-modal.css" />
    <!-- Onboarding beacons: numbered, gently-pulsing dots that draw
         attention to features without blocking interaction. Content lives
         in js/onboarding/onboardingTips.js; behavior in
         js/onboarding/onboardingBeacons.js. Initialized from app.js after
         the rest of the UI mounts. -->
    <link rel="stylesheet" href="/css/onboarding.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/css/tom-select.css" />
    <script src="https://cdn.plot.ly/plotly-cartesian-2.32.0.min.js" defer></script>
    <script src="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.complete.min.js" defer></script>
    <!-- marked.js and lucide.js are lazy loaded on demand - see shared/lazyLoad.js -->
    <script>
      // Use production sessions by default even when the frontend runs
      // locally. This makes /c/{code} reproduce the exact production
      // payload on both desktop and mobile for debugging. Opt into the
      // local Worker explicitly with ?sessionApi=local.
      window.EASYCOMPARE_SESSION_API_BASE =
        new URLSearchParams(window.location.search).get("sessionApi") === "local"
          ? "http://127.0.0.1:8787"
          : "https://simpletrends-session-api.ashishgup.workers.dev";
    </script>
    <!-- Local fallback. Deployment replaces this with content versions for
         the complete module graph via scripts/version-modules.mjs.
         Keep this fallback identical in mobile.html. -->
    
    <script type="importmap">{"imports":{"/js/rawMode.js":"/js/rawMode.js?v=bb0980f2ac9a2a6e","/js/shared/shareLinks.js":"/js/shared/shareLinks.js?v=04a619dcf06af089","/js/mobile/boot.js":"/js/mobile/boot.js?v=3aa220a634974eb4","/js/admin.js":"/js/admin.js?v=c13cdb5ef3baaa8e","/js/admin/activityView.js":"/js/admin/activityView.js?v=0a7e4b4eb4487609","/js/admin/analyticsDashboard.js":"/js/admin/analyticsDashboard.js?v=1219263a38c31d83","/js/admin/backendMonitor.js":"/js/admin/backendMonitor.js?v=39b37574fafbc528","/js/admin/billingDashboard.js":"/js/admin/billingDashboard.js?v=4341d03ceb23559c","/js/admin/configurationView.js":"/js/admin/configurationView.js?v=a2621bd31421cc24","/js/app.js":"/js/app.js?v=20306c3ba74ccd1c","/js/backupRestore.js":"/js/backupRestore.js?v=cae54d11cd4de109","/js/baseline.js":"/js/baseline.js?v=aebe87250d6842f6","/js/beta.js":"/js/beta.js?v=b3ea653479111cdd","/js/billing.js":"/js/billing.js?v=d99c761a73bec9e3","/js/briefing/briefingCard.js":"/js/briefing/briefingCard.js?v=c6788567a7dff06b","/js/chart.js":"/js/chart.js?v=280ceb447d6b2a74","/js/chartCheatsheet.js":"/js/chartCheatsheet.js?v=8c220037edb2b4be","/js/chartExport.js":"/js/chartExport.js?v=9c6e72113c94f501","/js/chartFullscreen.js":"/js/chartFullscreen.js?v=63e3f8eb795c31df","/js/chartHighlight.js":"/js/chartHighlight.js?v=5c977bf51ce585e6","/js/chartMetricStrip.js":"/js/chartMetricStrip.js?v=9e3c15ebf2169161","/js/chartPlayback.js":"/js/chartPlayback.js?v=448f3be600c3196c","/js/chartSettings.js":"/js/chartSettings.js?v=0b5e9a58ad5a5d42","/js/chartStatStrip.js":"/js/chartStatStrip.js?v=1f2fe494f791fc8e","/js/chartTabs.js":"/js/chartTabs.js?v=bd83b251e14249cd","/js/collectionsModal.js":"/js/collectionsModal.js?v=7770304b64739428","/js/columnConfigModal.js":"/js/columnConfigModal.js?v=57319ae6ec444d67","/js/commandPalette.js":"/js/commandPalette.js?v=f8d7f5ae526191ea","/js/comparisonSets.js":"/js/comparisonSets.js?v=bfb08097ccecdc81","/js/comparisonSetsUI.js":"/js/comparisonSetsUI.js?v=99e9ac216d5cd300","/js/connectMac.js":"/js/connectMac.js?v=8ccd50190e36bec9","/js/correlationData.js":"/js/correlationData.js?v=e23724f189f56ba4","/js/correlationGroups.js":"/js/correlationGroups.js?v=228bb681ccb4667f","/js/correlationMap.js":"/js/correlationMap.js?v=a9f1e5e4d082dc04","/js/devModal.js":"/js/devModal.js?v=41880f21001a2c80","/js/etfHoldingsUI.js":"/js/etfHoldingsUI.js?v=18e155981a29a7ff","/js/exportPopover.js":"/js/exportPopover.js?v=62d0c727b7dfdf8d","/js/favPopover.js":"/js/favPopover.js?v=bf24b5db121049cc","/js/featureUseCatalog.js":"/js/featureUseCatalog.js?v=da98bb58b6e9455c","/js/featureUseModal.js":"/js/featureUseModal.js?v=189746a980d6df99","/js/features.js":"/js/features.js?v=7064733e68885fe1","/js/feedbackModal.js":"/js/feedbackModal.js?v=c0f639ed04468ee6","/js/heroReel.js":"/js/heroReel.js?v=301e281f3e6b8db0","/js/hintRotator.js":"/js/hintRotator.js?v=0085b70be4c8607e","/js/home.js":"/js/home.js?v=d057f83ab6413ac7","/js/intradayBanner.js":"/js/intradayBanner.js?v=183dabb96e5149c3","/js/intradayValidation.js":"/js/intradayValidation.js?v=b75f86e1079fe550","/js/landing.js":"/js/landing.js?v=45e74867b68641b1","/js/landingFeatureVisibility.js":"/js/landingFeatureVisibility.js?v=eef85288b7e701c3","/js/landingOutline.js":"/js/landingOutline.js?v=daab8be6017a7a42","/js/landingPricing.js":"/js/landingPricing.js?v=0b4e64ebdebcf3cb","/js/landingQuestions.js":"/js/landingQuestions.js?v=95b592212138f1da","/js/legendStates.js":"/js/legendStates.js?v=e8a4a2944c3a11cb","/js/lossProfileModal.js":"/js/lossProfileModal.js?v=9ff28f0ccc4752a2","/js/macLanding.js":"/js/macLanding.js?v=4ff9a6ca812c2982","/js/migration.js":"/js/migration.js?v=cd29a6d6e37145e9","/js/mobile-welcome.js":"/js/mobile-welcome.js?v=caf4c19782acaf6f","/js/mobile/app.js":"/js/mobile/app.js?v=662cba8e2c55716e","/js/mobile/chart.js":"/js/mobile/chart.js?v=0acfb2843e4260d8","/js/mobile/chartExport.js":"/js/mobile/chartExport.js?v=aeacf821e8c4e022","/js/mobile/chartStrips.js":"/js/mobile/chartStrips.js?v=4b107697d6d206c1","/js/mobile/exportOptionsSheet.js":"/js/mobile/exportOptionsSheet.js?v=53475372fe1b816e","/js/mobile/factSheet.js":"/js/mobile/factSheet.js?v=464656faf8840aa8","/js/mobile/helpSheet.js":"/js/mobile/helpSheet.js?v=6ae1019d6454e2c8","/js/mobile/menuSheet.js":"/js/mobile/menuSheet.js?v=ccda31f104dc54a7","/js/mobile/multiPeriodIcon.js":"/js/mobile/multiPeriodIcon.js?v=5285e8fc2b1c38e6","/js/mobile/presetSheet.js":"/js/mobile/presetSheet.js?v=e2d44576fc9cc3f4","/js/mobile/rankingTable.js":"/js/mobile/rankingTable.js?v=f2dcb759acac83a3","/js/mobile/readOnly.js":"/js/mobile/readOnly.js?v=5e7dde4fc8e9940f","/js/mobile/setSheet.js":"/js/mobile/setSheet.js?v=3a9b9224fd25bd18","/js/mobile/shareSheet.js":"/js/mobile/shareSheet.js?v=c447ac92055efc45","/js/mobile/sheet.js":"/js/mobile/sheet.js?v=8ae9fc60f519484f","/js/mobile/tickerSheet.js":"/js/mobile/tickerSheet.js?v=1b87b783b00e4b75","/js/mobile/upgradeSheet.js":"/js/mobile/upgradeSheet.js?v=762fb05418faa2a0","/js/multiPeriodChart.js":"/js/multiPeriodChart.js?v=a273b15f0480af25","/js/multiPeriodControls.js":"/js/multiPeriodControls.js?v=1900d00be05baf13","/js/multiPeriodLegend.js":"/js/multiPeriodLegend.js?v=9e2fcddbb2811a5a","/js/multiPeriodRankHeatmap.js":"/js/multiPeriodRankHeatmap.js?v=27be4d8431352f88","/js/notesModal.js":"/js/notesModal.js?v=ba22446fd0143efb","/js/onboarding.js":"/js/onboarding.js?v=5b899261da472636","/js/onboarding/onboardingBeacons.js":"/js/onboarding/onboardingBeacons.js?v=f9d3301f2f0b1aab","/js/onboarding/onboardingTips.js":"/js/onboarding/onboardingTips.js?v=cc96b28da8a0341a","/js/onboardingReset.js":"/js/onboardingReset.js?v=0b3f866a4838cdc5","/js/passwordLock.js":"/js/passwordLock.js?v=e968145bfd53df30","/js/presetBanner.js":"/js/presetBanner.js?v=b7ead5d3edaa395b","/js/pricing/common.js":"/js/pricing/common.js?v=95c0ecb20f8a4943","/js/pricing/page.js":"/js/pricing/page.js?v=fd03f2c606ea9e8f","/js/pricing/success.js":"/js/pricing/success.js?v=47edd261c63b8a4b","/js/quickSwitcher.js":"/js/quickSwitcher.js?v=0fcd71803a21c7dc","/js/rawModeBanner.js":"/js/rawModeBanner.js?v=5f841cd607ed38b2","/js/readOnlyMode.js":"/js/readOnlyMode.js?v=743c2f1229040f47","/js/resultsOverview.js":"/js/resultsOverview.js?v=0f37b39c83ee89c3","/js/scatterChart.js":"/js/scatterChart.js?v=e466b48733b1224d","/js/scatterControls.js":"/js/scatterControls.js?v=131e5af3a7d88eb3","/js/scatterLegend.js":"/js/scatterLegend.js?v=4489a7cfa2333215","/js/serverStatus.js":"/js/serverStatus.js?v=9d866e150dc351bb","/js/shareMode.js":"/js/shareMode.js?v=4caf57015f07b5ae","/js/shareSet.js":"/js/shareSet.js?v=3d72b8e28dba65e7","/js/shared/activityFeed.js":"/js/shared/activityFeed.js?v=875720ccd20cc9b5","/js/shared/analytics.js":"/js/shared/analytics.js?v=79e5c4fd6e9a9565","/js/shared/api.js":"/js/shared/api.js?v=b8776ec803d30497","/js/shared/apps.js":"/js/shared/apps.js?v=370e7b1a84fd83c7","/js/shared/auth.js":"/js/shared/auth.js?v=46ff50643a2a5921","/js/shared/backendError.js":"/js/shared/backendError.js?v=47855b05e81d9191","/js/shared/benchmarkMultiple.js":"/js/shared/benchmarkMultiple.js?v=31c704e0ea287d9c","/js/shared/billing.js":"/js/shared/billing.js?v=83bf2054f5444e31","/js/shared/brandLogo.js":"/js/shared/brandLogo.js?v=3f0426b35e4dd0da","/js/shared/cache.js":"/js/shared/cache.js?v=3f45c25adab95b46","/js/shared/chartExport/compose.js":"/js/shared/chartExport/compose.js?v=0ab1bf50fb9ce92f","/js/shared/chartExport/exportOptions.js":"/js/shared/chartExport/exportOptions.js?v=5ca8510b5065a4fe","/js/shared/chartExport/exportPeriods.js":"/js/shared/chartExport/exportPeriods.js?v=db6da014128b29b4","/js/shared/chartExport/filename.js":"/js/shared/chartExport/filename.js?v=f027b8ef47ed5bad","/js/shared/chartExport/metricCards.js":"/js/shared/chartExport/metricCards.js?v=2d05fe282988611e","/js/shared/chartExport/returnsTable.js":"/js/shared/chartExport/returnsTable.js?v=7268cf20761f9e7c","/js/shared/chartExport/share.js":"/js/shared/chartExport/share.js?v=77d50d72933a3181","/js/shared/colors.js":"/js/shared/colors.js?v=70c16e68cfaab348","/js/shared/config.js":"/js/shared/config.js?v=5422087d9eb69855","/js/shared/dates.js":"/js/shared/dates.js?v=8dbc55c7b06de5d0","/js/shared/deviceRouting.js":"/js/shared/deviceRouting.js?v=4fe1431b9dd163ae","/js/shared/distributionStrip.js":"/js/shared/distributionStrip.js?v=20bfd817a1720993","/js/shared/etfHoldings.js":"/js/shared/etfHoldings.js?v=21a29cfb48c7b331","/js/shared/etfPicker.js":"/js/shared/etfPicker.js?v=385ddabe82cdbd6c","/js/shared/featureConfig.js":"/js/shared/featureConfig.js?v=4ae019503af7c9b8","/js/shared/featureFlags.js":"/js/shared/featureFlags.js?v=002b59e5ae473d95","/js/shared/featureGate.js":"/js/shared/featureGate.js?v=04671d86b1dde6e1","/js/shared/featureReel.js":"/js/shared/featureReel.js?v=b6db9b9765d364b3","/js/shared/featureReelClips.js":"/js/shared/featureReelClips.js?v=acc1c4b841000877","/js/shared/freshness.js":"/js/shared/freshness.js?v=67e31f24083fad27","/js/shared/freshnessUI.js":"/js/shared/freshnessUI.js?v=2f7d775dbc9fe0a4","/js/shared/helpCard.js":"/js/shared/helpCard.js?v=fc7a2c250f064072","/js/shared/helpModal.js":"/js/shared/helpModal.js?v=6875e1741e869f1b","/js/shared/helpSeen.js":"/js/shared/helpSeen.js?v=eb92999b69fc0724","/js/shared/helpText.js":"/js/shared/helpText.js?v=d84a5e4e52eab564","/js/shared/helpTooltips.js":"/js/shared/helpTooltips.js?v=5acb75cb053fe77e","/js/shared/helpVisuals/annotations.js":"/js/shared/helpVisuals/annotations.js?v=573252ceb681122b","/js/shared/helpVisuals/index.js":"/js/shared/helpVisuals/index.js?v=bd2c8ed704142b04","/js/shared/helpVisuals/primitives.js":"/js/shared/helpVisuals/primitives.js?v=f3ced222a338e4aa","/js/shared/helpVisuals/register.js":"/js/shared/helpVisuals/register.js?v=6bef3367f6f35c4f","/js/shared/helpVisuals/scene.js":"/js/shared/helpVisuals/scene.js?v=f4c422faa368f294","/js/shared/helpVisuals/theme.js":"/js/shared/helpVisuals/theme.js?v=d7614534d712a6a7","/js/shared/helpVisuals/visuals/metricDrawdown.js":"/js/shared/helpVisuals/visuals/metricDrawdown.js?v=ea9673d413a2f57a","/js/shared/helpVisuals/visuals/metricMultiPeriodSummary.js":"/js/shared/helpVisuals/visuals/metricMultiPeriodSummary.js?v=5f78a4f9be60c12c","/js/shared/helpVisuals/visuals/metricVolatility.js":"/js/shared/helpVisuals/visuals/metricVolatility.js?v=5a703a87a5e06286","/js/shared/hints.js":"/js/shared/hints.js?v=2b10ba22c077fc49","/js/shared/iconTooltips.js":"/js/shared/iconTooltips.js?v=d70a46452a522de2","/js/shared/introModal.js":"/js/shared/introModal.js?v=a4847c475ca0c3f3","/js/shared/landingFeatureMap.js":"/js/shared/landingFeatureMap.js?v=e5745c56e0d896bf","/js/shared/lazyLoad.js":"/js/shared/lazyLoad.js?v=8fd29ab38f7729af","/js/shared/legendHeat.js":"/js/shared/legendHeat.js?v=59d539e0af82fc69","/js/shared/logTicks.js":"/js/shared/logTicks.js?v=a29670acdf76c7df","/js/shared/logger.js":"/js/shared/logger.js?v=e33b6632575d4ef6","/js/shared/membership.js":"/js/shared/membership.js?v=b0ff5c50406f910d","/js/shared/netAth.js":"/js/shared/netAth.js?v=68746102b3013fe8","/js/shared/newsletter-modal.js":"/js/shared/newsletter-modal.js?v=f089d5d7f1d44d8c","/js/shared/notesApi.js":"/js/shared/notesApi.js?v=80be26a0dcada491","/js/shared/paretoFrontier.js":"/js/shared/paretoFrontier.js?v=cecf5e6f0a45b95f","/js/shared/perf.js":"/js/shared/perf.js?v=f44b127aa005f727","/js/shared/plans.js":"/js/shared/plans.js?v=76e5e96aeed479e3","/js/shared/pricesRequest.js":"/js/shared/pricesRequest.js?v=3ea95c6e2877c359","/js/shared/rebase.js":"/js/shared/rebase.js?v=de80804ad39b0c95","/js/shared/relatedTickers.js":"/js/shared/relatedTickers.js?v=47eef09185f1fe4b","/js/shared/session.js":"/js/shared/session.js?v=78d1407733ca918b","/js/shared/tableUtils.js":"/js/shared/tableUtils.js?v=3a135a97f56c7572","/js/shared/tickerCollections.js":"/js/shared/tickerCollections.js?v=c05f3ea8290d1aaf","/js/shared/tickerDetailsCard.js":"/js/shared/tickerDetailsCard.js?v=c696e00a52f4219e","/js/shared/tickerPicker.js":"/js/shared/tickerPicker.js?v=3105015434205f62","/js/shared/timeAxisGrid.js":"/js/shared/timeAxisGrid.js?v=2009b983fa90794a","/js/shared/topN.js":"/js/shared/topN.js?v=9feed2eb664f012c","/js/skeletons.js":"/js/skeletons.js?v=80fa6e492766b5de","/js/sparkline/tickerSparklineModal.js":"/js/sparkline/tickerSparklineModal.js?v=a65cc9aff3472366","/js/startupStatus.js":"/js/startupStatus.js?v=e854026159706561","/js/state.js":"/js/state.js?v=ad391462ea5cd4e4","/js/status-page.js":"/js/status-page.js?v=5ae25ffc954bed2e","/js/status.js":"/js/status.js?v=14f7f35c03b73b59","/js/stories/heroGrowth.js":"/js/stories/heroGrowth.js?v=83b6427364ba1370","/js/stories/index.js":"/js/stories/index.js?v=4d8214f0eceefb56","/js/stories/mountainAndValley.js":"/js/stories/mountainAndValley.js?v=a397bfe2c2d0a726","/js/stories/returnSpectrum.js":"/js/stories/returnSpectrum.js?v=2e4fc30dd028985e","/js/stories/shared/storyAspect.js":"/js/stories/shared/storyAspect.js?v=3cd6222027edd0d5","/js/stories/shared/storyColors.js":"/js/stories/shared/storyColors.js?v=374a67a54d97f8fc","/js/stories/shared/storyD3.js":"/js/stories/shared/storyD3.js?v=66753d118ded523f","/js/stories/shared/storyExport.js":"/js/stories/shared/storyExport.js?v=39e128a352cc8f6f","/js/stories/shared/storyExportControls.js":"/js/stories/shared/storyExportControls.js?v=bbf06dc8daa7a980","/js/stories/shared/storyFormatters.js":"/js/stories/shared/storyFormatters.js?v=01d442e9c8cdd084","/js/stories/shared/storyFrame.js":"/js/stories/shared/storyFrame.js?v=8098d581de2503ac","/js/stories/shared/storyMetrics.js":"/js/stories/shared/storyMetrics.js?v=88c9a8d996f54926","/js/stories/shared/storyRasterize.js":"/js/stories/shared/storyRasterize.js?v=c9b97fccf0b75e9e","/js/stories/shared/storyRecorder.js":"/js/stories/shared/storyRecorder.js?v=34e5efbfa91bc021","/js/stories/shared/storyTypography.js":"/js/stories/shared/storyTypography.js?v=450a7c77cdbb9b62","/js/stories/tickerTimeframeMatchup.js":"/js/stories/tickerTimeframeMatchup.js?v=abd23df02f8d4436","/js/subapps/betterthanspy/account.js":"/js/subapps/betterthanspy/account.js?v=70b31d4860a5e055","/js/subapps/betterthanspy/app.js":"/js/subapps/betterthanspy/app.js?v=8320a2f8102bb4d4","/js/subapps/betterthanspy/data.js":"/js/subapps/betterthanspy/data.js?v=c01bc2d4d7b37b61","/js/subapps/betterthanspy/devModal.js":"/js/subapps/betterthanspy/devModal.js?v=95b726067829c744","/js/subapps/betterthanspy/favorites.js":"/js/subapps/betterthanspy/favorites.js?v=e5911445547b9399","/js/subapps/betterthanspy/graphic/guard.js":"/js/subapps/betterthanspy/graphic/guard.js?v=9842a39b1aaf4de7","/js/subapps/betterthanspy/graphic/model.js":"/js/subapps/betterthanspy/graphic/model.js?v=0caa7c30017f2a5b","/js/subapps/betterthanspy/graphic/studio.js":"/js/subapps/betterthanspy/graphic/studio.js?v=248437ccacb5ad20","/js/subapps/betterthanspy/graphic/templates.js":"/js/subapps/betterthanspy/graphic/templates.js?v=1366f4c3e95a2c51","/js/subapps/betterthanspy/help.js":"/js/subapps/betterthanspy/help.js?v=05837c066e46d249","/js/subapps/betterthanspy/intro.js":"/js/subapps/betterthanspy/intro.js?v=24ec5e5a3061e252","/js/subapps/betterthanspy/nav.js":"/js/subapps/betterthanspy/nav.js?v=37418b822204f358","/js/subapps/betterthanspy/render.js":"/js/subapps/betterthanspy/render.js?v=3340b7fdf4199c19","/js/subapps/betterthanspy/snapshotCache.js":"/js/subapps/betterthanspy/snapshotCache.js?v=e244832ce301302d","/js/subapps/betterthanspy/tooltip.js":"/js/subapps/betterthanspy/tooltip.js?v=fea5c2df0cd60c07","/js/subapps/betterthanspy/tutorial.js":"/js/subapps/betterthanspy/tutorial.js?v=2301b64e8396d8bb","/js/subapps/betterthanspy/ui.js":"/js/subapps/betterthanspy/ui.js?v=b4e035ae8d67b2df","/js/subapps/discover/activeView.js":"/js/subapps/discover/activeView.js?v=4e5beac895caa09e","/js/subapps/discover/activeViewUI.js":"/js/subapps/discover/activeViewUI.js?v=a864416c46b3f32a","/js/subapps/discover/app.js":"/js/subapps/discover/app.js?v=461ee6c8c4f7a6a7","/js/subapps/discover/compareChart.js":"/js/subapps/discover/compareChart.js?v=a91f5d7692a56fa4","/js/subapps/discover/favorites.js":"/js/subapps/discover/favorites.js?v=426e60500d0a9c78","/js/subapps/discover/savedViews.js":"/js/subapps/discover/savedViews.js?v=f1af3578fd7cd855","/js/table/RankingTableCore.js":"/js/table/RankingTableCore.js?v=89a46594689a40d8","/js/table/categoryController.js":"/js/table/categoryController.js?v=04aaeda495b0b4da","/js/table/columnExplanations.js":"/js/table/columnExplanations.js?v=e6065f450f83c738","/js/table/columnPinning.js":"/js/table/columnPinning.js?v=d5eb3f2613c3652b","/js/table/columnVisibility.js":"/js/table/columnVisibility.js?v=7e04abe7474c40f4","/js/table/columns.js":"/js/table/columns.js?v=d7fda49f741c3a92","/js/table/discoverTable.js":"/js/table/discoverTable.js?v=fd3e332d8415f9c1","/js/table/distributionStripAttacher.js":"/js/table/distributionStripAttacher.js?v=22c11b32a4113013","/js/table/drawdownRangeSummary.js":"/js/table/drawdownRangeSummary.js?v=fee74e1b1237adcc","/js/table/index.js":"/js/table/index.js?v=8705fb02b4d55bf5","/js/table/mainAppTable.js":"/js/table/mainAppTable.js?v=ddec296253d2b3b2","/js/table/scrollSync.js":"/js/table/scrollSync.js?v=aa8c93651c6a0a63","/js/table/stickyHeaderClone.js":"/js/table/stickyHeaderClone.js?v=8d2ba910953f0110","/js/table/tickerRowMenu.js":"/js/table/tickerRowMenu.js?v=7b5093e6af03afcb","/js/table/tooltipCore.js":"/js/table/tooltipCore.js?v=879d67e5b41f3ddc","/js/tableCategoryToggles.js":"/js/tableCategoryToggles.js?v=1ff1d10d31fba086","/js/tabs.js":"/js/tabs.js?v=fdef3e4d177933b9","/js/tickerColors.js":"/js/tickerColors.js?v=94e288a1be6153e1","/js/tickerCopyPaste.js":"/js/tickerCopyPaste.js?v=eca7d2c770e9bf4f","/js/tickerTypeToHighlight.js":"/js/tickerTypeToHighlight.js?v=7a7af411c70ea18b","/js/toast.js":"/js/toast.js?v=c7a95173cc9f3b38","/js/tutorial.js":"/js/tutorial.js?v=a39ec98049902bae","/js/tutorialPage.js":"/js/tutorialPage.js?v=97e70bec22cdcf46","/js/ui.js":"/js/ui.js?v=6242d49b62231362","/js/upgrade.js":"/js/upgrade.js?v=06e70b8efbb891b6","/js/upgradeModal.js":"/js/upgradeModal.js?v=7da9b96860c8d1b1","/js/utilityBar.js":"/js/utilityBar.js?v=eaf4e7598c9128eb","/js/yearlyRankHeatmap.js":"/js/yearlyRankHeatmap.js?v=5e8d48d677bbd512","/js/shared/auth.js?v=20260920-readonly":"/js/shared/auth.js?v=46ff50643a2a5921"}}</script>
<script type="module" src="/js/app.js?v=20306c3ba74ccd1c" defer></script>
    <script type="module" src="/js/shared/newsletter-modal.js?v=f089d5d7f1d44d8c" defer></script>
  </head>
  <body>
    <!-- Subscription Cancellation Banner -->
    <div id="cancellation-banner" class="cancellation-banner" style="display: none;">
      <div class="cancellation-banner-content">
        <span class="cancellation-banner-icon">⚠️</span>
        <span class="cancellation-banner-text"></span>
        <div class="cancellation-banner-actions">
          <button id="cancellation-banner-manage" class="cancellation-banner-btn primary">Manage Subscription</button>
          <button id="cancellation-banner-dismiss" class="cancellation-banner-btn dismiss">×</button>
        </div>
      </div>
    </div>

    <!-- Loading Modal -->
    <div id="loading-modal" class="loading-modal" hidden>
      <div class="loading-modal-content">
        <div class="loading-spinner"></div>
        <div class="loading-text">Computing...</div>
      </div>
    </div>

    <!-- Response Time Toast -->
    <div id="response-time-toast" class="response-time-toast" hidden>
      <span class="response-time-value"></span>
    </div>

    <!-- Remote-Supersede Toast: shown when a save was rejected by the
         worker because another device wrote a newer version of the
         account payload. We re-hydrate from the server and tell the
         user their last edits were dropped in favor of the newer
         remote version. Auto-dismisses after a few seconds. -->
    <div id="remote-supersede-toast" class="remote-supersede-toast" hidden role="status" aria-live="polite">
      <span class="remote-supersede-icon" aria-hidden="true">↻</span>
      <span class="remote-supersede-text">
        <strong class="remote-supersede-title">Updated from another device</strong>
        <span class="remote-supersede-detail">Your latest changes were replaced with a newer version saved elsewhere.</span>
      </span>
    </div>

    <!-- Password Lock Modal (if enabled in config.js) -->
    <div id="password-lock-modal">
      <div class="password-lock-content">
        <div class="password-lock-icon">🔒</div>
        <h2 class="password-lock-title">EasyCompare</h2>
        <p class="password-lock-subtitle">This site is password protected</p>
        
        <div class="password-lock-input-wrapper">
          <input 
            type="password" 
            id="password-lock-input" 
            placeholder="Enter password"
            autocomplete="off"
          />
          <div id="password-lock-error"></div>
        </div>
        
        <button id="password-lock-submit">Enter</button>
      </div>
    </div>

    <!-- New Session Confirmation Modal -->
    <div id="new-session-modal" class="confirmation-modal" hidden>
      <div class="confirmation-modal-content">
        <h2 class="confirmation-modal-title">Create New Session?</h2>
        <p class="confirmation-modal-subtitle">Your current session has your saved comparison lists and settings.</p>
        <p class="confirmation-modal-subtitle">Do you want to save or bookmark it first?</p>
        
        <div class="confirmation-modal-link-wrapper">
          <label class="confirmation-modal-link-label">Current Session Link:</label>
          <div class="confirmation-modal-link-display">
            <input 
              type="text" 
              id="new-session-modal-link" 
              readonly 
              value=""
            />
            <button id="new-session-modal-copy" class="icon-button" title="Copy to clipboard">
              <i data-lucide="copy" class="lucide-icon"></i>
            </button>
          </div>
        </div>
        
        <div class="confirmation-modal-buttons">
          <button id="new-session-modal-cancel" class="secondary">Cancel</button>
          <button id="new-session-modal-confirm" class="primary">Create New Session</button>
        </div>
      </div>
    </div>

    <div class="app-shell">
      <header class="app-header">
        <div class="title-stack">
          <span id="ec-logo" class="app-logo ec-brand-logo" role="img" aria-label="EasyCompare"></span>
          <p class="app-tagline">
            <span class="app-tagline-lead">In-depth <em>visual</em> and <em>quantitative</em> perspective for your investments</span>
            <span class="tagline-pills">
            <a href="/features" id="tagline-tour-pill" class="tagline-tour-pill" target="_blank" rel="noopener">
              <i data-lucide="play-circle" class="tagline-tour-pill-icon" aria-hidden="true"></i>
              <span>Visual Feature Tour</span>
              <i data-lucide="arrow-up-right" class="tagline-tour-pill-arrow" aria-hidden="true"></i>
            </a>
            <a href="/tutorial" id="tagline-tutorial-pill" class="tagline-tour-pill" target="_blank" rel="noopener">
              <i data-lucide="book-open" class="tagline-tour-pill-icon" aria-hidden="true"></i>
              <span>Tutorial</span>
              <i data-lucide="arrow-up-right" class="tagline-tour-pill-arrow" aria-hidden="true"></i>
            </a>
            </span>
          </p>
        </div>
        <div class="header-controls">
          <!-- Cluster 1: Status & Plan -->
          <div class="header-cluster header-cluster-status">
            <span id="header-save-indicator" class="header-save-indicator" hidden title="Saving session…" role="status" aria-live="polite">
              <i data-lucide="save" aria-hidden="true"></i>
            </span>
            <span id="server-status" class="server-status connecting"
                  data-tooltip="Backend status"
                  data-tooltip-detail="Live connection to the price/data backend. Green = healthy, amber = degraded, red = unreachable.">
              <span class="server-status-dot"></span>
              <span class="server-status-text">Connecting</span>
            </span>
            <span id="loading-indicator" class="status-indicator" hidden>
              <span class="spinner"></span>
              Computing...
            </span>
            <span id="beta-badge" class="beta-badge" style="display: none;"
                  data-tooltip="Beta mode"
                  data-tooltip-detail="You're seeing experimental features and pre-release UI. Behavior may change without notice.">
              <i data-lucide="flask-conical" aria-hidden="true"></i>
              BETA
            </span>
            <div class="header-plan-group">
              <span id="tier-badge" class="tier-badge free"
                    data-tooltip="Your plan"
                    data-tooltip-detail="Determines saved-comparison limits and access to advanced features. Click Upgrade to change plans.">
                <i data-lucide="zap" class="tier-icon"></i>
                <span id="tier-badge-text">Free</span>
              </span>
              <a href="/upgrade" id="upgrade-button" class="header-plan-upgrade"
                 data-tooltip="Upgrade your plan"
                 data-tooltip-detail="See plan options. Paid tiers unlock unlimited saved comparisons and advanced analytics.">
                <i data-lucide="sparkles" aria-hidden="true"></i>
                <span>Upgrade</span>
              </a>
            </div>
          </div>

          <span class="header-cluster-sep" aria-hidden="true"></span>

          <!-- Cluster 2: Primary Actions & Guide Submenu -->
          <div class="header-cluster header-cluster-actions">
            <a
              id="mac-app-link"
              class="header-action header-action-text"
              href="/mac"
              style="display: none"
              data-tooltip="EasyCompare for Mac"
              data-tooltip-detail="A native Mac app with your lists synced from this account. Free to download."
            >
              <i data-lucide="laptop" aria-hidden="true"></i>
              <span>Mac App</span>
            </a>
            <script>
              // Macs only; iPads also report "Macintosh" but have touch events.
              if (/Macintosh/.test(navigator.userAgent) && !('ontouchend' in document)) {
                document.getElementById('mac-app-link').style.display = '';
              }
            </script>
            <button
              id="share-button"
              class="header-action header-action-text"
              type="button"
              aria-label="Share this comparison"
              data-tooltip="Share"
              data-tooltip-detail="Get a link to share this comparison. Choose a special share link (preserves full state) or a raw URL (tickers and timeframe only)."
            >
              <i data-lucide="share-2" aria-hidden="true"></i>
              <span>Share</span>
            </button>

            <div class="header-menu-wrap" id="header-help-wrap">
              <button
                id="header-help-trigger"
                class="header-action header-action-text header-menu-trigger"
                type="button"
                aria-expanded="false"
                aria-haspopup="menu"
                title="Guides, tutorials, and feedback"
              >
                <i data-lucide="compass" aria-hidden="true"></i>
                <span>Guide &amp; Help</span>
                <i data-lucide="chevron-down" class="header-menu-caret" aria-hidden="true"></i>
              </button>
              <div id="header-help-dropdown" class="header-menu-dropdown" role="menu" hidden>
                <!-- #onboarding-tips-toggle ("Self Guided Tutorial") is injected at the top of this menu by onboardingBeacons.js -->
                <div class="header-menu-divider" role="separator"></div>
                <button
                  id="feedback-trigger"
                  class="header-menu-item"
                  type="button"
                  role="menuitem"
                >
                  <i data-lucide="message-circle" aria-hidden="true"></i>
                  <span>Send Feedback</span>
                </button>
                <div class="header-menu-divider header-session-divider" role="separator"></div>
                <button
                  id="copy-session-link"
                  class="header-menu-item"
                  type="button"
                  role="menuitem"
                >
                  <i data-lucide="link-2" aria-hidden="true"></i>
                  <span>Copy Session Link</span>
                </button>
                <button
                  id="new-session-button"
                  class="header-menu-item"
                  type="button"
                  role="menuitem"
                >
                  <i data-lucide="file-plus" aria-hidden="true"></i>
                  <span>Start New Session</span>
                </button>
              </div>
            </div>
            <span id="copy-session-feedback" class="copy-feedback" hidden>Copied!</span>
          </div>

          <span class="header-cluster-sep" aria-hidden="true"></span>

          <!-- Cluster 3: Theme & Account -->
          <div class="header-cluster header-cluster-account">
            <button
              id="theme-toggle"
              class="icon-btn theme-toggle header-action header-action-icon"
              type="button"
              aria-label="Toggle dark mode"
              data-tooltip="Toggle theme"
              data-tooltip-detail="Switch between light and dark mode. Your preference is remembered across visits."
              aria-pressed="false"
            >
              <i data-lucide="sun" class="theme-toggle-icon theme-toggle-sun" aria-hidden="true"></i>
              <i data-lucide="moon" class="theme-toggle-icon theme-toggle-moon" aria-hidden="true"></i>
            </button>
            <select id="theme-selector" class="visually-hidden" aria-hidden="true" tabindex="-1">
              <option value="light" selected>Light</option>
              <option value="dark">Dark</option>
            </select>
            <!-- Auth container (Clerk sign-in/user button) -->
            <div id="auth-container"></div>
          </div>
        </div>
      </header>

      <section class="qc-nl-sliver qc-nl-sliver--ec-app" aria-label="Monthly newsletter" data-newsletter-bar data-app="easycompare" data-source="ec_app_after_header">
        <div class="qc-nl-callout">
          <p class="qc-nl-callout__text"><span class="qc-nl-long">We send a monthly newsletter with relevant relative intelligence updates for all modern investors.</span><span class="qc-nl-short" hidden>Our monthly relative intelligence newsletter.</span></p>
          <button type="button" class="qc-nl-trigger" data-newsletter-open>Subscribe <span aria-hidden="true">→</span></button>
        </div>
      </section>

      <section class="controls-panel">
        <!-- Tabs -->
        <div class="controls-tabs">
          <div class="controls-tabs-nav">
            <button id="tab-essentials" class="tab-button active">Essentials</button>
            <button id="tab-configuration" class="tab-button">Configuration</button>
          </div>
          <div class="controls-tabs-utils"></div>
        </div>

        <!-- Essentials Tab Content -->
        <div id="tab-content-essentials" class="tab-content active">
          <!-- What to Compare Section -->
          <div class="control-section control-section-tickers">
            <span class="visually-hidden" id="what-to-compare-title">What to Compare</span>
            <div class="controls-row">
              <div class="control-group">
                <div class="ticker-label-row">
                  <label for="ticker-select" id="ticker-select-label" data-help="comparisonList">Add tickers</label>
                  <div class="etf-holdings-bar" id="etf-holdings-bar">
                    <button type="button" class="etf-holdings-btn" id="etf-holdings-btn" aria-expanded="false" aria-controls="etf-holdings-panel"
                            data-tooltip="Compare ETF Holdings"
                            data-tooltip-detail="Find out which ETF constituents are performing well and how they compare with the ETF itself.">
                      <span class="etf-holdings-btn__icon" aria-hidden="true">🧺</span>
                      <span class="etf-holdings-btn__label">Compare ETF Holdings</span>
                    </button>
                  </div>
                  <button id="ready-made-lists-btn" class="ready-made-lists-btn" type="button" hidden
                          data-tooltip="Browse ready-made lists"
                          data-tooltip-detail="Pick from 160+ pre-built ticker baskets, by sector, theme, factor, geography, and more.">
                    <svg class="rml-icon" viewBox="0 0 18 18" aria-hidden="true" focusable="false">
                      <rect class="rml-cell rml-c1" x="1"  y="1"  width="4" height="4" rx="1.2"/>
                      <rect class="rml-cell rml-c2" x="7"  y="1"  width="4" height="4" rx="1.2"/>
                      <rect class="rml-cell rml-c3" x="13" y="1"  width="4" height="4" rx="1.2"/>
                      <rect class="rml-cell rml-c4" x="1"  y="7"  width="4" height="4" rx="1.2"/>
                      <rect class="rml-cell rml-c5" x="7"  y="7"  width="4" height="4" rx="1.2"/>
                      <rect class="rml-cell rml-c6" x="13" y="7"  width="4" height="4" rx="1.2"/>
                      <rect class="rml-cell rml-c7" x="1"  y="13" width="4" height="4" rx="1.2"/>
                      <rect class="rml-cell rml-c8" x="7"  y="13" width="4" height="4" rx="1.2"/>
                      <rect class="rml-cell rml-c9" x="13" y="13" width="4" height="4" rx="1.2"/>
                    </svg>
                    <span class="rml-label">Ready-made lists</span>
                  </button>
                  <div class="ticker-actions-cluster">
                    <button id="ticker-save-preset-btn" class="ticker-save-preset-btn" type="button" hidden>
                      <i data-lucide="save" aria-hidden="true"></i>
                      <span>Save this preset</span>
                    </button>
                    <button id="ticker-copy-btn" class="ticker-clipboard-btn" type="button"
                            data-tooltip="Copy tickers"
                            data-tooltip-detail="Copy this comparison list's tickers to your clipboard as a comma-separated list."
                            aria-label="Copy tickers as CSV" hidden>
                      <i data-lucide="copy"></i>
                    </button>
                    <button id="ticker-paste-btn" class="ticker-clipboard-btn" type="button"
                            data-tooltip="Paste tickers"
                            data-tooltip-detail="Paste a list of tickers from anywhere, auto-detects CSV, spaces, or one-per-line."
                            aria-label="Paste tickers from CSV" hidden>
                      <i data-lucide="clipboard-paste"></i>
                    </button>
                    <button id="random-tickers-btn" class="random-tickers-btn" title="Fill with random tickers">
                      <span class="random-icon">🎲</span> Random
                    </button>
                    <button id="ticker-share-btn" class="ticker-clipboard-btn" type="button"
                            data-tooltip="Get sharing link"
                            data-tooltip-detail="Publish this set as a public read-only link anyone can open."
                            aria-label="Get sharing link" hidden>
                      <i data-lucide="share-2"></i>
                    </button>
                    <button id="ticker-apply-btn" class="ticker-apply-btn" style="display: none;">
                      Apply <span class="shortcut-hint"></span>
                    </button>
                    <button class="pro-sparkle-btn" data-upgrade-trigger title="Pro features available"><i data-lucide="sparkles"></i></button>
                  </div>
                </div>
                <div class="etf-holdings-panel" id="etf-holdings-panel" hidden></div>
                <div class="related-tickers" id="related-tickers" hidden></div>
                <select id="ticker-select" multiple placeholder="Choose tickers..."></select>
              </div>
            </div>
          </div>

          <!-- When to Compare Section -->
          <div class="control-section control-section-timeframe">
            <span class="visually-hidden" id="when-to-compare-title">When to Compare</span>
            <div class="controls-row timeframe-row">
              <div class="control-group timeframe-group">
                <span class="visually-hidden" id="timeframe-label">Timeframe</span>
                <div class="timeframe-line">
                  <div class="scenario-select-wrap" data-tooltip="Scenario tests" data-tooltip-detail="Jump to real market regimes like the 2008 Crisis, the COVID Crash or the 2022 Rate Shock to see how your list held up.">
                    <i data-lucide="flask-conical" class="scenario-select-icon" aria-hidden="true"></i>
                    <select id="important-period-select" aria-label="Scenarios">
                      <option value="">Scenarios</option>
                    </select>
                  </div>
                  <div class="preset-buttons" id="preset-buttons" role="group" aria-labelledby="timeframe-label">
                    <!-- Preset buttons and Pro icon populated by JS -->
                  </div>
                  <button type="button" class="custom-range-toggle" id="custom-range-toggle"
                    aria-expanded="false" aria-controls="custom-range-panel"
                    aria-label="Custom date range" data-tooltip="Custom date range">
                    <i data-lucide="calendar-range" aria-hidden="true"></i>
                  </button>
                </div>
              </div>
            </div>
            <div class="controls-row custom-range-row" id="custom-range-panel" hidden>
              <div class="control-group custom-range-group">
                <div class="date-inputs">
                  <span class="control-label custom-range-label" data-help="customRange">Custom range</span>
                  <input type="date" id="start-date" aria-label="Start date" />
                  <span class="date-separator">to</span>
                  <input type="date" id="end-date" aria-label="End date" />
                  <button id="apply-range" class="secondary">Apply</button>
                </div>
              </div>
            </div>
          </div>
        </div>

        <!-- Configuration Tab Content -->
        <div id="tab-content-configuration" class="tab-content">
          <!-- Quant Metrics Control -->
          <div class="config-control-block advanced-metrics-control" id="advanced-metrics-control" hidden>
            <div class="label-with-actions">
              <label for="metrics-select" id="metrics-select-label" data-help="tableMetrics">Quant Metrics</label>
              <div class="label-actions">
                <button id="select-all-metrics" class="link-button">Select All</button>
                <button id="select-none-metrics" class="link-button">Select None</button>
              </div>
            </div>
            <select id="metrics-select" multiple placeholder="Choose metrics..."></select>
          </div>

          <!-- Multi-Period Returns Control -->
          <div class="config-control-block advanced-metrics-control" id="preset-returns-control" hidden>
            <div class="label-with-actions">
              <label for="preset-returns-select" data-help="presetReturns">Multi-Period Returns</label>
              <div class="label-actions">
                <button id="select-all-presets" class="link-button">Select All</button>
                <button id="select-none-presets" class="link-button">Select None</button>
              </div>
            </div>
            <select id="preset-returns-select" multiple placeholder="Choose time periods..."></select>
          </div>

          <!-- Benchmark ticker control. Reuses the rich ticker picker (TomSelect)
               from the main ticker selector but constrained to a single
               selection. Wired in ui.js -> setupBenchmarkSelect; clearing the
               selection sets the benchmark to None and hides all benchmark UI. -->
          <div class="config-control-block" id="benchmark-control">
            <div class="label-with-actions">
              <label for="benchmark-select" data-help="benchmarkTicker">Benchmark</label>
              <div class="label-actions">
                <button id="clear-benchmark" class="link-button" type="button" title="Remove the benchmark for this set">Clear</button>
              </div>
            </div>
            <select id="benchmark-select" placeholder="Pick a benchmark ticker (e.g. SPY)..."></select>
          </div>
        </div>
      </section>

      <!-- Results Overview Strip
           Subtle 1-line summary that anchors the user in the moment they
           hit "Apply": "N tickers compared. K computations performed.
           Time taken: Xms. Following are now available:" + 2 mini cards
           that smooth-scroll to the chart and the rankings table.
           Wired by js/resultsOverview.js, updated live by app.js render(). -->
      <section class="results-overview" id="results-overview" hidden></section>

      <main class="content">
        <section class="chart-section" id="chart-section">
          <button id="chart-fullscreen-btn" class="chart-fullscreen-btn" type="button" title="Toggle fullscreen (Esc to exit)" aria-label="Toggle fullscreen">
            <i data-lucide="maximize-2"></i>
          </button>
          <!-- Read-only preset banner: visible when a ticker collection is active -->
          <div id="preset-banner" class="preset-banner" hidden>
            <div class="preset-banner-main">
              <span class="preset-banner-emoji" id="preset-banner-emoji">📋</span>
              <div class="preset-banner-text">
                <div class="preset-banner-title">
                  <span id="preset-banner-name"></span>
                  <span class="preset-banner-pill" id="preset-banner-pill">Read-only collection</span>
                </div>
                <div class="preset-banner-desc" id="preset-banner-desc"></div>
                <div class="preset-banner-truncation" id="preset-banner-truncation" hidden></div>
                <div class="holdings-scorecard" id="preset-banner-scorecard" aria-live="polite" hidden></div>
              </div>
            </div>
            <button type="button" class="preset-banner-fork" id="preset-banner-fork" title="Save as your own editable comparison list">
              <i data-lucide="copy"></i>
              <span>Save as my set</span>
            </button>
          </div>
          <!-- Raw URL-mode chip: visible when the page was loaded with
               ?tickers=... params (or back-navigated to such a URL).
               Wired in rawModeBanner.js. -->
          <div id="raw-mode-banner" class="raw-mode-chip" hidden>
            <i data-lucide="link" aria-hidden="true"></i>
            <span>Shared link</span>
            <button type="button" class="raw-mode-chip-copy" id="raw-mode-banner-copy" title="Copy this comparison's link">
              <span>Copy</span>
            </button>
          </div>
          <!-- Share-view banner: shown when the page was loaded at
               /s/{shareId} (a public share link from another user).
               Reuses .preset-banner shape so the placement matches
               the other read-only banners; tinted indigo to read as
               "shared content from someone else". The CTA flips
               between "Save a Copy" (signed in) and "Sign in to save"
               (anonymous): wired in shareMode.js. -->
          <div id="share-view-banner" class="preset-banner share-view-banner" hidden>
            <div class="preset-banner-main">
              <span class="preset-banner-emoji" aria-hidden="true">🔗</span>
              <div class="preset-banner-text">
                <div class="preset-banner-title">
                  <span id="share-view-banner-title">Shared comparison</span>
                  <span class="preset-banner-pill">Read-only · Public link</span>
                </div>
                <div class="preset-banner-desc">
                  You're viewing a comparison list someone shared with you.
                  Save a copy to make it your own.
                </div>
              </div>
            </div>
            <div class="readonly-set-banner-actions">
              <button type="button" class="preset-banner-fork" id="share-view-save-copy-btn" title="Save this comparison list to your account">
                <i data-lucide="copy" aria-hidden="true"></i>
                <span>Save a Copy</span>
              </button>
              <button type="button" class="preset-banner-fork" id="share-view-signin-btn" title="Sign in to save this set" hidden>
                <i data-lucide="log-in" aria-hidden="true"></i>
                <span>Sign in to save</span>
              </button>
            </div>
          </div>
          <!-- Read-only set banner: shown on the Free tier when the
               currently active comparison set is NOT the user's free
               slot (the most recently modified set, which is the only
               editable one). Reuses .preset-banner shape; tinted blue
               to read as "informational + actionable". Wired in
               readOnlyMode.js. -->
          <div id="readonly-set-banner" class="preset-banner readonly-set-banner" hidden>
            <div class="preset-banner-main">
              <span class="preset-banner-emoji" aria-hidden="true">🔒</span>
              <div class="preset-banner-text">
                <div class="preset-banner-title">
                  <span>Read-only set</span>
                  <span class="preset-banner-pill">Free tier · 1 editable slot</span>
                </div>
                <div class="preset-banner-desc">
                  This comparison list is locked because the Free tier includes only one editable slot, currently <strong id="readonly-set-banner-slot">your editable list</strong>. View it freely, or upgrade to edit any set.
                </div>
              </div>
            </div>
            <div class="readonly-set-banner-actions">
              <button type="button" class="preset-banner-fork" id="readonly-set-banner-switch" title="Switch to your editable list">
                <i data-lucide="corner-down-right" aria-hidden="true"></i>
                <span>Go to editable set</span>
              </button>
              <button type="button" class="preset-banner-fork readonly-set-banner-upgrade" id="readonly-set-banner-upgrade" data-upgrade-trigger="" title="Upgrade for unlimited editable sets">
                <i data-lucide="sparkles" aria-hidden="true"></i>
                <span>Upgrade</span>
              </button>
            </div>
          </div>
          <!-- Intraday-fallback banner: shown when the user picks an
               intraday-capable preset (e.g. 3D) but their tier doesn't
               include the intraday data feed, so the chart renders from
               daily prices instead. Reuses .preset-banner styling for
               placement consistency. Wired in intradayBanner.js. -->
          <div id="intraday-fallback-banner" class="preset-banner intraday-fallback-banner" hidden>
            <div class="preset-banner-main">
              <span class="preset-banner-emoji" aria-hidden="true">⏱️</span>
              <div class="preset-banner-text">
                <div class="preset-banner-title">
                  <span>Daily data only</span>
                  <span class="preset-banner-pill">Pro feature</span>
                </div>
                <div class="preset-banner-desc">
                  Intraday price comparison isn't available on the free tier, this chart is built from daily closes. Upgrade for minute-by-minute resolution on 1D, 2D and 3D.
                </div>
              </div>
            </div>
            <button type="button" class="preset-banner-fork" id="intraday-fallback-banner-upgrade" data-upgrade-trigger="" title="Upgrade for intraday data">
              <i data-lucide="sparkles" aria-hidden="true"></i>
              <span>Upgrade</span>
            </button>
          </div>
          <!-- Chart header. Three-zone layout: title (left), period chip
               (center), toolbar (right). Replaces the old single-line
               "Relative Performance Comparison for: 2023-04-17 → ..." dump
               with a clearer hierarchy: what + when + how. The chip mirrors
               the rankings table period chip so the two cards feel related.
               chartPlayback.js continues to inject the play/stop buttons
               into .chart-controls (now living inside the toolbar). -->
          <div class="chart-header">
            <!-- Performance Tradeoffs view tabs. Hidden when the
                 `tradeoffsScatter` feature flag is off (super-admin /
                 private mode only for now). When visible, switching
                 tabs flips activeSet.chartView via setChartView() and
                 the chartTabs controller toggles the visibility of the
                 line chart cluster vs. the scatter cluster below. The
                 .is-active class is the source of truth in the DOM. -->
            <div class="chart-tabs" id="chart-tabs" hidden role="tablist" aria-label="Chart view">
              <button type="button" class="chart-tab is-active" id="chart-tab-line" role="tab" aria-selected="true" data-chart-view="line">
                <i data-lucide="line-chart" class="chart-tab-icon" aria-hidden="true"></i>
                <span>Performance Over Time</span>
              </button>
              <button type="button" class="chart-tab" id="chart-tab-heatmap" role="tab" aria-selected="false" data-chart-view="heatmap" data-help="rankHeatmap">
                <i data-lucide="layout-grid" class="chart-tab-icon" aria-hidden="true"></i>
                <span>Performance Grid</span>
              </button>
              <button type="button" class="chart-tab" id="chart-tab-yearly" role="tab" aria-selected="false" data-chart-view="yearly">
                <i data-lucide="calendar-range" class="chart-tab-icon" aria-hidden="true"></i>
                <span>Year by Year</span>
                <span class="chart-tab-beta-pill">Beta</span>
              </button>
              <button type="button" class="chart-tab" id="chart-tab-corrmap" role="tab" aria-selected="false" data-chart-view="corrmap">
                <i data-lucide="orbit" class="chart-tab-icon" aria-hidden="true"></i>
                <span>Correlation Map</span>
                <span class="chart-tab-beta-pill">Beta</span>
              </button>
              <button type="button" class="chart-tab" id="chart-tab-corrgroups" role="tab" aria-selected="false" data-chart-view="corrgroups">
                <i data-lucide="layers" class="chart-tab-icon" aria-hidden="true"></i>
                <span>Correlated Groups</span>
                <span class="chart-tab-beta-pill">Beta</span>
              </button>
              <button type="button" class="chart-tab" id="chart-tab-scatter" role="tab" aria-selected="false" data-chart-view="scatter" data-help="performanceTradeoffs">
                <i data-lucide="scatter-chart" class="chart-tab-icon" aria-hidden="true"></i>
                <span>Performance Tradeoffs</span>
                <span class="chart-tab-beta-pill">Beta</span>
              </button>
              <button type="button" class="chart-tab" id="chart-tab-multiperiod" role="tab" aria-selected="false" data-chart-view="multiperiod" data-help="multiPeriodReturns">
                <i data-lucide="trending-up" class="chart-tab-icon" aria-hidden="true"></i>
                <span>Multi-Period Returns</span>
                <span class="chart-tab-beta-pill">Beta</span>
              </button>
              <button type="button" class="chart-tab" id="chart-tab-stories" role="tab" aria-selected="false" data-chart-view="stories" data-help="storiesGallery">
                <i data-lucide="sparkles" class="chart-tab-icon" aria-hidden="true"></i>
                <span>Stories</span>
                <span class="chart-tab-beta-pill">Beta</span>
              </button>
            </div>
            <!-- Chart header: two-row layout.
                 Row 1 (identity + chart actions): section title on the left,
                   Y-scale toggle / playback / settings cog on the right.
                 Row 2 (data/view controls): rebase dropdown, period chip,
                   baseline alignment dropdown, left-aligned.
                 Subtitle banner (rebase explainer / clipping notice / intraday
                 filter notice) follows below as Row 3 when active.

                 The split keeps "what is this view" (title) and "how to play
                 / configure it" (chart actions) on one row, while the
                 data-shaping controls live on their own row where the user
                 can scan them without competing for space with the title.
                 -->
            <div class="chart-header-titlerow">
              <div class="chart-section-heading">
                <h3 id="chart-section-title" class="chart-section-title">Interactive Visual Comparison</h3>
              </div>
              <!-- Row 1 right-side cluster: chart actions (scale toggle,
                   playback buttons injected by chartPlayback.js, settings).
                   The .chart-controls class is retained for backwards
                   compatibility with code that still queries by that name. -->
              <div class="chart-toolbar chart-controls chart-header-actions">
                <!-- Hidden checkbox preserves the existing change-event API
                     consumed by app.js (`logScaleCheckbox.checked` /
                     "change" listener). The visible affordance is the
                     segmented [Linear | Log] control below; clicking either
                     segment flips the checkbox and dispatches "change". -->
                <input type="checkbox" id="log-scale-checkbox" hidden />
                <div class="chart-scale-segmented" role="group" aria-label="Y-axis scale">
                  <button type="button" class="chart-scale-segment is-active" data-scale="linear" aria-pressed="true">Linear</button>
                  <button type="button" class="chart-scale-segment" data-scale="log" aria-pressed="false">Log</button>
                </div>
                <span class="control-label scale-help" data-help="logScale"></span>
                <!-- chartPlayback.js inserts the play / stop buttons here
                     via #chart-playback-anchor (see js/chartPlayback.js). -->
                <span id="chart-playback-anchor" aria-hidden="true"></span>
                <button id="chart-export-btn" class="chart-toolbar-btn chart-toolbar-btn-icon" type="button" title="Share chart as an image" aria-label="Share chart as an image" aria-haspopup="menu">
                  <i data-lucide="camera"></i>
                </button>
                <button id="chart-settings-btn" class="chart-toolbar-btn chart-toolbar-btn-icon" type="button" title="Chart settings" aria-label="Chart settings">
                  <i data-lucide="settings"></i>
                </button>
              </div>
              <!-- Row 2: data / view controls. Left-aligned cluster
                   containing the read-only period chip, rebase ("Relative to"),
                   and baseline alignment. -->
              <div class="chart-header-data-controls">
                <div class="chart-period-chip table-period-chip" id="chart-period-chip" hidden>
                  <i data-lucide="calendar-days" class="table-period-chip-icon" aria-hidden="true"></i>
                  <span class="table-period-chip-label">Period</span>
                  <span class="table-period-chip-value" id="chart-period-chip-value"></span>
                </div>
                <!-- Top-N chart cap. Shown once the set reaches TOP_N_TRIGGER
                     tickers; options are rebuilt with the live total by
                     app.js > updateTopPerformersControl(). -->
                <div class="rebase-selector top-performers-selector" id="top-performers-selector" hidden>
                  <label class="rebase-selector-label" for="top-performers-select">Show</label>
                  <select id="top-performers-select" aria-label="How many top performers to plot"></select>
                  <span class="control-label top-performers-help" data-help="topPerformers"></span>
                </div>
                <!-- Rebase ("Relative to") dropdown. Default "Cash" = no
                     rebase = the legacy absolute-return view. Populated by
                     app.js > updateRebaseSelector. See js/shared/rebase.js
                     for the math. -->
                <div class="rebase-selector" id="rebase-selector" hidden>
                  <label class="rebase-selector-label" for="rebase-select">Relative to</label>
                  <select id="rebase-select" aria-label="Rebase chart relative to ticker">
                    <option value="">Cash</option>
                  </select>
                  <span class="control-label rebase-help" data-help="relativeTo"></span>
                </div>
                <div class="baseline-selector" id="baseline-selector" hidden>
                  <label class="baseline-selector-label" for="baseline-select">Align start</label>
                  <select id="baseline-select" aria-label="Align to ticker start date">
                    <option value="">Loading...</option>
                  </select>
                  <span class="control-label baseline-help" data-help="alignToTicker"></span>
                </div>
                <!-- Filled by freshnessUI.js from the freshness poll. -->
                <span class="chart-last-quote" id="chart-last-quote" hidden>
                  <i data-lucide="clock" aria-hidden="true"></i>
                  Last quote <time></time>
                </span>
              </div>
            </div>
            <!-- Chart-rebase subtitle. Visible only when the active
                 comparison set has a rebaseTicker. Lives in the chart
                 container (NOT a top-of-page banner) so it sits right
                 between the toolbar and the chart canvas, where the
                 user's eyes are already looking. Text is updated by
                 app.js > updateRebaseSubtitle(). Hidden via the
                 `hidden` attribute by default. -->
            <div id="chart-rebase-subtitle" class="chart-rebase-subtitle" role="status" hidden>
              <i data-lucide="git-compare-arrows" aria-hidden="true"></i>
              <span id="chart-rebase-subtitle-text"></span>
            </div>
            <!-- Intraday validation notice. Populated by updateIntradayFilterInfo()
                 in app.js whenever one or more tickers are dropped from the chart
                 because their series isn't intraday-shaped (e.g. mutual funds with
                 only end-of-day NAV stamps). Hidden by default; the `hidden`
                 attribute needs the explicit display:none override in CSS. -->
            <div id="chart-intraday-filter-info" class="chart-clipping-info chart-clipping-info-warn" hidden>
              <i data-lucide="alert-triangle" aria-hidden="true"></i>
              <span id="chart-intraday-filter-text"></span>
            </div>
            <!-- Data alignment note: one line when some tickers start later
                 than the period. Rendered by js/status.js. -->
            <div class="status-section" id="status-section" hidden></div>
          </div>
          <!-- Line chart view cluster. The chart-tabs controller toggles
               this container's `.is-hidden` class when the user switches
               to the scatter tab, keeping the line chart's DOM intact
               (cheap show/hide, no re-render cost on tab switch back). -->
          <div class="chart-line-view" id="chart-line-view">
          <!-- Leader / worst / benchmark summary cards. js/chartStatStrip.js -->
          <div class="chart-stat-strip" id="chart-stat-strip" hidden></div>
          <div id="chart"></div>
          <!-- Chart skeleton (shown until first render).
               Three polylines emerge from the same point on the left and
               diverge: green up, blue middle, red slightly down, to
               emphasize the comparison nature of the app while loading.
               pathLength="100" normalizes the draw-on animation across
               the three different polyline lengths so they finish in sync. -->
          <div id="chart-skeleton" class="chart-skeleton">
            <div class="skeleton-chart-area">
              <svg class="skeleton-line" viewBox="0 0 800 240" preserveAspectRatio="none" aria-hidden="true">
                <polyline class="skeleton-line-path skeleton-line-green" pathLength="100" points="40,150 100,140 160,135 220,118 280,110 340,95 400,82 460,75 520,62 580,55 640,45 700,38 760,40" />
                <polyline class="skeleton-line-path skeleton-line-blue" pathLength="100" points="40,150 100,148 160,143 220,138 280,132 340,128 400,122 460,118 520,112 580,108 640,103 700,100 760,100" />
                <polyline class="skeleton-line-path skeleton-line-red" pathLength="100" points="40,150 100,152 160,158 220,160 280,168 340,172 400,180 460,184 520,190 580,195 640,200 700,205 760,210" />
              </svg>
              <div class="skeleton-grid"></div>
            </div>
            <div class="skeleton-caption">Loading chart…</div>
          </div>
          <!-- Hidden by default. chart.js writes the visible-range string
               here and toggles `is-visible` only when the visible range
               differs from the requested range (i.e., the user dragged to
               zoom the rangeslider). The period chip in the header covers
               the requested-range case, so showing this caption every
               render created three places the same dates appeared at once. -->
          <div id="chart-range-label" class="chart-range-label" hidden></div>
          <!-- Best / worst per risk metric. js/chartMetricStrip.js -->
          <div class="chart-metric-strip" id="chart-metric-strip" hidden></div>
          <div class="chart-legend-divider" id="chart-legend-divider" aria-hidden="true"></div>
          <div class="chart-legend" id="chart-legend"></div>
          </div><!-- /.chart-line-view -->

          <!-- Performance Tradeoffs scatter view cluster. Hidden until
               the user clicks the scatter tab (which also flips
               activeSet.chartView to 'scatter'). The controls bar holds
               the X/Y metric pickers and reference-line toggle; the
               #scatter-chart div is the Plotly target. The container
               is wired by js/chartTabs.js + js/scatterControls.js +
               js/scatterChart.js: see those files for behavior. -->
          <!-- Performance Grid view: ticker x period heatmap of returns and ranks,
               rendered by js/multiPeriodRankHeatmap.js from the cached rankings. -->
          <div class="chart-heatmap-view" id="chart-heatmap-view" hidden>
            <p class="rank-heatmap-intro">How each ticker ranks against the others across many time horizons. Spot consistent performers at a glance.</p>
            <div id="rank-heatmap-root" class="rank-heatmap-root"></div>
          </div>

          <!-- Year by Year view: calendar-year returns and ranks per ticker,
               rendered by js/yearlyRankHeatmap.js from rankings[].yearly_returns. -->
          <div class="chart-heatmap-view" id="chart-yearly-view" hidden>
            <p class="rank-heatmap-intro">How each ticker ranked in each calendar year. Separate years do not overlap, so a steady average rank means consistent performance, not one great year counted several times. The current year is year to date. Untick any year to leave it out of Avg Rank.</p>
            <div id="yearly-heatmap-root" class="rank-heatmap-root"></div>
          </div>

          <!-- Correlation Map: other tickers placed by correlation with a chosen
               center ticker and by return, rendered by js/correlationMap.js. -->
          <div class="chart-heatmap-view" id="chart-corrmap-view" hidden>
            <p class="rank-heatmap-intro">Pick a ticker and see what moves with it and what moves on its own. Names on the left that sit above the dashed line earned more while moving differently, which is what makes a holding a useful diversifier.</p>
            <div id="correlation-map-root" class="correlation-root"></div>
          </div>

          <!-- Correlated Groups: one row per group whose every pair correlates
               above the threshold, rendered by js/correlationGroups.js. -->
          <div class="chart-heatmap-view" id="chart-corrgroups-view" hidden>
            <p class="rank-heatmap-intro">Groups of names that rise and fall together. Every pair inside a group moves together above the threshold, so owning several names from one group spreads risk less than it looks.</p>
            <div id="correlation-groups-root" class="correlation-root"></div>
          </div>

          <div class="chart-scatter-view" id="chart-scatter-view" hidden>
            <!-- Performance Tradeoffs section header.
                 Sits *outside* the toolbar container so it reads as a
                 view-level title/description, not as something tucked
                 inside the controls. Eyebrow pill on the left, tagline
                 to its right. -->
            <div class="scatter-controls-intro">
              <span class="scatter-controls-eyebrow">Performance tradeoffs</span>
              <span class="scatter-controls-tagline">Compare any two metrics visually across your tickers</span>
            </div>
            <!-- Performance Tradeoffs toolbar (redesigned).
                 Card-style layout: each control sits on its own soft
                 panel so the eye groups [Y-axis stuff] · [X-axis stuff]
                 · [Reference-lines] without needing a visual hierarchy
                 hint. The "vs." badge between Y and X is the connector
                 - it makes the relationship explicit (we're plotting Y
                 vs X) instead of leaving it to inference. -->
            <div class="scatter-controls scatter-controls-v2" id="scatter-controls">
              <div class="scatter-controls-row">
                <div class="scatter-control-card" data-axis="y">
                  <div class="scatter-control-card-header">
                    <label for="scatter-y-select" class="scatter-control-card-label">
                      <span class="scatter-control-axis-badge scatter-control-axis-badge-y" aria-hidden="true">Y</span>
                      Y axis
                    </label>
                    <div class="scatter-scale-segmented" role="group" aria-label="Y-axis scale" data-scale-axis="y">
                      <button type="button" class="scatter-scale-segment" data-scale="linear">Lin</button>
                      <button type="button" class="scatter-scale-segment" data-scale="log">Log</button>
                    </div>
                  </div>
                  <select id="scatter-y-select" aria-label="Y-axis metric"></select>
                </div>

                <span class="scatter-vs-badge" aria-hidden="true">vs</span>

                <div class="scatter-control-card" data-axis="x">
                  <div class="scatter-control-card-header">
                    <label for="scatter-x-select" class="scatter-control-card-label">
                      <span class="scatter-control-axis-badge scatter-control-axis-badge-x" aria-hidden="true">X</span>
                      X axis
                    </label>
                    <div class="scatter-scale-segmented" role="group" aria-label="X-axis scale" data-scale-axis="x">
                      <button type="button" class="scatter-scale-segment" data-scale="linear">Lin</button>
                      <button type="button" class="scatter-scale-segment" data-scale="log">Log</button>
                    </div>
                  </div>
                  <select id="scatter-x-select" aria-label="X-axis metric"></select>
                </div>

                <div class="scatter-control-card scatter-refline-control-card">
                  <div class="scatter-control-card-header">
                    <label class="scatter-control-card-label" data-help="scatterReferenceLines">Reference lines</label>
                  </div>
                  <div class="scatter-refline-segmented" role="group" aria-label="Reference lines">
                    <button type="button" class="scatter-refline-segment" data-refline="median">Median</button>
                    <button type="button" class="scatter-refline-segment" data-refline="mean">Mean</button>
                    <button type="button" class="scatter-refline-segment" data-refline="none">Off</button>
                  </div>
                </div>
              </div>
            </div>
            <div id="scatter-chart" class="scatter-chart"></div>
            <div class="scatter-empty" id="scatter-empty" hidden>
              <i data-lucide="scatter-chart" aria-hidden="true"></i>
              <p>Pick at least two tickers with data on both selected metrics to see the scatter plot.</p>
            </div>
            <!-- Chip strip below the chart, mirroring the multi-period
                 legend pattern. Hovering a chip pops the rich-hover
                 dossier (full quant cheatsheet); clicking pins the
                 ticker's highlight visual on the scatter point. Built
                 by js/scatterLegend.js. -->
            <div class="chart-legend-divider" aria-hidden="true"></div>
            <!-- Sort-by toggle for the chip strip below. Defaults to X axis
                 (matches the user's mental model of reading the chart left-
                 to-right) and respects each metric's "better is up/down"
                 direction so the highest-rank ticker always lands first.
                 Wired by js/scatterLegend.js. -->
            <div class="scatter-legend-toolbar" id="scatter-legend-toolbar">
              <span class="scatter-legend-toolbar-label">Sort by</span>
              <div class="scatter-legend-sort-segmented" role="group" aria-label="Sort legend by axis">
                <button type="button" class="scatter-legend-sort-segment is-active" data-sort-axis="x">X axis</button>
                <button type="button" class="scatter-legend-sort-segment" data-sort-axis="y">Y axis</button>
              </div>
            </div>
            <div class="scatter-legend" id="scatter-legend"></div>
          </div><!-- /.chart-scatter-view -->

          <!-- Multi-Period Returns view cluster. Hidden until the user
               clicks the multi-period tab. The toolbar holds the period
               scope toggle (selected vs all PRESETS) and the Lin/Spread
               Y-scale toggle. The Y-axis is locked to raw returns;
               "vs Benchmark" was retired (see multiPeriodControls.js
               header for rationale). The #multiperiod-chart div is the
               Plotly target. The custom legend below is rendered by
               multiPeriodLegend.js and reuses the same legendStates.js
               highlight semantics as the line-chart legend. -->
          <div class="chart-multiperiod-view" id="chart-multiperiod-view" hidden>
            <div class="multiperiod-controls" id="multiperiod-controls">
              <!-- The "Y axis: Returns | vs Benchmark" toggle used to live
                   here. The "vs Benchmark" mode was removed; with only
                   "Returns" left, a single-button segmented control is
                   visual noise, so the whole row is gone. The renderer
                   still hard-codes 'returns' (and the state setter
                   normalizes legacy 'excess' values back to 'returns'),
                   so persisted comparison sets keep working. -->
              <div class="multiperiod-control">
                <label class="multiperiod-control-label" data-help="multiPeriodPeriodScope">Periods</label>
                <div class="multiperiod-scope-segmented" role="group" aria-label="Periods shown">
                  <button type="button" class="multiperiod-scope-segment" data-scope="selected">Selected</button>
                  <button type="button" class="multiperiod-scope-segment" data-scope="all">All</button>
                </div>
              </div>
              <div class="multiperiod-control">
                <label class="multiperiod-control-label" data-help="multiPeriodYScale">Y scale</label>
                <div class="multiperiod-scale-segmented" role="group" aria-label="Y-axis scale">
                  <button type="button" class="multiperiod-scale-segment" data-scale="linear" title="Plot raw values on a linear axis">Linear</button>
                  <button type="button" class="multiperiod-scale-segment" data-scale="scaled" title="asinh: keeps small moves close to zero while compressing long-horizon blowups">Spread</button>
                </div>
              </div>
            </div>
            <div id="multiperiod-chart" class="multiperiod-chart"></div>
            <div class="multiperiod-empty" id="multiperiod-empty" hidden>
              <i data-lucide="trending-up" aria-hidden="true"></i>
              <p>Pick at least two tickers with multi-period return data to see this chart.</p>
            </div>
            <div class="chart-legend-divider" aria-hidden="true"></div>
            <div class="multiperiod-legend" id="multiperiod-legend"></div>
          </div><!-- /.chart-multiperiod-view -->

          <!-- Stories gallery view. A grid of magazine-worthy
               visualizations of the active comparison set, designed for
               sharing on social media and professional publications.
               Each tile is one "story" (Hero Growth chart first; more
               will be added: Drawdown Rivers, Time Underwater, Risk vs
               Return, etc.). Two display states:
                 1. Gallery mode (default): grid of icon tiles, each
                    showing a thumbnail preview of the actual story
                    rendered with the user's current data.
                 2. Stage mode: one story expanded full-width with an
                    aspect-ratio switcher (16:9 / 4:5 / 1:1), back
                    button, and (later) export controls.
               All heavy assets (D3 modules, per-story renderers,
               display fonts) are lazy-loaded on first tab open via
               js/stories/index.js: zero boot cost when the storiesGallery
               feature flag is off. -->
          <div class="chart-stories-view" id="chart-stories-view" hidden>
            <div class="stories-gallery" id="stories-gallery"></div>
            <div class="stories-stage" id="stories-stage" hidden></div>
            <div class="stories-empty" id="stories-empty" hidden>
              <i data-lucide="sparkles" aria-hidden="true"></i>
              <p>Pick at least two tickers to generate stories from your comparison.</p>
            </div>
          </div><!-- /.chart-stories-view -->
        </section>

        <section class="table-section" id="table-section" hidden>
          <div class="section-header">
            <div class="table-title-row">
              <div class="table-title-left">
                <h2 id="rankings-title" data-help="rankingsTable">Quantitative Rankings</h2>
                <button
                  type="button"
                  id="rankings-column-config-btn"
                  class="rankings-column-config-btn"
                  aria-label="Configure rankings table columns"
                  title="Configure columns">
                  <i data-lucide="sliders-horizontal" aria-hidden="true"></i>
                </button>
              </div>
              <div class="table-period-chip" id="table-period-chip">
                <i data-lucide="calendar-days" class="table-period-chip-icon" aria-hidden="true"></i>
                <span class="table-period-chip-label">Period</span>
                <span class="table-period-chip-value table-date-range" id="table-date-range"></span>
              </div>
            </div>
            <!-- Subtitle reads as one paragraph but is paced into ~5 phrases.
                 Each separator is a lucide icon that telegraphs the concept of
                 the upcoming phrase (palette → color-coded ranks; line-chart
                 → multi-period chart; columns → return columns; mouse-pointer
                 → click/hover affordances). Per-icon stroke color is set
                 inline so each separator carries its own accent (rest of the
                 styling lives in .rankings-subtitle-sep in style.css). The
                 global loadAndCreateIcons() call during ui.js init
                 materializes these <i> placeholders into <svg>. -->
            <p class="rankings-subtitle">
              Compare every ticker side-by-side across <strong>ranked returns</strong>, <strong>quant metrics</strong>, and across <strong>multiple time windows</strong><span class="rankings-subtitle-sep" aria-hidden="true"><i data-lucide="palette" style="color:#f59e0b"></i></span><strong>Color-coded ranks</strong> for instant visual comparison<span class="rankings-subtitle-sep" aria-hidden="true"><i data-lucide="line-chart" style="color:#3b82f6"></i></span><strong>Multi-period chart</strong> spanning a wide range of presets for instant performance reads<span class="rankings-subtitle-sep" aria-hidden="true"><i data-lucide="mouse-pointer-click" style="color:#8b5cf6"></i></span>Click any column header to sort, or hover any cell for the underlying numbers.
            </p>
            <div class="table-coverage-warning" id="table-coverage-warning" hidden></div>
            <div class="table-category-toggles" role="tablist" aria-label="Rankings column category">
              <button type="button" role="tab" class="category-toggle active" id="toggle-basic" data-category="basic" aria-selected="true" aria-controls="ranking-table">
                <i data-lucide="trending-up" class="category-toggle-icon" aria-hidden="true"></i>
                <span class="category-toggle-label">Performance Insights</span>
                <span class="category-count"></span>
              </button>
              <button type="button" role="tab" class="category-toggle" id="toggle-special" data-category="special" aria-selected="false" aria-controls="ranking-table" tabindex="-1">
                <i data-lucide="sigma" class="category-toggle-icon" aria-hidden="true"></i>
                <span class="category-toggle-label">Quant Insights</span>
                <span class="category-count"></span>
                <i data-lucide="lock" class="category-lock" aria-hidden="true" hidden></i>
              </button>
            </div>
          </div>
          <!-- Top scrollbar for better UX -->
          <div class="table-top-scrollbar" id="table-top-scrollbar">
            <div id="table-top-scrollbar-content"></div>
          </div>
          <div class="table-scroll-wrapper" id="table-scroll-wrapper">
            <table id="ranking-table">
            <thead>
              <tr>
                <th class="visibility-header" data-help="columnVisibility" title="Toggle visibility in chart">📈</th>
                <th data-col="ticker" data-help="columnTicker">Ticker</th>
                <th class="fav-header" data-help="columnFav" title="Add or remove this ticker from your lists">Fav</th>
                <th class="links-header" title="Quick research shortcuts">Links</th>
                <!-- COLUMN ORDER: "signals before facts" (May 2026)
                     The table is laid out in five clusters, left to right:
                       1. Identity     visibility / ticker / fav / links
                       2. Headline     Return → 1D Return → vs Bench → Pareto
                       3. Across-time  Sparkline → Multi-Period Ranks → Beat
                       4. Quant        registry-generated block (core 5 risk
                                       metrics first: ATH Trend, Volatility,
                                       Sharpe, Drawdown, SPY Corr: then
                                       deeper-analysis tail, then Range)
                       5. Reference    Market Cap → CAGR → Duration → Price
                                       (demoted right: these are "facts about
                                       the asset", not analytical signals)
                       6. Tail         dynamic per-preset return strip
                     The "canonical" order is ultimately enforced by
                     columnPinning._restoreCanonicalOrder which runs on every
                     render, so this static order is mostly cosmetic (avoids a
                     reorder flash on first paint), but `_createRow`'s cell
                     append sequence in mainAppTable.js MUST mirror it or
                     column-to-cell alignment breaks before pinning runs. -->

                <!-- HEADLINE PERFORMANCE: the columns the user actually came
                     here to compare. Return is the headline for the active
                     period; 1D Return reads entry.preset_returns['1D'] which
                     the backend ships unconditionally; vs Bench is hidden
                     via columnVisibilityRules when no benchmark is set. The
                     Pareto "Efficient" <th> is injected immediately after
                     vs Bench by _setupParetoFrontierHeader (internal-only). -->
                <th class="sortable" data-sort="return" data-help="columnReturn">Return <span class="sort-icon"></span></th>
                <th class="sortable day-return-header" data-sort="day_return" data-help="columnDayReturn" title="Today's price change (latest close vs previous close)">1D Return <span class="sort-icon"></span></th>
                <th class="sortable vs-bench-header" data-sort="vs_bench" data-help="columnVsBench" title="Outperformance vs benchmark for the active range">vs Bench <span class="sort-icon"></span></th>

                <!-- ACROSS-TIME: visual + summary reads that span multiple
                     periods. The Sparkline column is the active-period mini
                     chart (click opens a multi-period grid modal). The
                     "Multi-Period Ranks" <th> to its right is injected by
                     RankingTableCore._updatePresetHeaders when the user has
                     any presets selected; Beat count summarizes "how many
                     periods beat the benchmark" before the per-preset
                     return strip at the far right of the table. -->
                <th class="sparkline-header" data-help="columnSparkline" title="Mini-chart of the active period; click to see all periods">Multi-Period Comparison</th>
                <th class="sortable beat-count-header" data-sort="beat_count" data-help="columnBeatCount" title="Number of multi-period returns where this ticker beat the benchmark">Beat <span class="sort-icon"></span></th>

                <!-- QUANT METRICS: generated at startup from the column
                     registry in `js/table/columns.js`. The placeholder
                     marker below is the insertion point; mainAppTable.js
                     replaces it with one <th> per quant metric. Registry
                     order today: ATH Trend → Volatility → Sharpe →
                     Drawdown → SPY Corr (the "core 5"), then ATH Trend
                     Return → Loss Profile → Recommended Allocation →
                     Trend Score → Trend Return → Range (deeper analysis +
                     visual close). To add or reorder metrics, edit the
                     registry: NOT this file. -->
                <th class="quant-metrics-insertion-marker" hidden></th>

                <!-- REFERENCE / VALUATION: facts about the asset, demoted
                     to the right of the analytical signals. Duration auto-
                     hides via columnVisibilityRules when implied by the
                     active range. Price is also surfaced in the ticker
                     info-icon hover so it remains one-hover-away even
                     when the column is scrolled off. -->
                <th class="sortable market-cap-header" data-sort="market_cap" data-help="columnMarketCap">Market Cap <span class="sort-icon"></span></th>
                <th class="sortable" data-sort="cagr" data-help="columnCAGR">CAGR <span class="sort-icon"></span></th>
                <th class="sortable" data-sort="duration" data-help="columnDuration">Duration <span class="sort-icon"></span></th>
                <th class="latest-price-header" data-help="columnLatestPrice">Price</th>

                <!-- MULTI-PERIOD RETURNS: dynamic per-preset cells (1M, YTD,
                     1Y, …) are inserted here by RankingTableCore. -->
                <th class="multi-period-insertion-marker" style="display: none;"></th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
          <!-- Table skeleton (shown until first render, sits below the real header) -->
          <div id="table-skeleton" class="table-skeleton">
            <div class="skeleton-row"></div>
            <div class="skeleton-row"></div>
            <div class="skeleton-row"></div>
            <div class="skeleton-row"></div>
            <div class="skeleton-row"></div>
            <div class="skeleton-row"></div>
            <div class="skeleton-row"></div>
            <div class="skeleton-row"></div>
          </div>
          </div>
        </section>
      </main>

      <footer class="app-footer">
        <div class="footer-legal">
          <a href="/privacy">Privacy</a>
          <span class="footer-separator">·</span>
          <a href="/terms">Terms</a>
          <span class="footer-separator">·</span>
          <a href="https://betterthanspy.com/">BetterThanSPY</a>
          <span class="footer-separator">·</span>
          <a href="https://betterthanspy.com/research">Free stock and ETF research</a>
        </div>
        <span id="status-indicator" class="status">Loading…</span>
      </footer>
    </div>

    <!-- Sticky bottom utility bar, global preset switcher (visible from anywhere) -->
    <div id="utility-bar" class="utility-bar" aria-label="Quick timeframe switcher"></div>

    <!-- Upgrade Summary Modal -->
    <div id="upgrade-summary-modal" class="upgrade-modal" hidden>
      <div class="upgrade-modal-backdrop"></div>
      <div class="upgrade-modal-container">
        <button class="upgrade-modal-close" aria-label="Close">
          <i data-lucide="x"></i>
        </button>
        <div class="upgrade-modal-header">
          <i data-lucide="sparkles" class="upgrade-modal-icon"></i>
          <h2 data-upgrade-title>EasyCompare Pro</h2>
        </div>
        <div class="upgrade-modal-content">
          <ul class="upgrade-features-list" data-upgrade-list></ul>
          <a href="/upgrade" class="upgrade-modal-cta">
            View upgrade options
            <i data-lucide="arrow-right"></i>
          </a>
        </div>
      </div>
    </div>

    <!-- Email Feedback Modal -->
    <div
      id="feedback-modal"
      class="feedback-modal"
      role="dialog"
      aria-modal="true"
      aria-labelledby="feedback-modal-title"
      hidden
    >
      <div class="feedback-modal-backdrop" data-feedback-close></div>
      <div class="feedback-dialog">
        <button class="feedback-close" type="button" aria-label="Close feedback" data-feedback-close>
          <i data-lucide="x" aria-hidden="true"></i>
        </button>
        <div class="feedback-header">
          <span class="feedback-header-icon" aria-hidden="true">
            <i data-lucide="message-circle"></i>
          </span>
          <div>
            <h2 id="feedback-modal-title">We’d love your feedback</h2>
            <p>Bug reports, feature ideas, and honest feedback are always welcome.</p>
          </div>
        </div>
        <div class="feedback-content">
          <p class="feedback-welcome">
            Please email us at <a data-contact="feedback"></a>
          </p>

          <div class="feedback-email-row">
            <span class="feedback-email-address" data-contact="feedback"></span>
            <button id="feedback-copy-email" class="feedback-compact-button" type="button">
              <i data-lucide="copy" aria-hidden="true"></i>
              Copy email
            </button>
          </div>

          <label class="feedback-template-label" for="feedback-template">
            Supplementary information to include in your email
          </label>
          <textarea id="feedback-template" class="feedback-template" rows="13" spellcheck="true"></textarea>

          <div id="feedback-copy-status" class="feedback-copy-status" role="status" aria-live="polite"></div>
          <div class="feedback-actions">
            <button class="feedback-secondary-button" type="button" data-feedback-close>Cancel</button>
            <button id="feedback-copy-template" class="feedback-primary-button" type="button">
              <i data-lucide="copy" aria-hidden="true"></i>
              Copy template
            </button>
          </div>
        </div>
      </div>
    </div>
<script>window.addEventListener('load', function() { if (window.logger) { logger.debug('%c🚀 SimpleTrends Deployment Info', 'color: #4CAF50; font-weight: bold; font-size: 14px;'); logger.debug('Commit: 993ab92'); logger.debug('Branch: main'); logger.debug('Built: 2026-10-30 18:05:43 UTC'); } });</script>
  </body>
</html>

