1<!doctype html> 2<html data-echo-prerender="landing" lang="en" class="notranslate" translate="no"> 3 4<head> 5 <meta name="echo-app" content="true" /> 6 <!-- Machine translators replace React-owned text nodes with <font> wrappers, 7 which breaks React's insertBefore/removeChild invariants on live feeds. --> 8 <meta name="google" content="notranslate"> 9 <meta name="apple-itunes-app" content="app-id=6760357340"> 10 <!-- CRITICAL: Apply cached theme IMMEDIATELY to prevent flash --> 11 <!-- This script MUST run synchronously before any rendering --> 12
12<script> 13 (function () { 14 try { 15 // Some Android System WebView versions used by installed Echo builds do 16 // not provide Array.prototype.at. App code and web-vitals both use 17 // `.at(-1)`, so install the compatibility method before any module runs. 18 if (typeof Array.prototype.at !== 'function') { 19 Object.defineProperty(Array.prototype, 'at', { 20 configurable: true, 21 writable: true, 22 value: function (index) { 23 var length = Number(this.length) || 0; 24 var integer = Number(index) || 0; 25 integer = integer < 0 ? Math.ceil(integer) : Math.floor(integer); 26 var resolvedIndex = integer < 0 ? length + integer : integer; 27 return resolvedIndex < 0 || resolvedIndex >= length 28 ? undefined 29 : this[resolvedIndex]; 30 } 31 }); 32 } 33 34 var launchSurfaceColor = '#FCF6F5'; 35 var root = document.documentElement; 36 var bootStart = typeof performance !== 'undefined' && typeof performance.now === 'function' 37 ? performance.now() 38 : Date.now(); 39 var userAgent = navigator.userAgent || ''; 40 var supabaseUrl = 'https://lacgasmxkbfxyfybpeop.supabase.co'; 41 var supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxhY2dhc214a2JmeHlmeWJwZW9wIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjY2NjgyNTMsImV4cCI6MjA4MjI0NDI1M30.R5LoArlO4HEJpKpYt7u4R-I8TSfHv_CMeeWvPzeF8js'; 42 var isNativeAndroidShell = 43 userAgent.indexOf('Echo/') !== -1 && userAgent.indexOf('(Android)') !== -1; 44 var isNativeIosShell = 45 (userAgent.indexOf('Echo/') !== -1 && userAgent.indexOf('(iOS)') !== -1) || 46 window.location.protocol === 'capacitor:' || 47 window.location.protocol === 'ionic:'; 48 var isNativeShell = 49 isNativeAndroidShell || 50 isNativeIosShell; 51 var normalizedBootPath = window.location.pathname.replace(/\/+$/, '') || '/'; 52 53 // The public landing page is real build-time HTML. Suppress it before 54 // first paint when the SPA shell is serving an authenticated, native, 55 // callback, or unrelated route; the app clears this guard on mount. 56 if (root.hasAttribute('data-echo-prerender')) { 57 var skipLandingPrerender = isNativeShell || 58 (root.getAttribute('data-echo-prerender') === 'editorial' 59 ? !/^\/(blog|alternatives)(\/[^/]+)?$/.test(normalizedBootPath) 60 : (normalizedBootPath !== '/' && normalizedBootPath !== '/frontpage')); 61 var landingCallbackParams = new URLSearchParams( 62 window.location.search + '&' + window.location.hash.replace(/^#/, '') 63 ); 64 skipLandingPrerender = skipLandingPrerender || 65 ['code', 'access_token', 'refresh_token', 'error', 'error_code'].some(function (key) { 66 return landingCallbackParams.has(key); 67 }); 68 try { 69 var prerenderProjectRef = 'lacgasmxkbfxyfybpeop'.trim().toLowerCase(); 70 if (!/^[a-z0-9]+$/.test(prerenderProjectRef)) { 71 prerenderProjectRef = new URL(supabaseUrl).hostname.split('.')[0]; 72 } 73 ['localStorage', 'sessionStorage'].forEach(function (storageName) { 74 try { 75 var storedSession = JSON.parse(window[storageName].getItem('sb-' + prerenderProjectRef + '-auth-token') || 'null'); 76 if (storedSession && typeof storedSession.access_token === 'string' && storedSession.access_token.length > 0 && 77 typeof storedSession.refresh_token === 'string' && storedSession.refresh_token.length > 0) { 78 skipLandingPrerender = true; 79 } 80 } catch (_) { /* An unreadable session is not an auth hint. */ } 81 }); 82 } catch (_) { /* Match the application's no-session fallback. */ } 83 if (skipLandingPrerender) root.setAttribute('data-echo-prerender-skip', 'true'); 84 } 85 86 // The signed-out landing hero is static and otherwise cannot be 87 // discovered until the full React shell and lazy LandingPage chunk 88 // have evaluated. Preload the exact responsive candidate used by the 89 // center rail card so image transfer overlaps JavaScript startup. 90 if (!isNativeShell && (normalizedBootPath === '/' || normalizedBootPath === '/frontpage')) { 91 try { 92 var landingProjectRef = new URL(supabaseUrl).hostname.split('.')[0]; 93 var landingSessionKey = 'sb-' + landingProjectRef + '-auth-token'; 94 var hasLandingSession = Boolean(window.localStorage.getItem(landingSessionKey)); 95 var landingR2Base = String('https://cdn.myechoboard.com' || '').replace(/\/+$/, ''); 96 if (!hasLandingSession && landingR2Base) { 97 var landingHeroPath = '74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg'; 98 var landingHeroMobile = window.matchMedia('(max-width: 640px)').matches; 99 var landingHeroWidths = landingHeroMobile ? [200, 400] : [400, 800, 1200]; 100 var landingHeroQuality = landingHeroMobile ? 70 : 80; 101 var landingHeroUrl = function (width) { 102 return landingR2Base + '/cdn-cgi/image/width=' + width + 103 ',quality=' + landingHeroQuality + ',format=auto/' + landingHeroPath; 104 }; 105 var landingHeroPreload = document.createElement('link'); 106 landingHeroPreload.rel = 'preload'; 107 landingHeroPreload.as = 'image'; 108 landingHeroPreload.href = landingHeroUrl(landingHeroMobile ? 400 : 800); 109 landingHeroPreload.setAttribute( 110 'imagesrcset', 111 landingHeroWidths.map(function (width) { 112 return landingHeroUrl(width) + ' ' + width + 'w'; 113 }).join(', ') 114 ); 115 landingHeroPreload.setAttribute('imagesizes', '(max-width: 640px) 72vw, 340px'); 116 landingHeroPreload.setAttribute('fetchpriority', 'high'); 117 landingHeroPreload.setAttribute('data-echo-landing-lcp', 'true'); 118 document.head.appendChild(landingHeroPreload); 119 } 120 } catch (landingHeroPreloadError) { 121 // A preload hint is optional and must never interfere with boot. 122 } 123 } 124 125 // Start the signed-out Explore data request directly from the parsed 126 // HTML. Keeping this ahead of the module graph lets the first visible 127 // images download while the application JavaScript is still loading. 128 // The React feed adopts this exact promise, so this does not duplicate 129 // the request or change the rendered data. 130 var exploreBootParams = new URLSearchParams(window.location.search); 131 var exploreHasOnlyDiagnosticParams = Array.from(exploreBootParams.keys()).every(function (key) { 132 return key === '_r' || key === '_cb' || key === '_ra'; 133 }); 134 if (!isNativeShell && normalizedBootPath === '/explore' && exploreHasOnlyDiagnosticParams) { 135 try { 136 var supabaseProjectRef = new URL(supabaseUrl).hostname.split('.')[0]; 137 var supabaseSessionKey = 'sb-' + supabaseProjectRef + '-auth-token'; 138 var hasSupabaseSession = Boolean(window.localStorage.getItem(supabaseSessionKey)); 139 if (!hasSupabaseSession && !window.__ECHO_DIRECT_EXPLORE_WARMUP__) { 140 var exploreWarmupController = new AbortController(); 141 var exploreWarmupTimeout = window.setTimeout(function () { 142 exploreWarmupController.abort(); 143 }, 6500); 144 var exploreWarmupParams = new URLSearchParams({ 145 apikey: supabaseAnonKey, 146 limit_count: '16', 147 p_offset: '0', 148 p_time_range: 'all_time', 149 p_ai_filters: '{}' 150 }); 151 var exploreWarmupRequest = fetch( 152 supabaseUrl + 153 '/rest/v1/rpc/get_public_explore_echo_feed_new_fast?' + 154 exploreWarmupParams.toString(), 155 { 156 method: 'GET', 157 signal: exploreWarmupController.signal 158 } 159 ); 160 161 window.__ECHO_DIRECT_EXPLORE_WARMUP__ = exploreWarmupRequest 162 .then(function (response) { 163 if (!response.ok) { 164 throw new Error('Explore bootstrap feed failed: ' + response.status); 165 } 166 return response.json(); 167 }) 168 .then(function (value) { 169 var rows = Array.isArray(value) ? value : []; 170 var r2Base = String('https://cdn.myechoboard.com' || '').replace(/\/+$/, ''); 171 var firstRowLimit = window.matchMedia('(max-width: 767px)').matches ? 1 : 2; 172 var seenImageUrls = {}; 173 var preloadedCount = 0; 174 175 rows.some(function (row) { 176 var metadata = row && row.metadata && typeof row.metadata === 'object' 177 ? row.metadata 178 : {}; 179 var source = String(metadata.thumbnail_url || (row && row.image_url) || '').trim(); 180 if (!source) return false;
181 var imageUrl = source; 182 if ( 183 r2Base && 184 source.indexOf(r2Base + '/') === 0 && 185 source.indexOf('/cdn-cgi/image/') === -1 186 ) { 187 imageUrl = r2Base + 188 '/cdn-cgi/image/width=400,quality=70,format=auto/' + 189 source.slice(r2Base.length).replace(/^\/+/, ''); 190 } 191 if (seenImageUrls[imageUrl]) return false; 192 seenImageUrls[imageUrl] = true; 193 194 var preload = document.createElement('link'); 195 preload.rel = 'preload'; 196 preload.as = 'image'; 197 preload.href = imageUrl; 198 if (r2Base && source.indexOf(r2Base + '/') === 0 && source.indexOf('/cdn-cgi/image/') === -1) { 199 var imagePath = source.slice(r2Base.length).replace(/^\/+/, ''); 200 preload.setAttribute('imagesrcset', 201 r2Base + '/cdn-cgi/image/width=200,quality=65,format=auto/' + imagePath + ' 200w, ' + 202 r2Base + '/cdn-cgi/image/width=360,quality=65,format=auto/' + imagePath + ' 360w, ' + 203 imageUrl + ' 400w' 204 ); 205 // Cap anonymous mobile thumbnails near 2x CSS density. 206 // A full 3x candidate adds bytes without a visible card- 207 // grid benefit on throttled first paint. 208 preload.setAttribute('imagesizes', '(max-width: 700px) 33vw, 244px'); 209 } 210 preload.setAttribute('fetchpriority', 'high'); 211 preload.setAttribute('data-echo-direct-explore-image', imageUrl); 212 document.head.appendChild(preload); 213 preloadedCount += 1; 214 return preloadedCount >= firstRowLimit; 215 }); 216 return rows; 217 }) 218 .finally(function () { 219 window.clearTimeout(exploreWarmupTimeout); 220 }); 221 window.__ECHO_DIRECT_EXPLORE_WARMUP__.catch(function () {}); 222 } 223 } catch (exploreWarmupError) { 224 // Speculative startup work must never block normal application boot. 225 } 226 } 227 var bootDebugEnabled = 228 window.location.search.indexOf('echoBootDebug=1') !== -1; 229 try { 230 bootDebugEnabled = 231 bootDebugEnabled || 232 window.localStorage.getItem('echo:debug:startup') === 'true'; 233 } catch (error) { 234 // Startup tracing stays disabled when storage is unavailable. 235 } 236 if (bootDebugEnabled) { 237 root.setAttribute('data-echo-startup-debug', 'true'); 238 } 239 240 window.__ECHO_BOOT_LOGS__ = window.__ECHO_BOOT_LOGS__ || []; 241 window.__ECHO_NATIVE_AUTH_QUEUE__ = window.__ECHO_NATIVE_AUTH_QUEUE__ || []; 242 window.__ECHO_PUSH_BOOT_LOG__ = function (label, detail) { 243 if (!bootDebugEnabled) return; 244 try { 245 var now = typeof performance !== 'undefined' && typeof performance.now === 'function' 246 ? performance.now() 247 : Date.now(); 248 var entry = { 249 t: Math.max(0, Math.round(now - bootStart)), 250 label: String(label || 'event'), 251 detail: detail ? String(detail) : '' 252 }; 253 window.__ECHO_BOOT_LOGS__.push(entry); 254 255 console.log('[boot][' + entry.t + 'ms]', entry.label, entry.detail || ''); 256 } catch (e) { 257 console.log('[boot]', label, detail || ''); 258 } 259 }; 260 261 window.__ECHO_PUSH_BOOT_LOG__('inline-bootstrap-start'); 262 263 if (isNativeShell) { 264 root.setAttribute('data-native-bootstrap', 'true'); 265 root.setAttribute('data-native-platform', isNativeAndroidShell ? 'android' : 'ios'); 266 root.classList.add(isNativeAndroidShell ? 'native-android' : 'native-ios'); 267 window.__ECHO_PUSH_BOOT_LOG__('native-shell-detected', window.location.pathname); 268 269 var nativeAuthHandler = 270 window.webkit && 271 window.webkit.messageHandlers && 272 window.webkit.messageHandlers.echoNativeAuth; 273 274 if (nativeAuthHandler) { 275 var storageKey = (function () { 276 try { 277 return 'sb-' + new URL(supabaseUrl).hostname.split('.')[0] + '-auth-token'; 278 } catch (error) { 279 return 'sb-native-auth-token'; 280 } 281 })(); 282 283 function postNativeAuth(payload) { 284 try { 285 nativeAuthHandler.postMessage(payload); 286 } catch (error) { 287 console.warn('[native-auth-inline] Failed to post native payload', error); 288 } 289 } 290 291 function persistNativeSession(response) { 292 if (!response || !response.access_token || !response.refresh_token) return false;
293 294 var session = { 295 access_token: response.access_token, 296 refresh_token: response.refresh_token, 297 token_type: response.token_type, 298 expires_in: response.expires_in, 299 expires_at: response.expires_at || Math.floor(Date.now() / 1000) + Number(response.expires_in || 3600), 300 user: response.user || null 301 }; 302 303 localStorage.setItem(storageKey, JSON.stringify(session)); 304 305 if (response.user) { 306 localStorage.setItem(storageKey + '-user', JSON.stringify({ user: response.user })); 307 } 308 309 return true; 310 } 311 312 function finishNativeAuthToApp() { 313 try { 314 window.__ECHO_FORCE_FULL_APP__ = true; 315 window.__ECHO_PUSH_BOOT_LOG__('native-auth-inline-finish-to-app'); 316 window.location.replace('/?nativeAuth=' + Date.now()); 317 } catch (error) { 318 window.location.href = '/'; 319 } 320 } 321 322 async function handleInlineNativeAuth(payload) { 323 var command = typeof payload === 'string' ? JSON.parse(payload) : payload; 324 var headers = { 325 'Content-Type': 'application/json', 326 'apikey': supabaseAnonKey 327 }; 328 329 try { 330 if (command.type === 'social') { 331 // Queue social login until the module bridge is ready. It owns 332 // the PKCE verifier and one-time callback transaction; this 333 // early shell must never start an implicit token flow. 334 return false; 335 } 336 337 if (command.type === 'signIn') { 338 var signInResponse = await fetch(supabaseUrl + '/auth/v1/token?grant_type=password', { 339 method: 'POST', 340 headers: headers, 341 body: JSON.stringify({ 342 email: String(command.email || '').trim(), 343 password: String(command.password || '') 344 }) 345 }); 346 var signInJson = await signInResponse.json(); 347 348 if (!signInResponse.ok) { 349 throw new Error(signInJson.msg || signInJson.error_description || signInJson.error || 'Authentication failed.'); 350 } 351 352 persistNativeSession(signInJson); 353 postNativeAuth({ type: 'command-result', success: true }); 354 postNativeAuth({ type: 'session-state', event: 'SIGNED_IN', authenticated: true }); 355 finishNativeAuthToApp(); 356 return true; 357 } 358 359 if (command.type === 'signUp') { 360 var signUpResponse = await fetch(supabaseUrl + '/auth/v1/signup', { 361 method: 'POST', 362 headers: headers, 363 body: JSON.stringify({ 364 email: String(command.email || '').trim(), 365 password: String(command.password || ''), 366 data: { 367 full_name: String(command.fullName || '').trim() 368 } 369 }) 370 }); 371 var signUpJson = await signUpResponse.json(); 372 373 if (!signUpResponse.ok) { 374 throw new Error(signUpJson.msg || signUpJson.error_description || signUpJson.error || 'Could not create account.'); 375 } 376 377 var authenticated = persistNativeSession(signUpJson); 378 postNativeAuth({ 379 type: 'command-result', 380 success: true, 381 message: authenticated 382 ? 'Account created.' 383 : 'Account created. Check your email to verify your address.' 384 }); 385 postNativeAuth({ 386 type: 'session-state', 387 event: authenticated ? 'SIGNED_IN' : 'SIGNED_OUT', 388 authenticated: authenticated 389 }); 390 if (authenticated) { 391 finishNativeAuthToApp(); 392 } 393 return true; 394 } 395 } catch (authError) { 396 postNativeAuth({ 397 type: 'command-result', 398 success: false, 399 message: authError && authError.message ? authError.message : 'Authentication failed.' 400 }); 401 return true; 402 } 403 404 return false;
405 } 406 407 window.__ECHO_NATIVE_AUTH_RUN__ = function (payload) { 408 try { 409 handleInlineNativeAuth(payload).then(function (handled) { 410 if (!handled) { 411 window.__ECHO_NATIVE_AUTH_QUEUE__.push(payload); 412 } 413 }); 414 window.__ECHO_PUSH_BOOT_LOG__('native-auth-command-queued'); 415 return true; 416 } catch (queueError) { 417 console.warn('[native-auth-queue] Failed to queue payload', queueError); 418 return false; 419 } 420 }; 421 422 try { 423 postNativeAuth({ type: 'bridge-ready', stage: 'inline-shell' }); 424 window.__ECHO_PUSH_BOOT_LOG__('native-auth-inline-bridge-ready'); 425 if (window.location.pathname === '/auth' || window.location.pathname === '/auth2') { 426 postNativeAuth({ type: 'present-native-auth', stage: 'inline-auth-route' }); 427 window.__ECHO_PUSH_BOOT_LOG__('native-auth-inline-present-requested'); 428 } 429 } catch (bridgeError) { 430 console.warn('[native-auth-queue] Failed to notify native shell', bridgeError); 431 } 432 } 433 } 434 435 var isNativeAuthRoute = isNativeShell && ( 436 window.location.pathname === '/' || 437 window.location.pathname === '/auth' || 438 window.location.pathname === '/auth2' 439 ); 440 441 function loadGtm() { 442 if (window.location.pathname === '/__feed-performance') return; 443 if (window.__echoGtmLoaded) return; 444 window.__echoGtmLoaded = true; 445 window.dataLayer = window.dataLayer || []; 446 window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); 447 var f = document.getElementsByTagName('script')[0]; 448 var j = document.createElement('script'); 449 var dl = 'dataLayer' !== 'dataLayer' ? '&l=dataLayer' : ''; 450 j.async = true; 451 j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WLQS285X' + dl; 452 f.parentNode.insertBefore(j, f); 453 } 454 455 function scheduleGtm() { 456 if (window.__echoGtmScheduled || window.__echoGtmLoaded) return; 457 window.__echoGtmScheduled = true; 458 459 var fallbackTimer = null; 460 var interactionEvents = ['pointerdown', 'touchstart', 'keydown']; 461 var startGtm = function (event) { 462 interactionEvents.forEach(function (eventName) { 463 window.removeEventListener(eventName, startGtm); 464 }); 465 if (fallbackTimer !== null) { 466 window.clearTimeout(fallbackTimer); 467 fallbackTimer = null; 468 } 469 // Do not make the first user gesture contend with vendor parsing. 470 window.setTimeout(loadGtm, event ? 1000 : 0); 471 }; 472 var scheduleFallback = function () { 473 // Keep analytics and tag-manager vendors out of the route/LCP 474 // critical path. dataLayer events queue until GTM is available. 475 fallbackTimer = window.setTimeout(startGtm, 30000); 476 }; 477 478 interactionEvents.forEach(function (eventName) { 479 window.addEventListener(eventName, startGtm, { 480 once: true, 481 passive: true 482 }); 483 }); 484 485 if (document.readyState === 'complete') { 486 scheduleFallback(); 487 } else { 488 window.addEventListener('load', scheduleFallback, { once: true }); 489 } 490 } 491 492 function loadGoogleFonts() { 493 if (window.__echoFontsLoaded) return; 494 window.__echoFontsLoaded = true; 495 var href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Crimson+Pro:ital,wght@0,400;0,700;1,400&display=swap'; 496 var preconnectApi = document.createElement('link'); 497 preconnectApi.rel = 'preconnect'; 498 preconnectApi.href = 'https://fonts.googleapis.com'; 499 document.head.appendChild(preconnectApi); 500 501 var preconnectStatic = document.createElement('link'); 502 preconnectStatic.rel = 'preconnect';
503 preconnectStatic.href = 'https://fonts.gstatic.com'; 504 preconnectStatic.crossOrigin = 'anonymous'; 505 document.head.appendChild(preconnectStatic); 506 507 var preload = document.createElement('link'); 508 preload.rel = 'preload'; 509 preload.as = 'style'; 510 preload.href = href; 511 document.head.appendChild(preload); 512 513 var stylesheet = document.createElement('link'); 514 stylesheet.rel = 'stylesheet'; 515 stylesheet.href = href; 516 stylesheet.media = 'print'; 517 stylesheet.onload = function () { stylesheet.media = 'all'; }; 518 document.head.appendChild(stylesheet); 519 } 520 521 function loadDeferredHeadAssets() { 522 if (isNativeShell) { 523 return; 524 } 525 526 loadGoogleFonts(); 527 scheduleGtm(); 528 } 529 530 if (isNativeShell) { 531 window.addEventListener('echo:first-frame', function onFirstFrame() { 532 window.removeEventListener('echo:first-frame', onFirstFrame); 533 window.__ECHO_FIRST_FRAME_COMPLETE__ = true; 534 window.__ECHO_PUSH_BOOT_LOG__('first-frame-event-received'); 535 window.setTimeout(loadDeferredHeadAssets, 600); 536 }); 537 } else { 538 loadDeferredHeadAssets(); 539 } 540 541 var cache = isNativeAuthRoute ? null : (localStorage.getItem('echo_theme_cache') || localStorage.getItem('echo_mood_theme')); 542 var isDarkFlag = isNativeAuthRoute ? false : localStorage.getItem('echo_theme_is_dark') === 'true'; 543 544 // Helper to check if a hex color is dark 545 function isDarkColor(hex) { 546 if (!hex || hex.length < 7) return false; 547 hex = hex.replace(/^#/, ''); 548 var r = parseInt(hex.substring(0, 2), 16); 549 var g = parseInt(hex.substring(2, 4), 16); 550 var b = parseInt(hex.substring(4, 6), 16); 551 return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 < 0.5; 552 } 553 554 var theme = null; 555 if (cache) { 556 theme = JSON.parse(cache); 557 } 558 559 window.__ECHO_PUSH_BOOT_LOG__('theme-cache-check', theme ? 'hit' : 'miss'); 560 561 // 1. Apply dark class FIRST (before any styles) 562 var shouldBeDark = isDarkFlag || (theme && theme.is_dark) || (theme && theme.canvas_tone && isDarkColor(theme.canvas_tone)); 563 if (shouldBeDark) { 564 root.classList.add('dark'); 565 } 566 567 // 2. Build and inject critical CSS + Mark as applied 568 if (theme) { 569 // Mark theme as applied so CSS doesn't override 570 root.setAttribute('data-theme-applied', 'true'); 571 572 var css = ''; 573 574 // Background color - most critical 575 if (theme.canvas_tone) { 576 root.style.backgroundColor = theme.canvas_tone; 577 css += 'html, body { background-color: ' + theme.canvas_tone + ' !important; }'; 578 } 579 580 // Font 581 var font = theme.font_family || localStorage.getItem('echo_last_font_family'); 582 if (font) { 583 var fontStack = font === 'Manrope' ? '"Manrope", "Manrope Fallback", sans-serif' : '"' + font + '", sans-serif'; 584 css += 'html, body, button, input, select, textarea { font-family: var(--font-mood, ' + fontStack + ') !important; }'; 585 } 586 587 // Inject the style tag 588 if (css) { 589 var style = document.createElement('style'); 590 style.id = 'echo-critical-css'; 591 style.textContent = css; 592 document.head.appendChild(style); 593 } 594 } else { 595 // No cache - keep auth/native startup on the warm launch color to avoid dark flashes. 596 root.style.backgroundColor = isNativeShell ? launchSurfaceColor : '#ffffff'; 597 } 598 } catch (e) { 599 // Fail silently, React will handle it 600 document.documentElement.style.backgroundColor = '#FCF6F5'; 601 if (window.__ECHO_PUSH_BOOT_LOG__) { 602 window.__ECHO_PUSH_BOOT_LOG__('inline-bootstrap-error', e && e.message ? e.message : 'unknown'); 603 } 604 } 605 })(); 606 </script>
606 607 <meta charset="UTF-8" /> 608 <meta name="viewport" 609 content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> 610 <meta name="author" content="Echo" /> 611 612 <!-- Native App Meta Tags --> 613 <meta name="apple-mobile-web-app-capable" content="yes" /> 614 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> 615 <meta name="apple-mobile-web-app-title" content="Echo" /> 616 <meta name="theme-color" content="#FCF6F5" media="(prefers-color-scheme: dark)" /> 617 <meta name="theme-color" content="#FCF6F5" media="(prefers-color-scheme: light)" /> 618 <meta name="format-detection" content="telephone=no" /> 619 <meta name="mobile-web-app-capable" content="yes" /> 620 621 <!-- Performance: Preconnect to Supabase for faster image loading --> 622 <link rel="preconnect" href="https://lacgasmxkbfxyfybpeop.supabase.co" crossorigin> 623 <link rel="dns-prefetch" href="https://lacgasmxkbfxyfybpeop.supabase.co"> 624 <!-- Performance: Preconnect to R2 CDN for faster image loading --> 625 <link rel="preconnect" href="https://cdn.myechoboard.com" crossorigin> 626 <link rel="dns-prefetch" href="https://cdn.myechoboard.com"> 627 628 <link rel="icon" type="image/x-icon" href="/favicon.ico" sizes="any" /> 629 <link rel="icon" type="image/png" href="/favicon.png" sizes="512x512" /> 630 <link rel="apple-touch-icon" href="/favicon.png" /> 631 632 <!-- Open Graph / Facebook --> 633 <!-- Twitter --> 634 <style> 635 :root { 636 --echo-launch-surface: #FCF6F5; 637 } 638 639 /* 640 * Z-INDEX LAYERS: 641 * html = background canvas (z-auto) 642 * #star-container = z-2 (stars above background) 643 * #root = z-3 (content above stars, must be transparent) 644 */ 645 #star-container { 646 position: fixed; 647 inset: 0; 648 pointer-events: none; 649 z-index: 2; 650 } 651 652 html, 653 body { 654 background-color: var(--echo-launch-surface); 655 /* Lean auth/startup routes do not load the full application stylesheet. */
656 overscroll-behavior: none; 657 } 658 659 #root { 660 position: relative; 661 z-index: 3; 662 background-color: transparent; 663 } 664 665 html[data-echo-prerender-skip="true"] #root[data-echo-prerender] { 666 display: none; 667 } 668 669 /* The rendered public page is usable without JavaScript. */ 670 html[data-echo-prerender] noscript .echo-boot-recovery { 671 display: none; 672 } 673 674 .echo-boot-recovery { 675 box-sizing: border-box; 676 position: fixed; 677 inset: 0; 678 z-index: 2147483647; 679 display: flex; 680 align-items: center; 681 justify-content: center; 682 padding: 24px; 683 background: rgba(252, 246, 245, 0.92); 684 background: color-mix(in srgb, var(--echo-launch-surface) 88%, transparent); 685 color: #211f2a; 686 font-family: Manrope, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; 687 } 688 689 .echo-boot-recovery__card { 690 width: min(100%, 420px); 691 border: 1px solid rgba(76, 65, 108, 0.16); 692 border-radius: 24px; 693 padding: 28px; 694 background: rgba(255, 255, 255, 0.82); 695 box-shadow: 0 24px 70px rgba(55, 43, 86, 0.12); 696 text-align: center; 697 } 698 699 .echo-boot-recovery__card h1 { 700 margin: 0 0 10px; 701 font-size: 24px; 702 line-height: 1.2; 703 } 704 705 .echo-boot-recovery__card p { 706 margin: 0 0 20px; 707 color: #625d70; 708 line-height: 1.55; 709 } 710 711 .echo-boot-recovery__card button { 712 -webkit-appearance: none; 713 -moz-appearance: none; 714 appearance: none; 715 border: 0; 716 border-radius: 999px; 717 padding: 12px 20px; 718 background: #6d52a3; 719 color: #fff; 720 font: inherit; 721 font-weight: 700; 722 cursor: pointer; 723 } 724 725 .echo-boot-recovery__card button:disabled { 726 cursor: wait; 727 opacity: 0.72; 728 } 729 730 .star { 731 position: absolute; 732 border-radius: 50%; 733 background-color: var(--star-color, #ffffff); 734 animation-name: twinkle; 735 animation-timing-function: ease-in-out; 736 animation-iteration-count: infinite; 737 animation-direction: alternate; 738 } 739 740 @keyframes twinkle { 741 0% { 742 transform: scale(1); 743 opacity: 0.3; 744 } 745 746 100% { 747 transform: scale(1); 748 opacity: 1; 749 } 750 } 751 </style> 752
752<script type="module" crossorigin src="/assets/app-_uSXIk0p.js"></script>
752 753 <link rel="modulepreload" crossorigin href="/assets/runtime-preload-CFP917pd.js"> 754 <link rel="modulepreload" crossorigin href="/assets/startup-core-CGeUO7uJ.js"> 755 <link rel="modulepreload" crossorigin href="/assets/startup-platform-OWIpYyjW.js"> 756 <link rel="modulepreload" crossorigin href="/assets/startup-diagnostics-bridge-BB8dzHvV.js"> 757<link rel="manifest" href="/manifest.webmanifest"><title>Echo â The Ad-Free Pinterest Alternative</title> 758<meta name="description" content="Looking for a Pinterest alternative? Discover inspiration, organize ideas into boards, and import Pinterest boards with Echo. Ad-free and private by default." data-echo-seo-managed="true"> 759<link rel="canonical" href="https://myechoboard.com/" data-echo-seo-managed="true"> 760<meta property="og:type" content="website" data-echo-seo-managed="true"> 761<meta property="og:title" content="Echo â A Pinterest alternative without ads" data-echo-seo-managed="true"> 762<meta property="og:description" content="Discover ideas you love. Save them into beautiful boards. Your inspiration, organized and ad-free." data-echo-seo-managed="true"> 763<meta property="og:url" content="https://myechoboard.com/" data-echo-seo-managed="true"> 764<meta property="og:image" content="https://myechoboard.com/og/echo-pinterest-alternative.jpg" data-echo-seo-managed="true"> 765<meta property="og:image:secure_url" content="https://myechoboard.com/og/echo-pinterest-alternative.jpg" data-echo-seo-managed="true"> 766<meta property="og:image:alt" content="Echo â Keep what inspires you. A preview of an interior inspiration board in Echo." data-echo-seo-managed="true"> 767<meta property="og:image:width" content="1200" data-echo-seo-managed="true"> 768<meta property="og:image:height" content="630" data-echo-seo-managed="true"> 769<meta property="og:site_name" content="Echo" data-echo-seo-managed="true"> 770<meta property="og:locale" content="en_US" data-echo-seo-managed="true"> 771<meta name="twitter:card" content="summary_large_image" data-echo-seo-managed="true"> 772<meta name="twitter:title" content="Echo â A Pinterest alternative without ads" data-echo-seo-managed="true"> 773<meta name="twitter:description" content="Discover ideas you love. Save them into beautiful boards. Your inspiration, organized and ad-free." data-echo-seo-managed="true"> 774<meta name="twitter:image" content="https://myechoboard.com/og/echo-pinterest-alternative.jpg" data-echo-seo-managed="true"> 775<meta name="twitter:image:alt" content="Echo â Keep what inspires you. A preview of an interior inspiration board in Echo." data-echo-seo-managed="true"> 776<meta name="twitter:url" content="https://myechoboard.com/" data-echo-seo-managed="true"> 777<link rel="stylesheet" crossorigin href="/assets/index-Dx_MVqAM.css">
778<script type="application/ld+json" data-echo-seo-managed="true">{"@context":"https://schema.org","@graph":[{"@type":"WebSite","@id":"https://myechoboard.com/#website","name":"Echo","url":"https://myechoboard.com/","description":"Echo is an ad-free Pinterest alternative for discovering inspiration and organizing ideas. Available on web, iOS, and Android, it lets you save images, links, and notes into boards. Personal collections are private by default, with public boards available for sharing.","publisher":{"@id":"https://myechoboard.com/#organization"}},{"@type":"WebPage","@id":"https://myechoboard.com/#webpage","name":"Echo â The Ad-Free Pinterest Alternative","url":"https://myechoboard.com/","description":"Echo is an ad-free Pinterest alternative for discovering inspiration and organizing ideas. Available on web, iOS, and Android, it lets you save images, links, and notes into boards. Personal collections are private by default, with public boards available for sharing.","isPartOf":{"@id":"https://myechoboard.com/#website"},"about":{"@id":"https://myechoboard.com/#application"}},{"@type":"Organization","@id":"https://myechoboard.com/#organization","name":"Echo","url":"https://myechoboard.com/","logo":"https://myechoboard.com/logo.png","description":"Echo builds a private-by-default platform for collecting inspiration, organizing boards, and rediscovering ideas over time."},{"@type":"SoftwareApplication","@id":"https://myechoboard.com/#application","name":"Echo","applicationCategory":"CreativeWorkApplication","operatingSystem":"Web, iOS, Android","url":"https://myechoboard.com/","publisher":{"@id":"https://myechoboard.com/#organization"},"description":"Echo is an ad-free Pinterest alternative for discovering inspiration and organizing ideas. Available on web, iOS, and Android, it lets you save images, links, and notes into boards. Personal collections are private by default, with public boards available for sharing.","featureList":["Private visual feed without ads","Save images, links, quotes, notes, and ideas","Organize inspiration in folders, boards, sections, and subsections","Explore public inspiration boards","Filter inspiration by color, material, aesthetic, subject, and mood","Hide community-flagged AI content","Share public boards","Import public Pinterest boards, with Chrome extension saving for private boards"]}]}</script>
778 779</head> 780 781<body> 782 <style> 783 #echo-native-launch { display: none; } 784 html[data-native-bootstrap] #echo-native-launch { 785 position: fixed; inset: 0; z-index: 9999; display: grid; 786 place-items: center; background: #fcf6f5; pointer-events: none; 787 } 788 #echo-native-launch img { display: block; width: 84px; height: 84px; } 789 @media (prefers-color-scheme: dark) { 790 html[data-native-bootstrap] #echo-native-launch { background: #0a090d; } 791 } 792 </style> 793 <div id="echo-native-launch" role="status" aria-label="Opening Echo" style="display:none;position:fixed;inset:0;z-index:9999;place-items:center;pointer-events:none;background:#fcf6f5"> 794 <img id="echo-native-launch-logo" width="84" height="84" alt="" fetchpriority="high" /> 795 </div> 796
796<script> 797 if (document.documentElement.hasAttribute('data-native-bootstrap')) { 798 // Keep the cover hidden until its geometry is usable. WebKit can expose 799 // the parsed image before the preceding style block has been applied. 800 var nativeLaunchCover = document.getElementById('echo-native-launch'); 801 nativeLaunchCover.style.backgroundColor = window.matchMedia('(prefers-color-scheme: dark)').matches ? '#0a090d' : '#fcf6f5'; 802 var launchLogo = document.getElementById('echo-native-launch-logo'); 803 launchLogo.srcset = '/logo-168.png 2x, /logo-252.png 3x'; 804 launchLogo.src = '/logo-168.png'; 805 nativeLaunchCover.style.display = 'grid'; 806 } 807 </script>
807 808 <!-- Full theme application after DOM is available --> 809
809<script> 810 (function () { 811 if (!window.__ECHO_PUSH_BOOT_LOG__) return; 812 813 window.__ECHO_PUSH_BOOT_LOG__('body-ready'); 814 815 document.addEventListener('DOMContentLoaded', function () { 816 window.__ECHO_PUSH_BOOT_LOG__('dom-content-loaded'); 817 }, { once: true }); 818 819 window.addEventListener('load', function () { 820 window.__ECHO_PUSH_BOOT_LOG__('window-load'); 821 }, { once: true }); 822 })(); 823 </script>
823 824
824<script> 825 (function () { 826 try { 827 // Helper: Convert hex to HSL 828 function hexToHsl(hex) { 829 hex = hex.replace(/^#/, ''); 830 var r = parseInt(hex.substring(0, 2), 16) / 255; 831 var g = parseInt(hex.substring(2, 4), 16) / 255; 832 var b = parseInt(hex.substring(4, 6), 16) / 255; 833 var max = Math.max(r, g, b), min = Math.min(r, g, b); 834 var h = 0, s, l = (max + min) / 2; 835 if (max !== min) { 836 var d = max - min; 837 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 838 switch (max) { 839 case r: h = (g - b) / d + (g < b ? 6 : 0); break; 840 case g: h = (b - r) / d + 2; break; 841 case b: h = (r - g) / d + 4; break; 842 } 843 h /= 6; 844 } else { s = 0; } 845 return Math.round(h * 360) + ' ' + Math.round(s * 100) + '% ' + Math.round(l * 100) + '%'; 846 } 847 848 // Helper: Check if color is dark 849 function isDarkColor(hex) { 850 hex = hex.replace(/^#/, ''); 851 var r = parseInt(hex.substring(0, 2), 16); 852 var g = parseInt(hex.substring(2, 4), 16); 853 var b = parseInt(hex.substring(4, 6), 16); 854 return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 < 0.5; 855 } 856 857 // Apply cached theme (priority: ThemeContext > MoodContext) 858 var cached = localStorage.getItem('echo_theme_cache') || localStorage.getItem('echo_mood_theme'); 859 if (cached) { 860 var theme = JSON.parse(cached); 861 var root = document.documentElement; 862 863 // Colors - Apply as CSS variables for Tailwind 864 if (theme.primary_color) { 865 var primaryHsl = hexToHsl(theme.primary_color); 866 root.style.setProperty('--primary', primaryHsl); 867 root.style.setProperty('--ring', primaryHsl); 868 // CRITICAL: Set border to match primary for themed borders 869 root.style.setProperty('--border', primaryHsl); 870 root.style.setProperty('--border-hsl', primaryHsl); 871 872 // Primary foreground based on primary color darkness 873 // Same rule as Themes.tsx getReadableTextColor so editor and app agree 874 var isPrimaryDark = (function(h){h=String(h||'').replace(/^#/,'');var b=(parseInt(h.slice(0,2),16)*299+parseInt(h.slice(2,4),16)*587+parseInt(h.slice(4,6),16)*114)/1000;return !(b>160);})(theme.primary_color); 875 root.style.setProperty('--primary-foreground', isPrimaryDark ? '0 0% 100%' : '222.2 47.4% 11.2%'); 876 } 877 if (theme.canvas_tone) { 878 root.style.setProperty('--background', hexToHsl(theme.canvas_tone)); 879 // Also set direct style for immediate effect 880 root.style.backgroundColor = theme.canvas_tone; 881 document.body.style.backgroundColor = theme.canvas_tone; 882 883 // CRITICAL: Set foreground/text colors based on background darkness 884 var isBgDark = isDarkColor(theme.canvas_tone); 885 if (isBgDark) { 886 root.style.setProperty('--foreground', '0 0% 98%'); 887 root.style.setProperty('--muted-foreground', '0 0% 70%'); 888 root.style.setProperty('--card-foreground', '210 40% 98%'); 889 // Extract hue from primary for muted color 890 if (theme.primary_color) { 891 var hue = hexToHsl(theme.primary_color).split(' ')[0]; 892 root.style.setProperty('--muted', hue + ' 15% 20%'); 893 } 894 } else { 895 root.style.setProperty('--foreground', '222.2 84% 4.9%'); 896 root.style.setProperty('--muted-foreground', '215.4 16.3% 46.9%'); 897 root.style.setProperty('--card-foreground', '222.2 84% 4.9%'); 898 root.style.setProperty('--muted', '210 40% 96.1%'); 899 } 900 } 901 if (theme.surface_color) { 902 var surfaceHsl = hexToHsl(theme.surface_color); 903 // For dark themes, ensure surface is dark even if theme.surface_color is light 904 var isBgDark = theme.canvas_tone ? isDarkColor(theme.canvas_tone) : false; 905 var useSurface = isBgDark && !isDarkColor(theme.surface_color) ? '240 10% 8%' : surfaceHsl; 906 root.style.setProperty('--card', useSurface); 907 root.style.setProperty('--surface', useSurface); 908 root.style.setProperty('--popover', useSurface); 909 } 910 if (theme.accent_color) { 911 root.style.setProperty('--accent', hexToHsl(theme.accent_color)); 912 } 913 914 // Font - Mood always controls font, even if theme is active 915 var moodCached = localStorage.getItem('echo_mood_theme'); 916 var fontToApply = localStorage.getItem('echo_last_font_family'); 917 918 if (moodCached) { 919 try { 920 var moodTheme = JSON.parse(moodCached); 921 if (moodTheme.font_family) fontToApply = moodTheme.font_family; 922 } catch (e) { } 923 } 924 925 if (fontToApply) { 926 var fontStackToApply = fontToApply === 'Manrope' ? '"Manrope", "Manrope Fallback", sans-serif' : '"' + fontToApply + '", sans-serif'; 927 root.style.setProperty('--font-mood', fontStackToApply, 'important'); 928 929 // Nuclear style injection for head-start 930 var existingStyle = document.getElementById('echo-font-overrider-pre'); 931 if (!existingStyle) { 932 var style = document.createElement('style'); 933 style.id = 'echo-font-overrider-pre'; 934 style.textContent = 'html, body, button, input, select, textarea { font-family: var(--font-mood, ' + fontStackToApply + ') !important; }'; 935 document.head.appendChild(style); 936 } 937 } 938 939 // Glass & Material 940 if (theme.glass_blur !== undefined) { 941 root.style.setProperty('--glass-blur', theme.glass_blur + 'px'); 942 } 943 if (theme.glass_transparency !== undefined) { 944 root.style.setProperty('--glass-transparency', theme.glass_transparency + '%'); 945 } 946 if (theme.corner_radius !== undefined) { 947 root.style.setProperty('--card-radius', theme.corner_radius + 'px'); 948 } 949 if (theme.shadow_depth !== undefined) { 950 root.style.setProperty('--card-shadow-depth', theme.shadow_depth + 'px'); 951 } 952 if (theme.border_weight !== undefined) { 953 root.style.setProperty('--card-border-weight', theme.border_weight + 'px'); 954 } 955 956 // Dark mode - Ensure it's applied 957 var isBgDark = theme.canvas_tone ? isDarkColor(theme.canvas_tone) : false;
958 if (theme.is_dark || isBgDark) { 959 root.classList.add('dark'); 960 } 961 962 // Starry Night: Instant Native Generation 963 var isNativeShell = root.hasAttribute('data-native-bootstrap'); 964 var isStarry = !isNativeShell && ( 965 (theme.background_effects && theme.background_effects.includes('blinking_stars')) || 966 theme.background_effect === 'blinking_stars' 967 ); 968 var container = document.getElementById('star-container'); 969 970 if (isStarry) { 971 root.classList.add('starry-active'); 972 var starColor = (theme.is_dark || isBgDark) ? '#ffffff' : '#444444'; 973 root.style.setProperty('--star-color', starColor); 974 // React's deferred StarryBackground owns decorative DOM after the 975 // first route is usable. Avoid creating 150 animated nodes in the 976 // synchronous body bootstrap. 977 } else { 978 root.classList.remove('starry-active'); 979 if (container) container.innerHTML = ''; 980 } 981 } 982 } catch (e) { console.warn('Theme preload failed:', e); } 983 })(); 984 </script>
984 985 <div id="root" data-echo-prerender="landing"><div class="relative min-h-screen overflow-x-hidden bg-[#030303] text-white"><nav aria-hidden="false" class="fixed inset-x-0 top-4 z-50 px-4 transition-all duration-500 ease-out translate-y-0 opacity-100"><div class="mx-auto flex w-full max-w-5xl items-center justify-between gap-3 rounded-[1.75rem] px-4 py-3 sm:px-5 isolate border border-white/10 bg-[linear-gradient(180deg,rgba(12,15,23,0.92),rgba(7,9,15,0.88))] shadow-[0_16px_50px_rgba(0,0,0,0.30)] backdrop-blur-xl [backface-visibility:hidden] [contain:paint] [transform:translateZ(0)]"><div class="flex items-center gap-2 sm:gap-3"><a class="flex items-center gap-3 text-left transition-opacity hover:opacity-90" href="/" data-discover="true"><div class="h-[3.15rem] w-[3.15rem] overflow-hidden sm:h-[3.4rem] sm:w-[3.4rem]"><img src="/logo-128.png" width="128" height="125" alt="Echo logo" class="h-full w-full object-cover"/></div><div class="hidden sm:block"><p class="text-base font-semibold tracking-tight text-white">Echo</p><p class="text-[10px] uppercase tracking-[0.22em] text-white/70">Keep what inspires you.</p></div></a><a class="items-center justify-center gap-2 whitespace-nowrap transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-10 py-2 rounded-full bg-rose-600 px-4 text-sm font-semibold text-white shadow-[0_16px_34px_rgba(244,63,94,0.22)] hover:bg-rose-700 sm:px-5 hidden sm:inline-flex" href="/community" data-discover="true"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users mr-2 h-4 w-4"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>Community</a></div><div class="flex items-center gap-2"><div class="hidden items-center gap-2 lg:flex" role="group" aria-label="Get the Echo browser extension"><a href="https://chromewebstore.google.com/detail/echo-save-to-your-feed/bafdokbhgelomohejghkkhgmmecfpbpi" target="_blank" rel="noopener noreferrer" aria-label="Get Echo extension for Chrome" class="inline-flex h-10 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full px-3 text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:ring-offset-2 bg-white text-black hover:bg-white/90"><img src="/landing/chrome-web-store-logo.webp" alt="" class="h-5 w-5 shrink-0" decoding="async"/>Chrome</a><a href="https://addons.mozilla.org/en-US/firefox/addon/echo-save-to-your-feed/" target="_blank" rel="noopener noreferrer" aria-label="Get Echo extension for Firefox" class="inline-flex h-10 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full px-3 text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:ring-offset-2 bg-white text-black hover:bg-white/90"><img src="/landing/firefox-browser-logo.svg" alt="" class="h-5 w-5 shrink-0" decoding="async"/>Firefox</a></div><a class="items-center justify-center gap-2 whitespace-nowrap transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-transparent h-10 py-2 hidden rounded-full px-4 text-sm font-medium sm:inline-flex text-white hover:bg-white/10 hover:text-white" href="/auth" data-discover="true">Log in</a><a class="inline-flex items-center justify-center gap-2 whitespace-nowrap transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-10 py-2 rounded-full bg-rose-600 font-semibold text-white shadow-[0_16px_34px_rgba(244,63,94,0.22)] hover:bg-rose-700 px-3 text-xs sm:px-5 sm:text-sm" href="/auth?mode=signup" data-discover="true">Create your feed</a></div></div></nav><main class="relative z-10 overflow-hidden pb-0"><section class="mx-auto max-w-none px-0 pt-0"><div class="relative overflow-hidden bg-[#030303] px-0 pb-0"><div class="absolute inset-0 bg-[radial-gradient(circle_at_50%_30%,rgba(255,255,255,0.12)_0%,rgba(255,255,255,0.04)_34%,rgba(3,3,3,0)_66%)]"></div><div class="relative z-20 mx-auto flex min-h-[calc(100svh-2rem)] max-w-[122rem] flex-col justify-end px-5 pb-0 pt-36 text-center sm:px-8 sm:pt-40 lg:px-10 lg:pt-44"><div class="mx-auto w-full max-w-[94rem]"><p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/58 sm:text-xs">An ad-free Pinterest alternative</p><h1 class="mx-auto mt-7 max-w-[12ch] text-[clamp(3.2rem,7.5vw,7.5rem)] font-semibold leading-[0.95] tracking-tight text-white">Keep what inspires you.</h1><p class="mx-auto mt-7 max-w-[50rem] text-base leading-7 text-white/76 sm:text-xl sm:leading-8">Discover inspiration, organize your ideas, and bring your Pinterest boards with you. Private by default. Always room for your next idea.<
985/p><div class="mt-10 flex flex-col items-center justify-center gap-3 sm:flex-row"><a class="inline-flex items-center justify-center gap-2 whitespace-nowrap transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-[3.25rem] rounded-full bg-white px-7 text-sm font-semibold text-black shadow-[0_18px_40px_rgba(255,255,255,0.12)] hover:bg-white/90 sm:h-14 sm:px-8 sm:text-base" href="/auth?mode=signup" data-discover="true">Create your first board<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right ml-2 h-4 w-4"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg></a><a href="#trending-boards" class="inline-flex items-center justify-center gap-2 whitespace-nowrap transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-[3.25rem] rounded-full bg-primary px-7 text-sm font-semibold text-primary-foreground shadow-[0_18px_40px_hsl(var(--primary)/0.24)] hover:bg-primary/90 sm:h-14 sm:px-8 sm:text-base">See what's trending</a></div><p class="mt-6 text-sm leading-6 text-white/65">Already have a collection?<!-- --> <a class="inline-flex min-h-11 items-center text-white underline decoration-white/40 underline-offset-4 hover:decoration-white" href="/import-pinterest-boards" data-discover="true">Bring your Pinterest boards<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right ml-1.5 h-3.5 w-3.5" aria-hidden="true"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg></a></p><div class="mx-auto mt-6 flex flex-col items-center justify-center gap-3 sm:flex-row sm:flex-wrap"><a href="https://apps.apple.com/app/echo-your-feed/id6760357340" target="_blank" rel="noreferrer" aria-label="Download Echo on the App Store" class="inline-flex transition-opacity hover:opacity-82"><img src="/landing/app-store-badge-320.png" alt="Download on the App Store" width="320" height="111" class="h-12 w-auto max-w-[14rem] sm:h-14 sm:max-w-[16rem]" loading="lazy" decoding="async"/></a><a href="https://play.google.com/store/apps/details?id=app.myechoboard.echo" target="_blank" rel="noreferrer" aria-label="Get Echo on Google Play" class="inline-flex transition-opacity hover:opacity-82"><img src="/landing/google-play-store-badge-320.png" alt="Get it on Google Play" width="320" height="111" class="h-12 w-auto max-w-[14rem] sm:h-14 sm:max-w-[16rem]" loading="lazy" decoding="async"/></a></div></div><div class="pointer-events-none relative z-10 mt-14 overflow-hidden px-0 sm:mt-16 lg:mt-20"><div class="h-[390px] overflow-hidden sm:h-[540px] lg:h-[620px] xl:h-[700px]"><div class="flex h-full items-end gap-3 sm:gap-4 lg:gap-5 w-max animate-hero-rail-marquee-reverse pr-3 sm:pr-4 lg:pr-5"><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rounded-[1.65rem] h-[74%] opacity-75"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="lazy" fetchpriority="auto" decoding="async" class="h-full w-full object-cover"/>
985</div><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rounded-[1.65rem] h-[86%] opacity-95"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820412589-5dda8170-6512-4176-a78c-5e0d3d796d79.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820412589-5dda8170-6512-4176-a78c-5e0d3d796d79.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820412589-5dda8170-6512-4176-a78c-5e0d3d796d79.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820412589-5dda8170-6512-4176-a78c-5e0d3d796d79.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="lazy" fetchpriority="auto" decoding="async" class="h-full w-full object-cover"/></div><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rounded-[1.65rem] h-full"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="eager" fetchpriority="high" decoding="async" class="h-full w-full object-cover"/></div><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rounded-[1.65rem] h-[86%] opacity-95"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820022450-47cf14e8-f3c8-4c17-829a-2437fe108cfe.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820022450-47cf14e8-f3c8-4c17-829a-2437fe108cfe.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820022450-47cf14e8-f3c8-4c17-829a-2437fe108cfe.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820022450-47cf14e8-f3c8-4c17-829a-2437fe108cfe.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="lazy" fetchpriority="auto" decoding="async" class="h-full w-full object-cover"/></div><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rounded-[1.65rem] h-[74%] opacity-75"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770819586119-d7e09fd8-7d8f-4106-8022-547f0d6c2509.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770819586119-d7e09fd8-7d8f-4106-8022-547f0d6c2509.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770819586119-d7e09fd8-7d8f-4106-8022-547f0d6c2509.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770819586119-d7e09fd8-7d8f-4106-8022-547f0d6c2509.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="lazy" fetchpriority="auto" decoding="async" class="h-full w-full object-cover"/></div><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rou
985nded-[1.65rem] h-[74%] opacity-75"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="lazy" fetchpriority="auto" decoding="async" class="h-full w-full object-cover"/></div><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rounded-[1.65rem] h-[86%] opacity-95"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820412589-5dda8170-6512-4176-a78c-5e0d3d796d79.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820412589-5dda8170-6512-4176-a78c-5e0d3d796d79.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820412589-5dda8170-6512-4176-a78c-5e0d3d796d79.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820412589-5dda8170-6512-4176-a78c-5e0d3d796d79.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="lazy" fetchpriority="auto" decoding="async" class="h-full w-full object-cover"/></div><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rounded-[1.65rem] h-full"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820854971-4ca7e03e-c001-4579-b5b0-f17d0d15d942.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="lazy" fetchpriority="auto" decoding="async" class="h-full w-full object-cover"/></div><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rounded-[1.65rem] h-[86%] opacity-95"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820022450-47cf14e8-f3c8-4c17-829a-2437fe108cfe.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820022450-47cf14e8-f3c8-4c17-829a-2437fe108cfe.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820022450-47cf14e8-f3c8-4c17-829a-2437fe108cfe.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770820022450-47cf14e8-f3c8-4c17-829a-2437fe108cfe.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="lazy" fetchpriority="auto" decoding="async" class="h-full w-full object-cover"/></div><div style="aspect-ratio:0.78" class="relative shrink-0 overflow-hidden rounded-[1.25rem] shadow-[0_30px_80px_rgba(0,0,0,0.56)] sm:rounded-[1.65rem] h-[74%] opacity-75"><img src="https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770819586119-d7e09fd8-7d8f-4106-8022-547f0d6c2509.jpg" srcSet="https://cdn.myechoboard.com/cdn-cgi/image/width=400,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770819586119-d7e09fd8-7d8f-4106-8022-547f0d6c2509.jpg 400w, https://cdn.myechoboard.com/cdn-cgi/image/width=800,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770819586119-d7e09fd8-7d8f-4106-8022-547f0d6c2509.jpg 800w, https://cdn.myechoboard.com/cdn-cgi/image/width=1200,quality=80,format=auto/74c3ce13-29a1-4e45-ac73-d73b5b26718b/1770819586119-d7e09fd8-7d8f-4106-8022-547f0d6c2509.jpg 1200w" sizes="(max-width: 640px) 72vw, 340px" alt="" loading="lazy" fetchpriority="auto" decoding="async" class="h-full w-full object-cover"/>
985</div></div></div></div></div></div></section><div class="relative z-10"><section class="relative bg-[#030303] text-white border-t border-white/10"><div class="mx-auto w-full max-w-7xl px-5 py-24 sm:px-6 lg:px-8 lg:py-28"><div class="mx-auto max-w-6xl text-center"><p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/52">Curated by Echo</p><h2 class="mx-auto mt-7 max-w-[17ch] text-[clamp(2.8rem,7vw,6.8rem)] font-semibold leading-[0.9] text-white">Boards worth<!-- --> <span class="bg-white/10 px-2 font-serif italic text-white/72">exploring.</span></h2><p class="mx-auto mt-7 max-w-3xl text-base leading-8 text-white/64 sm:text-lg">A hand-picked set of public boards from Echo to start exploring visual ideas.</p></div><div class="mt-14 grid gap-x-4 gap-y-8 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5"><a class="group block text-left" href="/u/sebbe/beauty" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Beauty preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">B</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Beauty</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/food-drinks" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Food & Drinks preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">F</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Food & Drinks</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/interior-design" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Interior Design preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">I</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Interior Design</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/stars" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Stars preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">S</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Stars</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/sunrise-x-sunset" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Sunrise x Sunset preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">S</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Sunrise x Sunset</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/sketch-ideas" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Sketch Ideas preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">S</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Sketch Ideas</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/garden-outdoors" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Garden & Outdoors preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">G</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Garden & Outdoors</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/cozy-places" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Cozy Places preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">C</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Cozy Places</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/aesthetics" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Aesthetics preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">A</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Aesthetics</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/best-of-echo" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden animate-pulse" role="status" aria-label="Loading Best of Echo preview"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">B</div><div class="absolute inset-x-6 bottom-6 h-2 rounded-full bg-white/10"></div></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Best of Echo</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a></div></div></section><section id="trending-boards" class="relative bg-[#030303] text-white border-t border-white/10"><div class="mx-auto w-full max-w-7xl px-5 py-24 sm:px-6 lg:px-8 lg:py-28"><div class="mx-auto max-w-6xl text-center"><p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/52">Trending Boards</p><h2 class="mx-auto mt-7 max-w-[17ch] text-[clamp(2.8rem,7vw,6.8rem)] font-semibold leading-[0.9] text-white">What people are<!-- --> <span class="bg-white/10 px-2 font-serif italic text-white/72">saving now.</span></h2><p class="mx-auto mt-7 max-w-3xl text-base leading-8 text-white/64 sm:text-lg">A live look at public boards gathering attention across Echo.</p></div><div class="mt-14 grid gap-x-4 gap-y-8 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5"><a class="group block text-left" href="/u/sebbe/beauty" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden" role="img" aria-label="Beauty preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">B</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Beauty</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/food-drinks" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden" role="img" aria-label="Food & Drinks preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">F</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Food & Drinks</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/interior-design" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-fu
985ll overflow-hidden" role="img" aria-label="Interior Design preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">I</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Interior Design</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/stars" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden" role="img" aria-label="Stars preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">S</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Stars</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/sunrise-x-sunset" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden" role="img" aria-label="Sunrise x Sunset preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">S</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Sunrise x Sunset</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/sketch-ideas" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden" role="img" aria-label="Sketch Ideas preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">S</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Sketch Ideas</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/garden-outdoors" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden" role="img" aria-label="Garden & Outdoors preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">G</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Garden & Outdoors</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/cozy-places" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden" role="img" aria-label="Cozy Places preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">C</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Cozy Places</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/aesthetics" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden" role="img" aria-label="Aesthetics preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">A</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Aesthetics</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a><a class="group block text-left" href="/u/sebbe/best-of-echo" data-discover="true"><div class="overflow-hidden rounded-[1rem] bg-white/[0.04]"><div class="aspect-[1.05] bg-white/[0.04]"><div class="relative col-span-2 row-span-2 h-full overflow-hidden" role="img" aria-label="Best of Echo preview unavailable"><div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(circle_at_28%_22%,rgba(255,255,255,0.13),transparent_34%),linear-gradient(145deg,rgba(239,68,68,0.28),rgba(124,58,237,0.22)_55%,rgba(15,23,42,0.9))] text-4xl font-semibold text-white/70">B</div><span class="absolute inset-x-3 bottom-4 text-center text-[11px] font-medium uppercase tracking-[0.14em] text-white/48">Preview unavailable</span></div></div></div><div class="mt-3 flex items-start gap-2.5"><span class="relative flex shrink-0 overflow-hidden rounded-full h-7 w-7 bg-white/10"><span class="flex h-full w-full items-center justify-center rounded-full bg-white/10 text-[11px] font-semibold text-white/64">S</span></span><div class="min-w-0"><p class="truncate text-sm font-semibold leading-tight text-white transition-colors group-hover:text-white/82">Best of Echo</p><p class="mt-1 truncate text-xs text-white/54">@<!-- -->sebbe</p></div></div></a></div></div></section><section class="relative bg-[#030303] text-white border-t border-white/10"><div class="mx-auto flex w-full max-w-5xl flex-col items-center px-5 py-24 text-center sm:px-6 lg:px-8 lg:py-28"><p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/52">Start Your Space</p><h2 class="mt-7 max-w-[12ch] text-[clamp(3.2rem,8vw,7rem)] font-semibold leading-[0.88] text-white">Create your<!-- --> <span class="bg-white/10 px-2 font-serif italic text-white/72">feed.</span></h2><p class="mx-auto mt-7 max-w-2xl text-base leading-8 text-white/66 sm:text-lg">Build a private visual home for the references, ideas, and fragments you keep coming back to.</p><div class="mt-10 flex flex-col items-center justify-center gap-3 sm:flex-row"><a class="inline-flex items-center justify-center gap-2 whitespace-nowrap transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-[3.25rem] rounded-full bg-white px-7 text-sm font-semibold text-black shadow-[0_18px_40px_rgba(255,255,255,0.12)] hover:bg-white/90 sm:h-14 sm:px-8 sm:text-base" href="/auth?mode=signup" data-discover="true">Create your feed<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right ml-2 h-4 w-4"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg></a></div></div></section><section class="relative bg-[#030303] text-white border-t border-white/10"><div class="mx-auto w-full max-w-7xl px-5 py-24 sm:px-6 lg:px-8 lg:py-28"><div class="mx-auto max-w-5xl text-center"><p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/52">Our Top 10 Features</p><h2 class="mt-7 text-[clamp(3rem,7vw,6.8rem)] font-semibold leading-[0.9] text-white">What users love about Echo</h2></div><ol class="mx-auto mt-16 max-w-5xl divide-y divide-white/10 border-y border-white/10"><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">01</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl">No ads</span></li><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">02</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl">Your own private feed</span></li><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">03</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl">Filters for everything and anything: colors, materials, aesthetics, objects, subjects, humans, mood, and aspect ratio</span></li><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">04</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl">Customizable themes</span></li><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">05</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl">Customizable feed design</span></li><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">06</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl">Organize ideas in folders, boards, sections, and infinite subsections</span></li><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">07</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl">Save images, links, quotes, notes, and ideas in one visual space</span></li><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">08</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl">Create public boards when you want to share</span></li><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">09</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl">Rediscover old saves when they become useful again</span></li><li class="grid gap-4 py-5 sm:grid-cols-[4rem_1fr] sm:items-center sm:py-6"><span class="font-serif text-3xl italic leading-none text-white/36 sm:text-4xl">10</span><span class="text-xl font-semibold leading-tight text-white sm:text-2xl"><span class="inline"><span class="inline-flex items-baseline gap-2.5 align-baseline"><span class="inline-flex h-[1.35em] w-[1.35em] translate-y-[0.23em] items-center justify-center overflow-hidden"><img src="/logo-128.png" width="128" height="125" alt="Echo logo" loading="lazy" decoding="async" class="h-full w-full object-cover"/>
985</span><span>Echo</span></span> <!-- -->is built on <strong class="font-black">your feedback</strong>. Just email<!-- --> <a href="/cdn-cgi/l/email-protection#431026212230372a222d032e3a26202b2c212c2231276d202c2e" class="text-[#ff6f86] underline decoration-current/50 underline-offset-4 transition-colors hover:text-[#ff91a2]"><span class="__cf_email__" data-cfemail="663503040715120f0708260b1f03050e0904090714024805090b">[email protected]</span></a> <!-- -->if you have requests, ideas, issues, or just want to say hi!</span></span></li></ol></div></section><section aria-labelledby="landing-questions" class="border-t border-white/10 bg-[#030303] text-white"><div class="mx-auto grid max-w-7xl gap-12 px-5 py-24 sm:px-6 lg:grid-cols-[0.8fr_1.2fr] lg:gap-20 lg:px-8 lg:py-28"><div><p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/52">A little more about Echo</p><h2 id="landing-questions" class="mt-7 text-4xl font-semibold leading-tight tracking-tight sm:text-5xl">Your inspiration.<br/><span class="font-serif italic text-white/72">Your way.</span></h2><p class="mt-6 max-w-sm text-base leading-7 text-white/66">For designers collecting references, creatives building moodboards, and anyone planning a home, a trip, or their next idea.</p><a class="mt-7 inline-flex min-h-11 items-center text-sm text-white underline decoration-white/40 underline-offset-4 hover:decoration-white" href="/about" data-discover="true">Meet Echo</a></div><div class="divide-y divide-white/10 [&_a]:underline [&_a]:decoration-white/40 [&_a]:underline-offset-4 hover:[&_a]:decoration-white"><div class="pb-7"><h3 class="text-lg font-semibold">What is Echo?</h3><p class="mt-3 text-base leading-7 text-white/70">Echo is an ad-free Pinterest alternative for discovering inspiration and organizing ideas. Available on web, iOS, and Android, it lets you save images, links, and notes into boards. Personal collections are private by default, with public boards available for sharing.</p></div><div class="py-7"><h3 class="text-lg font-semibold">Can I use Echo as a Pinterest alternative?</h3><p class="mt-3 text-base leading-7 text-white/70">Yes. Echo combines public inspiration with a private-by-default library, without ads. Organize saves in folders, boards, sections, and nested subsections. You can <a href="/import-pinterest-boards" data-discover="true">import Pinterest boards</a> from a public board URL, or use the Echo Chrome extension for private boards and browser saving. See the <a href="/blog/pinterest-vs-echo" data-discover="true">Pinterest and Echo comparison</a> to choose what fits your workflow.</p></div><div class="py-7"><h3 class="text-lg font-semibold">How do I find and organize inspiration?</h3><p class="mt-3 text-base leading-7 text-white/70">Explore public boards and filter visual inspiration by color, material, aesthetic, object, subject, mood, and aspect ratio. AI-assisted image metadata helps make these filters searchable. Collect references for a project in your own boards, or explore an <a href="/u/sebbe/interior-design" data-discover="true">interior design board</a> to see how a collection looks.</p></div><div class="py-7"><h3 class="text-lg font-semibold">Can I hide AI-generated content?</h3><p class="mt-3 text-base leading-7 text-white/70">Echo lets you hide community-flagged AI content in Content Preferences. The filter uses the majority of community votes, and content you personally mark as AI is hidden for you immediately. It does not guarantee that every AI-generated image is identified.</p></div><div class="py-7"><h3 class="text-lg font-semibold">Are my boards private?</h3><p class="mt-3 text-base leading-7 text-white/70">Your personal feed is private. Keep your inspiration to yourself, or create public boards when you want to share. Read Echoâs <a href="/privacy" data-discover="true">privacy policy</a> for details about how your data is handled.</p></div><div class="pt-7"><h3 class="text-lg font-semibold">Can I use Echo on my phone?</h3><p class="mt-3 text-base leading-7 text-white/70">Echo is available on the web, iOS, and Android. Use the App Store and Google Play links on this page to get the mobile app. For help or product feedback, <a href="/support" data-discover="true">contact Echo support</a>.</p></div></div></div></section></div></main><footer class="relative z-10 border-t border-white/10 bg-[linear-gradient(180deg,rgba(12,15,23,0.96)_0%,rgba(7,9,15,0.98)_100%)] text-white"><div class="mx-auto grid w-full max-w-7xl gap-x-8 gap-y-12 px-5 py-16 text-sm text-white sm:px-6 md:grid-cols-2 lg:grid-cols-[1.5fr_0.8fr_0.95fr_0.8fr_0.9fr] lg:px-8 [&_a]:inline-flex [&_a]:min-h-6 [&_a]:items-center"><div class="lg:pr-10"><p class="text-base font-semibold tracking-tight text-white">
985Echo</p><p class="mt-3 max-w-md text-[15px] leading-7 text-white/88">Keep what inspires you. An ad-free Pinterest alternative for discovering and organizing ideas.</p><p class="mt-6 max-w-sm whitespace-pre-line text-sm leading-6 text-white/80">ð¸ðª 986Designed & Engineered in Ljungby, Sweden</p></div><div><p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/70">Company</p><div class="mt-5 flex flex-col gap-3.5"><a class="transition-colors hover:text-white" href="/about" data-discover="true">About</a><a class="transition-colors hover:text-white" href="/news" data-discover="true">News</a><a class="transition-colors hover:text-white" href="/community" data-discover="true">Community</a><a class="transition-colors hover:text-white" href="/ai" data-discover="true">AI Index</a><a class="transition-colors hover:text-white" href="/privacy" data-discover="true">Privacy</a><a class="transition-colors hover:text-white" href="/terms" data-discover="true">Terms</a><a class="transition-colors hover:text-white" href="/support" data-discover="true">Support</a></div></div><div><p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/70">Compare</p><div class="mt-5 flex flex-col gap-3.5"><a class="transition-colors hover:text-white" href="/blog" data-discover="true">Journal & guides</a><a class="transition-colors hover:text-white" href="/alternatives/arena" data-discover="true">Are.na Alternatives</a><a class="transition-colors hover:text-white" href="/alternatives/cosmos" data-discover="true">Cosmos Alternatives</a><a class="transition-colors hover:text-white" href="/alternatives/eagle" data-discover="true">Eagle Alternatives</a><a class="transition-colors hover:text-white" href="/alternatives/raindrop" data-discover="true">Raindrop.io Alternatives</a><a class="transition-colors hover:text-white" href="/alternatives/pinterest" data-discover="true">Pinterest Alternatives</a></div></div><div><p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/70">Get Started</p><div class="mt-5 flex flex-col gap-3.5"><a class="font-medium text-white transition-colors hover:text-white" href="/auth?mode=signup" data-discover="true">Create account</a><a class="transition-colors hover:text-white" href="/auth" data-discover="true">Log in</a><a class="transition-colors hover:text-white" href="/explore" data-discover="true">Explore</a></div></div></div></footer></div></div>
vendor: 111 bytes, lines 986-987
986 987 <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
987<script> 988 (function () { 989 var cover = document.getElementById('echo-native-launch'); 990 if (!document.documentElement.hasAttribute('data-native-bootstrap')) { 991 cover.remove(); 992 return; 993 } 994 performance.mark('echo-startup:document-ready'); 995 var root = document.getElementById('root'); 996 var observer = new MutationObserver(function () { 997 if (!root.childElementCount) return; 998 cover.remove(); 999 observer.disconnect(); 1000 }); 1001 observer.observe(root, { childList: true }); 1002 })(); 1003 </script>
1003 1004 <noscript> 1005 <div class="echo-boot-recovery"> 1006 <div class="echo-boot-recovery__card"> 1007 <h1>Echo needs JavaScript</h1> 1008 <p>Enable JavaScript for myechoboard.com, then reload this page.</p> 1009 </div> 1010 </div> 1011 </noscript> 1012
1012<script> 1013 (function () { 1014 if (document.documentElement.hasAttribute('data-native-bootstrap')) return; 1015 if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.hostname === '[::1]') return; 1016 1017 var recoveryShown = false; 1018 var reloadStarted = false; 1019 var watchdogTimer = 0; 1020 1021 function recoveryAttempt() { 1022 try { 1023 var url = new URL(window.location.href); 1024 var rawAttempt = url.searchParams.get('_ra'); 1025 if (rawAttempt !== null) { 1026 var parsedAttempt = Number(rawAttempt); 1027 if (Number.isSafeInteger(parsedAttempt) && parsedAttempt >= 0) { 1028 return Math.min(parsedAttempt, 99); 1029 } 1030 } 1031 return url.searchParams.has('_r') ? 1 : 0; 1032 } catch (error) { 1033 return 0; 1034 } 1035 } 1036 1037 function clearStaleAppCaches() { 1038 var unregister = Promise.resolve(); 1039 var clearCaches = Promise.resolve(); 1040 1041 try { 1042 if ('serviceWorker' in navigator) { 1043 unregister = navigator.serviceWorker.getRegistrations().then(function (registrations) { 1044 return Promise.all(registrations.map(function (registration) { 1045 return registration.unregister(); 1046 })); 1047 }).catch(function () {}); 1048 } 1049 } catch (error) {} 1050 1051 try { 1052 if ('caches' in window) { 1053 clearCaches = window.caches.keys().then(function (cacheNames) { 1054 return Promise.all(cacheNames 1055 .filter(function (cacheName) { 1056 return cacheName.indexOf('workbox-precache') === 0 || cacheName.indexOf('echo-feature-') === 0; 1057 }) 1058 .map(function (cacheName) { return window.caches.delete(cacheName); })); 1059 }).catch(function () {}); 1060 } 1061 } catch (error) {} 1062 1063 var cleanup = Promise.all([unregister, clearCaches]); 1064 var timeout = new Promise(function (resolve) { window.setTimeout(resolve, 2000); }); 1065 return Promise.race([cleanup, timeout]); 1066 } 1067 1068 function reloadLatest() { 1069 if (reloadStarted || document.documentElement.hasAttribute('data-echo-reload-started')) { 1070 return Promise.resolve(); 1071 } 1072 reloadStarted = true; 1073 document.documentElement.setAttribute('data-echo-reload-started', 'true'); 1074 var attempt = recoveryAttempt(); 1075 var retryDelay = 0; 1076 var url = new URL(window.location.href);
1077 url.searchParams.set('_cb', String(Date.now())); 1078 url.searchParams.set('_r', 'boot-watchdog'); 1079 url.searchParams.set('_ra', String(attempt + 1)); 1080 var destination = url.toString(); 1081 var navigated = false; 1082 var navigate = function () { 1083 if (navigated) return; 1084 navigated = true; 1085 try { 1086 window.location.replace(destination); 1087 } catch (error) { 1088 try { window.location.reload(); } catch (reloadError) {} 1089 } 1090 }; 1091 1092 // Recovery must not depend on browser cache APIs settling correctly. 1093 window.setTimeout(navigate, Math.max(2250, retryDelay + 250)); 1094 window.setTimeout(function () { 1095 reloadStarted = false; 1096 document.documentElement.removeAttribute('data-echo-reload-started'); 1097 }, Math.max(5000, retryDelay + 2500)); 1098 var delay = new Promise(function (resolve) { window.setTimeout(resolve, retryDelay); }); 1099 return Promise.all([clearStaleAppCaches(), delay]).then(navigate, navigate); 1100 } 1101 1102 function showRecovery(cause) { 1103 if (document.documentElement.hasAttribute('data-echo-app-mounted')) return; 1104 if (recoveryShown) { 1105 if (cause !== 'asset') return; 1106 var previousRecovery = document.getElementById('echo-boot-recovery'); 1107 if (previousRecovery) previousRecovery.remove(); 1108 recoveryShown = false; 1109 } 1110 recoveryShown = true; 1111 1112 var recovery = document.createElement('div'); 1113 recovery.id = 'echo-boot-recovery'; 1114 recovery.className = 'echo-boot-recovery'; 1115 recovery.setAttribute('role', 'alert'); 1116 var card = document.createElement('div'); 1117 card.className = 'echo-boot-recovery__card'; 1118 var title = document.createElement('h1'); 1119 title.textContent = cause === 'asset' 1120 ? 'Echo could not finish loading' 1121 : 'Echo is taking longer than expected'; 1122 var message = document.createElement('p'); 1123 message.textContent = cause === 'asset' 1124 ? 'A required app file did not load. Echo will make one safe recovery attempt.' 1125 : 'Echo is still loading. You can wait, or retry once without getting stuck in a reload loop.'; 1126 var button = document.createElement('button'); 1127 button.type = 'button'; 1128 button.textContent = cause === 'asset' ? 'Retry Now' : 'Retry Loading'; 1129 button.addEventListener('click', function () { 1130 button.disabled = true; 1131 button.textContent = 'Reloadingâ¦'; 1132 void reloadLatest(); 1133 window.setTimeout(function () { 1134 button.disabled = false; 1135 button.textContent = 'Try Again'; 1136 }, 5000); 1137 }); 1138 card.append(title, message, button); 1139 recovery.appendChild(card); 1140 document.body.appendChild(recovery); 1141 1142 var attempt = recoveryAttempt(); 1143 // A timer only proves that startup is slow. Automatically navigating on 1144 // a slow CPU/network resets all progress and can create an endless loop. 1145 // Only a concrete missing-asset signal is eligible for one automatic 1146 // recovery; subsequent attempts remain explicitly user-controlled. 1147 if (cause === 'asset' && attempt < 1) void reloadLatest(); 1148 } 1149 1150 function looksLikeBootAssetFailure(value) { 1151 var message = String(value && value.message ? value.message : value || '').toLowerCase(); 1152 return message.indexOf('failed to fetch dynamically imported module') !== -1 || 1153 message.indexOf('error loading dynamically imported module') !== -1 || 1154 message.indexOf('importing a module script failed') !== -1 || 1155 message.indexOf('unable to preload css') !== -1 || 1156 message.indexOf('loading chunk') !== -1; 1157 } 1158 1159 function handleBootError(event) { 1160 if (document.documentElement.hasAttribute('data-echo-app-mounted')) return; 1161 var target = event.target; 1162 var assetUrl = target && (target.src || target.href); 1163 var errorFile = event.filename; 1164 if ((assetUrl && /\/assets\/.+\.(?:js|css)(?:[?#]|$)/i.test(assetUrl)) || 1165 (errorFile && /\/assets\/.+\.js(?:[?#]|$)/i.test(errorFile)) || 1166 looksLikeBootAssetFailure(event.error)) { 1167 showRecovery('asset'); 1168 } 1169 } 1170 1171 function handleBootRejection(event) { 1172 if (!document.documentElement.hasAttribute('data-echo-app-mounted') && looksLikeBootAssetFailure(event.reason)) { 1173 showRecovery('asset'); 1174 } 1175 } 1176 1177 function disarmRecovery() { 1178 if (watchdogTimer) { 1179 window.clearTimeout(watchdogTimer); 1180 watchdogTimer = 0; 1181 } 1182 window.removeEventListener('error', handleBootError, true); 1183 window.removeEventListener('unhandledrejection', handleBootRejection); 1184 var recovery = document.getElementById('echo-boot-recovery'); 1185 if (recovery) recovery.remove();
1186 recoveryShown = false; 1187 try { delete window.__ECHO_BOOT_RECOVERY__; } catch (error) { 1188 window.__ECHO_BOOT_RECOVERY__ = undefined; 1189 } 1190 } 1191 1192 window.__ECHO_BOOT_RECOVERY__ = { 1193 show: function () { showRecovery('asset'); }, 1194 disarm: disarmRecovery 1195 }; 1196 window.addEventListener('error', handleBootError, true); 1197 window.addEventListener('unhandledrejection', handleBootRejection); 1198 watchdogTimer = window.setTimeout(function () { showRecovery('slow'); }, 20000); 1199 })(); 1200 </script>
1200 1201<!-- Cloudflare Pages Analytics -->
vendor: 99 bytes, line 1201
1201<script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "
1201b7528674cc3f4c81972d67a23163ce88
vendor: 13 bytes, line 1201
1201"}'></script>
1201<!-- Cloudflare Pages Analytics --></body> 1202 1203</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.