1<!DOCTYPE html> 2<html lang="en"> 3<head> 4<meta charset="UTF-8" /> 5<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> 6<meta name="apple-mobile-web-app-capable" content="yes" /> 7<meta name="mobile-web-app-capable" content="yes" /> 8<meta name="apple-mobile-web-app-status-bar-style" content="default" /> 9<meta name="theme-color" content="#FBFAF7" /> 10<title>Giftella â Gifting Made Easy | Thoughtful Gift Ideas, Wishlists & Reminders</title> 11<meta name="description" content="Giftella is the thoughtful-gifting app. Describe who you're shopping for and our AI curates the perfect gift ideas in seconds â plus shareable wishlists, a friends circle, birthday reminders, and gift notes. Free on the web and iOS." /> 12<meta name="keywords" content="Giftella, gift ideas, gift finder, personalised gifts, wishlist app, birthday reminders, gift app, thoughtful gifts" /> 13<meta name="author" content="Giftella" /> 14<link rel="canonical" href="https://giftella.app/" /> 15<meta property="og:site_name" content="Giftella" /> 16<meta property="og:title" content="Giftella â Gifting Made Easy" /> 17<meta property="og:description" content="AI-curated gift ideas in seconds, plus wishlists, your circle, reminders & gift notes. Free on the web and iOS." /> 18<meta property="og:type" content="website" /> 19<meta property="og:url" content="https://giftella.app/" /> 20<meta property="og:image" content="https://giftella.app/press-assets/giftella-logo-mark-1024.png" /> 21<meta property="og:image:width" content="1024" /> 22<meta property="og:image:height" content="1024" /> 23<meta property="og:image:alt" content="Giftella logo" /> 24<meta name="twitter:card" content="summary" /> 25<meta name="twitter:title" content="Giftella â Gifting Made Easy" /> 26<meta name="twitter:description" content="AI-curated gift ideas in seconds, plus wishlists, your circle, reminders & gift notes." /> 27<meta name="twitter:image" content="https://giftella.app/press-assets/giftella-logo-mark-1024.png" /> 28<meta name="twitter:image:alt" content="Giftella logo" /> 29<meta name="image" content="https://giftella.app/press-assets/giftella-logo-mark-1024.png" /> 30<link rel="icon" href="/favicon.ico?v=4" sizes="any" /> 31<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png?v=4" /> 32<link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png?v=4" /> 33<link rel="icon" type="image/png" sizes="512x512" href="/favicon-512.png?v=4" /> 34<link rel="shortcut icon" href="/favicon.ico?v=4" /> 35<link rel="apple-touch-icon" href="/favicon-180.png?v=4" /> 36<link rel="manifest" href="/site.webmanifest" />
37<script type="application/ld+json"> 38{ 39 "@context": "https://schema.org", 40 "@graph": [ 41 { 42 "@type": "Organization", 43 "@id": "https://giftella.app/#organization", 44 "name": "Giftella", 45 "legalName": "Giftella, Inc.", 46 "alternateName": [ 47 "Giftella App", 48 "Giftella Inc.", 49 "Giftella LLC" 50 ], 51 "url": "https://giftella.app/", 52 "logo": { 53 "@type": "ImageObject", 54 "url": "https://giftella.app/press-assets/giftella-logo-mark-1024.png", 55 "width": 1024, 56 "height": 1024 57 }, 58 "image": "https://giftella.app/press-assets/giftella-banner-og.png", 59 "description": "Giftella is an AI-powered thoughtful-gifting app on iOS, Android and the web. Describe who you are shopping for and Giftella curates real, shoppable gift ideas in seconds, plus shareable wishlists, registries, a friends circle, birthday reminders, group gifts and gift notes.", 60 "disambiguatingDescription": "Giftella, Inc. is an American consumer software company, incorporated in Delaware and based in Maryland, USA. It publishes the Giftella gifting app on iOS, Android and the web at giftella.app. It is not affiliated with, and should not be confused with, any similarly named party supply, balloon, decoration or gift box retailer.", 61 "slogan": "Shop smarter. Gift warmer.", 62 "foundingDate": "2025", 63 "founder": [ 64 { 65 "@type": "Person", 66 "name": "Samson Jiya" 67 }, 68 { 69 "@type": "Person", 70 "name": "Mohamed Haidara" 71 } 72 ], 73 "email": "[email protected]", 74 "address": { 75 "@type": "PostalAddress", 76 "addressRegion": "MD", 77 "addressCountry": "US" 78 }, 79 "areaServed": "Worldwide", 80 "knowsAbout": [ 81 "gift ideas", 82 "personalized gifting", 83 "AI gift recommendations", 84 "wishlists", 85 "gift registries", 86 "birthday reminders", 87 "group gifting", 88 "corporate gifting" 89 ], 90 "contactPoint": [ 91 { 92 "@type": "ContactPoint", 93 "contactType": "customer support", 94 "email": "[email protected]", 95 "url": "https://giftella.app/support", 96 "areaServed": "Worldwide", 97 "availableLanguage": [ 98 "English" 99 ] 100 }, 101 { 102 "@type": "ContactPoint", 103 "contactType": "press", 104 "email": "[email protected]", 105 "url": "https://giftella.app/press", 106 "areaServed": "Worldwide", 107 "availableLanguage": [ 108 "English" 109 ] 110 } 111 ], 112 "sameAs": [ 113 "https://apps.apple.com/app/giftella/id6761128527", 114 "https://play.google.com/store/apps/details?id=app.giftella.giftella", 115 "https://www.instagram.com/giftella.app/", 116 "https://www.tiktok.com/@giftella.app", 117 "https://www.linkedin.com/company/giftella-app", 118 "https://giftella.app/press" 119 ] 120 }, 121 { 122 "@type": "WebSite", 123 "@id": "https://giftella.app/#website", 124 "url": "https://giftella.app/", 125 "name": "Giftella", 126 "publisher": { 127 "@id": "https://giftella.app/#organization" 128 }, 129 "inLanguage": "en-US" 130 }, 131 { 132 "@type": "WebApplication", 133 "name": "Giftella", 134 "url": "https://giftella.app/", 135 "applicationCategory": "LifestyleApplication", 136 "operatingSystem": "Any (web browser), iOS", 137 "browserRequirements": "Requires JavaScript.", 138 "offers": { 139 "@type": "Offer", 140 "price": "0", 141 "priceCurrency": "USD" 142 }, 143 "description": "Log in and find the perfect gift with AI, keep wishlists, see your circle's wishlists, set reminders, and send gift notes.", 144 "publisher": { 145 "@id": "https://giftella.app/#organization" 146 } 147 } 148 ] 149} 150</script>
150 151<style> 152 :root { 153 /* One theme (Sep 10): cream, gold and warm ink. No purple anywhere. */ 154 --bg: #FBFAF7; 155 --bg2: #F4F1EC; 156 --card: #FFFFFF; 157 --text: #1E1913; 158 --muted: #776C60; 159 --plum: #7A5C16; /* legacy name, now the gold-ink accent */ 160 --plum2: #A8842F; /* legacy name, now deep gold */ 161 --gold: #C9A24A; 162 --gold2: #E7C77B; 163 --rose: #C75273; 164 --border: rgba(30,25,19,0.10); 165 --border2: rgba(30,25,19,0.06); 166 --ink: #FFFFFF; 167 --surface-2: rgba(30,25,19,0.045); 168 --shadow: 0 8px 24px rgba(30,25,19,0.08); 169 --shadow-lift: 0 14px 34px rgba(30,25,19,0.14); 170 --radius: 18px; 171 --danger: #C0392B; 172 --sage: #3E8E6E; 173 --topbar-bg: rgba(251,250,247,.86); 174 --panel-bg: linear-gradient(145deg, #ffffff, #fbf7ef); 175 } 176 177 /* Dark mode â applied when <html data-theme="dark"> */ 178 :root[data-theme="dark"] { 179 --bg: #100E0B; 180 --bg2: #0C0A08; 181 --card: #1B1712; 182 --text: #F6F1E7; 183 --muted: #B1A594; 184 --plum: #E7C77B; /* legacy name, now light gold */ 185 --plum2: #D9B45C; 186 --gold: #C8A84B; 187 --gold2: #E7CA79; 188 --rose: #DB6B8C; 189 --border: rgba(255,255,255,0.12); 190 --border2: rgba(255,255,255,0.07); 191 --ink: #100E0B; 192 --surface-2: rgba(255,255,255,0.06); 193 --shadow: 0 8px 24px rgba(0,0,0,0.45); 194 --shadow-lift: 0 14px 34px rgba(0,0,0,0.55); 195 --danger: #E5675A;
196 --sage: #61A68C; 197 --topbar-bg: rgba(16,14,11,.86); 198 --panel-bg: linear-gradient(145deg, #201B15, #16120E); 199 } 200 201 * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } 202 html, body { margin: 0; padding: 0; min-height: 100%; } 203 /* Global horizontal-overflow guard â stops any row/rail from making the 204 whole page scroll sideways on narrow phones (iPhone 15 & smaller). 205 Uses `clip` (not `hidden`) so the sticky topbar keeps working and 206 internal swipe-rails still scroll normally. */ 207 html, body { overflow-x: clip; max-width: 100%; } 208 img, video, canvas { max-width: 100%; } 209 body { 210 background: var(--bg); 211 color: var(--text); 212 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; 213 -webkit-font-smoothing: antialiased; 214 line-height: 1.45; 215 } 216 a { color: inherit; } 217 button { font-family: inherit; cursor: pointer; border: none; color: var(--text); background: none; } 218 input, textarea, select { 219 width: 100%; font-family: inherit; font-size: 15px; color: var(--text); 220 background: var(--card); border: 1px solid var(--border); border-radius: 12px; 221 padding: 12px 14px; outline: none; transition: border-color .15s ease, box-shadow .15s ease; 222 } 223 input::placeholder, textarea::placeholder { color: var(--muted); opacity: .85; } 224 input:focus, textarea:focus, select:focus { border-color: var(--plum2); box-shadow: 0 0 0 3px rgba(201,162,74,.12); } 225 textarea { resize: vertical; min-height: 84px; } 226 227 .wordmark { 228 font-family: Georgia, "Times New Roman", serif; font-weight: 700; 229 letter-spacing: .3px; text-decoration: none; 230 background: linear-gradient(120deg, var(--gold2), var(--gold)); 231 -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; 232 color: var(--gold); 233 } 234 h1, h2, h3, .serif { font-family: Georgia, "Times New Roman", serif; font-weight: 600; } 235 .muted { color: var(--muted); } 236 .center { text-align: center; } 237 238 /* buttons */ 239 .btn-primary { 240 width: 100%; padding: 14px 18px; border-radius: 13px; font-size: 16px; font-weight: 700; 241 color: var(--ink); background: linear-gradient(120deg, var(--gold2), var(--gold)); 242 box-shadow: 0 8px 20px rgba(201,162,74,.28); transition: transform .12s ease, opacity .12s ease; 243 } 244 .btn-primary:active { transform: scale(.98); } 245 .btn-primary:disabled { opacity: .55; cursor: default; } 246 /* The primary action inside the sign-in panel and the app screens. It was 247 violet, so clicking a gold "Get started free" handed you a violet button 248 on the very next screen. Same action, same colour. */ 249 .btn-plum { 250 width: 100%; padding: 14px 18px; border-radius: 13px; font-size: 16px; font-weight: 700; 251 color: #1E1913; background: linear-gradient(120deg, var(--gold2), var(--gold)); 252 box-shadow: 0 8px 20px rgba(201,162,74,.30); transition: transform .12s ease, opacity .12s ease; 253 } 254 .btn-plum:active { transform: scale(.98); } 255 .btn-plum:disabled { opacity: .55; cursor: default; } 256 .btn-ghost { 257 width: 100%; padding: 13px 18px; border-radius: 13px; font-size: 15px; font-weight: 600; 258 color: var(--text); background: var(--surface-2); border: 1px solid var(--border); 259 transition: transform .12s ease, background .12s ease; 260 } 261 .btn-ghost:active { transform: scale(.98); } 262 .text-link { background: none; color: var(--muted); font-size: 14px; text-decoration: underline; padding: 8px; } 263 264 /* ===== Layout shell ===== */ 265 .screen { display: none; } 266 .screen.active { display: block; animation: fade .15s ease; } 267 @keyframes fade { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } } 268 269 #boot-loader { 270 position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; 271 background: var(--bg); z-index: 200; 272 } 273 274 /* ===== LANDING / LOGIN ===== */ 275 #login-screen { min-height: 100vh; } 276 .landing-hero { 277 position: relative; overflow: hidden; 278 background: 279 radial-gradient(900px 420px at 80% -10%, rgba(231,199,123,.45), transparent 60%), 280 radial-gradient(820px 460px at 0% 110%, rgba(199,82,115,.28), transparent 55%), 281 linear-gradient(160deg, #2A2219, #1E1913); 282 color: #fff; 283 padding: calc(env(safe-area-inset-top) + 28px) 22px 64px; 284 } 285 .landing-nav { max-width: 1100px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; } 286 .landing-nav .wordmark { font-size: 30px; -webkit-text-fill-color: transparent; } 287 .landing-inner { max-width: 1100px; margin: 0 auto; display: grid; grid-template-columns: 1.05fr .95fr; gap: 36px; align-items: center; padding-top: 42px; } 288 .landing-copy h1 { font-size: 52px; line-height: 1.04; margin: 0 0 16px; color: #fff; } 289 .landing-copy h1 .accent { background: linear-gradient(120deg, var(--gold2), #fff); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; } 290 .landing-copy p.lede { font-size: 18px; opacity: .92; max-width: 480px; margin: 0 0 26px; } 291 .landing-feats { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 26px; } 292 .landing-feat { display: flex; align-items: center; gap: 9px; font-size: 14px; opacity: .95; } 293 .landing-feat .e { font-size: 20px; } 294 295 .auth-card { 296 background: var(--card); color: var(--text); border-radius: 22px; padding: 28px 24px; 297 box-shadow: 0 24px 60px rgba(30,25,19,.28); max-width: 380px; width: 100%; margin-left: auto; 298 } 299 .auth-card .hero-emoji { font-size: 40px; text-align: center; } 300 .auth-card h2 { font-size: 24px; text-align: center; margin: 6px 0 2px; } 301 .auth-card .sub { text-align: center; color: var(--muted); font-size: 14px; margin-bottom: 20px; } 302 .login-stack > * + * { margin-top: 11px; } 303 .divider { display: flex; align-items: center; gap: 12px; color: var(--muted); font-size: 13px; margin: 16px 0; } 304 .divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--border); } 305 .provider-icon { width: 18px; height: 18px; vertical-align: -3px; margin-right: 8px; } 306 .email-row { display: flex; flex-direction: column; gap: 10px; } 307 .login-links { display: flex; justify-content: space-between; gap: 12px; margin-top: 2px; } 308 .login-links .link-btn { background: none; width: auto; padding: 4px 2px; color: var(--muted); font-size: 13px; font-weight: 600; } 309 .login-links .link-btn:hover { color: var(--plum); text-decoration: underline; } 310 .landing-foot { max-width: 1100px; margin: 30px auto 0; opacity: .85; font-size: 13px; } 311 312 /* ===== ONBOARD ===== */ 313 #onboard-screen { min-height: 100vh; display: flex; flex-direction: column; justify-content: center; padding: 28px 22px; } 314 .ob-card { max-width: 420px; width: 100%; margin: 0 auto; background: var(--card); border: 1px solid var(--border); border-radius: 22px; padding: 24px; box-shadow: var(--shadow); } 315 .field-label { font-size: 13px; color: var(--muted); margin: 16px 0 6px; display: block; font-weight: 600; } 316 .emoji-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; margin-top: 4px; } 317 .emoji-btn { aspect-ratio: 1; font-size: 22px; border-radius: 12px; background: var(--bg); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; transition: transform .1s ease; } 318 .emoji-btn.selected { border-color: var(--gold); background: rgba(201,162,74,.16); } 319 .emoji-btn:active { transform: scale(.92); } 320 321 /* ===== APP CHROME ===== */ 322 #main-app { display: none; } 323 #main-app.active { display: block; } 324 325 .topbar { 326 position: sticky; top: 0; z-index: 30; 327 background: var(--topbar-bg); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); 328 border-bottom: 1px solid var(--border2); 329 padding: calc(env(safe-area-inset-top) + 10px) 22px 10px; 330 } 331 .topbar-inner { max-width: 1180px; margin: 0 auto; display: flex; align-items: center; gap: 18px; } 332 .topbar .wordmark { font-size: 26px; } 333 .brand-home { display: flex; align-items: center; gap: 9px; flex-shrink: 0; cursor: pointer; } 334 .brand-logo { height: 30px; width: auto; display: block; flex-shrink: 0; } 335 .topsearch { flex: 1; display: flex; align-items: center; gap: 8px; background: var(--card); border: 1px solid var(--border); border-radius: 999px; padding: 4px 4px 4px 16px; box-shadow: var(--shadow); max-width: 560px; } 336 .topsearch input { border: none; background: none; padding: 9px 0; box-shadow: none; } 337 .topsearch input:focus { box-shadow: none; } 338 .topsearch .go { flex-shrink: 0; width: 40px; height: 40px; border-radius: 999px; background: linear-gradient(120deg, var(--gold2), var(--gold)); color: var(--ink); font-size: 17px; display: flex; align-items: center; justify-content: center; } 339 .topnav { display: flex; align-items: center; gap: 6px; flex-shrink: 0; } 340 .topnav .navlink { padding: 9px 14px; border-radius: 999px; font-size: 14px; font-weight: 600; color: var(--muted); position: relative; transition: background .12s ease, color .12s ease; } 341 .topnav .navlink:hover { background: var(--surface-2); color: var(--text); } 342 .topnav .navlink-plus { color: #C8A84B !important; font-weight: 800; text-decoration: none; display: inline-block; } 343 .topnav .navlink-plus:hover { background: rgba(200,168,75,.12); } 344 345 /* ---- Giftella+ band (signed-in Discover) â obsidian, member-aware ---- */ 346 .plusband { position: relative; overflow: hidden; background: #0E0A17; color: #FDF7EE; border-radius: 22px; padding: 30px 26px; margin: 26px 0 8px; text-align: center; border: 1px solid rgba(200,168,75,.28); } 347 .plusband::before { content: ""; position: absolute; width: 380px; height: 280px; border-radius: 50%; background: #3D235D; filter: blur(120px); opacity: .6; top: -120px; right: -110px; } 348 .plusband::after { content: ""; position: absolute; width: 300px; height: 220px; border-radius: 50%; background: rgba(200,168,75,.16); filter: blur(110px); bottom: -110px; left: -90px; } 349 .plusband > * { position: relative; z-index: 1; } 350 .pb-crest { display: inline-flex; align-items: center; gap: 8px; font-size: 10px; font-weight: 800; letter-spacing: 3px; color: #C8A84B; border: 1px solid rgba(200,168,75,.45); border-radius: 999px; padding: 6px 15px; margin-bottom: 14px; } 351 .pb-title { font-family: Georgia, serif; font-weight: 400; font-size: 24px; margin: 0 0 6px; color: #FDF7EE; } 352 .pb-lead { font-size: 13.5px; color: rgba(253,247,238,.62); margin: 0 auto 18px; max-width: 460px; } 353 .pb-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; text-align: left; } 354 .pb-card { background: rgba(255,255,255,.045); border: 1px solid rgba(255,255,255,.09); border-radius: 15px; padding: 13px 14px; transition: border-color .15s ease; } 355 .pb-card:hover { border-color: rgba(200,168,75,.4); } 356 .pb-card .m { display: block; font-size: 18px; margin-bottom: 7px; } 357 .pb-card b { display: block; font-size: 13px; color: #FDF7EE; margin-bottom: 3px; } 358 .pb-card i { display: block; font-style: normal; font-size: 11.5px; line-height: 1.45; color: rgba(253,247,238,.58); } 359 .pb-cta { display: inline-block; margin-top: 18px; background: linear-gradient(120deg,#DBC06A,#C8A84B); color: #14101F; font-weight: 800; font-size: 14px; padding: 12px 28px; border-radius: 999px; text-decoration: none; box-shadow: 0 8px 26px rgba(200,168,75,.35); } 360 .pb-note { margin-top: 8px; font-size: 11.5px; color: rgba(253,247,238,.5); } 361 .pb-memberchip { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 800; letter-spacing: .6px; color: #9DC08B; border: 1px solid rgba(157,192,139,.4); border-radius: 999px; padding: 8px 18px; margin: 4px 0 10px; } 362 .topnav .navlink.active { color: var(--plum); background: rgba(201,162,74,.10); } 363 .topnav .navlink .nbadge { position: absolute; top: 2px; right: 4px; background: var(--rose); color: #fff; font-size: 10px; font-weight: 700; border-radius: 999px; min-width: 16px; height: 16px; padding: 0 4px; display: none; align-items: center; justify-content: center; } 364 .theme-toggle { width: 38px; height: 38px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 16px; background: var(--surface-2); border: 1px solid var(--border); color: var(--text); flex-shrink: 0; transition: background .12s ease; } 365 .theme-toggle:hover { background: var(--card); } 366 367 .avatar { width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 30px; background: radial-gradient(circle at 30% 25%, rgba(201,162,74,.22), var(--surface-2)); border: 1px solid var(--border); overflow: hidden; flex-shrink: 0; } 368 .avatar img { width: 100%; height: 100%; object-fit: cover; } 369 .avatar.sm { width: 44px; height: 44px; font-size: 22px; } 370 .avatar.xs { width: 34px; height: 34px; font-size: 17px; } 371 .avatar-btn { width: 40px; height: 40px; font-size: 19px; cursor: pointer; flex-shrink: 0; transition: transform .1s ease; } 372 .avatar-btn:active { transform: scale(.93); } 373 374 /* avatar menu */ 375 .avmenu { position: absolute; top: 100%; right: 0; margin-top: 8px; background: var(--card); border: 1px solid var(--border); border-radius: 14px; box-shadow: var(--shadow-lift); min-width: 180px; padding: 6px; display: none; z-index: 40; } 376 .avmenu.open { display: block; animation: fade .15s ease; } 377 .avmenu .mi { display: flex; align-items: center; gap: 10px; padding: 11px 12px; border-radius: 10px; font-size: 14px; font-weight: 600; width: 100%; text-align: left; } 378 .avmenu .mi:hover { background: var(--surface-2); } 379 .avmenu .mhead { padding: 10px 12px 8px; } 380 .avmenu .mhead .nm { font-weight: 700; } 381 .avmenu .mhead .hd { font-size: 12px; color: var(--muted); } 382 .avmenu-wrap { position: relative; } 383 384 .content { max-width: 1180px; margin: 0 auto; padding: 26px 22px calc(40px + env(safe-area-inset-bottom)); } 385 .page-title { font-size: 30px; margin: 2px 0 4px; } 386 .page-sub { color: var(--muted); margin: 0 0 22px; font-size: 15px; } 387 .section-title { font-size: 12px; color: var(--muted); margin: 30px 0 12px; font-weight: 700; letter-spacing: .7px; text-transform: uppercase; display: flex; align-items: center; } 388 .section-title .badge { margin-left: auto; background: var(--rose); color: #fff; font-size: 11px; font-weight: 700; border-radius: 10px; padding: 2px 8px; letter-spacing: 0; } 389 390 /* ===== Discover hero panel ===== */ 391 .find-panel { 392 background: 393 radial-gradient(600px 300px at 100% 0%, rgba(231,199,123,.20), transparent 60%), 394 var(--panel-bg); 395 border: 1px solid var(--border); border-radius: 24px; padding: 26px; box-shadow: var(--shadow); 396 } 397 .find-panel h2 { font-size: 26px; margin: 0 0 4px; } 398 .find-panel .ph-sub { color: var(--muted); margin: 0 0 20px; font-size: 15px; } 399 .field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; } 400 .field-grid .full { grid-column: 1 / -1; } 401 .lbl { font-size: 12px; color: var(--muted); font-weight: 700; letter-spacing: .4px; text-transform: uppercase; display: block; margin-bottom: 6px; } 402 .chipset { display: flex; flex-wrap: wrap; gap: 8px; } 403 .selchip { background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; padding: 8px 14px; font-size: 14px; font-weight: 600; color: var(--text); transition: all .12s ease; } 404 .selchip:hover { border-color: var(--plum2); } 405 .selchip.on { background: rgba(201,162,74,.12); border-color: var(--plum2); color: var(--plum); } 406 407 /* vibe chips */ 408 .vibes { display: flex; gap: 10px; overflow-x: auto; padding: 4px 2px 8px; scrollbar-width: none; } 409 .vibes::-webkit-scrollbar { display: none; } 410 .vibe { flex-shrink: 0; display: flex; align-items: center; gap: 8px; padding: 10px 16px; border-radius: 999px; background: var(--card); border: 1px solid var(--border); font-size: 14px; font-weight: 600; box-shadow: var(--shadow); transition: transform .12s ease, border-color .12s ease; } 411 .vibe:hover { transform: translateY(-2px); border-color: var(--gold); } 412 .vibe .ve { font-size: 18px; } 413 414 /* ===== Product / gift cards ===== */ 415 .gift-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; } 416 .gift-card { 417 background: var(--card); border: 1px solid var(--border); border-radius: 18px; overflow: hidden; 418 display: flex; flex-direction: column; box-shadow: var(--shadow); 419 transition: transform .16s ease, box-shadow .16s ease; 420
420} 421 .gift-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); } 422 .gift-head { height: 168px; display: flex; align-items: center; justify-content: center; font-size: 54px; position: relative; overflow: hidden; } 423 .gift-body { padding: 14px 15px 16px; display: flex; flex-direction: column; flex: 1; } 424 .gift-name { font-weight: 700; font-size: 15.5px; line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } 425 .gift-why { color: var(--muted); font-size: 13px; margin-top: 6px; flex: 1; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } 426 427 /* Compact cards â used inside the friend profile modal so they aren't huge. */ 428 .gift-grid.compact { grid-template-columns: repeat(2, 1fr); gap: 12px; } 429 .gift-grid.compact .gift-head { height: 116px; font-size: 40px; } 430 .gift-grid.compact .gift-body { padding: 11px 12px 12px; } 431 .gift-grid.compact .gift-name { font-size: 13.5px; } 432 .gift-grid.compact .gift-why { font-size: 12px; margin-top: 4px; -webkit-line-clamp: 2; } 433 .gift-grid.compact .price-chip { font-size: 11px; padding: 3px 9px; margin-top: 7px; } 434 .gift-grid.compact .gift-actions { gap: 7px; margin-top: 8px; } 435 .gift-grid.compact .shop { padding: 8px 10px; font-size: 12.5px; } 436 .gift-grid.compact .save { width: 34px; height: 34px; } 437 .gift-grid.compact .gift-amazon { font-size: 11.5px; margin-top: 7px; } 438 .price-chip { align-self: flex-start; margin-top: 12px; background: rgba(201,162,74,.14); color: var(--gold); border: 1px solid rgba(201,162,74,.3); font-weight: 700; font-size: 12.5px; border-radius: 999px; padding: 4px 11px; } 439 .gift-actions { display: flex; gap: 8px; margin-top: 14px; } 440 .gift-actions .shop { flex: 1; padding: 10px; border-radius: 11px; background: linear-gradient(120deg, var(--gold2), var(--gold)); color: var(--ink); font-weight: 700; font-size: 13.5px; text-align: center; text-decoration: none; transition: transform .1s ease; } 441 .gift-actions .shop:active { transform: scale(.97); } 442 .gift-actions .save { width: 46px; flex-shrink: 0; border-radius: 11px; background: var(--surface-2); border: 1px solid var(--border); font-size: 17px; transition: all .12s ease; } 443 .gift-actions .save:hover { border-color: var(--rose); color: var(--rose); } 444 .gift-actions .save.saved { background: rgba(199,82,115,.12); border-color: var(--rose); color: var(--rose); } 445 .gift-amazon { display: inline-block; margin-top: 9px; color: var(--muted); font-size: 12.5px; font-weight: 600; text-decoration: none; align-self: flex-start; } 446 .gift-amazon:hover { color: var(--plum); text-decoration: underline; } 447 .gift-links { display: flex; gap: 12px; flex-wrap: wrap; } 448 .gift-links .gift-amazon { margin-top: 9px; } 449 .gen-more-wrap { display: flex; justify-content: center; margin: 22px 0 4px; } 450 .gen-more-wrap .btn-ghost { width: auto; padding: 11px 22px; } 451 452 /* note segments + sent rows */ 453 .seg { display: inline-flex; background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; padding: 3px; margin-bottom: 14px; } 454 .seg button { padding: 7px 18px; border-radius: 999px; font-size: 14px; font-weight: 700; color: var(--muted); } 455 .seg button.on { background: var(--card); color: var(--plum); box-shadow: var(--shadow); } 456 .status-chip { font-size: 11.5px; font-weight: 700; border-radius: 999px; padding: 3px 10px; flex-shrink: 0; } 457 .status-chip.opened { background: rgba(62,142,110,.14); color: var(--sage); border: 1px solid rgba(62,142,110,.3); } 458 .status-chip.delivered { background: var(--surface-2); color: var(--muted); border: 1px solid var(--border); } 459 .fm-get { color: var(--gold); font-size: 13px; text-decoration: none; font-weight: 700; flex-shrink: 0; padding: 6px 10px; border-radius: 9px; background: rgba(201,162,74,.12); border: 1px solid rgba(201,162,74,.3); } 460 .fm-get:hover { background: rgba(201,162,74,.2); } 461 462 /* skeletons */ 463 .sk-card { background: var(--card); border: 1px solid var(--border); border-radius: 18px; overflow: hidden; box-shadow: var(--shadow); } 464 .sk-head { height: 168px; } 465 .sk-line { height: 12px; border-radius: 6px; margin: 12px 15px 0; } 466 .shimmer { background: linear-gradient(100deg, var(--surface-2) 30%, rgba(30,25,19,.10) 50%, var(--surface-2) 70%); background-size: 200% 100%; animation: shimmer 1.2s infinite; } 467 @keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } } 468 469 /* ===== Circle (stories + friends) ===== */ 470 .stories { display: flex; gap: 16px; overflow-x: auto; padding: 6px 2px 12px; scrollbar-width: none; } 471 .stories::-webkit-scrollbar { display: none; } 472 .story { flex-shrink: 0; width: 78px; text-align: center; cursor: pointer; } 473 .story-ring { width: 70px; height: 70px; border-radius: 50%; margin: 0 auto 7px; padding: 3px; background: linear-gradient(135deg, var(--gold2), var(--gold)); display: flex; align-items: center; justify-content: center; } 474 .story-ring .inner { width: 100%; height: 100%; border-radius: 50%; overflow: hidden; background: var(--card); display: flex; align-items: center; justify-content: center; font-size: 30px; } 475 .story-ring .inner img { width: 100%; height: 100%; object-fit: cover; } 476 .story-ring.soon { background: linear-gradient(135deg, var(--rose), var(--gold)); } 477 .story-bday { font-size: 10.5px; font-weight: 700; color: var(--rose);
477 margin-top: 1px; } 478 .bday-chip { display: inline-block; margin-top: 5px; font-size: 11px; font-weight: 700; color: #fff; background: linear-gradient(120deg, var(--rose), var(--gold)); padding: 2px 9px; border-radius: 999px; } 479 .fm-actions { display: flex; flex-direction: column; gap: 6px; align-items: stretch; flex-shrink: 0; } 480 .fm-claim { font-size: 12.5px; font-weight: 700; color: var(--ink); background: linear-gradient(120deg, var(--gold2), var(--gold)); border-radius: 10px; padding: 7px 12px; white-space: nowrap; } 481 .fm-group { font-size: 12.5px; font-weight: 700; color: #fff; background: linear-gradient(120deg, var(--plum), var(--plum-deep, #7A5C16)); border: none; border-radius: 10px; padding: 7px 12px; white-space: nowrap; cursor: pointer; } 482 .fm-group:hover { filter: brightness(1.06); } 483 .fm-group:disabled { opacity: .6; cursor: default; } 484 .fm-claimed { font-size: 11.5px; font-weight: 700; color: var(--sage); white-space: nowrap; text-align: center; } 485 .claimed-tag { font-size: 10px; font-weight: 700; color: var(--sage); background: rgba(97,166,140,.16); padding: 1px 7px; border-radius: 999px; vertical-align: middle; } 486 .story-name { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } 487 488 .grid-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; } 489 .friend-card { display: flex; align-items: center; gap: 13px; background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 14px; box-shadow: var(--shadow); cursor: pointer; transition: transform .12s ease, box-shadow .12s ease; } 490 .friend-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); } 491 .fc-body { flex: 1; min-width: 0; } 492 .fc-name { font-weight: 700; } 493 .fc-handle { font-size: 13px; color: var(--muted); } 494 .chevron { color: var(--muted); font-size: 20px; } 495 496 /* list items (wishlist) */ 497 .list { display: flex; flex-direction: column; gap: 10px; } 498 .item-card { display: flex; align-items: center; gap: 13px; background: var(--card); border: 1px solid var(--border); border-radius: 15px; padding: 14px; box-shadow: var(--shadow); } 499 .item-emoji { font-size: 24px; flex-shrink: 0; } 500 .item-body { flex: 1; min-width: 0; } 501 .item-title { font-weight: 600; word-break: break-word; } 502 .item-sub { font-size: 13px; color: var(--muted); margin-top: 2px; } 503 .cat-chip { display: none; margin-left: 8px; font-size: 10.5px; font-weight: 700; color: var(--plum); background: rgba(201,162,74,.12); border: 1px solid rgba(201,162,74,.22); padding: 1px 8px; border-radius: 999px; vertical-align: middle; } 504 .open-link { color: var(--gold); font-size: 13px; text-decoration: none; font-weight: 700; } 505 .remove-x { color: var(--muted); font-size: 18px; padding: 6px 9px; line-height: 1; flex-shrink: 0; border-radius: 9px; } 506 .remove-x:hover { background: var(--surface-2); color: var(--danger); } 507 508 .empty { text-align: center; color: var(--muted); padding: 44px 20px; font-size: 15px; } 509 .inline-err { color: var(--danger); font-size: 14px; text-align: center; padding: 20px; } 510 .inline-err button { color: var(--plum); text-decoration: underline; font-weight: 700; margin-left: 6px; } 511 512 .add-row { display: flex; flex-direction: column; gap: 9px; margin-bottom: 22px; max-width: 640px; } 513 .add-row .two { display: flex; gap: 9px; } 514 .add-row .two input { flex: 1; } 515 516 /* notes */ 517 .notes-cols { display: grid; grid-template-columns: 1.4fr 1fr; gap: 24px; align-items: start; } 518 .note-card { background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 16px; box-shadow: var(--shadow); cursor: pointer; transition: transform .12s ease; } 519 .note-card:hover { transform: translateY(-2px); } 520 .note-head { display: flex; align-items: center; gap: 10px; } 521 .note-from { font-weight: 700; flex: 1; } 522 .unread-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--rose); flex-shrink: 0; } 523 .note-msg { margin-top: 10px; white-space: pre-wrap; word-break: break-word; } 524 .note-msg.collapsed { color: var(--muted); } 525 .composer { background: var(--card); border: 1px solid var(--border); border-radius: 18px; padding: 18px; box-shadow: var(--shadow); position: sticky; top: 92px; } 526 527 /* profile */ 528 .profile-head { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px; padding: 14px 0 6px; } 529 .profile-head .avatar { width: 92px; height: 92px; font-size: 44px; } 530 .profile-name { font-family: Georgia, serif; font-size: 26px; } 531 .profile-handle { color: var(--muted); font-size: 14px; } 532 .profile-bio { font-size: 14px; margin-top: 4px; max-width: 420px; } 533 .chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; } 534 .chip { background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; padding: 8px 15px; font-size: 14px; } 535 .ios-promo { margin-top: 26px; text-align: center; font-size: 14px; } 536 .ios-promo a { color: var(--gold); text-decoration: none; font-weight: 700; } 537 538 /* spinner */ 539 .spinner { width: 22px; height: 22px; border-radius: 50%; border: 2.5px solid var(--surface-2); border-top-color: var(--gold); animation: spin .8s linear infinite; display: inline-block; } 540 @keyframes spin { to { transform: rotate(360deg); } } 541 .loading-wrap { display: flex; justify-content: center; padding: 36px 0; } 542 .btn-spin { width: 16px; height: 16px; border-width: 2px; vertical-align: -3px; margin-right: 8px; border-top-color: #fff; } 543 544 .msg { font-size: 14px; margin-top: 10px; min-height: 18px; } 545 .msg.err { color: var(--danger); } 546 .msg.ok { color: var(--sage); } 547 548 /* ===== Modal / sheet ===== */ 549 .modal-backdrop { position: fixed; inset: 0; z-index: 80; background: rgba(30,25,19,.45); backdrop-filter: blur(4px); display: none; align-items: center; justify-content: center; padding: 20px; } 550 .modal-backdrop.open { display: flex; animation: fade .2s ease; } 551 .modal { width: 100%; max-width: 520px; background: var(--bg); border: 1px solid var(--border); border-radius: 22px; padding: 22px; max-height: 86vh; overflow-y: auto; box-shadow: var(--shadow-lift); animation: pop .22s ease; } 552 .modal.friend-sheet { max-width: 640px; } 553 @media (max-width: 640px) {
554 .modal-backdrop { padding: 0; align-items: stretch; } 555 .modal.friend-sheet { max-width: none; max-height: 100vh; min-height: 100vh; border-radius: 0; border: none; } 556 } 557 @keyframes pop { from { transform: scale(.96); opacity: 0; } to { transform: none; opacity: 1; } } 558 .modal-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } 559 .modal-head .close { margin-left: auto; font-size: 22px; color: var(--muted); padding: 4px 8px; } 560 561 /* toast */ 562 .toast { position: fixed; left: 50%; bottom: calc(96px + env(safe-area-inset-bottom)); transform: translateX(-50%) translateY(20px); background: var(--text); color: #fff; padding: 12px 20px; border-radius: 999px; font-size: 14px; font-weight: 600; box-shadow: var(--shadow-lift); opacity: 0; pointer-events: none; transition: opacity .25s ease, transform .25s ease; z-index: 120; } 563 .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } 564 565 /* ===== Bottom tab bar (mobile) ===== */ 566 .tabbar { display: none; } 567 568 /* ===== RESPONSIVE ===== */ 569 @media (max-width: 899px) { 570 .topnav, .topsearch { display: none; } 571 .topbar-inner { justify-content: space-between; } 572 .brand-logo { height: 24px; } 573 .topbar .wordmark { font-size: 22px; } 574 .content { padding: 20px 16px calc(96px + env(safe-area-inset-bottom)); } 575 .page-title { font-size: 26px; } 576 .gift-grid { grid-template-columns: 1fr 1fr; gap: 13px; } 577 .gift-head { height: 150px; font-size: 46px; } 578 .field-grid { grid-template-columns: 1fr; } 579 .notes-cols { grid-template-columns: 1fr; } 580 .composer { position: static; } 581 .landing-inner { grid-template-columns: 1fr; gap: 28px; padding-top: 28px; } 582 .landing-copy h1 { font-size: 38px; } 583 .auth-card { margin: 0 auto; } 584 585 .tabbar { 586 display: grid; position: fixed; left: 0; right: 0; bottom: 0; z-index: 60; 587 grid-template-columns: repeat(4, 1fr); 588 background: var(--topbar-bg); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); 589 border-top: 1px solid var(--border); 590 padding-bottom: env(safe-area-inset-bottom); 591 box-shadow: 0 -4px 18px rgba(30,25,19,.06); 592 } 593 .tab { display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 10px 0 8px; font-size: 11px; font-weight: 600; color: var(--muted); position: relative; } 594 .tab .ico { font-size: 21px; line-height: 1; } 595 .tab.active { color: var(--plum); } 596 .tab .tbadge { position: absolute; top: 4px; right: 50%; margin-right: -22px; background: var(--rose); color: #fff; font-size: 9px; font-weight: 700; border-radius: 999px; min-width: 15px; height: 15px; padding: 0 4px; display: none; align-items: center; justify-content: center; } 597 } 598 @media (min-width: 900px) and (max-width: 1100px) { 599 .gift-grid { grid-template-columns: repeat(3, 1fr); } 600 } 601 /* Extra-narrow phones (iPhone 15 / SE and smaller, ~360â393px): 602 tighten gutters and make sure horizontal rows fit / scroll inside 603 the viewport instead of spilling past the edge. */ 604 @media (max-width: 430px) { 605 .content { padding-left: 14px; padding-right: 14px; } 606 .gift-grid { gap: 11px; } 607 .vibes, .stories, .rail-scroll, .chipset, 608 #page-ecosystem .eco-pills, #page-ecosystem .eco-grid, #page-ecosystem .eco-feat { max-width: 100%; } 609 } 610 611 /* ============================================================ */ 612 /* ===== LANDING / HOMEPAGE (logged-out) ==================== */ 613 /* ============================================================ */ 614 html { scroll-behavior: smooth; } 615 #login-screen { min-height: 100vh; display: block; } 616 .lp { color: var(--text); } 617 .lp-wrap { max-width: 1160px; margin: 0 auto; padding: 0 22px; } 618 .lp-section { padding: 84px 0; } 619 .lp-eyebrow { font-size: 12px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; color: var(--plum); margin: 0 0 12px; } 620 .lp-h2 { font-size: 40px; line-height: 1.08; margin: 0 0 14px; } 621 .lp-lead { font-size: 18px; color: var(--muted); max-width: 640px; margin: 0 auto; } 622 .lp-center { text-align: center; } 623 624 /* ----- Hero trust strip ----- */ 625 .lp-trust { display:flex; flex-wrap:wrap; gap:8px 18px; margin-top:16px; align-items:center; } 626 .lp-trust span { font-size:13px; font-weight:600; color:var(--muted); display:flex; align-items:center; gap:6px; } 627 .lp-trust .tick { color:var(--gold); font-weight:800; } 628 /* ----- Any-store marquee ----- */ 629 .store-marquee { overflow:hidden; position:relative; margin-top:26px; -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent); mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent); } 630 .store-track { display:flex; gap:10px; width:max-content; animation:storeScroll 36s linear infinite; }
631 .store-marquee:hover .store-track { animation-play-state:paused; } 632 @keyframes storeScroll { from { transform:translateX(0);} to { transform:translateX(-50%);} } 633 .store-pill { flex:0 0 auto; padding:10px 18px; border-radius:999px; background:var(--surface-2); border:1px solid rgba(201,162,74,.14); font-size:14px; font-weight:700; color:var(--text); } 634 .store-note { text-align:center; font-size:13px; color:var(--muted); margin-top:14px; } 635 /* ----- Occasions grid ----- */ 636 .occasion-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:12px; margin-top:28px; } 637 .occ-card { display:flex; flex-direction:column; align-items:center; gap:8px; padding:20px 12px 16px; border-radius:18px; background:var(--surface-2); border:1px solid rgba(201,162,74,.12); text-decoration:none; transition:transform .18s ease, border-color .18s ease; } 638 .occ-card:hover { transform:translateY(-3px); border-color:var(--plum2); } 639 .occ-card .oe { font-size:30px; } 640 .occ-card .ot { font-size:13.5px; font-weight:700; color:var(--text); text-align:center; } 641 .occ-card .oc { font-size:11px; font-weight:800; letter-spacing:1px; color:var(--gold); text-transform:uppercase; } 642 .occ-card.occ-all { background:rgba(201,162,74,.10); border-color:rgba(201,162,74,.34); } 643 .occ-card.occ-all:hover { border-color:var(--gold); } 644 .occ-group { margin-top:42px; } 645 .occ-group + .occ-group { margin-top:52px; } 646 .occ-group .occasion-grid { margin-top:16px; grid-template-columns:repeat(5,minmax(0,1fr)); } 647 @media (max-width:1000px){ .occ-group .occasion-grid { grid-template-columns:repeat(4,minmax(0,1fr)); } } 648 @media (max-width:760px){ .occ-group .occasion-grid { grid-template-columns:repeat(3,minmax(0,1fr)); } } 649 @media (max-width:520px){ .occ-group .occasion-grid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; } } 650 .occ-group-head { display:flex; flex-wrap:wrap; align-items:baseline; gap:4px 16px; padding-bottom:12px; border-bottom:1px solid var(--border); text-align:left; } 651 .occ-group-head h3 { font-family:Georgia,serif; font-weight:400; font-size:23px; margin:0; color:var(--text); } 652 .occ-group-head p { margin:0; font-size:13.5px; color:var(--muted); flex:1 1 240px; } 653 .occ-group-link { font-size:13px; font-weight:800; color:var(--plum2); text-decoration:none; white-space:nowrap; } 654 .occ-group-link:hover { text-decoration:underline; } 655 @media (max-width:560px){ 656 .occ-group-head h3 { font-size:20px; } 657 .occ-group-head p { flex:1 1 100%; } 658 } 659 /* ----- Trust bullets under steps ----- */ 660 .trust-row { display:flex; flex-wrap:wrap; gap:12px; justify-content:center; margin-top:26px; } 661 .trust-chip { display:flex; align-items:center; gap:8px; background:var(--surface-2); border:1px solid rgba(201,162,74,.12); border-radius:999px; padding:10px 16px; font-size:13px; font-weight:600; color:var(--text); } 662 /* ----- FAQ ----- */ 663 .faq-wrap { max-width:760px; margin:0 auto; } 664 .faq-item { border:1px solid rgba(201,162,74,.14); border-radius:16px; background:var(--surface-2); margin-top:12px; overflow:hidden; } 665 .faq-item summary { cursor:pointer; list-style:none; padding:16px 20px; font-size:15px; font-weight:700; color:var(--text); display:flex; justify-content:space-between; align-items:center; } 666 .faq-item summary::-webkit-details-marker { display:none; } 667 .faq-item summary::after { content:'+'; font-size:20px; color:var(--gold); font-weight:400; } 668 .faq-item[open] summary::after { content:'â'; } 669 .faq-item .fa { padding:0 20px 16px; font-size:14px; line-height:1.6; color:var(--muted); } 670 /* ----- Footer mega links ----- */ 671 .lp-footer-mega { display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:22px; margin:26px 0 18px; text-align:left; max-width:900px; } 672 .lp-footer-mega h5 { font-size:11px; letter-spacing:1.6px; font-weight:800; color:var(--gold); text-transform:uppercase; margin-bottom:10px; } 673 .lp-footer-mega a { display:block; font-size:13px; color:var(--muted); text-decoration:none; padding:3px 0; } 674 .lp-footer-mega a:hover { color:var(--text); } 675 676 /* ----- Sticky nav ----- */ 677 .lp-nav { 678 position: sticky; top: 0; z-index: 50; 679 background: var(--topbar-bg); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); 680 border-bottom: 1px solid var(--border2); 681 padding: calc(env(safe-area-inset-top) + 10px) 0 10px; 682 } 683 .lp-nav-inner { max-width: 1160px; margin: 0 auto; padding: 0 22px; display: flex; align-items: center; gap: 16px; } 684 .lp-brand { display: flex; align-items: center; gap: 9px; flex-shrink: 0; text-decoration: none; } 685 .lp-brand img { height: 30px; width: auto; display: block; } 686 .lp-brand .wordmark { font-size: 26px; } 687 .lp-nav-links { margin-left: auto; display: flex; align-items: center; gap: 4px; } 688 .lp-nav-links a.lp-link { padding: 9px 13px; border-radius: 999px; font-size: 14px; font-weight: 600; color: var(--muted); text-decoration: none; transition: background .14s ease, color .14s ease; } 689 .lp-nav-links a.lp-link:hover { background: var(--surface-2); color: var(--text); } 690 .lp-nav-cta { 691 padding: 10px 20px; border-radius: 999px; font-size: 14px; font-weight: 700; 692 color: #1E1913; background: linear-gradient(120deg, var(--gold2), var(--gold)); 693 text-decoration: none; box-shadow: 0 6px 16px rgba(201,162,74,.30); transition: transform .12s ease, box-shadow .12s ease; 694 } 695 .lp-nav-cta:hover { transform: translateY(-1px); box-shadow: 0 10px 22px rgba(201,162,74,.40); } 696 697 /* ----- Hero ----- */ 698 /* Theme-aware hero: the page now revolves around LIGHT colors in light mode 699 and DARK colors in dark mode, with gold + plum only as soft accent glows 700 (no full purple block). */ 701 .lp-hero { 702 position: relative; overflow: hidden; 703 background: 704 radial-gradient(880px 460px at 84% -10%, rgba(201,162,74,.16), transparent 60%), 705 radial-gradient(820px 520px at -6% 112%, rgba(201,162,74,.12), transparent 55%), 706 var(--bg); 707 color: var(--text); 708 border-bottom: 1px solid var(--border2); 709 } 710 :root[data-theme="dark"] .lp-hero { 711 background: 712 radial-gradient(880px 460px at 84% -10%, rgba(231,199,123,.14), transparent 60%), 713 radial-gradient(820px 520px at -6% 112%, rgba(139,92,214,.22), transparent 55%), 714 var(--bg); 715 } 716 .lp-hero-inner { 717 max-width: 1160px; margin: 0 auto; padding: 64px 22px 80px; 718 display: grid; grid-template-columns: 1.05fr .95fr; gap: 48px; align-items: center; 719 } 720 .lp-hero-copy h1 { font-size: 56px; line-height: 1.02; margin: 0 0 18px; color: var(--text); } 721 .lp-hero-copy h1 .accent { background: linear-gradient(120deg, var(--gold), var(--plum2)); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; } 722 .lp-hero-copy .lp-sub { font-size: 19px; line-height: 1.5; color: var(--muted); max-width: 500px; margin: 0 0 26px; } 723 .lp-pills { display: flex; gap: 10px; flex-wrap: wrap; margin: 0 0 30px; } 724 .lp-pill { display: inline-flex; align-items: center; gap: 7px; font-size: 13.5px; font-weight: 600; padding: 8px 14px; border-radius: 999px; background: var(--card); color: var(--text); border: 1px solid var(--border); box-shadow: var(--shadow); } 725 .lp-pill .e { font-size: 16px; } 726 .lp-hero-ctas { display: flex; gap: 12px; flex-wrap: wrap; } 727 .store-badges { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; } 728 .store-badge { display: inline-flex; align-items: center; gap: 10px; background: #000; color: #fff; border: 1px solid rgba(255,255,255,.22); border-radius: 12px; padding: 9px 16px; text-decoration: none; transition: transform .14s ease, box-shadow .14s ease; } 729 .store-badge:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(0,0,0,.35); } 730 .store-badge svg { width: 24px; height: 24px; flex-shrink: 0; } 731 .store-badge .sb-text { display: flex; flex-direction: column; line-height: 1.05; text-align: left; } 732 .store-badge .sb-text small { font-size: 10px; opacity: .85; letter-spacing: .2px; } 733 .store-badge .sb-text strong { font-size: 16px; font-weight: 700; } 734 .lp-cta-primary { 735 display: inline-flex; align-items: center; justify-content: center; gap: 8px; 736 padding: 15px 26px; border-radius: 14px; font-size: 16px; font-weight: 800; text-decoration: none; 737 color: var(--ink); background: linear-gradient(120deg, var(--gold2), var(--gold)); 738 box-shadow: 0 12px 28px rgba(201,162,74,.40); transition: transform .12s ease, box-shadow .12s ease; 739 } 740 .lp-cta-primary:hover { transform: translateY(-2px); box-shadow: 0 16px 34px rgba(201,162,74,.50); } 741 .lp-cta-secondary { 742 display: inline-flex; align-items: center; justify-content: center; gap: 8px; 743 padding: 15px 24px; border-radius: 14px; font-size: 16px; font-weight: 700; text-decoration: none; 744 color: var(--text); background: var(--card); border: 1px solid var(--border); 745 box-shadow: var(--shadow); transition: background .14s ease, transform .12s ease, box-shadow .14s ease; 746 } 747 .lp-cta-secondary:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); border-color: var(--plum2); } 748 749 /* hero right column wraps the auth card + floating phone */ 750 .lp-hero-right { position: relative; display: flex; justify-content: flex-end; } 751 .lp-hero-phone { 752 position: absolute; right: -56px; bottom: -80px; width: 210px; height: auto; 753 filter: drop-shadow(0 26px 50px rgba(0,0,0,.42)); transform: rotate(6deg); 754 pointer-events: none; z-index: 1; 755 } 756 757 /* ----- Auth card ----- */ 758 .lp-auth { 759 position: relative; z-index: 2; 760 background: var(--card); color: var(--text); border-radius: 24px; padding: 30px 26px; 761 box-shadow: 0 28px 64px rgba(30,25,19,.34); max-width: 400px; width: 100%; 762 } 763 .lp-auth .hero-emoji { font-size: 42px; text-align: center; line-height: 1; } 764 .lp-auth h2 { font-size: 25px; text-align: center; margin: 8px 0 2px; } 765 .lp-auth .sub { text-align: center; color: var(--muted); font-size: 14.5px; margin: 0 0 22px; } 766 .login-stack > * + * { margin-top: 11px; } 767 .lp-auth .btn-ghost { display: flex; align-items: center; justify-content: center; } 768 .lp-auth .reassure { text-align: center; color: var(--muted); font-size: 12px; margin-top: 16px; } 769 770 /* ----- Gift showcase grid (homepage centerpiece) ----- */ 771 .lp-showcase { background: var(--bg2); } 772 .lp-showcase-head { max-width: 760px; margin: 0 auto; } 773 .lp-showcase-head .lp-h2 { font-size: 44px; } 774 .show-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; margin-top: 48px; } 775 .show-card { 776 display: flex; flex-direction: column; align-items: stretch; gap: 12px; 777 background: var(--card); border: 1px solid var(--border); border-radius: 20px; 778 padding: 12px 12px 16px; text-decoration: none; color: var(--text); 779 box-shadow: var(--shadow); transition: transform .16s ease, box-shadow .16s ease; 780 overflow: hidden; 781 } 782 .show-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-lift); } 783 .show-tile { 784 position: relative; width: 100%; height: 140px; border-radius: 14px; overflow: hidden; 785 display: flex; align-items: center; justify-content: center; font-size: 40px; 786 } 787 .show-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; } 788 .show-emoji { position: relative; z-index: 0; } 789 .show-card .lbl-name { text-align: center; } 790 .show-card .lbl-name { font-size: 14.5px; font-weight: 700; letter-spacing: 0; text-transform: none; color: var(--text); margin: 0; display: block; } 791 .t-mom { background: linear-gradient(140deg, #f6d7e2, #f0b8cd); } 792 .t-dad { background: linear-gradient(140deg, #d9d6f0, #bcc0ec); } 793 .t-coffee { background: linear-gradient(140deg, #e8d6c2, #d2ac86); } 794 .t-tech { background: linear-gradient(140deg, #cfe4f0, #a9cbe8); } 795 .t-kids { background: linear-gradient(140deg, #fbe6c2, #f6cf8e); } 796 .t-cozy { background: linear-gradient(140deg, #dcecdf, #b9d9c2); } 797 .t-food { background: linear-gradient(140deg, #f7ddc6, #f0bd9b); } 798 .t-travel { background: linear-gradient(140deg, #cfe7e6, #a3d2cf); } 799 .t-pets { background: linear-gradient(140deg, #efe2cf, #e0c39c); } 800 .t-gamer { background: linear-gradient(140deg, #d6d2f2, #b3a9ec); } 801 .t-fitness { background: linear-gradient(140deg, #d4ecea, #9fd3cf); } 802 .t-beauty { background: linear-gradient(140deg, #f8d9e6, #efb1cf); }
803 .t-books { background: linear-gradient(140deg, #e6ddf0, #c6b6e6); } 804 .t-home { background: linear-gradient(140deg, #e4ecd9, #c2d8a8); } 805 .t-baby { background: linear-gradient(140deg, #fde7d6, #f8c9b0); } 806 .t-couple { background: linear-gradient(140deg, #f4d9d2, #ecb6ac); } 807 :root[data-theme="dark"] .show-tile { box-shadow: inset 0 -3px 8px rgba(0,0,0,.25); filter: saturate(.92) brightness(.92); } 808 .show-cta-wrap { display: flex; justify-content: center; margin-top: 44px; } 809 .show-cta { 810 display: inline-flex; align-items: center; justify-content: center; gap: 8px; 811 padding: 14px 28px; border-radius: 14px; font-size: 16px; font-weight: 800; text-decoration: none; 812 color: var(--ink); background: linear-gradient(120deg, var(--gold2), var(--gold)); 813 box-shadow: 0 12px 28px rgba(201,162,74,.34); transition: transform .12s ease, box-shadow .12s ease; 814 } 815 .show-cta:hover { transform: translateY(-2px); box-shadow: 0 16px 34px rgba(201,162,74,.46); } 816 817 /* ----- Amazon-style browse rails (occasions + genres) ----- */ 818 /* One line, always. 819 These used to wrap: eight chips then a stranded row of two on a laptop, 820 and FIVE rows on a phone â so on mobile you scrolled past a wall of 821 buttons before you saw a single gift. `width:fit-content` + `max-width` 822 centres the row while it fits and lets it scroll once it doesn't, which 823 is the behaviour at every width without a media query. */ 824 .browse-tabs { 825 display: flex; gap: 8px; flex-wrap: nowrap; justify-content: flex-start; 826 width: fit-content; max-width: 100%; margin: 26px auto 0; 827 overflow-x: auto; overscroll-behavior-x: contain; 828 -webkit-overflow-scrolling: touch; 829 scroll-snap-type: x proximity; padding-bottom: 4px; 830 scrollbar-width: none; 831 } 832 .browse-tabs::-webkit-scrollbar { display: none; } 833 /* The fade is only honest when there IS more to see, so JS adds .is-scrollable. */ 834 .browse-tabs.is-scrollable { 835 -webkit-mask-image: linear-gradient(90deg, #000 0 90%, transparent 100%); 836 mask-image: linear-gradient(90deg, #000 0 90%, transparent 100%); 837 } 838 .browse-tabs.is-scrollable.at-end { 839 -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 6% 100%); 840 mask-image: linear-gradient(90deg, transparent 0, #000 6% 100%); 841 } 842 .browse-tab { 843 flex: 0 0 auto; scroll-snap-align: start; white-space: nowrap; 844 font-size: 13px; font-weight: 700; padding: 8px 15px; border-radius: 999px; cursor: pointer; 845 background: var(--card); color: var(--text); border: 1px solid var(--border); transition: all .15s ease; 846 } 847 .browse-tab:hover { border-color: rgba(201,162,74,.55); } 848 .browse-tab.on { background: rgba(201,162,74,.14); border-color: var(--gold); color: #7A5C16; } 849 .gift-rails { margin-top: 30px; display: flex; flex-direction: column; gap: 34px; } 850 .gift-rail { scroll-margin-top: 80px; } 851 /* Every rail is built once and the chips only flip which one shows, so this 852 has to beat any later `display` rule that might land on .gift-rail. */ 853 .gift-rail[hidden] { display: none !important; } 854 .rail-head { display: flex; align-items: center; gap: 10px; margin: 0 0 14px; padding: 0 2px; } 855 .rail-head .rh-emoji { font-size: 22px; } 856 .rail-head h3 { font-size: 21px; margin: 0; color: var(--text); font-weight: 700; } 857 .rail-head .rh-sub { color: var(--muted); font-size: 13px; margin-left: 2px; } 858 .rail-head .rh-spacer { flex: 1; } 859 .rail-head .rh-all { font-size: 13px; font-weight: 700; color: var(--plum); text-decoration: none; white-space: nowrap; } 860 .rail-head .rh-all:hover { text-decoration: underline; } 861 .rail-scroll { 862 display: grid; grid-auto-flow: column; grid-auto-columns: 184px; gap: 14px; 863 overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x proximity; 864 padding: 4px 2px 14px; -webkit-overflow-scrolling: touch; scrollbar-width: thin; 865 scrollbar-color: var(--border) transparent; 866 } 867 .rail-scroll::-webkit-scrollbar { height: 8px; } 868 .rail-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; } 869 .rail-card { 870 scroll-snap-align: start; background: var(--card); border: 1px solid var(--border); 871 border-radius: 16px; overflow: hidden; text-decoration: none; color: var(--text); 872 display: flex; flex-direction: column; box-shadow: var(--shadow); 873 transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease; 874 } 875 .rail-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); border-color: rgba(201,162,74,.55); } 876 .rail-tile { position: relative; width: 100%; aspect-ratio: 1 / 1; overflow: hidden; display: flex; align-items: center; justify-content: center; 877 background: linear-gradient(150deg, var(--bg2), var(--surface-2)); } 878 /* glossy product-photo feel: top-left highlight + soft corner orb */ 879 .rail-tile::before { content: ""; position: absolute; inset: 0; z-index: 1; 880 background: radial-gradient(150px 130px at 28% 18%, rgba(255,255,255,.55), transparent 62%); mix-blend-mode: soft-light; pointer-events: none; } 881 .rail-tile::after { content: ""; position: absolute; right: -22px; bottom: -28px; width: 104px; height: 104px; border-radius: 50%; 882 background: rgba(255,255,255,.18); z-index: 1; pointer-events: none; } 883 .rail-emoji { position: relative; z-index: 2; font-size: 62px; line-height: 1; 884 filter: drop-shadow(0 7px 12px rgba(30,25,19,.24)); transition: transform .25s ease; } 885 .rail-card:hover .rail-emoji { transform: scale(1.07) rotate(-2deg); } 886 /* real product photo layer â fades in over the gradient/emoji once resolved */ 887 .rail-photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; 888 z-index: 3; opacity: 0; transition: opacity .45s ease; } 889 /* The white stage goes on with the picture, never before it. As a base rule 890 it WAS the flash: a tile whose bytes had not landed yet painted a white 891 rectangle over the emoji and the gradient. */ 892 .rail-photo.loaded { opacity: 1; background: #fff; } 893 /* A photo the browser already holds has nothing to fade FROM. */ 894 .rail-photo.instant { transition: none; } 895 /* A tile we already have the ADDRESS for is opaque from its very first 896 paint â no class has to be added by script for the picture to be visible. 897 An <img>
897 with no bytes yet draws nothing, so the emoji underneath still 898 covers the gap; the difference is that when the bytes are already warm 899 (which, after `warmRailBytes`, they are) the photo is simply there. */ 900 .rail-photo.ready { opacity: 1; transition: none; } 901 .rail-card:hover .rail-photo.loaded { transform: scale(1.05); transition: opacity .45s ease, transform .3s ease; } 902 /* per-category gift-tile gradients â always present, no external images */ 903 .tile-birthday { background: linear-gradient(150deg, #FFE3EE, #FFC4D7 58%, #FFD79A); } 904 .tile-anniversary { background: linear-gradient(150deg, #F6DCE7, #E7B6CB 55%, #D9C2EC); } 905 .tile-forher { background: linear-gradient(150deg, #FDE2EE, #F6B8D2); } 906 .tile-forhim { background: linear-gradient(150deg, #DCE4EE, #AEC2DB); } 907 .tile-graduation { background: linear-gradient(150deg, #DAE8F6, #AEC9EC); } 908 .tile-wedding { background: linear-gradient(150deg, #FBF2DE, #ECD8AA); } 909 .tile-coffee { background: linear-gradient(150deg, #ECD9C5, #C9A984); } 910 .tile-selfcare { background: linear-gradient(150deg, #DEEFE0, #B7DBC4); } 911 .tile-gamer { background: linear-gradient(150deg, #E0DBF5, #B5A9EC); } 912 .tile-kids { background: linear-gradient(150deg, #FEEECB, #F8CE8B); } 913 .tile-housewarming{ background: linear-gradient(150deg, #E7F0DB, #C4D9A6); } 914 .tile-fitness { background: linear-gradient(150deg, #D8EFEC, #A1D5D0); } 915 :root[data-theme="dark"] .rail-tile { filter: brightness(.8) saturate(.95); } 916 :root[data-theme="dark"] .rail-tile::before { background: radial-gradient(150px 130px at 28% 18%, rgba(255,255,255,.22), transparent 62%); } 917 918 /* sign-in card attention pulse when a gift is clicked while logged out */ 919 .lp-auth.pulse { animation: authpulse 1.2s ease; } 920 @keyframes authpulse { 921 0% { box-shadow: 0 28px 64px rgba(30,25,19,.34); } 922 28% { box-shadow: 0 0 0 4px rgba(201,162,74,.6), 0 28px 64px rgba(30,25,19,.4); } 923 60% { box-shadow: 0 0 0 4px rgba(201,162,74,.45), 0 28px 64px rgba(30,25,19,.36); } 924 100% { box-shadow: 0 28px 64px rgba(30,25,19,.34); } 925 } 926 .rail-body { padding: 10px 11px 12px; display: flex; flex-direction: column; gap: 3px; flex: 1; } 927 .rail-name { font-size: 13px; font-weight: 600; line-height: 1.3; color: var(--text); 928 display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } 929 .rail-meta { display: flex; align-items: center; justify-content: space-between; margin-top: auto; padding-top: 6px; } 930 .rail-price { font-size: 13px; font-weight: 800; color: var(--gold); } 931 :root[data-theme="dark"] .rail-price { color: var(--gold2); } 932 .rail-shop { font-size: 11px; font-weight: 800; color: var(--plum); display: inline-flex; align-items: center; gap: 3px; } 933 @media (max-width: 600px) { 934 .rail-scroll { grid-auto-columns: 156px; } 935 .rail-head h3 { font-size: 18px; } 936 .rail-head .rh-sub { display: none; } 937 } 938 939 /* ----- How it works ----- */ 940 .steps-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; margin-top: 44px; } 941 .step-card { 942 background: var(--card); border: 1px solid var(--border); border-radius: 22px; padding: 30px 26px; 943 box-shadow: var(--shadow); transition: transform .16s ease, box-shadow .16s ease; 944 } 945 .step-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); } 946 .step-num { 947 font-family: Georgia, serif; font-size: 46px; font-weight: 700; line-height: 1; 948 background: linear-gradient(120deg, var(--gold2), var(--gold)); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; 949 margin-bottom: 14px; 950 } 951 .step-card h3 { font-size: 20px; margin: 0 0 8px; } 952 .step-card p { color: var(--muted); font-size: 15px; margin: 0; line-height: 1.5; } 953 954 /* ----- Final CTA band ----- */ 955 .lp-cta-band { 956 position: relative; overflow: hidden; 957 background: 958 radial-gradient(700px 340px at 90% 0%, rgba(201,162,74,.16), transparent 60%), 959 radial-gradient(640px 320px at 4% 100%, rgba(201,162,74,.14), transparent 55%), 960 var(--bg2); 961 color: var(--text); text-align: center; border-radius: 30px; padding: 64px 30px; margin: 30px 0; 962 border: 1px solid var(--border); 963 } 964 :root[data-theme="dark"] .lp-cta-band { 965 background: 966 radial-gradient(700px 340px at 90% 0%, rgba(231,199,123,.14), transparent 60%), 967 radial-gradient(640px 320px at 4% 100%, rgba(139,92,214,.22), transparent 55%), 968 var(--bg2); 969 } 970 .lp-cta-band h2 { font-size: 38px; color: var(--text); margin: 0 0 12px; } 971 .lp-cta-band p { font-size: 17px; color: var(--muted); margin: 0 0 26px; } 972 .lp-cta-band .row { display: flex; gap: 14px; justify-content: center; align-items: center; flex-wrap: wrap; } 973 .lp-cta-band .ios-link { color: var(--plum2); text-decoration: underline; font-weight: 700; font-size: 15px; } 974 975 /* ----- Footer ----- */ 976 .lp-footer { border-top: 1px solid var(--border2); padding: 40px 0 calc(40px + env(safe-area-inset-bottom)); } 977 .lp-footer-inner { max-width: 1160px; margin: 0 auto; padding: 0 22px; display: flex; align-items: center; gap: 18px; flex-wrap: wrap; } 978 .lp-footer .wordmark { font-size: 24px; } 979 .lp-footer-links { margin-left: auto; display: flex; gap: 18px; flex-wrap: wrap; } 980 .lp-footer-links a { color: var(--muted); text-decoration: none; font-size: 14px; font-weight: 600; } 981 .lp-footer-links a:hover { color: var(--text); } 982 .lp-footer-made { width: 100%; color: var(--muted); font-size: 13px; padding-top: 8px; } 983 .lp-socials { display: flex; gap: 10px; } 984 .lp-socials a { width: 38px; height: 38px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--muted); border: 1px solid var(--border); transition: all 0.15s; } 985 .lp-socials a:hover { color: var(--text); border-color: var(--gold); background: rgba(200,168,75,0.10); } 986 .lp-socials svg { width: 18px; height: 18px; } 987 988 /* ----- Landing responsive ----- */ 989 @media (max-width: 980px) { 990 .lp-hero-inner { grid-template-columns: 1fr; gap: 36px; padding: 44px 22px 60px; } 991 .lp-hero-copy h1 { font-size: 42px; } 992 .lp-hero-right { justify-content: center; } 993 .lp-auth { margin: 0 auto; } 994 .lp-hero-phone { display: none; } 995 .show-grid { grid-template-columns: repeat(3, 1fr); } 996 .lp-showcase-head .lp-h2 { font-size: 34px; } 997 .steps-grid { grid-template-columns: 1fr; } 998 } 999 @media (max-width: 640px) { 1000 .lp-section { padding: 56px 0; } 1001 .lp-h2 { font-size: 30px; } 1002 .lp-hero-copy h1 { font-size: 34px; } 1003 .lp-hero-copy .lp-sub { font-size: 17px; } 1004 .lp-showcase-head .lp-h2 { font-size: 28px; } 1005 .show-grid { grid-template-columns: repeat(2, 1fr); } 1006 .lp-cta-band { padding: 48px 22px; border-radius: 22px; } 1007 .lp-cta-band h2 { font-size: 28px; } 1008 .lp-nav-links a.lp-link { display: none; } 1009 .lp-nav-links .lp-link.keep { display: inline-block; } 1010 .lp-footer-inner { flex-direction: column; align-items: flex-start; } 1011 .lp-footer-links { margin-left: 0; } 1012 } 1013 1014 /* ===== Native-app feel: press feedback, spring, active tab (added to match iOS/Android) ===== */ 1015 :root { --spring: cubic-bezier(.34,1.5,.6,1); } 1016 .tab, .navlink, .btn-primary, .btn, .close { transition: transform .14s var(--spring), color .18s ease, opacity .14s ease; } 1017 .tab:active, .navlink:active, .btn-primary:active, .btn:active, .close:active { transform: scale(.94); } 1018 .card { transition: transform .16s var(--spring), box-shadow .2s ease; } 1019 .card:active { transform: scale(.99); } 1020 .tab .ico { transition: transform .22s var(--spring); } 1021 .tab.active .ico { transform: translateY(-1px) scale(1.08); } 1022 .tab.active::after { content:""; position:absolute; left:50%; bottom:4px; width:5px; height:5px; margin-left:-2.5px; border-radius:999px; background:var(--plum); } 1023 1024 /* Giftella+ member hub */ 1025 .pp-concierge { display:flex; gap:8px; margin:14px 0 18px; flex-wrap:wrap; } 1026 .pp-concierge input { flex:1; min-width:220px; padding:13px 16px; border-radius:999px; border:1px solid var(--border); background:var(--card); color:var(--text); font-size:15px; } 1027 .pp-concierge button { padding:13px 22px; border-radius:999px; border:none; background:linear-gradient(120deg,var(--gold2),var(--gold)); color:#1E1913; font-weight:700; cursor:pointer; transition:transform .14s var(--spring); } 1028 .pp-concierge button:active { transform:scale(.95); } 1029 .pp-hub { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px; margin:6px 0 18px; } 1030 .pp-hubcard { text-align:left; background:var(--card); border:1px solid var(--border); border-radius:16px; padding:14px; cursor:pointer; display:flex; flex-direction:column; gap:3px; transition:transform .16s var(--spring), border-color .2s; } 1031 .pp-hubcard:hover { border-color:var(--gold); transform:translateY(-3px); } 1032 .pp-hubcard:active { transform:scale(.97); } 1033 .pp-hubcard span { font-size:22px; } 1034 .pp-hubcard b { font-size:14px; color:var(--text); } 1035 .pp-hubcard i { font-size:12px; color:var(--muted); font-style:normal; } 1036 1037 /* You-page quick search (mobile â the header search is hidden < 900px) */ 1038 .you-search { display:flex; gap:8px; align-items:center; background:var(--card); border:1px solid var(--border); border-radius:999px; padding:5px 5px 5px 16px; box-shadow:var(--shadow); margin:2px 0 22px; } 1039 .you-search input { flex:1; border:none; background:none; padding:11px 0; font-size:15px;
1039 color:var(--text); outline:none; } 1040 .you-search button { flex-shrink:0; width:42px; height:42px; border-radius:999px; border:none; background:linear-gradient(120deg,var(--gold2),var(--gold)); color:var(--ink); font-size:17px; cursor:pointer; transition:transform .14s var(--spring); } 1041 .you-search button:active { transform:scale(.92); } 1042 @media (min-width:900px){ .you-search { display:none; } } 1043</style>
1044<script> 1045 // Apply saved/system theme BEFORE paint to avoid a flash. 1046 (function () { 1047 try { 1048 var saved = localStorage.getItem("giftella_theme"); 1049 var theme = saved || (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"); 1050 document.documentElement.setAttribute("data-theme", theme); 1051 } catch (e) {} 1052 })(); 1053</script>
1053 1054<!-- Region-aware Amazon routing (local marketplace + matching Associates tag) --> 1055<link rel="preconnect" href="https://www.gstatic.com" crossorigin /> 1056<link rel="preconnect" href="https://firestore.googleapis.com" crossorigin /> 1057<link rel="preconnect" href="https://us-central1-giftella-69c1d.cloudfunctions.net" crossorigin /> 1058<!-- FOUNDER (11 Sep 2026): "images immediately loading across the entire app". 1059 Every avatar and every product photo comes from one of these four hosts, and 1060 the first one costs a DNS lookup plus a TLS handshake before a single byte of 1061 image arrives. Warming them at parse time takes that cost off the first 1062 picture the page draws. --> 1063<link rel="preconnect" href="https://lh3.googleusercontent.com" crossorigin /> 1064<link rel="preconnect" href="https://firebasestorage.googleapis.com" crossorigin /> 1065<link rel="preconnect" href="https://encrypted-tbn0.gstatic.com" crossorigin /> 1066<link rel="dns-prefetch" href="https://encrypted-tbn1.gstatic.com" /> 1067<link rel="dns-prefetch" href="https://encrypted-tbn2.gstatic.com" /> 1068<link rel="dns-prefetch" href="https://encrypted-tbn3.gstatic.com" /> 1069<link rel="modulepreload" href="/app/core.js?v=2026.09.11a" /> 1070<link rel="stylesheet" href="/app/app.css?v=2026.09.11a" />
1071<script src="/affiliate.js" defer></script>
1071 1072</head> 1073<body> 1074 1075<div id="boot-loader"><span class="spinner"></span></div> 1076 1077<!-- ===================== LANDING / LOGIN ===================== --> 1078<section id="login-screen" class="screen"> 1079 <div class="lp"> 1080 1081<style id="lp-editorial"> 1082 /* ââ Editorial landing, Aug 2026 ââââââââââââââââââââââââââââââââââââââââ 1083 Rebuilt from eight stacked sections down to five. Every icon that used to 1084 be an emoji now carries a REAL product photo, resolved once and baked into 1085 the markup rather than fetched on load: an uncached resolve of that 1086 callable measured 8 to 14 seconds live, which is why all 72 photo slots on 1087 the old page were still empty when a visitor gave up and left. The emoji 1088 stays underneath as the instant paint and as the fallback if a baked URL 1089 ever 404s, in which case the tile silently re-resolves itself. 1090 All tokens are the app's own, so light and dark both work untouched. */ 1091 1092 .n-wrap{max-width:1180px;margin:0 auto;padding:0 24px} 1093 .n-sec{padding:86px 0} 1094 .n-sec + .n-sec{border-top:1px solid var(--border2)} 1095 .n-eyebrow.n-eyebrow-deep{color:#7A5C16} 1096 .n-eyebrow{font:700 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace; 1097 letter-spacing:2.6px;text-transform:uppercase;color:var(--gold);margin:0 0 14px} 1098 .n-h2{font-family:Georgia,'Times New Roman',serif;font-weight:400; 1099 font-size:clamp(30px,4.4vw,46px);line-height:1.06;letter-spacing:-.4px; 1100 color:var(--text);margin:0} 1101 .n-lead{margin:14px 0 0;color:var(--muted);font-size:17px;line-height:1.6;max-width:60ch} 1102 1103 /* ââ hero âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 1104 .n-hero{position:relative;overflow:hidden;padding:52px 0 92px;background: 1105 radial-gradient(820px 520px at 84% -14%, rgba(201,162,74,.16), transparent 64%), 1106 radial-gradient(700px 600px at -10% 22%, rgba(201,162,74,.12), transparent 62%)} 1107 /* align-items was `center`. The right column held a 661px sign-in card, so the 1108 row ran ~880px tall while the copy column was ~420px, and centring the short 1109 one left ~230px of dead space above the headline. Top-align, and let the 1110 stage be tall rather than the row. */ 1111 .n-hero-grid{display:grid;grid-template-columns:1.02fr .98fr;gap:48px;align-items:start} 1112 .n-hero-copy{padding-top:26px} 1113 1114 /* ââ the stage: phone with real gift photos in orbit ââââââââââââââââââââ */ 1115 .n-stage{position:relative;min-height:560px;display:flex;align-items:center;justify-content:center} 1116 .n-stage-glow{position:absolute;width:440px;height:440px;border-radius:50%; 1117 background:radial-gradient(circle,rgba(201,162,74,.20),transparent 66%);filter:blur(12px)} 1118 .n-stage-phone{position:relative;z-index:2;width:264px;height:auto; 1119 filter:drop-shadow(0 30px 60px rgba(30,25,19,.26))} 1120 .n-orb{position:absolute;z-index:3;border-radius:18px;overflow:hidden; 1121 background:var(--card);border:1px solid var(--border2); 1122 box-shadow:0 16px 38px rgba(30,25,19,.16); 1123 animation:nfloat 7s ease-in-out infinite} 1124 .n-orb img{width:100%;height:100%;object-fit:cover;display:block;position:relative;z-index:2; 1125 opacity:0;transition:opacity .4s ease} 1126 .n-orb img.loaded{opacity:1} 1127 .n-orb-fb{position:absolute;inset:0;display:flex;align-items:center;justify-content:center; 1128 font-size:30px;z-index:1; 1129 background:linear-gradient(145deg,rgba(201,162,74,.16),rgba(201,162,74,.10))} 1130 /* Clustered around the phone rather than scattered across the column. The 1131 first pass put two of them far out in empty space, which read as debris 1132 instead of orbit. */ 1133 /* Kept OUTSIDE the phone's screen area. The previous placement put the ring 1134 and the cake directly on top of the app UI, which hid the product the 1135 picture exists to show. The phone is 264px centred, so each side has 1136 ~150px of clear runway; every orb is sized and offset to stay inside it. 1137 Verified by measuring the real rectangles, not by eye. */ 1138 .n-orb-1{width:96px;height:96px;top:7%; left:0; animation-delay:0s} 1139 .n-orb-2{width:80px;height:80px;top:1%; right:1%; animation-delay:-1.1s} 1140 .n-orb-3{width:88px;height:88px;top:35%; left:2%; animation-delay:-2.3s} 1141 .n-orb-4{width:76px;height:76px;bottom:22%;right:2%; animation-delay:-3.4s} 1142 .n-orb-5{width:92px;height:92px;bottom:6%; left:1%; animation-delay:-4.6s}
1143 .n-orb-6{width:70px;height:70px;top:17%; right:6%; animation-delay:-5.7s} 1144 @keyframes nfloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-12px)}} 1145 1146 /* ââ sign-in slide-over âââââââââââââââââââââââââââââââââââââââââââââââââ */ 1147 .n-auth-scrim{position:fixed;inset:0;z-index:900;background:rgba(20,14,32,.52); 1148 backdrop-filter:blur(3px);opacity:0;transition:opacity .26s ease} 1149 .n-auth-scrim.on{opacity:1} 1150 .n-auth-panel{position:fixed;top:0;right:0;bottom:0;z-index:901;width:min(440px,100%); 1151 background:var(--bg);border-left:1px solid var(--border); 1152 box-shadow:-24px 0 60px rgba(30,25,19,.22);overflow-y:auto; 1153 padding:26px 22px calc(34px + env(safe-area-inset-bottom)); 1154 transform:translateX(100%);transition:transform .26s cubic-bezier(.22,.9,.3,1)} 1155 .n-auth-panel.on{transform:translateX(0)} 1156 .n-auth-panel .lp-auth{box-shadow:none;border:none;background:transparent; 1157 width:100%;max-width:none;margin:0;padding:0} 1158 .n-auth-close{position:sticky;top:0;margin-left:auto;display:block; 1159 width:38px;height:38px;border-radius:50%;border:1px solid var(--border); 1160 background:var(--card);color:var(--text);font-size:15px;cursor:pointer} 1161 @media (max-width:560px){ .n-auth-panel{width:100%;border-left:none} } 1162 .n-hero h1{font-family:Georgia,'Times New Roman',serif;font-weight:400; 1163 font-size:clamp(40px,5.6vw,62px);line-height:1.0;letter-spacing:-1.2px; 1164 margin:0 0 18px;color:var(--text)}
1165 .n-hero h1 .accent{background:linear-gradient(120deg,var(--gold),var(--plum)); 1166 -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent} 1167 .n-hero-sub{font-size:18.5px;line-height:1.58;color:var(--muted);margin:0 0 26px;max-width:52ch} 1168 1169 /* Store badges sit HIGH on purpose. Founder call: the download has to be the 1170 first thing a phone visitor can act on, above every other CTA. */ 1171 .n-stores{display:flex;gap:12px;flex-wrap:wrap;margin:0 0 22px} 1172 .n-store{display:inline-flex;align-items:center;gap:11px;padding:12px 20px 12px 17px; 1173 border-radius:14px;background:var(--text);color:var(--bg);text-decoration:none; 1174 border:1px solid transparent;transition:transform .14s ease,box-shadow .14s ease; 1175 box-shadow:var(--shadow)} 1176 .n-store:hover{transform:translateY(-2px);box-shadow:var(--shadow-lift)} 1177 .n-store svg{width:22px;height:22px;flex:none} 1178 .n-store .t{display:flex;flex-direction:column;line-height:1.15;text-align:left} 1179 .n-store .t small{font-size:9.5px;letter-spacing:1.5px;text-transform:uppercase;opacity:.7} 1180 .n-store .t strong{font-size:15.5px;font-weight:700} 1181 1182 .n-hero-ctas{display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-bottom:22px} 1183 .n-cta{display:inline-block;padding:14px 26px;border-radius:999px;font-weight:700; 1184 font-size:15.5px;text-decoration:none; 1185 background:linear-gradient(120deg,var(--gold2),var(--gold));color:#1E1913; 1186 box-shadow:inset 0 0 0 1px #A8842F, 0 10px 24px rgba(201,162,74,.28); 1187 transition:transform .14s ease,box-shadow .14s ease} 1188 .n-cta:hover{transform:translateY(-2px); 1189 box-shadow:inset 0 0 0 1px #A8842F, 0 14px 30px rgba(201,162,74,.38)} 1190 .n-cta-ghost{display:inline-block;padding:14px 22px;border-radius:999px;font-weight:600; 1191 font-size:15.5px;text-decoration:none;color:var(--text); 1192 border:1px solid var(--border)} 1193 .n-trust{display:flex;gap:18px;flex-wrap:wrap;font-size:13px;color:var(--muted)} 1194 .n-trust b{color:var(--gold)} 1195 1196 .n-hero-right{display:flex;flex-direction:column;align-items:center;gap:18px} 1197 .n-hero-phone{width:200px;height:auto;filter:drop-shadow(0 22px 44px rgba(30,25,19,.22))} 1198 1199 /* ââ real-photo tile, shared by every card on the page ââââââââââââââââââ */ 1200 .n-shot{position:relative;aspect-ratio:1/1;border-radius:14px;overflow:hidden; 1201 background:linear-gradient(145deg,var(--surface-2),transparent); 1202 display:flex;align-items:center;justify-content:center} 1203 .n-shot .fallback{font-size:30px;line-height:1;transition:opacity .3s ease} 1204 .n-shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover; 1205 opacity:0;transition:opacity .45s ease} 1206 .n-shot img.loaded{opacity:1} 1207 .n-shot img.loaded ~ .fallback{opacity:0} 1208 1209 @media (max-width:900px){ 1210 .n-hero-grid{grid-template-columns:1fr;gap:30px} 1211 .n-hero-copy{padding-top:0} 1212 /* Copy leads on phones. The stage was ordered above it, which pushed the 1213 headline 572px down the page â a phone visitor's whole first screen was 1214 a picture with no words on it. The visual follows the promise here. */ 1215 .n-stage{min-height:400px;margin-top:8px} 1216 .n-stage-phone{width:230px} 1217 .n-stage-glow{width:320px;height:320px} 1218 .n-sec{padding:62px 0} 1219 } 1220 @media (max-width:620px){ 1221 /* Orbs are decoration. Below this width they crowd the phone and start 1222 colliding with the headline, so drop them rather than shrink them. */ 1223 .n-orb{display:none} 1224 .n-stage{min-height:340px} 1225 .n-stage-phone{width:210px} 1226 } 1227 @media (prefers-reduced-motion:reduce){ .n-orb{animation:none} } 1228 1229 /* ââ registry + guides blocks âââââââââââââââââââââââââââââââââââââââââââ */ 1230 .n-block + .n-block{margin-top:70px} 1231 .n-block-head{display:flex;justify-content:space-between;align-items:flex-end; 1232 gap:26px;flex-wrap:wrap;margin-bottom:30px} 1233 .n-block-link{white-space:nowrap;font-weight:700;font-size:15px;color:var(--plum); 1234 text-decoration:none;padding-bottom:4px;border-bottom:1.5px solid transparent} 1235 .n-block-link:hover{border-bottom-color:var(--plum)}
1236 .n-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px} 1237 .n-card{display:block;text-decoration:none;border-radius:18px;padding:12px 12px 15px; 1238 background:var(--card);border:1px solid var(--border2); 1239 transition:transform .16s ease,box-shadow .16s ease,border-color .16s ease} 1240 .n-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-lift);border-color:var(--border)} 1241 .n-card-t{display:block;margin-top:12px;text-align:center;font-weight:700; 1242 font-size:14.5px;color:var(--text);line-height:1.25} 1243 .n-card-k{display:block;margin-top:4px;text-align:center; 1244 font:700 9.5px/1 ui-monospace,Menlo,monospace;letter-spacing:1.7px; 1245 text-transform:uppercase;color:var(--gold)} 1246 .n-card-all .n-shot{background:linear-gradient(145deg,rgba(201,162,74,.16),transparent)} 1247 @media(max-width:980px){ .n-grid{grid-template-columns:repeat(3,1fr)} } 1248 .n-card-t{font-size:15.5px} 1249 .n-shot{aspect-ratio:4/3} 1250 @media(max-width:560px){ .n-grid{grid-template-columns:repeat(2,1fr);gap:12px} } 1251 1252 .n-center{text-align:center} 1253 .n-center .n-lead-c,.n-lead-c{margin-left:auto;margin-right:auto;text-align:center} 1254 1255 /* ââ any-store strip ââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 1256 .n-anystore{margin-top:56px;padding-top:34px;border-top:1px solid var(--border2)} 1257 .n-anystore-t{text-align:center;color:var(--muted);font-size:15px;margin:0 0 20px; 1258 max-width:66ch;margin-left:auto;margin-right:auto} 1259 1260 /* ââ steps ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 1261 .n-steps{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:44px} 1262 .n-step{padding:28px 24px;border-radius:20px;background:var(--card); 1263 border:1px solid var(--border2)} 1264 .n-step-n{font-family:Georgia,serif;font-size:34px;color:var(--gold);display:block; 1265 line-height:1;margin-bottom:14px} 1266 .n-step h3{font-family:Georgia,serif;font-weight:400;font-size:20px;margin:0 0 9px;color:var(--text)}
1267 .n-step p{margin:0;color:var(--muted);font-size:14.5px;line-height:1.6} 1268 .n-chips{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;margin-top:32px} 1269 .n-chip{padding:9px 15px;border-radius:999px;background:var(--surface-2); 1270 border:1px solid var(--border2);font-size:13px;color:var(--muted)} 1271 @media(max-width:860px){ .n-steps{grid-template-columns:1fr} } 1272 1273 /* ââ plus band ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 1274 .n-plus{padding:64px 0;position:relative; 1275 background:linear-gradient(140deg,#FBF7EE,#F6EFE0 58%,#FAF6EC)} 1276 /* A gold hairline top and bottom ties the dark band to the rest of the page 1277 instead of letting it sit there as a foreign block. */ 1278 .n-plus::before,.n-plus::after{content:"";position:absolute;left:0;right:0;height:1px; 1279 background:linear-gradient(90deg,transparent,rgba(201,162,74,.55),transparent)} 1280 .n-plus::before{top:0} .n-plus::after{bottom:0} 1281 /* CONTRAST (11 Sep 2026). The band's cream is the darkest ground on the 1282 page, so the page-wide --gold and --muted that clear WCAG AA everywhere 1283 else do not clear it here. Two band-local tokens fix that without 1284 touching the global palette: 1285 --plus-gold #AB8A3F · 3.12:1 on the tile fill (30px counts as large) 1286 --plus-gold-s #836930 · 4.55:1, for the 10.5px section label 1287 --plus-muted #766B5F · 4.54:1, where the page token lands at 4.48 1288 Measured composited over the tile and card fills, not over bare white, 1289 because an 8% tint read as opaque gives a number that is not true. */ 1290 .n-plus{--plus-gold:#AB8A3F;--plus-gold-s:#836930;--plus-muted:#766B5F} 1291 .n-plus .n-lead,.n-plus-note,.n-plus-try{color:var(--plus-muted)} 1292 .n-plus-top{display:flex;justify-content:space-between;align-items:flex-end; 1293 gap:34px;flex-wrap:wrap} 1294 .n-plus-say{max-width:60ch} 1295 .n-plus-cta{display:flex;flex-direction:column;align-items:flex-start;gap:10px} 1296 .n-plus-note{font-size:12.5px;color:var(--muted)} 1297 /* The four numbers people actually feel. Free value sits in the caption so 1298 the tile reads as a lift, not a boast. */ 1299 .n-plus-grid{list-style:none;margin:30px 0 0;padding:0;display:grid;gap:12px; 1300 grid-template-columns:repeat(4,minmax(0,1fr))} 1301 .n-plus-grid li{background:rgba(255,255,255,.66);border:1px solid rgba(201,162,74,.28); 1302 border-radius:14px;padding:16px 16px 14px} 1303 .n-plus-grid b{display:block;font-family:Georgia,"Times New Roman",serif; 1304 font-size:30px;line-height:1;color:var(--plus-gold);font-weight:400} 1305 /* Georgia's infinity is a hairline next to a numeral, so the "unlimited" 1306 tiles read as weaker than the one tile with a number in it, which is the 1307 opposite of the truth. Set it in the sans face at a heavier weight so all 1308 four tiles carry the same visual weight. */ 1309 .n-plus-grid b.inf{font-family:inherit;font-size:32px;font-weight:600; 1310 letter-spacing:-.01em} 1311 .n-plus-grid span{display:block;margin-top:7px;font-size:12.8px;line-height:1.45; 1312 color:var(--plus-muted)} 1313 .n-plus-rooms-lbl{margin:30px 0 12px;font-size:10.5px;font-weight:700; 1314 letter-spacing:.18em;text-transform:uppercase;color:var(--plus-gold-s)} 1315 /* Seven rooms. Four across leaves a tidy 4 + 3; auto-fit would give 5 + 2 1316 on a wide screen and strand three empty cells. */ 1317 .n-plus-rooms{display:grid;gap:10px; 1318 grid-template-columns:repeat(auto-fit,minmax(212px,1fr))} 1319 @media (min-width:980px){ .n-plus-rooms{grid-template-columns:repeat(4,minmax(0,1fr))} } 1320 .n-room{display:block;background:rgba(255,255,255,.5); 1321 border:1px solid var(--border2);border-radius:13px;padding:13px 14px} 1322 .n-room i{font-style:normal;font-size:15px;line-height:1} 1323 .n-room b{display:block;margin-top:6px;font-size:13.6px;color:var(--text);font-weight:650} 1324 .n-room em{display:block;margin-top:3px;font-style:normal;font-size:12.2px; 1325 line-height:1.5;color:var(--plus-muted)} 1326 .n-plus-try{margin:18px 0 0;font-size:13px;max-width:72ch}
1327 @media (max-width:820px){ .n-plus-grid{grid-template-columns:repeat(2,minmax(0,1fr))} } 1328 @media (max-width:560px){ .n-plus-top{align-items:flex-start} } 1329 1330 /* ââ faq ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 1331 .n-faq{max-width:760px;margin:56px auto 0;text-align:left} 1332 .n-faq-item{border-bottom:1px solid var(--border2)} 1333 .n-faq-item summary{cursor:pointer;padding:17px 0;font-weight:650;font-size:16px; 1334 color:var(--text);list-style:none;display:flex; 1335 justify-content:space-between;align-items:center;gap:14px} 1336 .n-faq-item summary::-webkit-details-marker{display:none} 1337 .n-faq-item summary::after{content:"+";color:var(--gold);font-size:20px;flex:none} 1338 .n-faq-item[open] summary::after{content:"\2013"} 1339 .n-faq-item div{padding:0 0 18px;color:var(--muted);font-size:14.8px;line-height:1.65} 1340 .n-close{padding-bottom:96px} 1341 1342 @media (prefers-reduced-motion:reduce){ 1343 .n-store,.n-cta{transition:none} 1344 .n-shot img{transition:none} 1345 } 1346</style> 1347 1348 <!-- ===== NAV ===== --> 1349 <nav class="lp-nav"> 1350 <div class="lp-nav-inner"> 1351 <a class="lp-brand" href="#top" aria-label="Giftella home"> 1352 <img src="/giftella-logo.png" alt="Giftella" /> 1353 <span class="wordmark">Giftella</span> 1354 </a> 1355 <div class="lp-nav-links"> 1356 <a class="lp-link keep" href="/plus" style="color:#C8A84B;font-weight:700">Giftella+ ✦</a> 1357 <a class="lp-link" href="/registry">Registry</a> 1358 <a class="lp-link" href="/guides">Gift guides</a> 1359 <!-- SITELINKS (17 Sep 2026, supersedes the 16 Sep note here). 1360 Checked against the live SERP: the two sitelinks Google is actually 1361 showing are /home (labelled from its H1, "Find the perfect gift for 1362 anyone") and /ecosystem. NOT /registry and /guides as the earlier 1363 note assumed. 1364 /ecosystem was deliberately added to this nav on 16 Sep to win a 1365 sitelink, and it worked. Founder has since decided /registry is the 1366 better use of a scarce slot on a consumer brand query, so the nav 1367 link is removed. /ecosystem stays linked twice from the footer 1368 below, so it remains crawlable and indexed, just not nav-prominent. 1369 Sitelinks cannot be specified or demoted; Search Console dropped 1370 that tool years ago. Link prominence and page titles are the only 1371 real inputs. --> 1372 1373 <a class="lp-link" href="/teams">For teams</a> 1374 <a class="lp-link" href="#how">How it works</a> 1375 <a class="lp-link" href="https://apps.apple.com/us/app/giftella/id6761128527" target="_blank" rel="noopener">iOS app</a> 1376 <a class="lp-link" href="https://play.google.com/store/apps/details?id=app.giftella.giftella" target="_blank" rel="noopener">Android app</a> 1377 <button class="theme-toggle keep" id="landing-theme-toggle" aria-label="Toggle dark mode" title="Toggle theme" style="margin-right:6px;">🌙</button> 1378 <a class="lp-nav-cta keep" href="#signin">Sign in</a> 1379 </div> 1380 </div> 1381 </nav> 1382 1383 <!-- ===== HERO ===== --> 1384 <header class="n-hero" id="top"> 1385 <div class="n-wrap n-hero-grid"> 1386 <div class="n-hero-copy"> 1387 <p class="n-eyebrow">Gift finder · wishlists · registries</p> 1388 <h1>Find the <span class="accent">perfect gift</span><br>for everyone you love.</h1> 1389 <p class="n-hero-sub">Tell Giftella who you're shopping for. Get real, ready to shop ideas in seconds — then keep the list, share it, and never miss a date again.</p> 1390 1391 <div class="n-stores"> 1392 <a class="n-store" href="https://apps.apple.com/us/app/giftella/id6761128527" target="_blank" rel="noopener" aria-label="Download on the App Store"> 1393 <svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M16.5 1.6c0 1.1-.4 2.1-1.2 3-.9 1-2 1.6-3.1 1.5-.1-1.1.4-2.2 1.2-3 .8-1 2.1-1.6 3.1-1.5zM20.7 17c-.5 1.2-.8 1.7-1.5 2.8-1 1.5-2.3 3.3-4 3.3-1.5 0-1.9-1-4-.9-2 0-2.5.9-4 .9-1.6 0-2.9-1.6-3.8-3.1C.8 16.6.5 11.7 2.5 9.1c1-1.3 2.5-2.1 4-2.1 1.5 0 2.5 1 3.8 1 1.2 0 2-1 3.8-1 1.3 0 2.7.7 3.7 2-3.3 1.8-2.7 6.4 0 8z"/></svg>
1394 <span class="t"><small>Download on the</small><strong>App Store</strong></span> 1395 </a> 1396 <a class="n-store" href="https://play.google.com/store/apps/details?id=app.giftella.giftella" target="_blank" rel="noopener" aria-label="Get it on Google Play"> 1397 <svg viewBox="0 0 20 22" aria-hidden="true"><path d="M1.6.7 12 11 1.6 21.3A1.7 1.7 0 0 1 1 20V2c0-.5.2-1 .6-1.3z" fill="#4ba1f2"/><path d="M15.9 7.4 12 11 1.6.7C1.9.4 2.4.4 2.9.7l13 6.7z" fill="#5be08a"/><path d="M15.9 14.6 2.9 21.3c-.5.3-1 .3-1.3 0L12 11l3.9 3.6z" fill="#f2564b"/><path d="M19.3 9.7c.7.4.7 1.9 0 2.3l-3.4 1.8-4-2.8 4-2.8 3.4 1.5z" fill="#f6c343"/></svg> 1398 <span class="t"><small>Get it on</small><strong>Google Play</strong></span> 1399 </a> 1400 </div> 1401 1402 <div class="n-hero-ctas"> 1403 <a class="n-cta" href="#signin">Get started free →</a> 1404 <a class="n-cta-ghost" href="/registry">Start a registry</a> 1405 </div> 1406 1407 <div class="n-trust"> 1408 <span><b>★</b> 5.0 on the App Store</span> 1409 <span><b>✓</b> Free to start</span> 1410 <span><b>✓</b> Surprises stay secret</span> 1411 </div> 1412 </div> 1413 1414 <!-- The stage. The phone carries the product; real gift photos orbit it, 1415 which is the whole promise of the app in one picture. --> 1416 <div class="n-stage" aria-hidden="true"> 1417 <div class="n-stage-glow"></div> 1418 <!-- The real Home screen: their name, their circle of faces, today's 1419 editor's pick, the partner brands. The old shot was a results list, 1420 which showed the OUTPUT of one search; this shows the app someone 1421 actually lives in. Height is declared so it reserves its space and 1422 the hero never jumps as it loads. --> 1423 <img class="n-stage-phone" src="/press-assets/hero-app.png" alt="The Giftella app home screen: your circle, today's pick and partner brands" width="264" height="534" loading="eager" decoding="async" fetchpriority="high"> 1424 <span class="n-orb n-orb-1"><img alt="" src="https://encrypted-tbn0.gstatic.com/shopping?q=tbn:ANd9GcQlqnanzM7eLos5y2KNEjNhgH3xEPY-puP3fPmh4XPheCxIBIoper2HNQ76O0MaZj_J9OUriYjxYZk-wuJI2XsxNx5j8cevu8AcTcu-tNOMdqYRW_lvdRxl6w" loading="lazy" decoding="async"><span class="n-orb-fb">ð</span></span> 1425 <span class="n-orb n-orb-2"><img alt="" src="https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcSfeUkIikrBDc7_1sGSokmkRBMH54EHKDH1s_pCRUyT2t2XMXLyKprRXd5BKAt5haM30GS4iLgGcyK_JBusNNW3xLJxbkw7MT90FrJaJ_-cZAf0SlGNAlbCurQs" loading="lazy" decoding="async"><span class="n-orb-fb">ð</span></span> 1426 <span class="n-orb n-orb-3"><img alt="" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQIkIww56mFH3p0a6GJ65uL3BSloUaYnCaNIPFNAMoSsg&s=10" loading="lazy" decoding="async"><span class="n-orb-fb">ð</span></span> 1427 <span class="n-orb n-orb-4"><img alt="" src="https://encrypted-tbn1.gstatic.com/shopping?q=tbn:ANd9GcSdrTWMn_9KYJ1qOZPPYd8c85tV_YvJaiZGIfvMRK-kvqafrWOY6aVKgSKci5LRTvI-cR5augEUwr4d6U2OMDYPg44aQdQIFdxXU7jhoCHQdWIcvr840EVO" loading="lazy" decoding="async"><span class="n-orb-fb">â¤ï¸</span></span> 1428 <span class="n-orb n-orb-5"><img alt="" src="https://encrypted-tbn1.gstatic.com/shopping?q=tbn:ANd9GcQMCVRq8sJNwVhrsqPEGROxcpwbKsDQHniMHYDUgQR7AuRUI-KzCnqiR-RgWt--X37T6NCXZJtVaR1NeY-AR2FzrqLcC81JmxEx1oYRC2q2trqCV10PAxgB" loading="lazy" decoding="async"><span class="n-orb-fb">ð¡</span></span> 1429 <span class="n-orb n-orb-6"><img alt="" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRZZJgiD2qL81Vn7G9MujT0IVsIYsdXJbaRrrqUs3N1Fg&s=10" loading="lazy" decoding="async"><span class="n-orb-fb">ð·</span></span> 1430 </div> 1431 </div> 1432 </header> 1433 1434 <!-- ===== BROWSE REAL GIFTS (absorbs the old any-store section) ===== --> 1435 <section class="n-sec" id="browse"> 1436 <div class="n-wrap"> 1437 <div class="n-center"> 1438 <p class="n-eyebrow">Browse real gift ideas</p> 1439 <h2 class="n-h2">Gifts for every person you have to buy for</h2> 1440 <p class="n-lead n-lead-c">Tap anything to shop it, or sign in and let Giftella build a list around your exact person.</p> 1441 </div> 1442 <div class="browse-tabs" id="browseTabs"></div> 1443 <div class="gift-rails" id="giftRails"></div> 1444 <div class="n-center" style="margin-top:30px"> 1445 <a class="n-cta" href="#signin">Get personalised picks →</a> 1446 </div> 1447 1448 <div class="n-anystore" id="anystore"> 1449 <p class="n-anystore-t">Works with every store — paste a link from anywhere and Giftella pulls the photo, price and details in automatically.</p> 1450 <div class="store-marquee"><div class="store-track"><span class="store-pill">Amazon</span><span class="store-pill">Target</span><span class="store-pill">Walmart</span><span class="store-pill">
1450Apple</span><span class="store-pill">Nike</span><span class="store-pill">Sephora</span><span class="store-pill">lululemon</span><span class="store-pill">Etsy</span><span class="store-pill">IKEA</span><span class="store-pill">Best Buy</span><span class="store-pill">Nordstrom</span><span class="store-pill">Zara</span><span class="store-pill">Adidas</span><span class="store-pill">Wayfair</span><span class="store-pill">Costco</span><span class="store-pill">Dyson</span><span class="store-pill">Amazon</span><span class="store-pill">Target</span><span class="store-pill">Walmart</span><span class="store-pill">Apple</span><span class="store-pill">Nike</span><span class="store-pill">Sephora</span><span class="store-pill">lululemon</span><span class="store-pill">Etsy</span><span class="store-pill">IKEA</span><span class="store-pill">Best Buy</span><span class="store-pill">Nordstrom</span><span class="store-pill">Zara</span><span class="store-pill">Adidas</span><span class="store-pill">Wayfair</span><span class="store-pill">Costco</span><span class="store-pill">Dyson</span></div></div> 1451 </div> 1452 </div> 1453 </section> 1454 1455 <!-- ===== REGISTRY + GIFT GUIDES â the centrepiece ===== --> 1456 <section class="n-sec" id="occasions"> 1457 <div class="n-wrap"> 1458 1459 <div class="n-block"> 1460 <div class="n-block-head"> 1461 <div> 1462 <p class="n-eyebrow">Registries & lists</p> 1463 <h2 class="n-h2">A list people actually gift from</h2> 1464 <p class="n-lead">Pick the occasion, build the list, share one link. Guests claim gifts secretly, so nothing gets doubled and nothing gets spoiled.</p> 1465 </div> 1466 <a class="n-block-link" href="/registry">Start a registry →</a> 1467 </div> 1468 <div class="n-grid"> 1469 <a class="n-card" href="/registry/for/wedding"><span class="n-shot"><img alt="Wedding" loading="lazy" decoding="async" data-q="wedding registry gift set" src="https://encrypted-tbn0.gstatic.com/shopping?q=tbn:ANd9GcQlqnanzM7eLos5y2KNEjNhgH3xEPY-puP3fPmh4XPheCxIBIoper2HNQ76O0MaZj_J9OUriYjxYZk-wuJI2XsxNx5j8cevu8AcTcu-tNOMdqYRW_lvdRxl6w"><span class="fallback">ð</span></span><span class="n-card-t">Wedding</span><span class="n-card-k">Create</span></a> 1470 <a class="n-card" href="/registry/for/baby"><span class="n-shot"><img alt="Baby shower" loading="lazy" decoding="async" data-q="baby shower gift basket" src="https://encrypted-tbn0.gstatic.com/shopping?q=tbn:ANd9GcTNBGvTYLM0m5YuRuGiaLuHFf15ziGFGCPR0JxMnCf4LrS_ZxL5o5KXLGkOBbYS-sfrrf0Cfg-6KNoG76Tb_3-66j-o3YxD327g6P7p9FGO"><span class="fallback">ð¶</span></span><span class="n-card-t">Baby shower</span><span class="n-card-k">Create</span></a> 1471 <a class="n-card" href="/registry/for/birthday"><span class="n-shot"><img alt="Birthday" loading="lazy" decoding="async" data-q="birthday gift box" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQIkIww56mFH3p0a6GJ65uL3BSloUaYnCaNIPFNAMoSsg&s=10"><span class="fallback">ð</span></span><span class="n-card-t">Birthday</span><span class="n-card-k">Create</span></a> 1472 <a class="n-card" href="/registry/for/graduation"><span class="n-shot"><img alt="Graduation" loading="lazy" decoding="async" data-q="graduation gift for her" src="https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcQ3uoJWaXXfCN6hM-9F6GGv5F4Oh3HMuMYrOwb9jXff5gnTUdQMMo9j4G6T2A1_VL3yT-yvsy3Wz1QHrFM_JHwJ5gM9QvQcbv1Mk55z6XQ"><span class="fallback">ð</span></span><span class="n-card-t">Graduation</span><span class="n-card-k">Create</span></a> 1473 <a class="n-card" href="/registry/for/housewarming"><span class="n-shot"><img alt="Housewarming" loading="lazy" decoding="async" data-q="housewarming gift set" src="https://encrypted-tbn1.gstatic.com/shopping?q=tbn:ANd9GcQMCVRq8sJNwVhrsqPEGROxcpwbKsDQHniMHYDUgQR7AuRUI-KzCnqiR-RgWt--X37T6NCXZJtVaR1NeY-AR2FzrqLcC81JmxEx1oYRC2q2trqCV10PAxgB"><span class="fallback">ð¡</span></span><span class="n-card-t">Housewarming</span><span class="n-card-k">Create</span></a> 1474 <a class="n-card" href="/registry/for/classroom"><span class="n-shot"><img alt="Classroom" loading="lazy" decoding="async" data-q="classroom supplies teacher gift" src="https://encrypted-tbn3.gstatic.
1474com/shopping?q=tbn:ANd9GcTIWsZj7DSy2vHnI2RMLQex91Y3M7V3l-t3l_32rg05OUUM9ZnpDT4oDxrjmQZzlXy8hjubwNMZos8dYlOCimX4K_7GeFrMyJinl7rrRjs"><span class="fallback">ð</span></span><span class="n-card-t">Classroom</span><span class="n-card-k">Create</span></a> 1475 </div> 1476 </div> 1477 1478 <div class="n-block"> 1479 <div class="n-block-head"> 1480 <div> 1481 <p class="n-eyebrow">Gift guides</p> 1482 <h2 class="n-h2">No list? Start from a good one</h2> 1483 <p class="n-lead">Real, shoppable picks for the moment you are actually buying for. Thirty one guides and counting.</p> 1484 </div> 1485 <a class="n-block-link" href="/guides">All 31 guides →</a> 1486 </div> 1487 <div class="n-grid"> 1488 <a class="n-card" href="/guides/christmas-gifts"><span class="n-shot"><img alt="Christmas" loading="lazy" decoding="async" data-q="christmas gift set" src="https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcSfeUkIikrBDc7_1sGSokmkRBMH54EHKDH1s_pCRUyT2t2XMXLyKprRXd5BKAt5haM30GS4iLgGcyK_JBusNNW3xLJxbkw7MT90FrJaJ_-cZAf0SlGNAlbCurQs"><span class="fallback">ð</span></span><span class="n-card-t">Christmas</span><span class="n-card-k">Ideas</span></a> 1489 <a class="n-card" href="/guides/valentines-day-gifts"><span class="n-shot"><img alt="Valentine's Day" loading="lazy" decoding="async" data-q="valentines day gift for her" src="https://encrypted-tbn1.gstatic.com/shopping?q=tbn:ANd9GcSdrTWMn_9KYJ1qOZPPYd8c85tV_YvJaiZGIfvMRK-kvqafrWOY6aVKgSKci5LRTvI-cR5augEUwr4d6U2OMDYPg44aQdQIFdxXU7jhoCHQdWIcvr840EVO"><span class="fallback">â¤ï¸</span></span><span class="n-card-t">Valentine's Day</span><span class="n-card-k">Ideas</span></a> 1490 <a class="n-card" href="/guides/mothers-day-gifts"><span class="n-shot"><img alt="Mother's Day" loading="lazy" decoding="async" data-q="mothers day gift basket" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRZZJgiD2qL81Vn7G9MujT0IVsIYsdXJbaRrrqUs3N1Fg&s=10"><span class="fallback">ð·</span></span><span class="n-card-t">Mother's Day</span><span class="n-card-k">Ideas</span></a> 1491 <a class="n-card" href="/guides/fathers-day-gifts"><span class="n-shot"><img alt="Father's Day" loading="lazy" decoding="async" data-q="fathers day gift for dad" src="https://encrypted-tbn0.gstatic.com/shopping?q=tbn:ANd9GcSLPjbiVDYvPSyf8X3G938bueZkvvxQRRb_H7kHW5II7S5jzLD4vgf92Pg2gfu4IEywi7xbG2RJcgDaXj2jtQvqwbLsrSS0JfoYk0PNO3whz0CQIHqphYFciS4"><span class="fallback">ð</span></span><span class="n-card-t">Father's Day</span><span class="n-card-k">Ideas</span></a> 1492 <a class="n-card" href="/guides/gifts-for-couples"><span class="n-shot"><img alt="For couples" loading="lazy" decoding="async" data-q="gift set for couples" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ0aMPKmqF_vic1GMbbMKyjeBIS5i366_P8kXGo9iLLKw&s=10"><span class="fallback">ð</span></span><span class="n-card-t">For couples</span><span class="n-card-k">Ideas</span></a> 1493 <a class="n-card" href="/guides/last-minute-gifts"><span class="n-shot"><img alt="Last minute" loading="lazy" decoding="async" data-q="last minute gift card set"><span class="fallback">â°</span></span><span class="n-card-t">Last minute</span><span class="n-card-k">Ideas</span></a> 1494 </div> 1495 </div> 1496 1497 </div> 1498 </section> 1499 1500 <!-- ===== HOW IT WORKS ===== --> 1501 <section class="n-sec" id="how"> 1502 <div class="n-wrap"> 1503 <div class="n-center"> 1504 <p class="n-eyebrow">How it works</p> 1505 <h2 class="n-h2">Thoughtful gifting in three steps</h2> 1506 </div> 1507 <div class="n-steps"> 1508 <div class="n-step"><span class="n-step-n">01</span><h3>Tell us who it is for</h3><p>A name, the relationship, what they are into, the occasion and your budget. A few seconds is all it takes.</p></div> 1509 <div class="n-step"><span class="n-step-n">02</span><h3>Get real, shoppable ideas</h3><p>Giftella returns actual products matched to them, each with a reason it fits. Not a category page. Things you can buy right now.</p></div> 1510 <div class="n-step"><span class="n-step-n">03</span><h3>Save, share, never miss a date</h3><p>Save to a wishlist and share the link. Friends claim gifts secretly so the surprise holds. Reminders keep every occasion on track.</p></div> 1511 </div> 1512 <div class="n-chips">
1513 <span class="n-chip">🤫 Claimed gifts stay hidden</span> 1514 <span class="n-chip">🔗 No account needed to claim from a link</span> 1515 <span class="n-chip">🎁 Group gifts split the cost automatically</span> 1516 </div> 1517 </div> 1518 </section> 1519 1520 <!-- ===== GIFTELLA+ ===== 1521 HONESTY PASS (11 Sep 2026). The old band promised "early access to 1522 drops and concierge wrapping". Neither ships: there is no drops 1523 programme, and the wrap-and-ship concierge is built but parked until 1524 fulfilment operations are live. Every number and every room below is 1525 read off the code that is running today: 1526 · free/Plus limits â public/app/core.js Plus.limits 1527 · free searches/person â public/app/core.js Plus.meter.freePerRecipient 1528 · the seven rooms â public/app/plus.js ROOMS / RAIL 1529 · free tries â public/app/plus.js TASTE_MAX 1530 The price is never written here. It is fetched live from Stripe, the 1531 same source checkout uses, so it can never drift from what people are 1532 actually charged. --> 1533 <section class="n-plus" id="plus"> 1534 <div class="n-wrap"> 1535 <div class="n-plus-top"> 1536 <div class="n-plus-say"> 1537 <p class="n-eyebrow n-eyebrow-deep">Giftella+</p> 1538 <h2 class="n-h2">Everything above, with the brakes off</h2> 1539 <p class="n-lead">Free gives you a real taste. Giftella+ lifts every limit and opens seven members-only rooms. One membership covers the web, iPhone and Android.</p> 1540 </div> 1541 <div class="n-plus-cta"> 1542 <a class="n-cta" href="/plus">See Giftella+ →</a> 1543 <span class="n-plus-note" id="n-plus-price">Cancel anytime</span> 1544 </div> 1545 </div> 1546 1547 <ul class="n-plus-grid"> 1548 <li><b>30</b><span>gift ideas a search, up from 6</span></li> 1549 <li><b class="inf">∞</b><span>AI searches per person, up from 3</span></li> 1550 <li><b class="inf">∞</b><span>people saved, up from 2</span></li> 1551 <li><b class="inf">∞</b><span>wishlists, up from 1</span></li> 1552 </ul> 1553 1554 <p class="n-plus-rooms-lbl">Members-only rooms</p> 1555 <div class="n-plus-rooms"> 1556 <span class="n-room"><i>💬</i><b>Gift Concierge</b><em>Talk to it like a friend. It finds the gift, you approve.</em></span> 1557 <span class="n-room"><i>🎯</i><b>Gift Autopilot</b><em>Name anyone and get THE gift, picked and sourced.</em></span> 1558 <span class="n-room"><i>✨</i><b>Gifts for You</b><em>Tell it the vibe. Picks for yourself, beyond Amazon.</em></span> 1559 <span class="n-room"><i>🔔</i><b>Price-Drop Alerts</b><em>Watch any wish. We tell you the moment it gets cheaper.</em></span> 1560 <span class="n-room"><i>📅</i><b>Year-Ahead Planner</b><em>Every birthday and holiday planned to a budget, in one sitting.</em></span> 1561 <span class="n-room"><i>📰</i><b>The Gift Wire</b><em>What the world is gifting today, fresh every morning.</em></span> 1562 <span class="n-room"><i>💎</i><b>The Vault</b><em>Your points and the gift cards they turn into.</em></span> 1563 </div> 1564 <p class="n-plus-try">Not sure? The Concierge, Autopilot, Gifts for You and the Wire each open free. You get three tries before the meter starts.</p> 1565 </div> 1566 </section> 1567 1568 <!-- ===== CLOSE + FAQ ===== --> 1569 <section class="n-sec n-close"> 1570 <div class="n-wrap n-center"> 1571 <h2 class="n-h2">Start gifting in seconds. Free.</h2> 1572 <p class="n-lead n-lead-c">Join Giftella and find the perfect gift for everyone you love.</p> 1573 <div class="n-hero-ctas" style="justify-content:center;margin-top:26px"> 1574 <a class="n-cta" href="#signin">Get started free →</a> 1575 <a class="n-cta-ghost" href="/registry">Start a registry</a> 1576 </div> 1577 1578 <div class="n-faq" id="faq"> 1579 <details class="n-faq-item"><summary>What is Giftella?</summary><div>A gift finder that returns real, shoppable ideas for any person and occasion, plus wishlists friends can secretly claim from, registries, birthday reminders and group gifts. On iOS, Android and the web.</div></details> 1580 <details class="n-faq-item"><summary>Is it free?</summary><div>Yes. Wishlists, registries, the gift finder, sharing and reminders are all free. Giftella+ adds extras like price drop watches, but you never need it to gift well.</div></details> 1581 <details class="n-faq-item"><summary>Can I add gifts from any store?</summary><div>Yes. Paste a link from any website and Giftella pulls the photo, price and details in automatically, from Amazon to a tiny boutique.</div></details> 1582 <details class="n-faq-item"><summary>Someone shared a list with me. Do I need an account?</summary><div>No. Open the link, browse, and claim what you are getting. Claims are hidden from the owner, so the surprise holds and no two people buy the same thing.</div></details> 1583 <details class="n-faq-item"><summary>How is this different from other wishlist apps?</summary><div>Most do one thing: a list, or a registry, or an exchange. Giftella finds the gift, holds the list, remembers the date, splits the cost and ships it wrapped.</div></details> 1584 </div> 1585 </div> 1586
1586<script type="application/ld+json">{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"What is Giftella?","acceptedAnswer":{"@type":"Answer","text":"Giftella is a gift finder that returns real, shoppable ideas for any person and occasion, plus wishlists friends can secretly claim from, registries, birthday reminders and group gifts. Available on iOS, Android, and the web."}},{"@type":"Question","name":"Is Giftella free?","acceptedAnswer":{"@type":"Answer","text":"Yes. Wishlists, registries, the gift finder, sharing and reminders are free. Giftella+ is an optional membership that raises every limit and opens seven members-only rooms including the Gift Concierge, Gift Autopilot and price-drop alerts."}},{"@type":"Question","name":"Can I add gifts from any store?","acceptedAnswer":{"@type":"Answer","text":"Yes. Paste a link from any website and Giftella pulls in the photo, price and details automatically."}},{"@type":"Question","name":"Do I need an account to view or claim from a shared wishlist?","acceptedA
1586nswer":{"@type":"Answer","text":"No. Open the shared link, browse the list, and claim a gift. Claims are hidden from the list owner so surprises are never spoiled."}},{"@type":"Question","name":"What makes Giftella different from other wishlist apps?","acceptedAnswer":{"@type":"Answer","text":"Giftella combines gift discovery, social wishlists, registries, birthday reminders and group gifting in one app, on iOS, Android and the web."}}]}</script>
1586 1587 </section> 1588 1589 <!-- ===== FOOTER ===== --> 1590 <footer class="lp-footer"> 1591 <div class="lp-footer-inner"> 1592 <span class="wordmark">Giftella</span> 1593 <div class="lp-footer-mega"> 1594 <div> 1595 <h5>Registry</h5> 1596 <a href="/registry">Gift Registry</a> 1597 <a href="/registry/for/wedding">Wedding</a> 1598 <a href="/babies">Baby Shower</a> 1599 <a href="/registry/for/classroom">Classroom</a> 1600 </div> 1601 <div> 1602 <h5>Gift Guides</h5> 1603 <a href="/guides">All Gift Guides</a> 1604 <a href="/guides/christmas-gifts">Christmas</a> 1605 <a href="/guides/mothers-day-gifts">Mother's Day</a> 1606 </div> 1607 <div> 1608 <h5>Ecosystem</h5> 1609 <a href="/ecosystem">Brand Ecosystem</a> 1610 <a href="/ecosystem">List your brand</a> 1611 </div> 1612 <div> 1613 <h5>For Teams</h5> 1614 <a href="/teams">Giftella for Teams</a> 1615 <a href="/teams/pricing">Team pricing</a> 1616 </div> 1617 <div> 1618 <h5>Support</h5> 1619 <a href="/support">Support</a> 1620 <a href="/plus">Giftella+</a> 1621 </div> 1622 </div> 1623 <nav class="lp-footer-links"> 1624 <a href="/privacy">Privacy</a> 1625 <a href="/terms">Terms</a> 1626 <a href="https://apps.apple.com/us/app/giftella/id6761128527" target="_blank" rel="noopener">iOS app</a> 1627 <a href="https://play.google.com/store/apps/details?id=app.giftella.giftella" target="_blank" rel="noopener">Android app</a> 1628 </nav> 1629 <div class="lp-socials"> 1630 <a href="https://instagram.com/giftella.app" target="_blank" rel="noopener" aria-label="Giftella on Instagram" title="Instagram"> 1631 <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.16c3.2 0 3.58.01 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.42.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s-.01 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.42.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58-.01-4.85-.07c-1.17-.05-1.8-.25-2.23-.41a3.7 3.7 0 0 1-1.38-.9 3.7 3.7 0 0 1-.9-1.38c-.16-.42-.36-1.06-.41-2.23-.06-1.27-.07-1.65-.07-4.85s.01-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.42-.16 1.06-.36 2.23-.41C8.42 2.17 8.8 2.16 12 2.16zm0 3.68A6.16 6.16 0 1 0 18.16 12 6.16 6.16 0 0 0 12 5.84zm0 10.16A4 4 0 1 1 16 12a4 4 0 0 1-4 4zm6.4-10.41a1.44 1.44 0 1 1-1.44-1.44 1.44 1.44 0 0 1 1.44 1.44z"/></svg> 1632 </a> 1633 <a href="https://www.tiktok.com/@giftella.app" target="_blank" rel="noopener" aria-label="Giftella on TikTok" title="TikTok"> 1634 <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M16.6 5.82A4.28 4.28 0 0 1 15.54 3h-3.2v12.2a2.6 2.6 0 1 1-2.6-2.6c.27 0 .53.04.78.12V9.4a5.9 5.9 0 0 0-.78-.05 5.82 5.82 0 1 0 5.82 5.82V8.9a7.4 7.4 0 0 0 4.34 1.39V7.1a4.28 4.28 0 0 1-3.3-1.28z"/></svg> 1635 </a> 1636 <a href="https://www.linkedin.com/company/giftella-app" target="_blank" rel="noopener" aria-label="Giftella on LinkedIn" title="LinkedIn"> 1637 <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M4.98 3.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5zM3 9h4v12H3zm6 0h3.8v1.64h.05c.53-1 1.83-2.06 3.76-2.06 4.02 0 4.76 2.65 4.76 6.1V21h-4v-5.4c0-1.29-.02-2.95-1.8-2.95-1.8 0-2.08 1.4-2.08 2.85V21H9z"/></svg> 1638 </a> 1639 <a href="https://www.facebook.com/giftella.app" target="_blank" rel="noopener" aria-label="Giftella on Facebook" title="Facebook"> 1640 <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M22 12a10 10 0 1 0-11.56 9.88v-6.99H7.9V12h2.54V9.8c0-2.5 1.49-3.89 3.78-3.89 1.09 0 2.24.2 2.24.2v2.46h-1.26c-1.24 0-1.63.77-1.63 1.56V12h2.78l-.44 2.89h-2.34v6.99A10 10 0 0 0 22 12z"/></svg> 1641 </a> 1642 </div> 1643 <div class="lp-footer-made">Made with love by Giftella · Shop smarter. Gift warmer.<br>© 2026 Giftella, Inc. · A Delaware corporation · All rights reserved · <a href="/privacy" style="color:inherit;">Privacy</a> · <a href="/terms" style="color:inherit;">Terms</a></div> 1644 </div> 1645 </footer> 1646
1647<script> 1648/* Occasion tiles: the photo URLs above are baked in, so they paint with the 1649 page and cost nothing at runtime. This only handles the two edge cases. 1650 1651 1. A tile we never managed to resolve ships with no src at all. 1652 2. A baked URL that has since expired 404s. 1653 1654 Both fall back to the emoji, then quietly re-resolve through the app's own 1655 resolver once it is ready, so the page heals itself instead of rotting. */ 1656(function () { 1657 function markLoaded(img){ img.classList.add("loaded"); } 1658 // Hero orbs: same fade-in, and the emoji tile underneath stays if a photo 1659 // never arrives, so an orb is never an empty white box. 1660 [].slice.call(document.querySelectorAll('.n-orb img')).forEach(function (im) { 1661 if (!im.getAttribute('src')) return; 1662 if (im.complete && im.naturalWidth) { im.classList.add('loaded'); return; } 1663 im.addEventListener('load', function () { im.classList.add('loaded'); }); 1664 im.addEventListener('error', function () { im.remove(); }); 1665 }); 1666 1667 var tiles = [].slice.call(document.querySelectorAll('.n-shot img[data-q]')); 1668 var needy = []; 1669 tiles.forEach(function (img) { 1670 if (!img.getAttribute("src")) { needy.push(img); return; } 1671 if (img.complete) { img.naturalWidth ? markLoaded(img) : needy.push(img); } 1672 else { img.addEventListener("load", function(){ markLoaded(img); }); 1673 img.addEventListener("error", function(){ needy.push(img); reheal(); }); } 1674 }); 1675 var healing = false; 1676 function reheal() { 1677 if (healing || !needy.length) return; 1678 healing = true; 1679 var tries = 0; 1680 var t = setInterval(function () { 1681 if (window.giftellaResolveImage) { clearInterval(t); run(); } 1682 else if (tries++ > 80) { clearInterval(t); } 1683 }, 100); 1684 function run() { 1685 var queue = needy.splice(0, needy.length); 1686 (function next() { 1687 var img = queue.shift(); 1688 if (!img) { healing = false; return; } 1689 var q = img.getAttribute("data-q"); 1690 if (!q) return next(); 1691 window.giftellaResolveImage(q).then(function (url) { 1692 if (url && img.isConnected) { 1693 img.onload = function () { markLoaded(img); }; 1694 img.src = url; 1695 } 1696 next(); 1697 }).catch(next); 1698 })(); 1699 } 1700 } 1701 if (needy.length) reheal(); 1702})(); 1703</script>
1703 1704 1705 <!-- ===== SIGN-IN PANEL ===== 1706 This was a card inside the hero grid, and it was both the layout bug and 1707 a design one. A 661px login form in the right column made that row ~880px 1708 tall while the copy column was ~420px; align-items:center then parked 1709 ~230px of dead space above the headline. It also meant the first thing a 1710 stranger saw on the page was a password field. 1711 It now slides in from the right, opened by any existing href="#signin" 1712 link, so every CTA on the page keeps working and every auth id, class and 1713 event handler below is byte-for-byte what it was. --> 1714 <div class="n-auth-scrim" id="authScrim" hidden></div> 1715 <aside class="n-auth-panel" id="authPanel" hidden aria-label="Sign in to Giftella"> 1716 <button class="n-auth-close" id="authClose" aria-label="Close sign in">✕</button> 1717 <div class="lp-auth" id="signin"> 1718 <div class="hero-emoji">🎁</div> 1719 <h2>Welcome to Giftella</h2> 1720 <p class="sub">Sign in to start gifting.</p> 1721 <div class="login-stack"> 1722 <button class="btn-ghost" id="btn-google"> 1723 <svg class="provider-icon" viewBox="0 0 48 48" aria-hidden="true"><path fill="#FFC107" d="M43.6 20.5H42V20H24v8h11.3c-1.6 4.7-6.1 8-11.3 8-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.5 4.5 29.5 2.5 24 2.5 12.1 2.5 2.5 12.1 2.5 24S12.1 45.5 24 45.5 45.5 35.9 45.5 24c0-1.2-.1-2.3-.4-3.5z"/><path fill="#FF3D00" d="M5.3 14.7l6.6 4.8C13.7 15.1 18.5 12 24 12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.5 6.1 29.5 4.1 24 4.1 16 4.1 9.1 8.6 5.3 14.7z"/><path fill="#4CAF50" d="M24 45.5c5.4 0 10.3-2 14-5.3l-6.5-5.5c-2 1.5-4.6 2.4-7.5 2.4-5.2 0-9.6-3.3-11.2-7.9l-6.5 5C9 41.2 15.9 45.5 24 45.5z"/><path fill="#1976D2" d="M43.6 20.5H42V20H24v8h11.3c-.8 2.2-2.2 4.1-4 5.4l6.5 5.5c-.5.4 7-5.1 7-14.4 0-1.2-.1-2.3-.2-3z"/></svg> 1724 Continue with Google 1725 </button> 1726 <button class="btn-ghost" id="btn-apple"> 1727 <svg class="provider-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M16.5 1.6c0 1.1-.4 2.1-1.2 3-.9 1-2 1.6-3.1 1.5-.1-1.1.4-2.2 1.2-3 .8-1 2.1-1.6 3.1-1.5zM20.7 17c-.5 1.2-.8 1.7-1.5 2.8-1 1.5-2.3 3.3-4 3.3-1.5 0-1.9-1-4-.9-2 0-2.5.9-4 .9-1.6 0-2.9-1.6-3.8-3.1C.8 16.6.5 11.7 2.5 9.1c1-1.3 2.5-2.1 4-2.1 1.5 0 2.5 1 3.8 1 1.2 0 2-1 3.8-1 1.3 0 2.7.7 3.7 2-3.3 1.8-2.7 6.4 0 8z"/></svg> 1728 Continue with Apple 1729 </button> 1730 <button class="btn-ghost" id="btn-yahoo"> 1731 <svg class="provider-icon" viewBox="0 0 24 24" aria-hidden="true"><rect width="24" height="24" rx="5" fill="#6001D2"/><text x="12" y="17" font-size="13" font-family="Arial, Helvetica, sans-serif" font-weight="700" fill="#fff" text-anchor="middle">Y!</text></svg> 1732 Continue with Yahoo 1733 </button> 1734 <div class="divider">or use your email & password</div> 1735 <div class="email-row"> 1736 <input type="email" id="email-input" placeholder="[email protected]" autocomplete="email" inputmode="email" /> 1737 <input type="password" id="password-input" placeholder="Password" autocomplete="current-password" /> 1738 <button class="btn-plum" id="btn-email-signin">Sign in</button> 1739 <div class="login-links"> 1740 <button type="button" class="link-btn" id="btn-toggle-signup">Create an account</button> 1741 <button type="button" class="link-btn" id="btn-forgot">Forgot password?</button> 1742 </div> 1743 </div> 1744 <div class="msg" id="login-msg"></div> 1745 </div> 1746 <p class="reassure">Free to start · No credit card needed</p> 1747 </div> 1748 </aside> 1749
1750<script> 1751/* Open the panel from any existing href="#signin" link, so the nav CTA, the 1752 hero CTA and the closing CTA all keep working without being rewritten. */ 1753(function () { 1754 var panel = document.getElementById('authPanel'); 1755 var scrim = document.getElementById('authScrim'); 1756 var close = document.getElementById('authClose'); 1757 if (!panel || !scrim) return; 1758 var lastFocus = null; 1759 1760 function open(e) { 1761 if (e) e.preventDefault(); 1762 lastFocus = document.activeElement; 1763 scrim.hidden = false; panel.hidden = false; 1764 requestAnimationFrame(function () { 1765 scrim.classList.add('on'); panel.classList.add('on'); 1766 }); 1767 document.body.style.overflow = 'hidden'; 1768 setTimeout(function () { 1769 var f = document.getElementById('btn-google'); 1770 try { f && f.focus({ preventScroll: true }); } catch (_) {} 1771 }, 260); 1772 } 1773 function shut() { 1774 scrim.classList.remove('on'); panel.classList.remove('on'); 1775 document.body.style.overflow = ''; 1776 setTimeout(function () { scrim.hidden = true; panel.hidden = true; }, 260); 1777 try { lastFocus && lastFocus.focus({ preventScroll: true }); } catch (_) {} 1778 } 1779 1780 document.addEventListener('click', function (e) { 1781 var a = e.target.closest && e.target.closest('a[href="#signin"]'); 1782 if (a) open(e); 1783 }); 1784 close && close.addEventListener('click', shut); 1785 scrim.addEventListener('click', shut); 1786 document.addEventListener('keydown', function (e) { 1787 if (e.key === 'Escape' && !panel.hidden) shut(); 1788 }); 1789 // Deep links like giftella.app/#signin still land on the form. 1790 if (location.hash === '#signin') setTimeout(open, 400); 1791})(); 1792</script>
1792 1793 </div> 1794</section> 1795 1796<!-- ===================== ONBOARDING ===================== --> 1797 1798<!-- ===================== ONBOARDING (legacy, kept hidden; profile setup now lives in-app) ===================== --> 1799<section id="onboard-screen" class="screen" style="display:none"></section> 1800 1801<!-- ===================== MAIN APP (Sep 2026 overhaul) ===================== --> 1802<div id="main-app"> 1803 <header class="ga-topbar"> 1804 <div class="ga-topbar-inner"> 1805 <a class="ga-brand" id="ga-brand" href="#you" aria-label="Giftella home"> 1806 <img src="/giftella-logo.png" alt="" /> 1807 <span class="wordmark">Giftella</span> 1808 </a> 1809 <!-- Founder (Sep 10): the search is back, but it is a SEARCH, not a second gift 1810 generator. It finds what already exists (wishlist, circle, partner brands, 1811 pages) and hands ideas off to the one finder on the You tab. --> 1812 <form class="ga-search" id="ga-search" role="search" aria-label="Search Giftella"> 1813 <div class="ga-search-box"> 1814 <span class="ga-search-ico" aria-hidden="true">ð</span> 1815 <input class="ga-search-input" id="ga-search-input" type="text" 1816 placeholder="Search your gifts, friends, wishlist, brands…" 1817 role="combobox" aria-expanded="false" aria-controls="ga-search-panel" 1818 aria-autocomplete="list" aria-label="Search your gifts, friends, wishlist and brands" 1819 autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" 1820 enterkeyhint="go" maxlength="80" /> 1821 <kbd class="ga-search-kbd" aria-hidden="true">⌘K</kbd> 1822 <button type="button" class="ga-search-x" id="ga-search-clear" aria-label="Clear search" hidden>✕</button> 1823 </div> 1824 <div class="ga-search-panel" id="ga-search-panel" role="listbox" aria-label="Search results" hidden></div> 1825 </form> 1826 <nav class="ga-nav" aria-label="Primary"> 1827 <button type="button" class="ga-navlink" data-nav="you">You</button> 1828 <button type="button" class="ga-navlink" data-nav="circle">Circle <span id="ga-req-badge" class="ga-badge-count ga-hide">0</span></button> 1829 <button type="button" class="ga-navlink" data-nav="wishlist">Wishlist</button> 1830 <button type="button" class="ga-navlink" data-nav="shop">Ecosystem</button> 1831 <button type="button" class="ga-navlink plus" data-nav="plus" id="ga-nav-plus">Giftella+ â¦</button> 1832 <button type="button" class="ga-iconbtn ga-search-toggle" id="ga-search-toggle" aria-label="Search" title="Search" aria-expanded="false">ð</button> 1833 <button type="button" class="ga-iconbtn" data-theme-toggle aria-label="Toggle dark mode" title="Toggle theme">ð</button> 1834 <div class="ga-menu-wrap"> 1835 <button type="button" class="ga-avatar" id="ga-av" aria-label="Account menu">ð</button> 1836 <div class="ga-menu" id="ga-menu" role="menu"> 1837 <div class="mh"><b id="ga-menu-name">You</b><span id="ga-menu-handle"></span></div> 1838 <button type="button" class="mi" id="ga-menu-profile">ð¤ Edit profile</button> 1839 <button type="button" class="mi" id="ga-menu-wishlist">ð My wishlist</button> 1840 <button type="button" class="mi" id="ga-menu-share">ð Share my wishlist</button> 1841 <button type="button" class="mi gold" id="ga-menu-plus">⦠Giftella+</button> 1842 <button type="button" class="mi" id="ga-menu-signout">ðª Sign out</button> 1843 </div> 1844 </div> 1845 </nav> 1846 </div> 1847 </header> 1848 1849 <main id="ga-main"> 1850 <div id="page-you" class="ga-screen"></div> 1851 <div id="page-circle" class="ga-screen"></div> 1852 <div id="page-wishlist" class="ga-screen"></div> 1853 <div id="page-shop" class="ga-screen"></div> 1854 <div id="page-plus" class="ga-screen"></div> 1855 </main> 1856 1857 <nav class="ga-tabbar" aria-label="Tabs"> 1858 <div class="tabs"> 1859 <button type="button" class="ga-tab" data-nav="you"><span class="ico">â¨</span><span class="lbl">You</span></button> 1860 <button type="button" class="ga-tab" data-nav="circle"><span class="ico">ð¥</span><span class="lbl">Circle</span><span class="dot ga-hide"></span></button> 1861 <button type="button" class="ga-tab" data-nav="wishlist"><span class="ico">ð</span><span class="lbl">Wishlist</span></button> 1862 <button type="button" class="ga-tab" data-nav="shop"><span class="ico">ðï¸</span><span class="lbl">Shop</span></button> 1863 <button type="button" class="ga-tab" data-nav="plus"><span class="ico">â¦</span><span class="lbl">Plus</span></button> 1864 </div> 1865 </nav> 1866</div> 1867 1868<div class="toast" id="toast"></div>
1869<div class="ga-toast" id="ga-toast" role="status" aria-live="polite"></div> 1870
1871<script type="module" src="/app/app.js?v=2026.09.11a"></script>
1871 1872 1873<!-- ===== Homepage gift-browse rails (occasions + genres) ===== -->
1874<script> 1875(function () { 1876 // Each rail: { id, emoji, title, sub, q (category search), items: [[name, emoji, price, query, imgKeywords]] } 1877 var RAILS = [ 1878 { id:"birthday", emoji:"ð", title:"Birthday gifts", sub:"Crowd-pleasers they'll actually use", q:"birthday gifts", items:[ 1879 ["Stanley Quencher 40oz Tumbler","ð¥¤","$45","Stanley Quencher 40oz tumbler","tumbler,steel"], 1880 ["Polaroid Now Instant Camera","ð·","$100","Polaroid Now instant camera","instant,camera"], 1881 ["JBL Flip 6 Bluetooth Speaker","ð","$100","JBL Flip 6 speaker","speaker,bluetooth"], 1882 ["LEGO Icons Bouquet","ð","$60","LEGO flower bouquet","lego,flowers"], 1883 ["Apple AirTag 4-Pack","ð","$79","Apple AirTag 4 pack","keychain,tracker"], 1884 ["Hatch Restore Sunrise Alarm","ð ","$170","Hatch Restore sunrise alarm","alarm,bedside"] 1885 ]}, 1886 { id:"anniversary", emoji:"ð", title:"Anniversary", sub:"Romantic, a little elevated", q:"anniversary gifts", items:[ 1887 ["Custom Star Map Print","â¨","$30","custom star map print","stars,art"], 1888 ["Le Creuset Dutch Oven","ð²","$380","Le Creuset dutch oven","cookware,pot"], 1889 ["Silk Pajama Set","ð","$60","silk pajama set","silk,pajamas"], 1890 ["Leather Watch","â","$150","minimalist leather watch","watch,leather"], 1891 ["Champagne Flute Set","ð¥","$40","champagne flutes set","champagne,glasses"], 1892 ["Photo Book, Hardcover","ð","$45","custom photo book hardcover","photo,album"] 1893 ]}, 1894 { id:"forher", emoji:"ð", title:"For Her", sub:"Thoughtful picks she'll love", q:"gifts for her", items:[ 1895 ["Gold Pendant Necklace","ð¿","$55","dainty gold pendant necklace","necklace,gold"], 1896 ["Diptyque Candle","ð¯ï¸","$40","Diptyque scented candle","candle,luxury"], 1897 ["Silk Hair Scrunchie Set","ð","$20","silk scrunchie set","silk,hair"], 1898 ["Cashmere Beanie","ð§£","$50","cashmere beanie","beanie,knit"], 1899 ["Theragun Mini","ð","$150","Theragun mini massager","massage,recovery"], 1900 ["Rare Beauty Blush","ð","$23","Rare Beauty blush","makeup,blush"] 1901 ]}, 1902 { id:"forhim", emoji:"ð§", title:"For Him", sub:"No more guessing", q:"gifts for him", items:[ 1903 ["Leather Dopp Kit","ð§°","$45","leather dopp kit toiletry bag","leather,bag"], 1904 ["Whiskey Stones Set","ð¥","$30","whiskey stones gift set","whiskey,bar"], 1905 ["Carhartt Beanie","ð§¢","$20","Carhartt knit beanie","beanie,winter"], 1906 ["Bose QuietComfort Earbuds","ð§","$180","Bose QuietComfort earbuds","earbuds,audio"], 1907 ["Ember Smart Mug","â","$130","Ember temperature mug","mug,coffee"], 1908 ["Multi-Tool Pocket Knife","ð§","$90","Leatherman multitool","tool,gear"] 1909 ]}, 1910 { id:"graduation", emoji:"ð", title:"Graduation", sub:"For the next chapter", q:"graduation gifts", items:[ 1911 ["Kindle Paperwhite","ð","$150","Kindle Paperwhite","ereader,books"], 1912 ["Apple AirPods Pro","ð§","$249","Apple AirPods Pro","earbuds,apple"], 1913 ["Leather Portfolio","ð¼","$40","leather portfolio padfolio","portfolio,leather"], 1914 ["Hydro Flask 32oz","ð§","$45","Hydro Flask 32oz","bottle,water"], 1915 ["Instant Photo Printer","ð¨ï¸","$100","HP Sprocket photo printer","printer,photo"], 1916 ["Desk Lamp, Modern","ð¡","$45","modern LED desk lamp","lamp,desk"] 1917 ]}, 1918 { id:"wedding", emoji:"ð", title:"Wedding", sub:"Registry-worthy & lasting", q:"wedding gifts", items:[ 1919 ["KitchenAid Stand Mixer","ð°","$430","KitchenAid stand mixer","mixer,kitchen"], 1920 ["Breville Espresso Machine","â","$700","Breville Barista espresso machine","espresso,machine"], 1921 ["Chef Knife Set","ðª","$120","chef knife block set","knives,kitchen"], 1922 ["Matching Robe Set","ð","$70","his and hers robe set","robe,spa"], 1923 ["Crystal Wine Decanter","ð·","$50","crystal wine decanter","wine,decanter"], 1924 ["Personalized Cutting Board","ðªµ","$40","personalized cutting board","wood,board"] 1925 ]}, 1926 { id:"coffee", emoji:"â", title:"Coffee lover", sub:"Café-grade at home", q:"coffee lover gifts", items:[ 1927 ["Fellow Stagg Gooseneck Kettle","ð«","$165","Fellow Stagg gooseneck kettle","kettle,coffee"], 1928 ["Baratza Encore Grinder","âï¸","$170","Baratza Encore burr grinder","grinder,coffee"], 1929 ["Chemex Pour-Over","ð","$45","Chemex pour over coffee maker","chemex,pourover"], 1930 ["Specialty Beans Sampler","ð«","$35","specialty coffee beans sampler","coffee,beans"], 1931 ["Ember Mug²","ð¥","$130","Ember smart mug","mug,smart"], 1932 ["French Press, Glass","â","$30","glass french press","frenchpress,coffee"] 1933 ]}, 1934 { id:"selfcare", emoji:"ð", title:"Cozy & self-care", sub:"Permission to slow down", q:"self care gifts", items:[ 1935 ["Weighted Blanket","ð","$60","weighted blanket 15lb","blanket,cozy"], 1936 ["Silk Eye Mask","ð´","$25","silk sleep eye mask","silk,sleep"], 1937 ["Aromatherapy Diffuser","ð«ï¸","$40","essential oil diffuser","diffuser,spa"], 1938 ["Bath & Body Gift Set","ð§´","$35","luxury bath gift set","bath,spa"], 1939 ["Shearling Slippers","ð¥¿","$50","cozy shearling slippers","slippers,cozy"], 1940 ["Loose-Leaf Tea Sampler","ðµ","$28","loose leaf tea sampler","tea,leaves"] 1941 ]}, 1942 { id:"gamer", emoji:"ð®", title:"Gamer", sub:"Level up their setup", q:"gamer gifts", items:[ 1943 ["SteelSeries Arctis Headset","ð§","$100","SteelSeries Arctis gaming headset","headset,gaming"], 1944 ["Mechanical Keyboard","â¨ï¸","$90","mechanical gaming keyboard","keyboard,rgb"], 1945 ["Extra Controller","ð®","$70","wireless game controller","controller,console"], 1946 ["Gaming Mouse","ð±ï¸","$60","Logitech gaming mouse","mouse,gaming"], 1947 ["RGB LED Light Strip","ð","$25","RGB LED light strip","rgb,lights"], 1948 ["Game Store Gift Card","ðï¸","$50","video game gift card","gift,card"] 1949 ]}, 1950 { id:"kids", emoji:"ð§", title:"For kids", sub:"Hours of (screen-free) fun", q:"kids gifts", items:[
1951 ["LEGO Classic Set","ð§±","$50","LEGO classic brick set","lego,bricks"], 1952 ["Magna-Tiles 100pc","ð·","$120","Magna-Tiles 100 piece","tiles,build"], 1953 ["Kids' Scooter","ð´","$70","kids 3 wheel scooter","scooter,kids"], 1954 ["Art Supplies Set","ð¨","$30","kids art supplies set","art,crayons"], 1955 ["Plush Stuffed Animal","ð§¸","$25","jumbo plush teddy bear","plush,teddy"], 1956 ["STEM Building Kit","ð¤","$40","kids STEM robot kit","robot,stem"] 1957 ]} 1958 ]; 1959 1960 function amzn(q) { 1961 try { return amazonLink(q); } 1962 catch (e) { return "https://www.amazon.com/s?k=" + encodeURIComponent(q) + "&tag=giftellaapp04-20"; } 1963 } 1964 1965 function esc(s) { 1966 return String(s == null ? "" : s).replace(/[&<>"]/g, function (c) { 1967 return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; 1968 }); 1969 } 1970 1971 function railHTML(rail) { 1972 var cards = rail.items.map(function (it) { 1973 var name = it[0], emoji = it[1], price = it[2], q = it[3]; 1974 return '' + 1975 '<a class="rail-card" href="' + amzn(q) + '" data-shop="' + esc(amzn(q)) + '" rel="noopener sponsored">' + 1976 '<span class="rail-tile tile-' + rail.id + '">' + 1977 '<span class="rail-emoji">' + emoji + '</span>' + 1978 // Painted from the batch the page already fetched, so a rail 1979 // opened later has its photo in the HTML the moment it renders. 1980 // referrerpolicy matters: the CDN these thumbnails come from 1981 // refuses some requests that carry a referrer, and a refused 1982 // image is exactly the blank tile this is fixing. 1983 // `ready` when we already hold the address: the photo layer is 1984 // opaque in the SAME paint that creates it, so nothing has to 1985 // fade in and no script has to run first. 1986 '<img class="rail-photo' + (railPhotoFor(q) ? ' ready' : '') + '" ' + 1987 'alt="' + esc(name) + '" decoding="async" loading="eager" ' + 1988 'referrerpolicy="no-referrer" data-q="' + esc(q) + '"' + 1989 (railPhotoFor(q) ? (' src="' + esc(railPhotoFor(q)) + '"') : '') + '>' + 1990 '</span>' + 1991 '<span class="rail-body">' + 1992 '<span class="rail-name">' + esc(name) + '</span>' + 1993 '<span class="rail-meta"><span class="rail-price">' + esc(price) + '</span><span class="rail-shop">Shop â</span></span>' + 1994 '</span>' + 1995 '</a>'; 1996 }).join(""); 1997 return '' + 1998 '<div class="gift-rail" id="rail-' + rail.id + '">' + 1999 '<div class="rail-head">' + 2000 '<span class="rh-emoji">' + rail.emoji + '</span>' + 2001 '<h3>' + esc(rail.title) + '</h3>' + 2002 '<span class="rh-sub">' + esc(rail.sub) + '</span>' + 2003 '<span class="rh-spacer"></span>' + 2004 '<a class="rh-all" href="' + amzn(rail.q) + '" data-shop="' + esc(amzn(rail.q)) + '" rel="noopener sponsored">See more â</a>' + 2005 '</div>' + 2006 '<div class="rail-scroll">' + cards + '</div>' + 2007 '</div>'; 2008 } 2009 2010 // ---- Login gate: every gift click asks the visitor to sign in first ---- 2011 function flashToast(msg) { 2012 var t = document.getElementById("toast"); 2013 if (!t) return; 2014 t.textContent = msg; 2015 t.classList.add("show"); 2016 clearTimeout(window.__giftToastTimer); 2017 window.__giftToastTimer = setTimeout(function () { t.classList.remove("show"); }, 2600); 2018 } 2019 function gateToLogin(url) { 2020 try { if (url) sessionStorage.setItem("giftella_pendingShop", url); } catch (e) {} 2021 var card = document.getElementById("signin"); 2022 if (card) { 2023 card.scrollIntoView({ behavior: "smooth", block: "center" }); 2024 card.classList.remove("pulse"); 2025 void card.offsetWidth; // restart the animation 2026 card.classList.add("pulse"); 2027 var email = document.getElementById("email-input"); 2028 setTimeout(function () { try { email && email.focus({ preventScroll: true }); } catch (e) {} }, 650); 2029 } 2030 flashToast("Sign in (free) to shop & save this gift â¨"); 2031 } 2032 2033 // ---- Real product photos: resolved lazily via the module's anonymous 2034 // image resolver (window.giftellaResolveImage), which proxies the same 2035 // SerpAPI-backed productImage Cloud Function the registry/app use. The 2036 // gradient + emoji tile shows underneath until the real photo fades in. ---- 2037 // 2038 // ONE REQUEST, NOT SIXTY (20 Sep 2026) 2039 // 2040 // FOUNDER: "some of the gifts don't have product images, all gifts should 2041 // have its product image." 2042 // 2043 // Every one of them resolves fine on its own. The problem was asking for 2044 // them the expensive way. This shelf is TEN fixed rails of six fixed 2045 // products: the same sixty items for every visitor, every day. The old code 2046 // resolved each one individually through the productImage Cloud Function, 2047 // which is rate limited to 120 lookups a minute PER IP and spends a SerpAPI 2048 // call on a cache miss. A visitor flicking through a few categories could 2049 // trip that limit, and every card that lost the race fell back to its emoji 2050 // with nothing left to retry it. Three blanks in a row of six is what that 2051 // looks like on screen. 2052 // 2053 // /api/guide-images answers all sixty out of the shared productImageCache in 2054 // ONE POST. Not rate limited, edge cached for a day, spends nothing. The 2055 // per-card resolver stays as the repair path for a product the cache has 2056 // never seen, which is now the rare case instead of the normal one. 2057 // 2058 // The addresses are memoised on the device, because they are the same sixty 2059 // products for everyone and they do not change. A returning visitor has the 2060 // whole map before the first byte of network, so the opening rail paints 2061 // with photos instead of fading them in. The batch below still runs and 2062 // still wins, it just is not the thing the first frame waits on. 2063 var RAIL_IMG_LS = "giftella.railImages.v1"; 2064 var RAIL_IMG_TTL = 7 * 24 * 60 * 60 * 1000; 2065 function loadRailMemo() { 2066 try { 2067 var raw = JSON.parse(localStorage.getItem(RAIL_IMG_LS) || "null"); 2068 if (!raw || !raw.at || !raw.images) return {}; 2069 if (Date.now() - raw.at > RAIL_IMG_TTL) return {}; 2070 return raw.images; 2071 } catch (e) { return {}; } 2072 } 2073 function saveRailMemo(images) { 2074 try { localStorage.setItem(RAIL_IMG_LS, JSON.stringify({ at: Date.now(), images: images })); } 2075 catch (e) { /* private mode, full quota â the batch still works */ } 2076 } 2077 2078 var _railImgs = loadRailMemo(); // slug -> url 2079 var _railImgsReady = null; // promise, resolves once the batch lands 2080 function railSlug(q) { 2081 return String(q || "").toLowerCase().replace(/[^a-z0-9\s]/g, " ").trim()
2082 .split(/\s+/).filter(Boolean).join("_").slice(0, 200); 2083 } 2084 function railPhotoFor(q) { return _railImgs[railSlug(q)] || ""; } 2085 2086 // Every query on the page: all ten rails, plus the occasion cards that ship 2087 // without a baked src. One call covers the whole page. 2088 function allPhotoQueries() { 2089 var out = [], seen = {}; 2090 var push = function (q) { if (q && !seen[q]) { seen[q] = 1; out.push(q); } }; 2091 for (var i = 0; i < RAILS.length; i++) { 2092 for (var j = 0; j < RAILS[i].items.length; j++) push(RAILS[i].items[j][3]); 2093 } 2094 document.querySelectorAll("img[data-q]:not([src])").forEach(function (img) { 2095 push(img.getAttribute("data-q")); 2096 }); 2097 return out.slice(0, 120); // the endpoint's ceiling 2098 } 2099 2100 function prefetchRailPhotos() { 2101 if (_railImgsReady) return _railImgsReady; 2102 var queries = allPhotoQueries(); 2103 if (!queries.length) { _railImgsReady = Promise.resolve({}); return _railImgsReady; } 2104 _railImgsReady = fetch("/api/guide-images", { 2105 method: "POST", 2106 headers: { "Content-Type": "application/json" }, 2107 body: JSON.stringify({ queries: queries }) 2108 }).then(function (r) { return r.ok ? r.json() : { images: {} }; }) 2109 .then(function (d) { 2110 var fresh = (d && d.images) || {}; 2111 // Merge rather than replace: a product the cache has not seen yet 2112 // keeps whatever the device already knew about it. 2113 Object.keys(fresh).forEach(function (k) { if (fresh[k]) _railImgs[k] = fresh[k]; }); 2114 saveRailMemo(_railImgs); 2115 warmRailBytes(); 2116 return _railImgs; 2117 }) 2118 .catch(function () { _railImgs = {}; return _railImgs; }); 2119 return _railImgsReady; 2120 } 2121 2122 // FOUNDER (20 Sep 2026): "when I click the different gift ideas there's this 2123 // lag, I see the emoji then the picture. It should be the gift image 2124 // immediately, no emoji." 2125 // 2126 // Having the URL is not the same as having the PICTURE. The batch gives the 2127 // page all sixty addresses in one request, but the browser still has to go 2128 // and fetch the bytes for a tile the moment it is rendered, and the reader 2129 // watches that happen: emoji, pause, crossfade. 2130 // 2131 // So every photo on the page is downloaded AND decoded off-screen as soon as 2132 // the addresses land, while the reader is still looking at the first rail. 2133 // By the time any chip is tapped the bytes are in the HTTP cache and already 2134 // decoded, so the tile paints in the same frame it renders. Decoding matters 2135 // as much as downloading: a downloaded-but-undecoded image still costs a 2136 // frame, which is exactly the flicker being removed. 2137 var _railBytesWarmed = {}; 2138 function warmRailBytes() { 2139 Object.keys(_railImgs).forEach(function (k) { 2140 var url = _railImgs[k]; 2141 if (!url || _railBytesWarmed[url]) return; 2142 _railBytesWarmed[url] = 1; 2143 var pre = new Image(); 2144 pre.referrerPolicy = "no-referrer"; 2145 pre.decoding = "async"; 2146 pre.src = url; 2147 if (typeof pre.decode === "function") { pre.decode().catch(function () {}); } 2148 }); 2149 } 2150 2151 function whenResolverReady(cb) { 2152 if (window.giftellaResolveImage) return cb(); 2153 var tries = 0; 2154 var t = setInterval(function () { 2155 if (window.giftellaResolveImage || tries++ > 60) { clearInterval(t); cb(); } 2156 }, 100); 2157 } 2158 2159 function paintRailPhoto(img, url) { 2160 if (!url || !img.isConnected) return false; 2161 // Already decoded (the warm pass above): show it in this frame with no 2162 // transition. Fading in a picture the browser already holds is the 2163 // flicker, not the cure for it. 2164 var showNow = function () { 2165 img.classList.add("instant"); 2166 img.classList.add("loaded"); 2167 }; 2168 img.onload = function () { 2169 // Only the photos that genuinely had to travel get the fade. 2170 img.classList.add("loaded"); 2171 }; 2172 // A photo that refuses to load must not leave a broken-image glyph sitting 2173 // on the tile: hide it and let the gradient and emoji stand on their own. 2174 img.onerror = function () { img.style.display = "none"; img.classList.remove("loaded"); }; 2175 if (img.getAttribute("src") !== url) img.src = url;
2176 if (img.complete && img.naturalWidth > 0) showNow(); 2177 return true; 2178 } 2179 2180 function hydrateRailPhotos(railEl) { 2181 var imgs = railEl.querySelectorAll("img.rail-photo[data-q]:not([data-done])"); 2182 if (!imgs.length) return; 2183 prefetchRailPhotos().then(function () { 2184 var leftovers = []; 2185 imgs.forEach(function (img) { 2186 img.setAttribute("data-done", "1"); 2187 var q = img.getAttribute("data-q"); 2188 if (!q) return; 2189 if (!paintRailPhoto(img, railPhotoFor(q))) leftovers.push(img); 2190 }); 2191 if (!leftovers.length) return; 2192 // Repair path. Anything the shared cache has never seen gets one live 2193 // lookup, and that result warms the cache for every visitor after this. 2194 whenResolverReady(function () { 2195 leftovers.forEach(function (img) { 2196 var q = img.getAttribute("data-q"); 2197 if (!q || !window.giftellaResolveImage) return; 2198 window.giftellaResolveImage(q).then(function (url) { 2199 if (!url) { img.removeAttribute("data-done"); return; } 2200 _railImgs[railSlug(q)] = url; 2201 paintRailPhoto(img, url); 2202 }).catch(function () { img.removeAttribute("data-done"); }); 2203 }); 2204 }); 2205 }); 2206 } 2207 2208 // The occasion and guide cards further down the page carry data-q too. The 2209 // ones that shipped without a baked src used to sit on their emoji forever. 2210 function hydrateLooseCards() { 2211 var imgs = document.querySelectorAll("img[data-q]:not(.rail-photo):not([src])"); 2212 if (!imgs.length) return; 2213 prefetchRailPhotos().then(function () { 2214 imgs.forEach(function (img) { 2215 var url = railPhotoFor(img.getAttribute("data-q")); 2216 if (!url) return; 2217 img.referrerPolicy = "no-referrer"; 2218 img.onload = function () { img.classList.add("loaded"); }; 2219 img.onerror = function () { img.style.display = "none"; }; 2220 img.src = url; 2221 }); 2222 }); 2223 } 2224 // startPhotoHydration() removed: rails are no longer all on the page at 2225 // once, so there is nothing to lazily observe â showRail() hydrates the one 2226 // rail it just rendered. 2227 2228 // ââ ONE RAIL AT A TIME âââââââââââââââââââââââââââââââââââââââââââââââ 2229 // 2230 // FOUNDER, 31 Aug 2026: "it's a little long now with a lot of things". 2231 // 2232 // This section used to stack EVERY category down the page â twelve rails, 2233 // six products each â and the chips above them were only anchor links that 2234 // scrolled you to one. So the chips looked like a filter, behaved like a 2235 // table of contents, and the page paid for all of it: roughly four thousand 2236 // pixels of scrolling, and seventy-two product photos resolving at once 2237 // through the image service. 2238 // 2239 // The chips are a real filter now. Pick who you are shopping for, see those 2240 // gifts. Same content, one screen instead of eleven, and six photos in 2241 // flight instead of seventy-two. 2242 var railsEl = document.getElementById("giftRails"); 2243 var tabsEl = document.getElementById("browseTabs"); 2244 var activeRail = null; 2245 2246 // THE ICON BEFORE THE PHOTO (20 Sep 2026) 2247 // 2248 // FOUNDER: "it doesn't make sense that when i click the different interests, 2249 // the gift images load with an icon first \u2014 especially since the images for 2250 // the gifts are already set." 2251 // 2252 // He was right, and the previous attempt at this could not have worked. 2253 // Every chip tap did `railsEl.innerHTML = railHTML(rail)`, which DESTROYS 2254 // the old <img> elements and creates brand new ones. The code right after it 2255 // then tried to mark the new images as loaded in the same tick, guarded by 2256 // `img.complete && img.naturalWidth > 0` \u2014 but a freshly parsed <img> is 2257 // never `complete`, even when its bytes are sitting in the HTTP cache, 2258 // because the load is queued as a task and cannot finish while this script 2259 // is still running. So the guard was false every single time. The photo 2260 // stayed at `opacity: 0`, the emoji underneath showed, and the picture only 2261 // faded in later when `onload` fired. That IS the icon he keeps
2261seeing. 2262 // 2263 // Rebuilding the markup was never necessary. Ten rails of six cards is sixty 2264 // thumbnails, and `warmRailBytes` already downloads and decodes all sixty at 2265 // boot, so there is no bandwidth to save by rendering them one rail at a 2266 // time. They are all built ONCE, and a chip tap now only flips which one is 2267 // hidden. Nothing is created, nothing reloads, no image event has to fire: 2268 // the photos for every rail are already decoded and already on screen the 2269 // frame the rail becomes visible. 2270 // 2271 // Hidden rails cost no layout (`hidden` is display:none) and their images 2272 // still download, which is exactly what we want. 2273 function buildRailsOnce() { 2274 if (!railsEl || railsEl.getAttribute("data-built")) return; 2275 railsEl.innerHTML = RAILS.map(railHTML).join(""); 2276 railsEl.setAttribute("data-built", "1"); 2277 railsEl.querySelectorAll(".gift-rail").forEach(function (el) { 2278 // Wire the tiles that shipped with an address BEFORE the async hydrate 2279 // runs, so `loaded` (the white stage and the hover zoom) lands the 2280 // moment the bytes do rather than a round trip later. 2281 el.querySelectorAll("img.rail-photo.ready").forEach(function (img) { 2282 img.setAttribute("data-done", "1"); 2283 if (img.complete && img.naturalWidth > 0) { 2284 img.classList.add("instant"); img.classList.add("loaded"); 2285 return; 2286 } 2287 img.addEventListener("load", function () { 2288 img.classList.add("instant"); img.classList.add("loaded"); 2289 }); 2290 img.addEventListener("error", function () { 2291 img.style.display = "none"; img.classList.remove("loaded"); 2292 }); 2293 }); 2294 // Everything else \u2014 tiles with no address yet \u2014 goes down the batch 2295 // lookup and, failing that, the one-off repair path. 2296 hydrateRailPhotos(el); 2297 }); 2298 } 2299 2300 function showRail(id) { 2301 if (!railsEl || id === activeRail) return; 2302 var rail = null; 2303 for (var i = 0; i < RAILS.length; i++) { if (RAILS[i].id === id) { rail = RAILS[i]; break; } } 2304 if (!rail) return; 2305 activeRail = id; 2306 buildRailsOnce(); 2307 var want = "rail-" + id; 2308 railsEl.querySelectorAll(".gift-rail").forEach(function (el) { 2309 el.hidden = el.id !== want; 2310 }); 2311 if (tabsEl) { 2312 tabsEl.querySelectorAll(".browse-tab").forEach(function (b) { 2313 var on = b.getAttribute("data-rail") === id; 2314 b.classList.toggle("on", on); 2315 b.setAttribute("aria-selected", on ? "true" : "false"); 2316 }); 2317 } 2318 } 2319 2320 if (railsEl) { 2321 railsEl.addEventListener("click", function (e) { 2322 var hit = e.target.closest("[data-shop]"); 2323 if (!hit) return; 2324 e.preventDefault(); 2325 gateToLogin(hit.getAttribute("data-shop")); 2326 }); 2327 } 2328 if (tabsEl) { 2329 tabsEl.setAttribute("role", "tablist"); 2330 tabsEl.innerHTML = RAILS.map(function (r, i) { 2331 return '<button class="browse-tab" role="tab" aria-selected="' + (i === 0) + 2332 '" data-rail="' + r.id + '">' + r.emoji + " " + esc(r.title) + '</button>'; 2333 }).join(""); 2334 tabsEl.addEventListener("click", function (e) { 2335 var btn = e.target.closest(".browse-tab"); 2336 if (!btn) return; 2337 showRail(btn.getAttribute("data-rail")); 2338 // Keep the chip you just picked fully on screen when the row scrolls. 2339 try { btn.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" }); } catch (err) {} 2340 }); 2341 // The edge fade should only appear when there is genuinely more to scroll to. 2342 var syncFade = function () { 2343 var over = tabsEl.scrollWidth - tabsEl.clientWidth > 2; 2344 tabsEl.classList.toggle("is-scrollable", over); 2345 tabsEl.classList.toggle("at-end", over && tabsEl.scrollLeft >= tabsEl.scrollWidth - tabsEl.clientWidth - 2); 2346 }; 2347 tabsEl.addEventListener("scroll", syncFade, { passive: true }); 2348 if (window.ResizeObserver) new ResizeObserver(syncFade).observe(tabsEl); 2349 else window.addEventListener("resize", syncFade); 2350 syncFade(); 2351 } 2352 // Fire the single photo request before the first rail is even rendered, so 2353 // the shelf people land on paints with photos instead of fading them in. 2354 // Anything the device already remembers starts downloading and decoding 2355 // right now, before the batch request has even been answered. 2356 if (Object.keys(_railImgs).length) warmRailBytes(); 2357 prefetchRailPhotos(); 2358 if (RAILS.length) showRail(RAILS[0].id); 2359 hydrateLooseCards(); 2360})(); 2361</script>
2361 2362<!-- Giftella premium polish layer --> 2363<style id="gf-polish"> 2364 html { scroll-behavior: smooth; } 2365 @media (prefers-reduced-motion: reduce){ html{ scroll-behavior:auto; } } 2366 .lp-nav-cta, .cta-primary, .store-badge, .occ-card, .gift-actions .shop, .lp-nav-links a.lp-link { 2367 transition: transform .18s cubic-bezier(.2,.8,.2,1), box-shadow .2s ease, background .18s ease, color .18s ease !important; 2368 } 2369 .lp-nav-cta:hover, .cta-primary:hover, .store-badge:hover { transform: translateY(-2px) scale(1.015) !important; } 2370 .occ-card:hover { transform: translateY(-4px) !important; } 2371 /* Scroll-reveal: only active once JS marks the doc ready (so content is never 2372 hidden without JS); opacity-only so it can't affect layout or positioning. */ 2373 html.gf-reveal [data-gf-reveal]{ opacity: 0; } 2374 html.gf-reveal [data-gf-reveal].gf-in{ opacity: 1; transition: opacity .7s cubic-bezier(.2,.7,.2,1); } 2375 @media (prefers-reduced-motion: reduce){ html.gf-reveal [data-gf-reveal]{ opacity:1 !important; } } 2376</style>
2377<script> 2378/* Giftella+ band price â fetched, never hardcoded. 2379 Stripe is the single source of truth for what a member pays, so the band 2380 asks the same endpoint checkout asks and prints whatever comes back. A 2381 price written into this file would drift the day it changes in Stripe, 2382 which is exactly what happened to the "$4.99/month" that used to sit here. */ 2383(function(){ 2384 var el = document.getElementById('n-plus-price'); 2385 if (!el || !window.fetch) return; 2386 fetch('/api/plus-checkout', { 2387 method: 'POST', 2388 headers: { 'Content-Type': 'application/json' }, 2389 body: JSON.stringify({ action: 'prices' }) 2390 }).then(function(r){ return r.ok ? r.json() : null; }).then(function(p){ 2391 if (!p || !p.monthly || !p.monthly.display) return; 2392 var bits = [p.monthly.display + '/month']; 2393 if (p.annual && p.annual.display) { 2394 bits.push(p.annual.display + '/year' + (p.annualSavingsPct ? ' (save ' + p.annualSavingsPct + '%)' : '')); 2395 } 2396 bits.push('cancel anytime'); 2397 el.textContent = bits.join(' \u00b7 '); 2398 }).catch(function(){}); 2399})(); 2400(function(){ 2401 try { 2402 var reduce = window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches; 2403 if (reduce || !('IntersectionObserver' in window)) return; 2404 var targets = Array.prototype.slice.call(document.querySelectorAll('section')); 2405 if (!targets.length) return; 2406 document.documentElement.classList.add('gf-reveal'); 2407 var vh = window.innerHeight || 800; 2408 var io = new IntersectionObserver(function(entries){ 2409 entries.forEach(function(e){ if (e.isIntersecting){ e.target.classList.add('gf-in'); io.unobserve(e.target); } }); 2410 }, { threshold: 0.06, rootMargin: '0px 0px -6% 0px' }); 2411 targets.forEach(function(el){ 2412 el.setAttribute('data-gf-reveal',''); 2413 if (el.getBoundingClientRect().top < vh * 0.95) el.classList.add('gf-in'); 2414 else io.observe(el); 2415 }); 2416 setTimeout(function(){ targets.forEach(function(el){ el.classList.add('gf-in'); }); }, 2200); 2417 } catch(e){} 2418})(); 2419</script>
2419 2420</body> 2421</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.