<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <base href="/" />
    <meta name="theme-color" content="#1f2027" />
    <meta name="mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
    <meta
      name="description"
      content="STREET VOICES is a social enterprise and media platform with an online directory that provides access to free and subsidized services in the Greater Toronto Area."
    />
    <title>StreetBot</title>
    <meta property="og:title" content="StreetBot" />
    <meta
      property="og:description"
      content="STREET VOICES is a social enterprise and media platform with an online directory that provides access to free and subsidized services in the Greater Toronto Area."
    />
    <meta property="og:type" content="website" />
    <meta property="og:site_name" content="StreetBot" />
    <meta property="og:url" content="https://streetvoices-directory.pages.dev/" />
    <meta
      property="og:image"
      content="https://streetvoices-directory.pages.dev/sv2-favicon.svg"
    />
    <meta name="twitter:title" content="StreetBot" />
    <meta
      name="twitter:description"
      content="STREET VOICES is a social enterprise and media platform with an online directory that provides access to free and subsidized services in the Greater Toronto Area."
    />
    <meta
      name="twitter:image"
      content="https://streetvoices-directory.pages.dev/sv2-favicon.svg"
    />
    <meta name="twitter:card" content="summary_large_image" />
    <link rel="icon" type="image/svg+xml" href="/sv2-favicon.svg" />
    <link rel="icon" type="image/png" sizes="32x32" href="/sv2-favicon-32x32.png" />
    <link rel="icon" type="image/png" sizes="16x16" href="/sv2-favicon-16x16.png" />
    <link rel="shortcut icon" href="/sv2-favicon.ico" />
    <link rel="apple-touch-icon" sizes="180x180" href="/sv2-apple-touch-icon-180x180.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content" />
    <!-- Preload critical fonts — eliminates FOIT/FOUT on first paint -->
    <link rel="preload" href="/fonts/Rubik-variable.woff2" as="font" type="font/woff2" crossorigin />
    <link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin />
    <link rel="preload" href="/fonts/Inter-SemiBold.woff2" as="font" type="font/woff2" crossorigin />
    <!-- Preload logo SVGs — visible above the fold on every landing -->
    <link rel="preload" href="/assets/streetvoices-text.svg" as="image" />
    <link rel="preload" href="/assets/streetvoices-text-dark.svg" as="image" />
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        height: 100%;
      }

      button[aria-label='Toggle theme']:not(.sv-theme-toggle-button),
      a[aria-label='Settings'] {
        display: none !important;
      }

      .relative.size-10.justify-center:not(:has(.sv-new-chat-landing-logo)) > * {
        opacity: 0 !important;
      }

      .relative.size-10.justify-center:not(:has(.sv-new-chat-landing-logo)) > .shadow-stroke {
        background: transparent !important;
        border-radius: 0 !important;
        box-shadow: none !important;
      }

      .relative.size-10.justify-center:not(:has(.sv-new-chat-landing-logo))::before {
        content: '';
        position: absolute;
        left: 50%;
        top: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
        display: block;
        background-image: url('/assets/streetbot-icon-home-dark-animated.svg?v=20260423k');
        background-repeat: no-repeat;
        background-position: center;
        background-size: contain;
        pointer-events: none;
      }

      html[data-theme='light']
        .relative.size-10.justify-center:not(:has(.sv-new-chat-landing-logo))::before {
          background-image: url('/assets/streetbot-icon-home-light-animated.svg?v=20260423b');
      }

      [aria-label='Select a model']:has(.sv-streetbot-chip-icon)::before,
      [aria-label='Select a model']:has(img[src*='streetbot-icon-home'])::before {
        content: none !important;
        display: none !important;
      }

      nav#chat-history-nav
        [role='grid'][aria-label='Conversations']
        [role='button'][aria-label$=' conversation']
        .flex.grow.items-center.gap-2
        > div:has(img[src*='street-voices-bot']),
      nav#chat-history-nav
        [role='grid'][aria-label='Conversations']
        [role='button'][aria-label$=' conversation']
        img[src*='street-voices-bot'] {
        display: none !important;
      }

      [aria-label='Select a model']:not(:has(.sv-streetbot-chip-icon)):not(:has(img[src*='streetbot-icon-home']))
        svg {
        display: none !important;
      }

      [aria-label='Select a model'][data-non-streetbot-agent='true']::before {
        content: none !important;
        display: none !important;
        background-image: none !important;
      }

      [aria-label='Select a model']:not(:has(.sv-streetbot-chip-icon)):not(:has(img[src*='streetbot-icon-home']))::before {
        content: '';
        display: inline-block;
        flex: 0 0 auto;
        width: 20px;
        height: 20px;
        margin-right: 6px;
        vertical-align: middle;
        background-image: url('/assets/streetbot-icon-home-dark-animated.svg?v=20260423k');
        background-repeat: no-repeat;
        background-position: center;
        background-size: contain;
      }

      html[data-theme='light']
        [aria-label='Select a model']:not(:has(.sv-streetbot-chip-icon)):not(:has(img[src*='streetbot-icon-home']))::before {
          background-image: url('/assets/streetbot-icon-home-light-animated.svg?v=20260423b');
      }

      [aria-label='Select a model'][data-non-streetbot-agent='true']:not(:has(.sv-streetbot-chip-icon)):not(:has(img[src*='streetbot-icon-home']))
        svg {
        display: block !important;
      }

      [aria-label='Select a model']:has(img[src*='agent-marketplace-icons'])::before {
        content: none !important;
        display: none !important;
        background-image: none !important;
      }

      body:has([aria-label='Select a model'] img[src*='agent-marketplace-icons'])
        .relative.size-10.justify-center:not(:has(.sv-new-chat-landing-logo)) > * {
        opacity: 1 !important;
      }

      body:has([aria-label='Select a model'][data-non-streetbot-agent='true'])
        .relative.size-10.justify-center:not(:has(.sv-new-chat-landing-logo)) > * {
        opacity: 1 !important;
      }

      body:has([aria-label='Select a model'] img[src*='agent-marketplace-icons'])
        .relative.size-10.justify-center:not(:has(.sv-new-chat-landing-logo))::before {
        content: none !important;
        display: none !important;
        background-image: none !important;
      }

      body:has([aria-label='Select a model'][data-non-streetbot-agent='true'])
        .relative.size-10.justify-center:not(:has(.sv-new-chat-landing-logo))::before {
        content: none !important;
        display: none !important;
        background-image: none !important;
      }
    </style>
    <script>
      const streetBotRefreshParams = new URLSearchParams(window.location.search);
      if (streetBotRefreshParams.has('sv-refresh')) {
        try {
          sessionStorage.setItem('streetbot:sw-reset-reloaded:v7', '1');
        } catch {}
        streetBotRefreshParams.delete('sv-refresh');
        const streetBotRefreshSearch = streetBotRefreshParams.toString();
        window.history.replaceState(
          window.history.state,
          '',
          window.location.pathname +
            (streetBotRefreshSearch ? `?${streetBotRefreshSearch}` : '') +
            window.location.hash,
        );
      }

      (function () {
        var isLocalDirectoryDev =
          (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') &&
          window.location.port === '3091';
        if (!isLocalDirectoryDev) return;

        var RESET_KEY = 'streetvoices:directory-3091-cache-reset:20260625-services-407716';
        try {
          if (localStorage.getItem(RESET_KEY) !== '1') {
            [localStorage, sessionStorage].forEach(function (storage) {
              for (var i = storage.length - 1; i >= 0; i -= 1) {
                var key = storage.key(i);
                if (
                  key &&
                  (key.indexOf('streetbot:directory:') === 0 ||
                    key.indexOf('streetvoices:directory:') === 0 ||
                    key === 'streetProfileDirectoryState')
                ) {
                  storage.removeItem(key);
                }
              }
            });
            localStorage.setItem(RESET_KEY, '1');
          }
        } catch (_) {}

        if ('serviceWorker' in navigator) {
          navigator.serviceWorker.getRegistrations()
            .then(function (registrations) {
              return Promise.all(registrations.map(function (registration) {
                return registration.unregister();
              }));
            })
            .catch(function () {});
        }

        if ('caches' in window) {
          caches.keys()
            .then(function (keys) {
              return Promise.all(keys.map(function (key) {
                return caches.delete(key);
              }));
            })
            .catch(function () {});
        }
      })();

      if (window.location.pathname === '/c/new') {
        document.documentElement.setAttribute('data-sv-streetbot-chat', 'true');
      }

      const theme = localStorage.getItem('color-theme');
      const loadingContainerStyle = document.createElement('style');
      let backgroundColor;

      if (theme === 'dark') {
        backgroundColor = '#1f2027';
      } else if (theme === 'light') {
        backgroundColor = '#f7f8fc';
      } else if (theme === 'system') {
        const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
        backgroundColor = prefersDarkScheme ? '#1f2027' : '#f7f8fc';
      } else {
        backgroundColor = '#1f2027';
      }

      loadingContainerStyle.innerHTML = `
        #loading-container {
          display: flex;
          align-items: center;
          justify-content: center;
          height: 100vh;
          background-color: ${backgroundColor};
        }
      `;
      document.head.appendChild(loadingContainerStyle);
    </script>
    <script>
      // Prefetch homepage news BEFORE any JS bundles load.
      // Writes to the same sessionStorage key that React reads synchronously,
      // so the news carousel renders with data on first paint.
      (function () {
        var CACHE_KEY = 'streetbot:homepage-news:v1';
        var FALLBACK_IMG = '/assets/street-news-placeholder.png';
        var isLocalDirectoryDev =
          (location.hostname === 'localhost' || location.hostname === '127.0.0.1') &&
          location.port === '3091';
        if (isLocalDirectoryDev) return;
        try {
          if (localStorage.getItem('streetbot:disable-perf-cache') === '1') return;
          var raw = sessionStorage.getItem(CACHE_KEY);
          if (raw) {
            var entry = JSON.parse(raw);
            if (entry && typeof entry.cachedAt === 'number' && Date.now() - entry.cachedAt < 300000) return;
          }
        } catch (e) { /* proceed to fetch */ }

        fetch('/sbapi/news/articles?status=published&limit=30')
          .then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); })
          .then(function (payload) {
            var items = Array.isArray(payload) ? payload : (payload && payload.items);
            if (!Array.isArray(items)) return;

            function isSV(a) {
              if (typeof a.source === 'string' && a.source.toLowerCase().indexOf('street voices') !== -1) return true;
              var cats = [];
              if (Array.isArray(a.categories)) cats = cats.concat(a.categories);
              if (typeof a.category === 'string' && a.category.trim()) {
                if (a.category.trim().charAt(0) === '[') {
                  try { var p = JSON.parse(a.category); if (Array.isArray(p)) cats = cats.concat(p); else cats.push(a.category); } catch (_) { cats.push(a.category); }
                } else { cats.push(a.category); }
              }
              for (var i = 0; i < cats.length; i++) {
                if (typeof cats[i] === 'string' && cats[i].toLowerCase().replace(/-/g, ' ').indexOf('street voices') !== -1) return true;
              }
              return false;
            }

            function toTitle(s) {
              return s.toLowerCase().split(/[\s-]+/).filter(Boolean).map(function (w) { return w.charAt(0).toUpperCase() + w.slice(1); }).join(' ');
            }

            function getCat(a) {
              var cats = [];
              if (Array.isArray(a.categories)) cats = cats.concat(a.categories);
              if (typeof a.category === 'string' && a.category.trim()) {
                if (a.category.trim().charAt(0) === '[') {
                  try { var p = JSON.parse(a.category); if (Array.isArray(p)) cats = cats.concat(p); else cats.push(a.category); } catch (_) { cats.push(a.category); }
                } else { cats.push(a.category); }
              }
              var norm = cats.filter(function (c) { return typeof c === 'string'; }).map(function (c) { return c.toLowerCase().replace(/-/g, ' ').trim(); }).filter(Boolean);
              var pick = null;
              for (var i = 0; i < norm.length; i++) { if (norm[i] !== 'street voices') { pick = norm[i]; break; } }
              return toTitle(pick || norm[0] || 'news');
            }

            var mapped = items.filter(isSV).map(function (a) {
              var id = String(a.id || '');
              var slug = typeof a.slug === 'string' && a.slug.trim() ? a.slug : '';
              return {
                id: id, title: String(a.title || ''),
                image: String(a.image_url || a.feature_image_url || FALLBACK_IMG) || FALLBACK_IMG,
                category: getCat(a), isInternal: true,
                href: slug ? '/news/' + slug : '/news/' + id,
                publishedAt: typeof a.published_at === 'string' ? a.published_at : undefined
              };
            }).filter(function (n) { return n.id && n.title; })
              .sort(function (a, b) {
                var ta = a.publishedAt ? new Date(a.publishedAt).getTime() : 0;
                var tb = b.publishedAt ? new Date(b.publishedAt).getTime() : 0;
                return tb - ta;
              }).slice(0, 12);

            try { sessionStorage.setItem(CACHE_KEY, JSON.stringify({ value: mapped, cachedAt: Date.now() })); } catch (_) {}

            // Preload the first 3 news card images so they render instantly
            for (var j = 0; j < Math.min(3, mapped.length); j++) {
              if (mapped[j].image && mapped[j].image !== FALLBACK_IMG) { new Image().src = mapped[j].image; }
            }
          })
          .catch(function () {});
      })();
    </script>
    <script>
      // Prefetch directory search BEFORE any JS bundles load.
      // Writes to the same sessionStorage key that useDirectorySearch reads
      // synchronously in its useState initializer, so the directory grid
      // renders with data on first paint.
      (function () {
        var PREFIX = 'streetbot:directory:search:v8:';
        var PARAMS = 'q=&page=0&per_page=12&facets=category_names%2Ccity%2Cprovince%2Cservice_type%2Cages_served%2Cgender_served';
        var KEY = PREFIX + PARAMS;
        try {
          if (localStorage.getItem('streetbot:disable-perf-cache') === '1') return;
          var raw = sessionStorage.getItem(KEY);
          if (raw) {
            var entry = JSON.parse(raw);
            if (entry && typeof entry.cachedAt === 'number' && Date.now() - entry.cachedAt < 300000) return;
          }
        } catch (e) { /* proceed to fetch */ }

        function tryFetch(url) {
          return fetch(url + '?' + PARAMS).then(function (r) {
            if (!r.ok) throw new Error(r.status);
            var ct = r.headers.get('content-type') || '';
            if (ct.indexOf('application/json') === -1) throw new Error('not json');
            return r.json();
          }).then(function (payload) {
            try { sessionStorage.setItem(KEY, JSON.stringify({ value: payload, cachedAt: Date.now() })); } catch (_) {}
          });
        }

        tryFetch('/api/directory/search').catch(function () {
          return tryFetch('/sbapi/services/search');
        }).catch(function () {});
      })();
    </script>
    <!-- Street Bot voice overlay (bottom-right mic icon). Assets live in public/images/. -->
    <link rel="stylesheet" href="/images/streetbot-voice.css?v=17">
    <script defer src="/images/streetbot-voice.js?v=17"></script>
    <script type="module" crossorigin src="/assets/mobile.D_yQvd8a.js"></script>
    <script type="module" crossorigin src="/assets/Icons.Cy8OPJ8Q.js"></script>
    <script type="module" crossorigin src="/assets/user.f-NlmySA.js"></script>
    <script type="module" crossorigin src="/assets/zoom-in.DSJtq1t8.js"></script>
    <script type="module" crossorigin src="/assets/trash-2.CCpzVFZ1.js"></script>
    <script type="module" crossorigin src="/assets/shield.DLi_S7Ny.js"></script>
    <script type="module" crossorigin src="/assets/arrow-left.CrgrUSkZ.js"></script>
    <script type="module" crossorigin src="/assets/index.BcUr3-4w.js"></script>
    <link rel="stylesheet" crossorigin href="/assets/mobile.CeyuhguU.css">
  <link rel="manifest" href="/manifest.webmanifest" crossorigin="use-credentials"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js?streetbot-reset=20260604-v1"></script></head>
  <body>
    <div id="root">
      <div id="loading-container"></div>
    </div>
  </body>
</html>
