PageSourceSearch

https://grocery-buddy-7497.taskade.app/

html grocery-buddy-7497.taskade.app collected 2026-10-03 15:00:55 UTC 28,142 bytes, 644 lines download raw bytes

1<!doctype html>
2<html lang="en">
3  <head>
4    <meta charset="UTF-8" />
5    <link rel="icon" href="/_genesis/assets/favicon" />
6    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7    
8
9
10<script type="module" src="/assets/main-QGCEE7RJ.js"></script>
10
11
12
13<link rel="stylesheet" href="/assets/main-EKZ3JE57.css" />
14    <title>Grocery Buddy — Built with Taskade</title>
15    
16    <style>
17      /* Genesis spinner styles */
18      .genesis__spinner-container {
19        position: fixed;
20        inset: 0;
21        background: #000000;
22        display: flex;
23        align-items: center;
24        justify-content: center;
25        z-index: 9999;
26      }
27
28      .genesis__spinner-ring {
29        width: 20px;
30        height: 20px;
31        border: 1.5px solid transparent;
32        border-top: 1.5px solid rgba(255, 255, 255, 0.6);
33        border-radius: 50%;
34        animation: genesis__spin 1s linear infinite;
35      }
36
37      @keyframes genesis__spin {
38        0% { transform: rotate(0deg); }
39        100% { transform: rotate(360deg); }
40      }
41
42      /* Respect reduced motion preferences */
43      @media (prefers-reduced-motion: reduce) {
44        .genesis__spinner-ring {
45          animation: none;
46          border-top-color: rgba(255, 255, 255, 0.9);
47        }
48      }
49    </style>
50  <meta charset="utf-8" /><meta name="language" content="en-US" /><meta name="robots" content="index" /><meta name="author" content="Taskade" /><link rel="icon" sizes="32x32" href="/_genesis/assets/favicon" /><link rel="apple-touch-icon" sizes="180x180" href="/_genesis/assets/favicon" /><meta name="title" content="Grocery Buddy — Built with Taskade" /><meta property="og:title" content="Grocery Buddy — Built with Taskade" /><meta name="twitter:title" content="Grocery Buddy — Built with Taskade" /><meta name="description" content="A living app built with Taskade Genesis. One prompt, one app. Projects remember, agents learn, and workflows move." /><meta property="og:description" content="A living app built with Taskade Genesis. One prompt, one app. Projects remember, agents learn, and workflows move." /><meta name="twitter:description" content="A living app built with Taskade Genesis. One prompt, one app. Projects remember, agents learn, and workflows move." /><meta property="og:type" content="website" /><meta property="og:url" content="https://grocery-buddy-7497.taskade.app" /><meta property="og:site_name" content="Taskade" /><meta property="og:image" content="https://grocery-buddy-7497.taskade.app/_internal/screenshot" /><meta property="og:image:alt" content="Grocery Buddy — Built with Taskade - A living app built with Taskade Genesis. One prompt, one app. Projects remember, agents learn, and w..." /><meta property="og:locale" content="en_US" /><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:site" content="@taskade" /><meta name="twitter:creator" content="@taskade" /><meta name="twitter:image" content="https://grocery-buddy-7497.taskade.app/_internal/screenshot" /><meta name="twitter:image:alt" content="Grocery Buddy — Built with Taskade - A living app built with Taskade Genesis. One prompt, one app. Projects remember, agents learn, and w..." /><meta name="format-detection" content="telephone=no" /><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="default" /><meta name="apple-mobile-web-app-title" content="Grocery Buddy — Built with Taskade" /><meta name="application-name" content="Taskade" /><link rel="canonical" href="https://grocery-buddy-7497.taskade.app" />
50<script type="application/ld+json">
51  {
52    "@context": "https://schema.org",
53    "@type": "WebSite",
54    "name": "Taskade",
55    "alternateName": "Grocery Buddy — Built with Taskade",
56    "description": "A living app built with Taskade Genesis. One prompt, one app. Projects remember, agents learn, and workflows move.",
57    "image": "https://grocery-buddy-7497.taskade.app/_internal/screenshot",
58    "url": "https://grocery-buddy-7497.taskade.app",
59    "author": {
60      "@type": "Organization",
61      "name": "Taskade"
62    },
63    "publisher": {
64      "@type": "Organization",
65      "name": "Taskade",
66      "url": "https://grocery-buddy-7497.taskade.app",
67      "logo": {
68        "@type": "ImageObject",
69        "url": "https://grocery-buddy-7497.taskade.app/_internal/screenshot"
70      }
71    }
72  }
73</script>
73<script defer src="/_genesis/vendor/flock.min.js" data-proxy-url="/_genesis/api/events" data-tenant-id="2q8n4wr162yenloc:default" data-domain="grocery-buddy-7497.taskade.app"></script>
73<script defer src="/_genesis/vendor/scroll-guard.js"></script>
73</head>
74  <body>
74<script>
75      (function() {
76        if (window.__genesisErrorCatchAll) return;
77        window.__genesisErrorCatchAll = true;
78        var esc = function(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); };
79        var errorHtml = function(msg, id) {
80          return '<div style="position:fixed;inset:0;background:#000;padding:2rem;font-family:system-ui,sans-serif;color:#fff;overflow:auto">' +
81            '<div style="max-width:600px;margin:0 auto">' +
82            '<h2 style="margin:0 0 0.5rem;font-size:1.25rem">Something went wrong</h2>' +
83            '<p style="color:#999;margin:0 0 1rem;font-size:0.95rem">The app failed to load. Try refreshing the page.</p>' +
84            '<pre style="background:#1a1a1a;padding:1rem;border-radius:8px;overflow:auto;font-size:0.8rem;color:#f87171;margin:0;white-space:pre-wrap;word-break:break-word">' +
85            esc(msg) + '<\/pre>' +
86            '<p style="color:#666;margin:1rem 0 0;font-size:0.75rem">Error ID: <code>' + esc(id) + '<\/code><\/p>' +
87            '<\/div><\/div>';
88        };
89        var reported = false;
90        var errorId = null;
91        function getErrorId() {
92          if (errorId != null) return errorId;
93          try {
94            if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { errorId = crypto.randomUUID().replace(/-/g, ''); return errorId; }
95          } catch (e) { /* Fall through to the time+random id below */ }
96          errorId = Date.now().toString(36) + Math.random().toString(36).slice(2, 12);
97          return errorId;
98        }
99        function reportError(code, msg, stack) {
100          if (reported) return;
101          reported = true;
102          try {
103            var logger = window.__TASKADE_APP_LIFECYCLE_LOGGER__;
104            if (typeof logger?.log === 'function') {
105              logger.log({ level: 'error', message: 'Runtime Error', data: { code: code, message: msg, stack: stack || '' } });
106              return;
107            }
108            // Published: no logger, so beacon to the Taskade gateway instead of
109            // dropping the crash on the floor (#27626). Relative path — the
110            // Director proxy injects the gateway token server-side. Once per page
111            // session (the latch above) so a crash loop cannot flood the sink.
112            fetch('/api/taskade/telemetry/errors', {
113              method: 'POST',
114              headers: { 'Content-Type': 'application/json' },
115              body: JSON.stringify({
116                errorId: getErrorId(),
117                code: code,
118                message: String(msg || 'Unknown error').slice(0, 4000),
119                stack: String(stack || '').slice(0, 20000),
120                path: location.pathname.slice(0, 1000),
121                userAgent: navigator.userAgent.slice(0, 512)
122              }),
123              keepalive: true
124              // Default same-origin credentials: a password-protected app gates
125              // /api/taskade/* on its signed access cookie, so omitting them
126              // would redirect the beacon to the password page.
127            }).catch(function() {});
128          } catch (e) { /* Swallow — never block the fallback error UI */ }
129        }
130        function showError(code, msg, stack) {
131          var root = document.getElementById('root');
132          if (!root) return;
133          var spinner = root.querySelector('.genesis__spinner-container');
134          if (spinner) {
135            // Scenario 1: React never mounted — replace spinner
136            reportError('error.fatal', msg, stack);
137            spinner.outerHTML = errorHtml(msg, getErrorId());
138          } else if (!root.childElementCount) {
139            // Scenario 2: React crashed and unmounted — #root is empty
140            reportError('error.fatal', msg, stack);
141            root.innerHTML = errorHtml(msg, getErrorId());
142          } else {
143            // React is running fine — don't interfere with the DOM,
144            // but still report with the original code for diagnostics
145            reportError(code, msg, stack);
146          }
147        }
148        window.addEventListener('error', function(e) {
149          var msg = e.message || String(e.error) || 'Unknown error';
150          var stack = e.error && e.error.stack ? e.error.stack : (e.filename ? e.filename + ':' + e.lineno + ':' + e.colno : '');
151          showError('error.runtime_error', msg, stack);
152        });
153        window.addEventListener('unhandledrejection', function(e) {
154          var msg = e.reason ? String(e.reason) : 'Unhandled promise rejection';
155          var stack = e.reason && e.reason.stack ? e.reason.stack : '';
156          showError('error.unhandled_promise', msg, stack);
157        });
158        // A dead <img> degrades to a neutral placeholder instead of painting its
159        // raw alt string across the page (#27730) — the symptom half of the
160        // fabricated-hot-link fix, and the only half that helps an ALREADY
161        // published app, whose images can rot long after generation.
162        //
163        // Resource load errors do not bubble, so they reach window only in the
164        // CAPTURE phase. That is why neither handler above has ever seen one, and
165        // why this cannot disturb them: a broken image must never render the fatal
166        // error overlay or spend the once-per-session crash beacon.
167        //
168        // Swapping in a 1x1 transparent GIF is what actually stops the alt text —
169        // the element becomes a SUCCESSFUL load, so the browser has no broken image
170        // to caption, while the alt attribute stays for assistive tech. Layout is
171        // untouched: the app's own width/height/aspect classes still apply. The grey
172        // is translucent so it reads correctly on a light or a dark surface without
173        // this script having to know the app's theme.
174        var deadImagePixel = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
175        window.addEventListener('error', function(e) {
176          var el = e.target;
177          if (!el || el.tagName !== 'IMG') return;
178          // The swap fires a fresh load on the same element, so the latch is what
179          // keeps a failing data URI from looping.
180          if (el.getAttribute('data-genesis-image-fallback') !== null) return;
181          el.setAttribute('data-genesis-image-fallback', '');
182          el.removeAttribute('srcset');
183          el.src = deadImagePixel;
184          el.style.background = 'linear-gradient(135deg, rgba(127,127,127,0.16), rgba(127,127,127,0.28))';
185        }, true);
186
187        // ---------------------------------------------------------------
188        // Dead env reads (advisory, NOT a failure)
189        // ---------------------------------------------------------------
190        // The bundle's import.meta.env / process.env shims are reporting
191        // Proxies (see genesisEnvBanner.ts). They are value-preserving: a
192        // missing key still reads undefined. This turns that read from invisible
193        // into observable, because a feature gated on one is permanently off and
194        // has no other symptom — the app builds, boots and looks healthy.
195        //
196        // Deliberately WARN, never error. The runtime cannot see whether a call
197        // site has a working fallback, so a correct
198        // VITE_API_BASE_URL || 'https://real' is reported too. Warn keeps it
199        // out of the "Fix with AI" popup set and out of FATAL_LIFECYCLE_CODES,
200        // both of which are error-level allowlists.
201        var deadEnvSeen = {};
202        var deadEnvSent = 0;
203        // Its OWN budget and latch: sharing the 'reported' latch above would let
204        // an advisory notice spend the one crash report this page session gets.
205        var DEAD_ENV_MAX = 5;
206        function reportDeadEnv(source, key) {
207          try {
208            var signature = source + '.' + key;
209            if (deadEnvSeen[signature] || deadEnvSent >= DEAD_ENV_MAX) return;
210            deadEnvSeen[signature] = 1;
211            deadEnvSent++;
212            var msg = signature + ' is read but a Genesis build never defines it, so the value is ' +
213              'always undefined. If it gates a feature, that feature is permanently off. Route ' +
214              'secret-bearing calls through GenesisClient.proxy() with a secretAlias instead.';
215            var logger = window.__TASKADE_APP_LIFECYCLE_LOGGER__;
216            if (typeof logger?.log === 'function') {
217              logger.log({ level: 'warn', message: 'Dead env read', data: { code: 'warn.dead_env', message: msg } });
218              return;
219            }
220            fetch('/api/taskade/telemetry/errors', {
221              method: 'POST',
222              headers: {
223                'Content-Type': 'application/json',
224                // Lets the publish Worker pick the advisory rate-limit bucket
225                // without reading this body. Doing that at the edge means I/O
226                // of unbounded duration ahead of the limiter meant to bound it,
227                // which is the whole class of problem the header avoids. The
228                // origin still classifies from the validated body, so this is a
229                // bucket hint and never the record (#29164).
230                'X-Genesis-Telemetry-Code': 'warn.dead_env'
231              },
232              body: JSON.stringify({
233                errorId: 'de' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8),
234                code: 'warn.dead_env',
235                message: msg.slice(0, 4000),
236                path: location.pathname.slice(0, 1000),
237                // The source and key as STRUCTURE, not only inside the prose.
238                // The gateway fingerprints on app + code alone, because this
239                // endpoint's token ships in the bundle and free text from it
240                // cannot be trusted; these two validated fields are how the
241                // key still reaches Sentry, as a tag.
242                // Sliced to the schema's bound like every other field here: the
243                // banner filters the key's SHAPE but not its length, and an
244                // over-long key would fail validation and lose the whole
245                // report rather than just the tag.
246                envSource: source,
247                envKey: String(key).slice(0, 64)
248              }),
249              keepalive: true
250            }).catch(function() {});
251          } catch (e) { /* Swallow — advisory telemetry must never break an app */ }
252        }
253        window.__genesisReportDeadEnv = reportDeadEnv;
254        // Drain anything the bundle's banner queued. This script is a classic
255        // inline script and the bundle is a deferred module, so in practice the
256        // reporter wins the race; the queue is what makes that not a matter of
257        // luck (and covers an app whose HTML predates this script).
258        try {
259          var queued = window.__genesisDeadEnvQueue;
260          if (queued) {
261            window.__genesisDeadEnvQueue = [];
262            for (var qi = 0; qi < queued.length; qi++) reportDeadEnv(queued[qi][0], queued[qi][1]);
263          }
264        } catch (e) { /* Ignore. */ }
265      })();
266</script>
266
267    <div id="root">
268      <div class="genesis__spinner-container">
269        <div class="genesis__spinner-ring"></div>
270      </div>
271    </div>
272  
272<script>
273  (function() {
274    if (typeof window === 'undefined' || typeof document === 'undefined') {
275      return;
276    }
277    if (typeof window.gtag === 'function') {
278      return;
279    }
280    window.dataLayer = window.dataLayer || [];
281    function gtag(){window.dataLayer.push(arguments);}
282    window.gtag = gtag;
283    gtag('js', new Date());
284    gtag('config', 'G-WWJTNN1SFE');
285    const script = document.createElement('script');
286    script.async = true;
287    script.src = 'https://www.googletagmanager.com/gtag/js?id=G-WWJTNN1SFE';
288    document.head.appendChild(script);
289  })();
290</script>
290
291
292<style>
293  @import url('https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600&display=swap');
294
295  :root {
296    /* Onboarding Black Glass - optimized for dark mode */
297    --badge-black: rgba(0, 0, 0, 0.75);
298    --badge-black-hover: rgba(0, 0, 0, 0.85);
299    --badge-text: rgba(255, 255, 255, 0.9);
300    --badge-text-hover: rgba(255, 255, 255, 1);
301    --badge-border: rgba(255, 255, 255, 0.15);
302    --badge-border-hover: rgba(255, 255, 255, 0.25);
303    --badge-shadow: rgba(0, 0, 0, 0.4);
304    
305    /* Rainbow gradient colors from Taskade Upgrade button */
306    --rainbow-color-1: #5cb2f9;  /* Blue */
307    --rainbow-color-2: #8787f7;  /* Purple-Blue */
308    --rainbow-color-3: #a774f7;  /* Purple */
309    --rainbow-color-4: #e376a8;  /* Pink */
310    --rainbow-color-5: #eb7551;  /* Orange */
311    --rainbow-color-6: #19bd59;  /* Green */
312    --rainbow-color-7: #dee325;  /* Yellow-Green */
313    --rainbow-color-8: #e32545;  /* Red */
314    
315    /* Cinematic easing */
316    --ease-cinematic: cubic-bezier(0.22, 1, 0.36, 1);
317    --ease-cinematic-slow: cubic-bezier(0.16, 1, 0.3, 1);
318  }
319
320  [data-theme="light"] {
321    --badge-black: rgba(0, 0, 0, 0.8);
322    --badge-black-hover: rgba(0, 0, 0, 0.9);
323    --badge-text: rgba(255, 255, 255, 0.95);
324    --badge-text-hover: rgba(255, 255, 255, 1);
325    --badge-border: rgba(255, 255, 255, 0.2);
326    --badge-border-hover: rgba(255, 255, 255, 0.3);
327    --badge-shadow: rgba(0, 0, 0, 0.5);
328  }
329
330  @media (prefers-color-scheme: light) {
331    :root {
332      --badge-black: rgba(0, 0, 0, 0.8);
333      --badge-black-hover: rgba(0, 0, 0, 0.9);
334      --badge-text: rgba(255, 255, 255, 0.95);
335      --badge-text-hover: rgba(255, 255, 255, 1);
336      --badge-border: rgba(255, 255, 255, 0.2);
337      --badge-border-hover: rgba(255, 255, 255, 0.3);
338      --badge-shadow: rgba(0, 0, 0, 0.5);
339    }
340  }
341
342  /* Onboarding Black Glass Badge - Bottom Center Position */
343  #taskade-badge {
344    position: fixed;
345    bottom: 24px;
346    left: 50%;
347    transform: translateX(-50%) scale(0.9);
348    z-index: 10001;
349    font-family: 'Geist', -apple-system, BlinkMacSystemFont, sans-serif;
350    animation: badgeGeistFadeIn 1.2s var(--ease-cinematic) forwards;
351    opacity: 0;
352    display: inline-flex;
353    align-items: center;
354    gap: 7px;
355    padding: 7px 12px;
356    font-size: 0.8125rem;
357    font-weight: 500;
358    letter-spacing: -0.01em;
359    line-height: 1;
360    color: var(--badge-text);
361    text-decoration: none;
362    background: var(--badge-black);
363    border: 1px solid var(--badge-border);
364    border-radius: 9999px;
365    backdrop-filter: blur(20px) saturate(1.4);
366    -webkit-backdrop-filter: blur(20px) saturate(1.4);
367    cursor: pointer;
368    transition: all 0.3s var(--ease-cinematic);
369    overflow: hidden;
370    will-change: transform, box-shadow;
371    white-space: nowrap;
372  }
373
374  #taskade-badge a {
375    display: flex;
376    align-items: center;
377    gap: 7px;
378    color: inherit;
379    text-decoration: none;
380  }
381
382  /* Default state with subtle rainbow glow */
383  #taskade-badge {
384    box-shadow: 
385      0 4px 16px var(--badge-shadow),
386      0 0 8px rgba(227, 118, 168, 0.2),
387      0 0 16px rgba(163, 116, 247, 0.15);
388  }
389
390  /* Black glass hover with enhanced rainbow glow */
391  #taskade-badge:hover {
392    background: var(--badge-black-hover);
393    border-color: var(--badge-border-hover);
394    color: var(--badge-text-hover);
395    box-shadow: 
396      0 8px 24px var(--badge-shadow),
397      0 0 16px rgba(227, 118, 168, 0.4),
398      0 0 32px rgba(163, 116, 247, 0.3),
399      inset 0 1px 0 rgba(255, 255, 255, 0.12);
400    transform: translateY(-3px) scale(1.03);
401  }
402
403  /* Taskade sheep mascot - optimized for dark mode */
404  #taskade-badge #taskade-logo {
405    width: 16px;
406    height: 16px;
407    flex-shrink: 0;
408    opacity: 0.95;
409    transition: all 0.3s var(--ease-cinematic);
410    filter: invert(1) brightness(1.1) contrast(1.05);
411  }
412
413  #taskade-badge:hover #taskade-logo {
414    opacity: 1;
415    transform: scale(1.08) rotate(-2deg);
416    filter: invert(1) brightness(1.2) contrast(1.1);
417  }
418
419  /* Light mode adjustments */
420  [data-theme="light"] #taskade-badge #taskade-logo {
421    filter: invert(0) brightness(1.1);
422  }
423
424  [data-theme="light"] #taskade-badge:hover #taskade-logo {
425    filter: invert(0) brightness(1.2);
426  }
427
428  @media (prefers-color-scheme: light) {
429    :root #taskade-badge #taskade-logo {
430      filter: invert(0) brightness(1.1);
431    }
432
433    :root #taskade-badge:hover #taskade-logo {
434      filter: invert(0) brightness(1.2);
435    }
436  }
437
438  /* Geist typography */
439  #taskade-badge .badge-text {
440    font-weight: 500;
441    letter-spacing: 0.005em;
442    transition: all 0.3s var(--ease-cinematic);
443    -webkit-font-smoothing: antialiased;
444    -moz-osx-font-smoothing: grayscale;
445  }
446
447  #taskade-badge.hidden {
448    display: none;
449  }
450
451  /* Geist fade-in animation */
452  @keyframes badgeGeistFadeIn {
453    0% {
454      opacity: 0;
455      transform: translateX(-50%) translateY(8px) scale(0.85);
456      filter: blur(6px);
457    }
458    60% {
459      opacity: 0.7;
460      filter: blur(2px);
461    }
462    100% {
463      opacity: 1;
464      transform: translateX(-50%) translateY(0) scale(0.9);
465      filter: blur(0);
466    }
467  }
468
469  /* Responsive */
470  @media (max-width: 768px) {
471    #taskade-badge {
472      bottom: 80px; /* Above mobile nav/TAA chat */
473      left: 50%;
474      transform: translateX(-50%) scale(0.85);
475      padding: 6px 10px;
476      font-size: 0.75rem;
477      gap: 5px;
478    }
479
480    #taskade-badge #taskade-logo {
481      width: 14px;
482      height: 14px;
483    }
484  }
485
486  /* Reduced motion */
487  @media (prefers-reduced-motion: reduce) {
488    #taskade-badge {
489      animation: none;
490      opacity: 1;
491    }
492
493    #taskade-badge a,
494    #taskade-badge #taskade-logo,
495    #taskade-badge .badge-text {
496      transition: none;
497      animation: none;
498    }
499  }
500
501  /* High contrast */
502  @media (prefers-contrast: high) {
503    #taskade-badge a {
504      border-width: 2px;
505      border-color: currentColor;
506    }
507  }
508
509  /* Hide when printing */
510  @media print {
511    #taskade-badge {
512      display: none !important;
513    }
514  }
515</style>
516
517<div id="taskade-badge" data-badge-mode="edit">
518  <a href="https://www.taskade.com/app/zvblrbdj1i4g8le2?utm_source=genesis_badge&utm_medium=app_badge&utm_campaign=p
518owered_by&utm_content=zvblrbdj1i4g8le2" target="_blank" rel="noopener noreferrer">
519    <img id="taskade-logo" src="./_assets/logo.png" alt="Taskade" />
520    <span class="badge-text" data-community-text="Edit in Taskade" data-default-text="Powered by Taskade">Edit in Taskade</span>
521  </a>
522</div>
523
524<script>
525  (function() {
526    // Geist theme detection - matching wait.html
527    function applyTheme() {
528      const urlParams = new URLSearchParams(window.location.search);
529      let theme = urlParams.get('theme');
530      
531      if (!theme) {
532        try {
533          if (window.parent !== window) {
534            const parentTheme = window.parent.document.documentElement.getAttribute('data-theme');                                                              
535            if (parentTheme) theme = parentTheme;
536          }
537        } catch (e) {}
538        
539        if (!theme) {
540          theme = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';                                                                
541        }
542      }
543      
544      document.documentElement.setAttribute('data-theme', theme === 'light' ? 'light' : 'dark');                                                                
545    }
546
547    // Apply theme immediately
548    applyTheme();
549
550    // Theme change listener
551    window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {                                                                      
552      const urlParams = new URLSearchParams(window.location.search);
553      if (!urlParams.get('theme')) {
554        document.documentElement.setAttribute('data-theme', e.matches ? 'light' : 'dark');                                                                      
555      }
556    });
557
558    // Analytics tracking and postMessage communication
559    const badgeLink = document.querySelector('#taskade-badge a');
560    if (badgeLink) {
561      let parentOrigin = '*';
562      let parentHostname = '';
563      if (document.referrer) {
564        try {
565          const referrerUrl = new URL(document.referrer);
566          parentOrigin = referrerUrl.origin;
567          parentHostname = referrerUrl.hostname;
568        } catch (err) {
569          parentOrigin = '*';
570          parentHostname = '';
571        }
572      }
573      // Taskade itself is framing us (the share page at /app/{id}, which
574      // iframes the app). parentOrigin stays '*' when there is no usable
575      // referrer, and then the framer is unknowable.
576      const framedByTaskade =
577        parentOrigin !== '*' &&
578        (parentHostname === 'taskade.com' || parentHostname.endsWith('.taskade.com'));
579
580      const badge = document.getElementById('taskade-badge');
581      const badgeText = document.querySelector('#taskade-badge .badge-text');
582      const communityText = badgeText ? badgeText.getAttribute('data-community-text') : null;
583      const badgeHref = badgeLink.getAttribute('href') || '';
584      const badgeMode = badge ? badge.getAttribute('data-badge-mode') : null;
585      const isCommunityShare =
586        badgeMode === 'edit' ||
587        (badgeText &&
588          communityText &&
589          badgeText.textContent &&
590          badgeText.textContent.trim() === communityText) ||
591        badgeHref.indexOf('/app/') !== -1;
592
593      if (window.parent !== window) {
594        // Hide only when embedded: on the standalone domain this badge IS the
595        // viral return path into Taskade, so it must stay visible there.
596        // Scope note, two independent reasons to hide inside a frame:
597        // 1. The Taskade share page frames the app and already renders its own
598        //    Clone CTA, so ANY badge mode is a duplicate there. A buyer of a
599        //    non-template listing stays on /app/{id} and sees exactly that.
600        // 2. An edit-mode badge hides in any frame, because its /app/{id} link
601        //    just points back at the framing page.
602        // A powered_by badge in an unrelated third-party embed still renders -
603        // there it is the only way back to Taskade. With no referrer the
604        // framer is unknowable, so only the mode-based reason applies.
605        if ((isCommunityShare || framedByTaskade) && badge) {
606          badge.classList.add('hidden');
607        }
608
609        const notifyParent = () => {
610          window.parent.postMessage(
611            {
612              type: 'TASKADE_BADGE_MODE',
613              source: 'taskade_badge',
614              mode: isCommunityShare ? 'edit' : 'powered_by',
615            },
616            parentOrigin
617          );
618        };
619
620        try {
621          notifyParent();
622          setTimeout(notifyParent, 500);
623        } catch (err) {
624          // Ignore postMessage failures.
625        }
626      }
627
628      badgeLink.addEventListener('click', function() {
629        // Track badge engagement
630        if (typeof window.gtag === 'function') {
631          window.gtag('event', 'genesis_badge_clicked', {
632            event_category: 'engagement',
633            event_label: 'edit_in_taskade'
634          });
635        }
636
637      });
638    }
639
640  })();
641</script>
vendor: 1 bytes, line 641
641
642<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"8d4254f20c524f6fbf6ecde0b2c89fb4","spa":2}' crossorigin="anonymous"></script>
642
643</body>
644</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.