PageSourceSearch

https://workspace-portal-7084.taskade.app/

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