PageSourceSearch

https://giftella.app/

html giftella.app collected 2026-10-02 00:25:25 UTC 157,273 bytes, 2,421 lines download raw bytes

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+ &#10022;</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;">&#127769;</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 &middot; wishlists &middot; 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 &mdash; 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 &rarr;</a>
1404          <a class="n-cta-ghost" href="/registry">Start a registry</a>
1405        </div>
1406
1407        <div class="n-trust">
1408          <span><b>&#9733;</b> 5.0 on the App Store</span>
1409          <span><b>&#10003;</b> Free to start</span>
1410          <span><b>&#10003;</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 &rarr;</a>
1446      </div>
1447
1448      <div class="n-anystore" id="anystore">
1449        <p class="n-anystore-t">Works with every store &mdash; 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 &amp; 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 &rarr;</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&amp;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 &rarr;</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&#x27;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&#x27;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&#x27;s Day" loading="lazy" decoding="async" data-q="mothers day gift basket" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRZZJgiD2qL81Vn7G9MujT0IVsIYsdXJbaRrrqUs3N1Fg&amp;s=10"><span class="fallback">🌷</span></span><span class="n-card-t">Mother&#x27;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&#x27;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&#x27;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&amp;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">&#129323; Claimed gifts stay hidden</span>
1514        <span class="n-chip">&#128279; No account needed to claim from a link</span>
1515        <span class="n-chip">&#127873; 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+ &rarr;</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">&#8734;</b><span>AI searches per person, up from 3</span></li>
1550        <li><b class="inf">&#8734;</b><span>people saved, up from 2</span></li>
1551        <li><b class="inf">&#8734;</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>&#128172;</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>&#127919;</i><b>Gift Autopilot</b><em>Name anyone and get THE gift, picked and sourced.</em></span>
1558        <span class="n-room"><i>&#10024;</i><b>Gifts for You</b><em>Tell it the vibe. Picks for yourself, beyond Amazon.</em></span>
1559        <span class="n-room"><i>&#128276;</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>&#128197;</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>&#128240;</i><b>The Gift Wire</b><em>What the world is gifting today, fresh every morning.</em></span>
1562        <span class="n-room"><i>&#128142;</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 &rarr;</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 &middot; Shop smarter. Gift warmer.<br>&copy; 2026 Giftella, Inc. &middot; A Delaware corporation &middot; All rights reserved &middot; <a href="/privacy" style="color:inherit;">Privacy</a> &middot; <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">&#10005;</button>
1717    <div class="lp-auth" id="signin">
1718      <div class="hero-emoji">&#127873;</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 &amp; 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 &middot; 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&#8230;"
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">&#8984;K</kbd>
1822          <button type="button" class="ga-search-x" id="ga-search-clear" aria-label="Clear search" hidden>&#10005;</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 { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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.