<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <!-- Content Security Policy -->
  

  <title>FinSwap - UK Fish Swap & Aquarium Marketplace | Buy, Sell, Swap Tropical Fish</title>
  <meta name="description" content="FinSwap is the UK's trusted fish swap marketplace. Buy, sell, and swap aquarium fish, tropical fish, and aquatic livestock safely. Find fish swaps near you or browse online listings." />
  <meta name="keywords" content="fish swap uk, aquarium fish swap, tropical fish swap, fish swap near me, buy aquarium fish uk, sell tropical fish, fish rehome uk, aquarium marketplace, fish swaps" />
  <!-- Canonical URL is set per-page via React Helmet - do not set globally here -->
  
  <!-- Google Analytics will be loaded conditionally after user consent -->
  <!-- See src/lib/cookieConsent.js and src/App.jsx -->
  
  <!-- Google Search Console Verification -->
  <!-- Replace YOUR_VERIFICATION_CODE with the code from Google Search Console -->
  <!-- <meta name="google-site-verification" content="YOUR_VERIFICATION_CODE" /> -->

  <!-- Open Graph (static so crawlers see it) -->
  <meta property="og:type" content="website" />
  <meta property="og:site_name" content="FinSwap" />
  <meta property="og:url" content="https://finswap.co.uk/" />
  <meta property="og:title" content="FinSwap - UK Fish Swap & Aquarium Marketplace | Buy, Sell, Swap Tropical Fish" />
  <meta property="og:description" content="FinSwap is the UK's trusted fish swap marketplace. Buy, sell, and swap aquarium fish, tropical fish, and aquatic livestock safely. Find fish swaps near you." />
  <meta property="og:image" content="https://gtgyotzghtwxeutzihhl.supabase.co/storage/v1/object/public/listing-images/Finswap%20Thumbnail%20Compressed.png" />
  <meta property="og:image:type" content="image/png" />
  <meta property="og:image:width" content="1200" />
  <meta property="og:image:height" content="630" />
  <meta property="og:image:alt" content="FinSwap thumbnail image" />

  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="FinSwap - UK Fish Swap & Aquarium Marketplace" />
  <meta name="twitter:description" content="Buy, sell, and swap aquarium fish, tropical fish, and aquatic livestock safely in the UK. Find fish swaps near you or browse online listings." />
  <meta name="twitter:image" content="https://gtgyotzghtwxeutzihhl.supabase.co/storage/v1/object/public/listing-images/Finswap%20Thumbnail%20Compressed.png" />

  <!-- (Optional) Favicon -->
  <link
    rel="icon"
    href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🐟</text></svg>"
  >
  <script type="module" crossorigin src="/assets/index-CyhBnFd7.js"></script>
  <link rel="stylesheet" crossorigin href="/assets/index-DCTzdiB6.css">
  <meta name="description" content="A vibrant community marketplace for aquatic enthusiasts to buy, sell, and trade aquarium livestock, plants, and equipment.">
  <meta name="twitter:card" content="summary_large_image">
  <meta http-equiv="Content-Security-Policy" content="default-src &#39;self&#39; data:; base-uri &#39;self&#39;; form-action &#39;self&#39;; script-src &#39;self&#39; &#39;unsafe-inline&#39; &#39;unsafe-eval&#39; https://js.stripe.com https://r.stripe.com https://m.stripe.com https://checkout-cookies.stripe.com https://www.googletagmanager.com https://*.googletagmanager.com; style-src &#39;self&#39; &#39;unsafe-inline&#39; https:; img-src &#39;self&#39; https://gtgyotzghtwxeutzihhl.supabase.co https://api.mapbox.com https://*.tiles.mapbox.com https://tile.openstreetmap.org https://ui-avatars.com https://placehold.co https://www.googletagmanager.com https://*.googletagmanager.com https://www.google-analytics.com https://*.google-analytics.com https://stats.g.doubleclick.net data: blob:; connect-src &#39;self&#39; data: https://gtgyotzghtwxeutzihhl.supabase.co https://gtgyotzghtwxeutzihhl.functions.supabase.co wss://gtgyotzghtwxeutzihhl.supabase.co https://api.stripe.com https://errors.stripe.com https://js.stripe.com https://r.stripe.com https://checkout-cookies.stripe.com https://merchant-ui-api.stripe.com https://stripe.com https://nominatim.openstreetmap.org https://*.openstreetmap.org https://api.mapbox.com https://events.mapbox.com https://finswap.co.uk https://www.google-analytics.com https://*.google-analytics.com https://region1.google-analytics.com https://stats.g.doubleclick.net http://localhost:9999 ws://localhost:9999 http://127.0.0.1:9999 ws://127.0.0.1:9999 http://localhost:8888 ws://localhost:8888 http://127.0.0.1:8888 ws://127.0.0.1:8888; font-src &#39;self&#39; data: https:; worker-src &#39;self&#39; blob:; frame-src &#39;self&#39; https://js.stripe.com https://hooks.stripe.com https://checkout-cookies.stripe.com; object-src &#39;none&#39;">
</head>
<body>
  <div id="root"></div>
  <!-- Suppress Chrome extension errors early (before React loads) -->
  <script>
    // Suppress errors from Chrome extensions, built-in features, and Stripe adblocker issues
    // Use capture phase and be very aggressive
    (function() {
      const originalError = window.onerror;
      window.onerror = function(message, source, lineno, colno, error) {
        const errorString = String(message || '') + ' ' + String(source || '') + ' ' + String(error?.stack || '');
        const sourceStr = String(source || '');
        const messageStr = String(message || '');
        
        // Check for extension errors
        if (
          sourceStr.includes('chrome-extension://') ||
          sourceStr.includes('moz-extension://') ||
          sourceStr.includes('safari-extension://') ||
          sourceStr.includes('webpage_content_reporter') ||
          messageStr.includes('chrome-extension://') ||
          messageStr.includes('webpage_content_reporter') ||
          (messageStr.includes('Unexpected token') && (messageStr.includes('export') || sourceStr.includes('webpage_content_reporter'))) ||
          errorString.includes('webpage_content_reporter') ||
          errorString.includes('chrome-extension://')
        ) {
          return true; // Suppress the error
        }
        
        // Check for Stripe adblocker errors
        if (
          messageStr.includes('ERR_BLOCKED_BY_ADBLOCKER') ||
          (errorString.includes('stripe.com') && (errorString.includes('ERR_BLOCKED_BY_ADBLOCKER') || errorString.includes('Failed to fetch'))) ||
          sourceStr.includes('out-') && sourceStr.includes('.js') && errorString.includes('stripe') ||
          sourceStr.includes('shared-') && sourceStr.includes('.js') && errorString.includes('stripe')
        ) {
          return true; // Suppress Stripe adblocker errors
        }
        
        // Call original handler if it exists
        if (originalError) {
          return originalError.call(this, message, source, lineno, colno, error);
        }
        return false;
      };
      
      // Also use addEventListener as backup with capture phase
      window.addEventListener('error', function(event) {
        const source = event.filename || '';
        const message = event.message || '';
        const errorString = source + ' ' + message + ' ' + (event.error?.stack || '');
        
        // Check for extension errors
        if (
          source.includes('chrome-extension://') ||
          source.includes('moz-extension://') ||
          source.includes('safari-extension://') ||
          source.includes('webpage_content_reporter') ||
          message.includes('chrome-extension://') ||
          message.includes('webpage_content_reporter') ||
          (message.includes('Unexpected token') && (message.includes('export') || source.includes('webpage_content_reporter'))) ||
          errorString.includes('webpage_content_reporter')
        ) {
          event.preventDefault();
          event.stopPropagation();
          event.stopImmediatePropagation();
          return false;
        }
        
        // Check for Stripe adblocker errors
        if (
          message.includes('ERR_BLOCKED_BY_ADBLOCKER') ||
          (errorString.includes('stripe.com') && (errorString.includes('ERR_BLOCKED_BY_ADBLOCKER') || errorString.includes('Failed to fetch'))) ||
          (source.includes('out-') && source.includes('.js') && errorString.includes('stripe')) ||
          (source.includes('shared-') && source.includes('.js') && errorString.includes('stripe')) ||
          (message.includes('FetchError') && errorString.includes('stripe.com'))
        ) {
          event.preventDefault();
          event.stopPropagation();
          event.stopImmediatePropagation();
          return false;
        }
      }, true);
      
      // Suppress unhandled promise rejections from extensions and Stripe
      window.addEventListener('unhandledrejection', function(event) {
        const reason = event.reason?.message || String(event.reason || '');
        const reasonString = reason + ' ' + (event.reason?.stack || '');
        const reasonObj = event.reason || {};
        const reasonName = reasonObj.name || '';
        
        // Extension errors
        if (
          reason.includes('chrome-extension://') ||
          reason.includes('webpage_content_reporter') ||
          reasonString.includes('webpage_content_reporter') ||
          (reason.includes('Unexpected token') && reason.includes('export'))
        ) {
          event.preventDefault();
          event.stopPropagation();
          return;
        }
        
        // Stripe adblocker errors (comprehensive pattern matching)
        if (
          reason.includes('ERR_BLOCKED_BY_ADBLOCKER') ||
          reasonString.includes('r.stripe.com') ||
          reasonString.includes('m.stripe.com') ||
          reason.includes('r.stripe.com') ||
          reason.includes('m.stripe.com') ||
          (reason.includes('Failed to fetch') && (reason.includes('stripe.com') || reasonString.includes('stripe.com'))) ||
          (reason.includes('FetchError') && (reason.includes('stripe.com') || reasonString.includes('stripe.com') || reasonString.includes('r.stripe') || reasonString.includes('m.stripe'))) ||
          (reasonName === 'FetchError' && (reasonString.includes('stripe.com') || reasonString.includes('r.stripe') || reasonString.includes('m.stripe'))) ||
          (reason.includes('Error fetching') && (reason.includes('stripe.com') || reasonString.includes('stripe.com')))
        ) {
          event.preventDefault();
          event.stopPropagation();
          return;
        }
      }, true);
      
      // Suppress console errors/warnings for these issues
      const originalConsoleError = console.error;
      const originalConsoleWarn = console.warn;
      
      console.error = function(...args) {
        const errorString = args.map(arg => String(arg || '')).join(' ');
        if (
          errorString.includes('ERR_BLOCKED_BY_ADBLOCKER') ||
          errorString.includes('webpage_content_reporter') ||
          (errorString.includes('Unexpected token') && errorString.includes('export')) ||
          (errorString.includes('Failed to fetch') && (errorString.includes('stripe.com') || errorString.includes('stripe'))) ||
          (errorString.includes('FetchError') && (errorString.includes('stripe.com') || errorString.includes('stripe') || errorString.includes('r.stripe') || errorString.includes('m.stripe'))) ||
          (errorString.includes('Error fetching') && (errorString.includes('stripe.com') || errorString.includes('r.stripe') || errorString.includes('m.stripe'))) ||
          errorString.includes('r.stripe.com') ||
          errorString.includes('m.stripe.com')
        ) {
          return; // Silently suppress
        }
        originalConsoleError.apply(console, args);
      };
      
      console.warn = function(...args) {
        const warnString = args.map(arg => String(arg || '')).join(' ');
        if (
          warnString.includes('ERR_BLOCKED_BY_ADBLOCKER') ||
          warnString.includes('webpage_content_reporter') ||
          warnString.includes('stripe.com') && (warnString.includes('Failed to fetch') || warnString.includes('blocked'))
        ) {
          return; // Silently suppress
        }
        originalConsoleWarn.apply(console, args);
      };
    })();
  </script>
  <script>
const observer = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    for (const addedNode of mutation.addedNodes) {
      if (
        addedNode.nodeType === Node.ELEMENT_NODE &&
        (
          addedNode.tagName?.toLowerCase() === 'vite-error-overlay' ||
          addedNode.classList?.contains('backdrop')
        )
      ) {
        handleViteOverlay(addedNode);
      }
    }
  }
});

observer.observe(document.documentElement, {
  childList: true,
  subtree: true
});

function handleViteOverlay(node) {
  if (!node.shadowRoot) {
    return;
  }

  const backdrop = node.shadowRoot.querySelector('.backdrop');
  if (backdrop) {
    const overlayHtml = backdrop.outerHTML;
    const parser = new DOMParser();
    const doc = parser.parseFromString(overlayHtml, 'text/html');
    const messageBodyElement = doc.querySelector('.message-body');
    const fileElement = doc.querySelector('.file');
    const messageText = messageBodyElement ? messageBodyElement.textContent.trim() : '';
    const fileText = fileElement ? fileElement.textContent.trim() : '';
    const error = messageText + (fileText ? ' File:' + fileText : '');

    window.parent.postMessage({
      type: 'horizons-vite-error',
      error,
    }, '*');
  }
}

window.onerror = (message, source, lineno, colno, errorObj) => {
  const errorDetails = errorObj ? JSON.stringify({
    name: errorObj.name,
    message: errorObj.message,
    stack: errorObj.stack,
    source,
    lineno,
    colno,
  }) : null;

  window.parent.postMessage({
    type: 'horizons-runtime-error',
    message,
    error: errorDetails
  }, '*');
};

const originalConsoleError = console.error;
console.error = function(...args) {
  originalConsoleError.apply(console, args);

  let errorString = '';
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg instanceof Error) {
      errorString += arg.stack || arg.message || arg.toString();
    } else {
      try {
        errorString += JSON.stringify(arg, null, 2);
      } catch {
        errorString += String(arg);
      }
    }
    if (i < args.length - 1) errorString += ' ';
  }

  if (errorString) {
    window.parent.postMessage({
      type: 'horizons-console-error',
      error: errorString
    }, '*');
  }
};
</script>
</body>
</html>
