PageSourceSearch

https://cardcompass.app/

html cardcompass.app collected 2026-10-02 01:12:48 UTC 117,249 bytes, 1,653 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4  <meta charset="UTF-8">
5  <meta name="fo-verify" content="7c1b2d98-52dc-49f3-95fc-792906ed2a9b" />
6  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
8  <meta http-equiv="Pragma" content="no-cache">
9  <title>CardCompass — Find Your Perfect Credit Card</title>
10
11  <!-- Favicon & PWA -->
12  <link rel="icon" href="images/logo-icon.svg" type="image/png">
13  <link rel="icon" href="favicon.svg" type="image/svg+xml">
14  <link rel="manifest" href="site.webmanifest">
15  <meta name="theme-color" content="#071E4A">
16
17  <!-- SEO -->
18  <meta name="description" content="CardCompass finds your best credit card in 60 seconds. Compare cash back, travel rewards, and hotel points cards — personalized to your spending habits.">
19  <link rel="canonical" href="https://cardcompass.app/">
20
21  <!-- Open Graph -->
22  <meta property="og:type" content="website">
23  <meta property="og:url" content="https://cardcompass.app/">
24  <meta property="og:title" content="CardCompass — Find Your Perfect Credit Card">
25  <meta property="og:description" content="Stop leaving money on the table. CardCompass matches you with the best credit card for your spending in under 60 seconds.">
26  <meta property="og:image" content="https://cardcompass.app/og-image.png">
27  <meta property="og:site_name" content="CardCompass">
28
29  <!-- Twitter Card -->
30  <meta name="twitter:card" content="summary_large_image">
31  <meta name="twitter:title" content="CardCompass — Find Your Perfect Credit Card">
32  <meta name="twitter:description" content="Find the best credit card for your spending in 60 seconds.">
33  <meta name="twitter:image" content="https://cardcompass.app/og-image.png">
34
35  <!-- Structured Data -->
36  
36<script type="application/ld+json">
37  {
38    "@context": "https://schema.org",
39    "@type": "Organization",
40    "@id": "https://cardcompass.app/#organization",
41    "name": "CardCompass",
42    "legalName": "CardCompass LLC",
43    "alternateName": ["Card Compass", "cardcompass.app"],
44    "url": "https://cardcompass.app",
45    "logo": "https://cardcompass.app/images/logo-icon.svg",
46    "description": "CardCompass (cardcompass.app) is a free credit card comparison website run by CardCompass LLC in the United States. It is a website, not a mobile app or browser extension.",
47    "contactPoint": { "@type": "ContactPoint", "email": "[email protected]", "contactType": "customer service" },
48    "sameAs": ["https://www.instagram.com/cardcompassapp/", "https://www.facebook.com/cardcompassapp", "https://www.youtube.com/channel/UC-Th-AsoiJTPhfZ3lj3-9fg"]
49  }
50  </script>
50
51
52  
52<script type="application/ld+json">
53  {
54    "@context": "https://schema.org",
55    "@type": "WebSite",
56    "name": "CardCompass",
57    "alternateName": ["Card Compass", "cardcompass.app"],
58    "url": "https://cardcompass.app",
59    "publisher": { "@id": "https://cardcompass.app/#organization" },
60    "description": "Credit card comparison and recommendation tool that helps consumers find the best credit card for their spending habits."
61  }
62  </script>
62
63
64  <!-- Google Analytics 4 (consent-gated) -->
vendor: 66 bytes, lines 64-65
64
65  <script async src="https://www.googletagmanager.com/gtag/js?id=
65G-CYNSKD3EBQ
vendor: 14 bytes, lines 65-66
65"></script>
66  
66<script>
67    window.dataLayer = window.dataLayer || [];
68    function gtag(){dataLayer.push(arguments);}
69    gtag('js', new Date());
70    // EU/UK visitors (Europe/* timezone): opt-in (GDPR). Everyone else: on by default with opt-out (US state law model).
71    gtag('consent', 'default', (function () { var v; try { v = localStorage.getItem('cc_consent') || (/^Europe\//.test(Intl.DateTimeFormat().resolvedOptions().timeZone) ? 'denied' : 'granted'); } catch (e) { v = 'denied'; } return { analytics_storage: v, ad_storage: v, ad_user_data: v, ad_personalization: v }; })());
72    gtag('config', 'G-CYNSKD3EBQ');
73  </script>
73
74  
74<script src="/meta-pixel.js?v=1" defer></script>
74
75
76  <link rel="preconnect" href="https://images.unsplash.com">
77  <link rel="preconnect" href="https://fonts.googleapis.com">
78  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
79  <!-- Inter and Font Awesome load without blocking first paint (media="print" until loaded). Text paints in "Inter Fallback" (Arial resized to Inter's metrics, below) so the swap doesn't shift layout; no Font Awesome icon is essential above the fold. -->
80  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
81  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" media="print" onload="this.media='all'">
82  <noscript>
83    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
84    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer">
85  </noscript>
86  <style>
87    @font-face { font-family: 'Inter Fallback'; src: local('Arial'), local('ArialMT'), local('Liberation Sans'), local('Helvetica'); font-weight: 100 599; size-adjust: 107.12%; ascent-override: 90.44%; descent-override: 22.52%; line-gap-override: 0%; }
88    @font-face { font-family: 'Inter Fallback'; src: local('Arial Bold'), local('Arial-BoldMT'), local('Liberation Sans Bold'), local('Helvetica Bold'); font-weight: 600 900; size-adjust: 107.12%; ascent-override: 90.44%; descent-override: 22.52%; line-gap-override: 0%; }
89  </style>
90  <!-- cards-data.js and recommend-engine.js load at the end of <body>, just above the script that reads them, so they don't block first paint -->
91  
91<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
91
92  <link rel="stylesheet" href="/styles.css?v=2">
93  <style>
94    body { font-family: 'Inter', 'Inter Fallback', -apple-system, BlinkMacSystemFont, sans-serif; }
95/* ─── NAVBAR ─── */
96    .navbar { position: sticky; top: 0; z-index: 1000; background: var(--primary-dark); box-shadow: 0 2px 12px rgba(0,0,0,0.35); }
97    .nav-inner { max-width: 1280px; margin: 0 auto; padding: 0 24px; display: flex; align-items: center; height: 64px; gap: 28px; }
98    .logo { display: flex; align-items: center; gap: 10px; text-decoration: none; flex-shrink: 0; }
99    .logo-icon { width: 40px; height: 40px; border-radius: 50%; overflow: hidden; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
100    .logo-icon img { width: 100%; height: 100%; object-fit: cover; display: block; }
101    .logo-text { font-size: 20px; font-weight: 800; color: #fff; letter-spacing: -0.5px; }
102    .logo-text span { color: var(--accent-light); }
103    .nav-links { display: flex; align-items: center; gap: 2px; flex: 1; }
104    .nav-item { position: relative; }
105    .nav-link { display: flex; align-items: center; gap: 5px; padding: 8px 14px; color: rgba(255,255,255,.85); font-size: 14px; font-weight: 500; text-decoration: none; border-radius: 6px; transition: all .2s; white-space: nowrap; cursor: pointer; background: none; border: none; font-family: inherit; }
106    .nav-link i { font-size: 10px; opacity: .7; }
107    .nav-link:hover { color: #fff; background: rgba(255,255,255,.1); }
108    .dropdown { position: absolute; top: calc(100% + 8px); left: 0; background: #fff; border-radius: var(--r-md); box-shadow: var(--shadow-lg); padding: 12px; min-width: 230px; opacity: 0; visibility: hidden; transform: translateY(-8px); transition: all .2s; border: 1px solid var(--border); max-height: calc(100vh - 80px); overflow-y: auto; }
109    .nav-item:hover .dropdown { opacity: 1; visibility: visible; transform: translateY(0); }
110    .dropdown a { display: flex; align-items: center; gap: 10px; padding: 9px 12px; color: var(--text-dark); text-decoration: none; font-size: 14px; border-radius: var(--r-sm); transition: background .15s; }
111    .dropdown a:hover { background: var(--bg-light); color: var(--primary); }
112    .dropdown a i { width: 18px; color: var(--accent); font-size: 13px; }
113    .nav-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
114    .btn-outline { padding: 8px 18px; border: 1.5px solid rgba(255,255,255,.4); color: #fff; background: transparent; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all .2s; text-decoration: none; font-family: inherit; }
115    .btn-outline:hover { border-color: #fff; background: rgba(255,255,255,.1); }
116    .btn-primary { padding: 8px 18px; background: var(--accent); color: #fff; border: none; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all .2s; text-decoration: none; font-family: inherit; }
117    .btn-primary:hover { background: var(--accent-light); transform: translateY(-1px); }
118
119    /* ─── SHARED ─── */
120    .section { padding: 76px 24px; }
121    .section-alt { background: var(--bg-light); }
122    .section-inner { max-width: 1280px; margin: 0 auto; }
123    .section-label { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.5px; color: var(--accent); margin-bottom: 10px; }
124    .section-title { font-size: 36px; font-weight: 800; color: var(--text-dark); letter-spacing: -.5px; line-height: 1.2; }
125    .section-sub { font-size: 16px; color: var(--text-gray); margin-top: 10px; }
126    .section-hd { margin-bottom: 44px; }
127
128    /* ─── COMPARISON TABLE ─── */
129    .comp-section { background: var(--primary-dark); padding: 76px 24px; color: #fff; }
130    .comp-section .section-title { color: #fff; }
131    .comp-section .section-sub { color: rgba(255,255,255,.6); }
132    .comp-section .section-label { color: var(--accent-light); }
133
134    /* ─── CATEGORY TABS (Best Cards section) ─── */
135    .comp-tabs-wrap { overflow-x: auto; scrollbar-width: none; margin-bottom: 28px; padding-bottom: 4px; }
136    .comp-tabs-wrap::-webkit-scrollbar { display: none; }
137    .comp-tabs { display: flex; gap: 8px; white-space: nowrap; }
138    .comp-tab { padding: 9px 18px; border: 1.5px solid rgba(255,255,255,.2); background: rgba(255,255,255,.06); color: rgba(255,255,255,.7); border-radius: 100px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all .2s; font-family: inherit; white-space: nowrap; }
139    .comp-tab:hover { background: rgba(255,255,255,.14); color: #fff; border-color: rgba(255,255,255,.4); }
140    .comp-tab.active { background: var(--accent); color: #fff; border-color: var(--accent); }
141    .comp-table-loading { text-align: center; padding: 40px; color: rgba(255,255,255,.45); font-size: 15px; }
142    .comp-table { background: rgba(255,255,255,.05); border-radius: var(--r-lg); overflow: hidden; border: 1px solid rgba(255,255,255,.1); }
143    .comp-header, .comp-row { display: grid; grid-template-columns: 2.2fr 1.1fr 1fr .9fr auto; gap: 16px; padding: 18px 28px; align-items: center; }
144    .comp-header { background: rgba(255,255,255,.08); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: rgba(255,255,255,.45); }
145    .comp-row { border-top: 1px solid rgba(255,255,255,.07); transition: background .2s; cursor: pointer; }
146    .comp-row:hover { background: rgba(255,255,255,.06); }
147    .comp-info { display: flex; align-items: center; gap: 14px; }
148    .comp-card-img { width: 90px; height: 57px; border-radius: 8px; overflow: hidden; flex-shrink: 0; position: relative; box-shadow: 0 3px 10px rgba(0,0,0,.4); }
149    .comp-card-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: 8px; display: block; }
150    /* Brand-accurate card badges — Chase Sapphire (navy), Amex Gold (gold), Cap One Venture X (dark), Discover (orange), Citi (blue) */
151    .ci-chase { background: linear-gradient(135deg,#0A2342 0%,#0F3D70 100%); border:1px solid rgba(255,255,255,.18); }
152    .ci-amex  { background: linear-gradient(135deg,#C9951A 0%,#9A7215 55%,#C9951A 100%); }
153    .ci-citi  { background: linear-gradient(135deg,#003B70 0%,#0057A8 100%); }
154    .ci-cap   { background: linear-gradient(135deg,#1A1A2E 0%,#2D1B4E 100%); border:1px solid rgba(209,48,39,.5); }
155    .ci-disc  { background: linear-gradient(135deg,#F76F20 0%,#E85D11 100%); }
156    .comp-name { font-size: 15px; font-weight: 600; }
157    .comp-sub { font-size: 12px; color: rgba(255,255,255,.45); margin-top: 2px; }
158    .comp-cell { font-size: 14px; color: rgba(255,255,255,.82); font-weight: 500; }
159    .comp-cell .hi { color: var(--accent-gold); font-weight: 700; }
160    .comp-cell .tiny { font-size: 11px; color: rgba(255,255,255,.4); }
161    .comp-cell .badge { background: rgba(30,127,212,.25); color: var(--accent-light); padding: 3px 9px; border-radius: 5px; font-size: 12px; font-weight: 600; }
162    .comp-btn { background: var(--accent); color: #fff; border: none; border-radius: 6px; padding: 9px 18px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all .2s; font-family: inherit; white-space: nowrap; }
163    .comp-btn:hover { background: var(--accent-light); transform: translateY(-1px); }
164    /* ─── COMP PAGINATION & FOOTER ─── */
165    .comp-footer { display: flex; align-items: center; justify-content: space-between; margin-top: 24px; gap: 16px; flex-wrap: wrap; }
166    .comp-pagination { display: flex; align-items: center; gap: 6px; }
167    .comp-pager-btn { width: 36px; height: 36px; border-radius: 8px; border: 1.5px solid rgba(255,255,255,.2); background: rgba(255,255,255,.06); color: rgba(255,255,255,.75); font-size: 13px; cursor: pointer; transition: all .2s; display: inline-flex; align-items: center; justify-content: center; font-family: inherit; }
168    .comp-pager-btn:hover:not([disabled]) { background: rgba(255,255,255,.15); color: #fff; border-color: rgba(255,255,255,.4); }
169    .comp-pager-btn[disabled] { opacity: .28;
169 cursor: not-allowed; }
170    .comp-pager-num { width: 36px; height: 36px; border-radius: 8px; border: 1.5px solid transparent; background: transparent; color: rgba(255,255,255,.6); font-size: 14px; font-weight: 600; cursor: pointer; transition: all .2s; display: inline-flex; align-items: center; justify-content: center; font-family: inherit; }
171    .comp-pager-num:hover { background: rgba(255,255,255,.08); color: #fff; }
172    .comp-pager-num.active { background: var(--accent); color: #fff; border-color: var(--accent); }
173    .comp-see-all { display: inline-flex; align-items: center; gap: 8px; background: rgba(255,255,255,.1); border: 1.5px solid rgba(255,255,255,.25); color: #fff; padding: 9px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; transition: all .2s; white-space: nowrap; }
174    .comp-see-all:hover { background: rgba(255,255,255,.18); border-color: rgba(255,255,255,.45); transform: translateY(-1px); }
175
176    /* ─── FOOTER ─── */
177    footer { background: var(--primary-dark); color: rgba(255,255,255,.65); padding: 60px 24px 28px; }
178    .footer-inner { max-width: 1280px; margin: 0 auto; }
179    .footer-top { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 48px; padding-bottom: 48px; border-bottom: 1px solid rgba(255,255,255,.1); }
180    .footer-brand p { font-size: 14px; line-height: 1.75; margin-top: 14px; color: rgba(255,255,255,.5); }
181    .footer-col h4 { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: rgba(255,255,255,.9); margin-bottom: 18px; }
182    .footer-col a { display: block; font-size: 14px; color: rgba(255,255,255,.5); text-decoration: none; margin-bottom: 11px; transition: color .2s; }
183    .footer-col a:hover { color: rgba(255,255,255,.9); }
184    .footer-bottom { padding-top: 26px; display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: rgba(255,255,255,.3); flex-wrap: wrap; gap: 12px; }
185    .footer-bottom a { color: rgba(255,255,255,.3); text-decoration: none; }
186    .footer-bottom a:hover { color: rgba(255,255,255,.6); }
187    .foot-links { display: flex; gap: 20px; }
188
189    /* ─── SCROLL TOP ─── */
190    .scroll-top { position: fixed; bottom: 24px; right: 24px; width: 46px; height: 46px; background: var(--primary); color: #fff; border: none; border-radius: 50%; font-size: 17px; cursor: pointer; box-shadow: var(--shadow-md); display: flex; align-items: center; justify-content: center; transition: all .2s; z-index: 999; }
191    .scroll-top:hover { background: var(--primary-light); transform: translateY(-2px); }
192
193    /* ════════════════════════════════════════
194       NEW CSS ADDITIONS
195    ════════════════════════════════════════ */
196
197    /* ─── REVEAL ANIMATIONS ─── */
198    .reveal { opacity: 0; transform: translateY(28px); transition: opacity .65s ease, transform .65s ease; }
199    .reveal.visible { opacity: 1; transform: translateY(0); }
200    .reveal-d1 { transition-delay: .08s; }
201    .reveal-d2 { transition-delay: .16s; }
202    .reveal-d3 { transition-delay: .24s; }
203    .reveal-d4 { transition-delay: .32s; }
204    .reveal-d5 { transition-delay: .40s; }
205    .reveal-d6 { transition-delay: .48s; }
206    .reveal-d7 { transition-delay: .56s; }
207
208    /* ─── HAMBURGER ─── */
209    .hamburger { display: none; flex-direction: column; justify-content: center; align-items: center; width: 40px; height: 40px; background: none; border: none; cursor: pointer; gap: 5px; padding: 6px; border-radius: 6px; transition: background .2s; margin-left: auto; flex-shrink: 0; }
210    .hamburger:hover { background: rgba(255,255,255,.1); }
211    .hamburger span { display: block; width: 22px; height: 2px; background: rgba(255,255,255,.85); border-radius: 2px; transition: all .3s ease; }
212    .hamburger.open span:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); }
213    .hamburger.open span:nth-child(2) { opacity: 0; transform: scaleX(0); }
214    .hamburger.open span:nth-child(3) { transform: rotate(-45deg) translate(5px, -5px); }
215
216    /* ─── MOBILE NAV ─── */
217    .nav-links.mobile-open { display: flex !important; flex-direction: column; align-items: flex-start; position: absolute; top: 64px; left: 0; right: 0; background: var(--primary-dark); padding: 12px 16px 20px; border-top: 1px solid rgba(255,255,255,.1); box-shadow: 0 8px 32px rgba(0,0,0,.3); z-index: 999; max-height: calc(100vh - 64px); overflow-y: auto; }
218    .nav-links.mobile-open .nav-link { width: 100%; padding: 12px 16px; }
219    .nav-links.mobile-open .nav-item { width: 100%; }
220    .nav-links.mobile-open .dropdown { position: static; opacity: 1; visibility: visible; transform: none; box-shadow: none; background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.1); margin-top: 4px; }
221    .nav-links.mobile-open .dropdown a { color: rgba(255,255,255,.75); }
222    .nav-links.mobile-open .dropdown a:hover { background: rgba(255,255,255,.08); color: #fff; }
223    .nav-links.mobile-open .dropdown a i { color: var(--accent-light); }
224    @media (max-width: 640px) { .logo-text { display: none; } .nav-inner { gap: 8px; padding: 0 16px; } }
225
226    /* ─── NEWSLETTER ─── */
227    .newsletter-section { background: var(--bg-light); padding: 64px 24px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
228    .newsletter-inner { max-width: 640px; margin: 0 auto; text-align: center; }
229    .newsletter-icon { font-size: 42px; margin-bottom: 16px; display: block; }
230    .newsletter-inner h2 { font-size: 28px; font-weight: 800; letter-spacing: -.5px; margin-bottom: 10px; }
231    .newsletter-inner > p { font-size: 16px; color: var(--text-gray); margin-bottom: 28px; line-height: 1.65; }
232    .newsletter-form { display: flex; gap: 10px; margin-bottom: 14px; }
233    .newsletter-input { flex: 1; padding: 13px 16px; border: 1.5px solid var(--border); border-radius: var(--r-sm); font-size: 15px; outline: none; transition: border-color .2s; font-family: inherit; background: #fff; }
234    .newsletter-input:focus { border-color: var(--accent); }
235    .newsletter-submit { padding: 13px 22px; background: var(--primary); color: #fff; border: none; border-radius: var(--r-sm); font-size: 15px; font-weight: 700; cursor: pointer; transition: all .2s; font-family: inherit; white-space: nowrap; }
236    .newsletter-submit:hover { background: var(--primary-light); transform: translateY(-1px); }
237    .newsletter-error { color: #DC2626; font-size: 13px; margin-bottom: 10px; display: none; }
238    .newsletter-meta { font-size: 13px; color: var(--text-light); display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; }
239    .newsletter-meta span { display: flex; align-items: center; gap: 5px; }
240    .newsletter-success { display: none; background: #ECFDF5; border-radius: var(--r-md); border: 1px solid #6EE7B7; padding: 24px; }
241    .newsletter-success h3 { font-size: 18px; font-weight: 700; color: #059669; margin-bottom: 6px; }
242    .newsletter-success p { font-size: 14px; color: #065F46; }
243    .newsletter-already { display: none; background: #EBF4FF; border-radius: var(--r-md); border: 1px solid var(--accent-light); padding: 16px 20px; font-size: 14px; color: var(--primary); font-weight: 500; }
244
245    /* ─── CARD DRAWER ─── */
246    .drawer-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.52); z-index: 1100; opacity: 0; visibility: hidden; transition: all .3s; }
247    .drawer-overlay.open { opacity: 1; visibility: visible; }
248    .drawer { position: fixed; top: 0; right: 0; width: 440px; height: 100vh; background: #fff; z-index: 1101; overflow-y: auto; transform: translateX(100%); transition: transform .35s cubic-bezier(.4,0,.2,1); box-shadow: -8px 0 40px rgba(0,0,0,.2); }
249    .drawer.open { transform: translateX(0); }
250    .drawer-header { display: flex; align-items: center; justify-content: space-between; padding: 18px 24px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: #fff; z-index: 2; }
251    .drawer-header span { font-size: 13px; color: var(--text-gray); }
252    .drawer-close { width: 32px; height: 32px; background: var(--bg-light); border: none; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 14px; color: var(--text-gray); transition: all .2s; }
253    .drawer-close:hover { background: var(--border); color: var(--text-dark); }
254    .drawer-body { padding: 24px; }
255    .drawer-card-hd { display: flex; align-items: center; gap: 16px; margin-bottom: 18px; }
256    .drawer-card-img { width: 72px; height: 46px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; flex-shrink: 0; }
257    .drawer-card-name { font-size: 18px; font-weight: 800; line-height: 1.3; }
258    .drawer-card-sub { font-size: 13px; color: var(--text-gray); margin-top: 3px; }
259    .drawer-score-bar { display: flex; align-items: center; gap: 10px; background: var(--bg-light); border-radius: var(--r-sm); padding: 10px 14px; margin-bottom: 20px; }
260    .drawer-stars { color: var(--accent-gold); font-size: 15px; letter-spacing: 1px; }
261    .drawer-score-val { font-size: 15px; font-weight: 700; }
262    .drawer-score-lbl { font-size: 13px; color: var(--text-gray); }
263    .drawer-divider { height: 1px; background: var(--border); margin: 18px 0; }
264    .drawer-details { display: flex; flex-direction: column; gap: 11px; }
265    .drawer-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
266    .drawer-lbl { font-size: 13px; color: var(--text-gray); font-weight: 500; }
267    .drawer-val { font-size: 14px; font-weight: 600; color: var(--text-dark); text-align: right; }
268    .drawer-val.gold { color: #D97706; }
269    .drawer-proscons { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
270    .drawerpc h5 { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .8px; margin-bottom: 10px; }
271    .pros-title { color: #059669; }
272    .cons-title { color: #DC2626; }
273    .drawerpc ul { list-style: none; display: flex; flex-direction: column; gap: 8px; }
274    .drawerpc ul li { font-size: 13px; color: var(--text-gray); display: flex; gap: 7px; align-items: flex-start; line-height: 1.4; }
275    .drawerpc ul li span { flex-shrink: 0; font-size: 12px;
275 margin-top: 1px; }
276    .drawer-actions { display: flex; gap: 12px; position: sticky; bottom: 0; background: #fff; padding: 16px 24px; border-top: 1px solid var(--border); margin: 0 -24px -24px; }
277    .drawer-apply { flex: 1; padding: 13px; background: var(--primary); color: #fff; border: none; border-radius: var(--r-sm); font-size: 15px; font-weight: 700; cursor: pointer; transition: all .2s; font-family: inherit; }
278    .drawer-apply:hover { background: var(--primary-light); }
279    .drawer-learn { padding: 13px 18px; background: var(--bg-light); color: var(--text-dark); border: 1.5px solid var(--border); border-radius: var(--r-sm); font-size: 14px; font-weight: 600; cursor: pointer; transition: all .2s; font-family: inherit; white-space: nowrap; }
280    .drawer-learn:hover { border-color: var(--accent); color: var(--accent); }
281
282    /* ════════════════════════════════════════
283       RESPONSIVE
284    ════════════════════════════════════════ */
285    @media (max-width: 1024px) {
286      .nav-links { display: none; }
287      .hamburger { display: flex; }
288      .nav-actions { gap: 6px; }
289      .nav-actions .btn-primary, .nav-actions .btn-outline { padding: 7px 10px; font-size: 12px; white-space: nowrap; }
290      .navbar { position: relative; }
291      .comp-header { display: none; }
292      .comp-row { grid-template-columns: 1fr 1fr; gap: 10px; padding: 16px 20px; }
293      .comp-info { grid-column: 1 / -1; }
294      .footer-top { grid-template-columns: 1fr 1fr; gap: 36px; }
295      .drawer { width: 100%; max-width: 440px; }
296    }
297    @media (max-width: 768px) {
298      .section { padding: 52px 20px; }
299      .section-title { font-size: 26px; }
300      .comp-row { grid-template-columns: 1fr; }
301      .footer-top { grid-template-columns: 1fr; gap: 28px; }
302      .footer-bottom { flex-direction: column; align-items: flex-start; gap: 10px; }
303      .newsletter-form { flex-direction: column; }
304      .newsletter-submit { width: 100%; }
305      .drawer { width: 100%; }
306    }
307
308    /* ─── SKIP LINK ─── */
309    .skip-link { position: absolute; top: -100%; left: 16px; background: var(--accent); color: #fff; padding: 10px 18px; border-radius: 0 0 var(--r-sm) var(--r-sm); font-size: 14px; font-weight: 700; text-decoration: none; z-index: 9999; transition: top .2s; }
310    .skip-link:focus { top: 0; }
311
312    /* ─── ADVERTISER DISCLOSURE BAR ─── */
313    .adv-bar { background: #FFFBEB; border-bottom: 1px solid #FDE68A; padding: 10px 24px; }
314    .adv-bar-inner { max-width: 1280px; margin: 0 auto; font-size: 13px; color: #78350F; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
315    .adv-bar-inner i { color: #D97706; flex-shrink: 0; }
316    .adv-bar-inner a { color: #92400E; font-weight: 700; text-decoration: underline; }
317    .adv-bar-inner a:hover { color: #78350F; }
318
319    /* ─── COOKIE BANNER ─── */
320    .cookie-banner { position: fixed; bottom: 0; left: 0; right: 0; background: var(--primary-dark); border-top: 1px solid rgba(255,255,255,.15); padding: 16px 24px; z-index: 2000; display: flex; align-items: center; gap: 20px; flex-wrap: wrap; box-shadow: 0 -4px 24px rgba(0,0,0,.3); animation: slideUp .35s ease; }
321    .cookie-banner.hidden { display: none; }
322    @keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
323    .cookie-banner p { font-size: 14px; color: rgba(255,255,255,.8); flex: 1; min-width: 200px; line-height: 1.5; margin: 0; }
324    .cookie-banner p a { color: var(--accent-light); text-decoration: underline; }
325    .cookie-actions { display: flex; gap: 10px; flex-shrink: 0; }
326    .cookie-accept { padding: 9px 20px; background: var(--accent); color: #fff; border: none; border-radius: 6px; font-size: 14px; font-weight: 700; cursor: pointer; transition: background .2s; font-family: inherit; }
327    .cookie-accept:hover { background: var(--accent-light); }
328    .cookie-decline { padding: 9px 16px; background: transparent; color: rgba(255,255,255,.65); border: 1px solid rgba(255,255,255,.25); border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all .2s; font-family: inherit; }
329    .cookie-decline:hover { border-color: rgba(255,255,255,.6); color: #fff; }
330
331    /* ─── USER NAV (logged-in state) ─── */
332    .user-nav { position: relative; }
333    .user-btn { display: flex; align-items: center; gap: 9px; padding: 5px 12px 5px 6px; background: rgba(255,255,255,.1); border: 1.5px solid rgba(255,255,255,.22); border-radius: 100px; cursor: pointer; transition: all .2s; color: #fff; font-family: inherit; font-size: 14px; font-weight: 600; }
334    .user-btn:hover { background: rgba(255,255,255,.18); border-color: rgba(255,255,255,.45); }
335    .user-avatar-sm { width: 30px; height: 30px; background: var(--accent); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 800; color: #fff; flex-shrink: 0; letter-spacing: -.5px; }
336    .user-name-sm { max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
337    .user-btn .fa-chevron-down { font-size: 10px; opacity: .65; }
338    .user-dropdown { position: absolute; top: calc(100% + 10px); right: 0; background: #fff; border-radius: var(--r-md); box-shadow: var(--shadow-lg); border: 1px solid var(--border); min-width: 226px; padding: 10px; opacity: 0; visibility: hidden; transform: translateY(-8px); transition: all .2s; z-index: 2000; }
339    .user-nav.open .user-dropdown { opacity: 1; visibility: visible; transform: translateY(0); }
340    .user-dropdown-header { padding: 10px 12px 14px; border-bottom: 1px solid var(--border); margin-bottom: 8px; }
341    .user-dropdown-name { font-size: 15px; font-weight: 700; color: var(--text-dark); }
342    .user-dropdown-email { font-size: 12px; color: var(--text-gray); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 190px; }
343    .user-dropdown a { display: flex; align-items: center; gap: 10px; padding: 9px 12px; color: var(--text-dark); text-decoration: none; font-size: 14px; border-radius: var(--r-sm); transition: background .15s; }
344    .user-dropdown a:hover { background: var(--bg-light); color: var(--primary); }
345    .user-dropdown a i { width: 16px; color: var(--accent); font-size: 13px; text-align: center; }
346    .user-dropdown-divider { height: 1px; background: var(--border); margin: 8px 0; }
347    .user-signout { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 12px; color: #DC2626; background: none; border: none; font-size: 14px; cursor: pointer; border-radius: var(--r-sm); transition: background .15s; font-family: inherit; text-align: left; }
348    .user-signout:hover { background: #FEF2F2; }
349    .user-signout i { width: 16px; font-size: 13px; text-align: center; }
350
351    /* ─── PERSONAL MATCH BANNER (logged-in) ─── */
352    .personal-banner { background: linear-gradient(135deg, #071E4A 0%, #0C2660 100%); border-bottom: 2px solid rgba(30,127,212,.6); padding: 13px 24px; }
353    .personal-banner.hidden { display: none; }
354    .personal-banner-inner { max-width: 1280px; margin: 0 auto; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
355    .pb-greeting { font-size: 14px; color: rgba(255,255,255,.65); white-space: nowrap; flex-shrink: 0; }
356    .pb-greeting b { color: #fff; font-weight: 700; }
357    .pb-sep { width: 1px;
357 height: 26px; background: rgba(255,255,255,.14); flex-shrink: 0; }
358    .pb-label { font-size: 11px; color: rgba(255,255,255,.45); white-space: nowrap; flex-shrink: 0; font-weight: 600; text-transform: uppercase; letter-spacing: .8px; }
359    .pb-match { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; }
360    .pb-badge { width: 78px; height: 49px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 8px; font-weight: 700; flex-shrink: 0; overflow: hidden; }
361    .pb-card-name { font-size: 14px; font-weight: 700; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
362    .pb-card-rate { font-size: 12px; color: rgba(255,255,255,.45); margin-top: 1px; white-space: nowrap; }
363    .pb-earn { font-size: 20px; font-weight: 800; color: var(--accent-gold); white-space: nowrap; flex-shrink: 0; line-height: 1; }
364    .pb-earn small { font-size: 10px; color: rgba(255,255,255,.4); font-weight: 400; display: block; margin-top: 2px; }
365    .pb-actions { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
366    .pb-acct-btn { padding: 7px 13px; background: rgba(255,255,255,.1); color: rgba(255,255,255,.85); border: 1px solid rgba(255,255,255,.2); border-radius: 6px; font-size: 12px; font-weight: 600; text-decoration: none; transition: all .2s; white-space: nowrap; display: inline-flex; align-items: center; gap: 5px; }
367    .pb-acct-btn:hover { background: rgba(255,255,255,.18); color: #fff; border-color: rgba(255,255,255,.45); }
368    .pb-retake { padding: 7px 13px; background: var(--accent); color: #fff; border: none; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; transition: background .2s; font-family: inherit; white-space: nowrap; }
369    .pb-retake:hover { background: var(--accent-light); }
370    @media (max-width: 768px) { .pb-sep { display: none; } .pb-label { display: none; } .pb-earn { font-size: 17px; } }
371
372    /* ─── HERO: SPEND CALCULATOR ─── */
373    .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
374    .hx-inner { max-width: 1280px; margin: 0 auto; }
375    /* the hand: photo tinted into the navy, small line, five real cards fanned across the seam */
376    .hx { position: relative; overflow: hidden; }
377    .hand { position: relative; overflow: hidden; background: var(--primary-dark); height: 490px; }
378    .hand::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 44px; z-index: 2; background: linear-gradient(to bottom right, transparent 49.5%, #fff 50.5%); }
379    .hand-photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 60% 40%; }
380    .hand-scrim { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(7,30,74,.8) 0, rgba(7,30,74,.8) 150px, rgba(7,30,74,.68) 230px, rgba(7,30,74,.68) 290px, var(--primary-dark) 400px); }
381    .hand-copy { position: relative; z-index: 3; padding: 36px 24px 0; }
382    .hand-copy h1 { font-size: 26px; font-weight: 800; color: #fff; line-height: 1.3; letter-spacing: -.5px; }
383    .hand-actions { display: flex; align-items: center; gap: 22px; margin-top: 16px; flex-wrap: wrap; }
384    .hand-go { display: inline-flex; align-items: center; height: 46px; padding: 0 20px; border: 1.5px solid rgba(255,255,255,.55); border-radius: 6px; color: #fff; font-size: 15px; font-weight: 600; text-decoration: none; }
385    .hand-go:hover { border-color: #fff; background: rgba(255,255,255,.08); }
386    .hand-all { display: inline-flex; align-items: center; min-height: 44px; color: #fff; font-size: 15px; font-weight: 600; }
387    /* the fan sits outside the clipped band so its lowest cards cross the seam onto the white */
388    .hand-fan { position: absolute; left: 50%; top: 250px; width: 1060px; height: 520px; margin-left: -530px; z-index: 2; pointer-events: none; }
389    .hf { position: absolute; transform-origin: 50% 160%; }
390    .hf img { display: block; width: 460px; height: 290px; object-fit: cover; border-radius: 24px; box-shadow: 0 8px 20px rgba(0,0,0,.5); }
391    .hf1 { left: 0; top: 70px; transform: rotate(-28deg); }
392    .hf2 { left: 150px; top: 22px; transform: rotate(-14deg); }
393    .hf3 { left: 300px; top: 0; z-index: 1; }
394    .hf4 { left: 450px; top: 22px; transform: rotate(14deg); }
395    .hf5 { left: 600px; top: 70px; transform: rotate(28deg); }
396    .hx-calc-hd { padding: 290px 24px 0; }
397    .hx-cue { display: flex; align-items: center; justify-content: center; gap: 13px; margin-bottom: 20px; font-size: 15px; font-weight: 700; letter-spacing: 1.6px; text-transform: uppercase; color: var(--text-light); }
398    .hx-cue span { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border: 1.5px solid var(--border); border-radius: 50%; color: var(--accent); flex-shrink: 0; }
399    .hx-calc-hd h2 { font-size: 52px; font-weight: 800; color: var(--primary-dark); line-height: 1.13; letter-spacing: -1.5px; font-variant-numeric: tabular-nums; }
400    .hero-badge { display: inline-flex; align-items: center; gap: 8px; background: rgba(255,255,255,.11); border: 1px solid rgba(255,255,255,.2); color: var(--accent-light); padding: 6px 14px; border-radius: 100px; font-size: 13px; font-weight: 500; margin-bottom: 18px; }
401    .hero-badge[hidden] { display: none; }
402    .hx-body { display: grid; grid-template-columns: minmax(0, 11fr) minmax(0, 13fr); gap: 72px; padding: 4px 24px 56px; align-items: start; }
403
404    .hx-head { display: flex; justify-content: space-between; padding-bottom: 10px; border-bottom: 1.5px solid var(--primary-dark); font-size: 13px; color: var(--text-gray); }
405    .hx-row { display: grid; grid-template-columns: 124px minmax(0, 1fr) 92px; align-items: center; gap: 16px; padding: 10px 0; border-bottom: 1px solid var(--border); }
406    .hx-row-label { font-size: 15px; font-weight: 600; color: var(--primary-dark); }
407    .hx-row-label small { display: block; font-size: 12px; font-weight: 400; line-height: 1.3; color: var(--text-gray); }
408    .hx-seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; border: 1px solid #CBD5E0; border-radius: 6px; overflow: hidden; }
409    .hx-seg button { height: 44px; padding: 0; border: 0; border-left: 1px solid #CBD5E0; background: #fff; color: var(--primary-dark); font: 600 14px/1 inherit; font-variant-numeric: tabular-nums; cursor: pointer; }
410    .hx-seg button:first-child { border-left: 0; }
411    .hx-seg button:hover { background: var(--bg-light); }
412    .hx-seg button[aria-pressed="true"] { background: var(--primary); color: #fff; }
413    .hx-seg button:focus-visible, .hx-tabs button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
414    .hx-earn { text-align: right; font-size: 14px; color: var(--text-gray); white-space: nowrap; font-variant-numeric: tabular-nums; }
415    .hx-earn strong { color: var(--primary-dark); font-weight: 700; }
416    .hx-note { margin-top: 16px; font-size: 13px; line-height: 1.6; color: var(--text-gray); }
417    .hx-note a, .hx-legal a { color: inherit; }
418
419    .hx-tabs { display: flex; gap: 24px; border-bottom: 1.5px solid var(--primary-dark); }
420    .hx-tabs button { height: 44px; padding: 0; border: 0; border-bottom: 3px solid transparent; margin-bottom: -1.5px; background: none; color: var(--text-gray); font: 600 14px/1 inherit; cursor: pointer; }
421    .hx-tabs button[aria-pressed="true"] { color: var(--primary-dark); border-bottom-color: var(--accent); }
422    .hx-stage { position: relative; height: 296px; margin-top: 8px; }
423    .hx-art { position: absolute; display: block; }
424    .hx-art img { display: block; width: 100%; height: 100%; }
425    /* Issuer art is a flat JPEG with white corners: clip them. 8.25% = 5.2% x the card's 1.586 ratio. */
426    .hx-art.flat img, .cc-thumb.flat img { object-fit: cover; border-radius: 5.2% / 8.25%; }
427    .hx-art.flat img { box-shadow: 0 2px 3px rgba(7,30,74,.25), 14px 30px 56px rgba(7,30,74,.3); }
428    .hx-art.asis img, .cc-thumb.asis img { object-fit: contain; }
429    .hx-art.asis img { filter: drop-shadow(8px 18px 22px rgba(7,30,74,.28)); }
430    /* The Amex Gold art is two cards on a white field: blend the white away, never clip or shadow it. */
431    .hx-art.gold img, .cc-thumb.gold img { object-fit: contain; mix-blend-mode: multiply; }
432    .hx-win { left: 0; top: 30px; width: 352px; height: 222px; transform: rotate(-8deg); z-index: 3; transition: transform .25s ease; }
433    .hx-win:hover { transform: rotate(-8deg) translateY(-6px); }
434    .hx-win.gold { transform: none; }
435    .hx-r2 { left: 206px; top: 14px; width: 280px; height: 177px; z-index: 2; }
436    .hx-r3 { left: 318px; top: 42px; width: 280px; height: 177px; transform: rotate(9deg); z-index: 1; }
437    .hx-meta { font-size: 13px; color: var(--text-gray); }
438    .hx-name { margin-top: 2px; font-size: 28px; font-weight: 800; letter-spacing: -.6px; line-height: 1.2; }
439    .hx-name a { color: var(--primary-dark); text-decoration: none; }
440    .hx-name a:hover { text-decoration: underline; }
441    .hx-figrow { display: flex; align-items: baseline; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
442    .hx-fig { font-size: 64px; font-weight: 800; letter-spacing: -2.1px; line-height: 1; color: var(--primary-dark); font-variant-numeric: tabular-nums; }
443    .hx-figlabel { font-size: 15px; font-weight: 600; color: var(--text-gray); }
444    .hx-recip { margin-left: auto; font-size: 14px; color: var(--text-gray); font-variant-numeric: tabular-nums; }
445    .hx-recip strong { color: var(--primary-dark); }
446    .hx-ledger { margin-top: 14px; border-top: 1px solid var(--border); font-size: 14px; font-variant-numeric: tabular-nums; }
447    .hx-ledger div { display: flex; justify-content: space-between; gap: 16px; padding: 8px 0; border-bottom: 1px solid var(--border); color: var(--text-gray); }
448    .hx-ledger strong { color: var(--primary-dark); white-space: nowrap; }
449    .hx-worth { margin-top: 10px; font-size: 13px; line-height: 1.5; color: var(--text-gray); }
450    .hx-next { display: flex; gap: 8px 24px; flex-wrap: wrap; margin-top: 12px; font-size: 14px; color: var(--text-gray); font-variant-numeric: tabular-nums; }
451    .hx-next strong { color: var(--primary-dark); }
452    .hx-actions { display: flex; align-items: center; gap: 18px; margin-top: 16px; flex-wrap: wrap; }
453    .hx-cta { display: inline-flex; align-items: center; height: 48px; padding: 0 26px; background: var(--primary); color: #fff; border-radius: 6px; font-size: 15px; font-weight: 700; text-decoration: none; }
454    .hx-cta:hover { background: var(--primary-light); }
455    .hx-actions span { font-size: 14px; color: var(--text-gray); }
456    .hx-links { display: flex; flex-wrap: wrap; gap: 4px 22px; margin-top: 6px; font-size: 14px; font-weight: 600; }
457    .hx-links a { display: inline-flex; align-items: center; min-height: 44px; color: var(--primary); }
458    .hx-legal { margin-top: 10px; padding-top: 12px; border-top: 1px solid var(--border); font-size: 12px; line-height: 1.5; color: var(--text-gray); }
459
460    /* ─── HOMEPAGE SECTIONS ─── */
461    .hp { padding: 0 24px; }
462    .hp-inner { max-width: 1280px; margin: 0 auto; padding: 56px 0 64px; border-top: 1px solid var(--border); }
463    .hp-first .hp-inner { border-top: 0; }
464    .hp-hd { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; margin-bottom: 24px; }
465    .hp-hd h2 { font-size: 36px; font-weight: 800; letter-spacing: -1px; line-height: 1.2; color: var(--primary-dark); }
466    .hp-hd > a, .hp-hd > span { font-size: 15px; white-space: nowrap; }
467    .hp-hd > a { display: inline-flex; align-items: center; min-height: 44px; font-weight: 600; color: var(--primary); }
468    .hp-hd > span { color: var(--text-gray); }
469    .cc-thumb { display: block; flex-shrink: 0; }
470    .cc-thumb img { display: block; width: 100%; height: 100%; }
471    .cc-thumb.flat img { box-shadow: 0 1px 2px rgba(7,30,74,.25), 4px 8px 16px rgba(7,30,74,.2); }
472
473    .tools { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 72px; align-items: start; }
474    .cmp-row { display: grid; grid-template-columns: 150px repeat(3, minmax(0, 1fr)); column-gap: 20px; align-items: baseline; padding: 13px 0; border-bottom: 1px solid var(--border); font-size: 15px; font-variant-numeric: tabular-nums; }
475    .cmp-row > span:first-child { font-size: 14px; color: var(--text-gray); }
476    .cmp-row > span + span { font-weight: 600; color: var(--primary-dark); }
477    .cmp-head { align-items: end; padding: 0 0 14px; border-bottom: 1.5px solid var(--primary-dark); }
478    .cmp-head > span:first-child { font-size: 13px; }
479    .cmp-card { display: flex; flex-direction: column; gap: 10px; font-weight: 700; line-height: 1.3; }
480    .cmp-card .cc-thumb { width: 132px; height: 83px; }
481    .quiz-q { padding-bottom: 14px; border-bottom: 1.5px solid var(--primary-dark); font-size: 17px; font-weight: 700; line-height: 1.4; color: var(--primary-dark); }
482    .quiz-opt { display: flex; align-items: center; justify-content: space-between; min-height: 52px; border-bottom: 1px solid var(--border); font-size: 15px; font-weight: 500; color: var(--primary-dark); text-decoration: none; }
483    .quiz-opt:hover { color: var(--accent); }
484    .quiz-foot { margin-top: 14px; font-size: 13px; color: var(--text-gray); }
485
486    .off-row { display: grid; grid-template-columns: 88px minmax(0, 1.1fr) minmax(0, 1.5fr) 96px 96px 104px; column-gap: 24px; align-items: center; padding: 12px 0; border-bottom: 1px solid var(--border); font-size: 15px; color: var(--primary-dark); text-decoration: none; font-variant-numeric: tabular-nums; }
487    a.off-row:hover .off-name { text-decoration: underline; }
488    .off-head { padding: 0 0 8px; border-bottom: 1.5px solid var(--primary-dark); font-size: 13px; color: var(--text-gray); }
489    .off-row .cc-thumb { width: 88px; height: 55px; }
490    .off-name { font-weight: 700; line-height: 1.3; }
491    .off-issuer { display: block; font-size: 13px; font-weight: 400; color: var(--text-gray); }
492    .off-desc { color: var(--text-gray); line-height: 1.4; }
493    .off-val { font-size: 22px; font-weight: 800; letter-spacing: -.5px; }
494    .off-r { text-align: right; }
495
496    .lanes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
497    .lane { display: flex; flex-direction: column; justify-content: space-between; gap: 20px; padding: 24px; border: 1px solid var(--border); border-radius: var(--r-sm); color: var(--primary-dark); text-decoration: none; overflow: hidden; }
498    .lane:hover { border-color: var(--primary); }
499    .lane-art { position: relative; height: 128px; }
500    .lane-art .cc-thumb { position: absolute; width: 170px; height: 107px; }
501    .lane-art .cc-thumb:first-child { left: 0; top: 18px; transform: rotate(-5deg); z-index: 1; }
502    .lane-art .cc-thumb + .cc-thumb { left: 96px; top: 0; transform: rotate(6deg); }
503    .lane-ft { display: flex; justify-content: space-between; align-items: baseline; }
504    .lane-ft strong { font-size: 22px; font-weight: 800; letter-spacing: -.4px; }
505    .lane-ft span { font-size: 14px; color: var(--text-gray); }
506
507    .issuers { padding: 20px 24px; background: var(--bg-light); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
508    .issuers p { max-width: 1280px; margin: 0 auto; display: flex; flex-wrap: wrap; gap: 6px 28px; align-items: baseline; font-size: 15px; font-weight: 700; color: var(--primary-dark); }
509    .issuers p span:first-child { font-size: 13px; font-weight: 400; color: var(--text-gray); margin-right: 8px; }
510
511    .guides { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 72px; }
512    .guides a { color: var(--primary-dark); text-decoration: none; }
513    .guides a:hover h3 { text-decoration: underline; }
514    .g-tag { font-size: 13px; color: var(--accent); font-weight: 600; }
515    .g-meta { font-size: 13px; color: var(--text-gray); }
516    .g-lead { display: flex; flex-direction: column; gap: 14px; padding-top: 20px; border-top: 1.5px solid var(--primary-dark); }
517    .g-lead h3 { font-size: 34px; font-weight: 800; letter-spacing: -.9px; line-height: 1.18; }
518    .g-lead p { font-size: 17px; line-height: 1.6; color: var(--text-gray); }
519    .g-item { display: flex; flex-direction: column; gap: 6px; padding: 20px 0; border-top: 1px solid var(--border); }
520    .g-item:first-child { border-top: 1.5px solid var(--primary-dark); }
521    .g-item h3 { font-size: 19px; font-weight: 700; letter-spacing: -.2px; line-height: 1.3; }
522    .g-item p { font-size: 14px; line-height: 1.55; color: var(--text-gray); }
523
524    @media (max-width: 1024px) {
525      .hx-body { grid-template-columns: minmax(0, 1fr); gap: 40px; }
526      .hx-result { order: -1; }
527      .tools, .guides { grid-template-columns: minmax(0, 1fr); gap: 48px; }
528      .off-row { grid-template-columns: 88px minmax(0, 1fr) 96px 104px; }
529      .off-desc, .off-fee { display: none; }
530    }
531    @media (max-width: 640px) {
532      .hand { height: 500px; }
533      .hand::after { height: 24px; }
534      .hand-scrim { background: linear-gradient(180deg, rgba(7,30,74,.8) 0, rgba(7,30,74,.8) 150px, rgba(7,30,74,.68) 210px, rgba(7,30,74,.68) 290px, var(--primary-dark) 400px); }
535      .hand-copy { padding: 28px 20px 0; }
536      .hand-copy h1 { font-size: 24px; line-height: 1.25; max-width: 300px; }
537      .hand-actions { gap: 4px 18px; margin-top: 12px; }
538      .hand-go { height: 44px; padding: 0 18px; font-size: 14px; }
539      .hand-fan { top: 236px; width: 390px; height: 330px; margin-left: -195px; }
540      .hf { transform-origin: 50% 130%; }
541      .hf img { width: 210px; height: 132px; border-radius: 11px; }
542      .hf1, .hf5 { display: none; }
543      .hf2 { left: 40px; top: 44px; transform: rotate(-13deg); }
544      .hf3 { left: 90px; top: 0; }
545      .hf4 { left: 140px; top: 44px; transform: rotate(13deg); }
546      .hx-calc-hd { padding: 36px 20px 0; }
547      .hx-cue { gap: 10px; margin-bottom: 14px; font-size: 13px; letter-spacing: 1.3px; }
548      .hx-cue span { width: 28px; height: 28px; }
549      .hx-calc-hd h2 { font-size: 28px; letter-spacing: -.8px; }
550      .hx-body { padding: 0 20px 40px; }
551      .hx-row { grid-template-columns: minmax(0, 1fr) auto; gap: 8px 12px; }
552      .hx-seg { grid-column: 1 / -1; grid-row: 2; }
553      .hx-stage { height: 226px; }
554      .hx-win { width: 290px; height: 183px; top: 18px; }
555      .hx-r2 { left: 150px; top: 6px; width: 200px; height: 126px; }
556      .hx-r3 { display: none; }
557      .hx-fig { font-size: 56px; letter-spacing: -1.8px; }
558      .hx-recip { margin-left: 0; flex-basis: 100%; }
559      .hx-cta { width: 100%; justify-content: center; }
560      .hp { padding: 0 20px; }
561      .hp-inner { padding: 40px 0 44px; }
562      .hp-hd h2 { font-size: 26px; letter-spacing: -.6px; }
563      .cmp-row { grid-template-columns: 96px repeat(3, minmax(0, 1fr)); column-gap: 10px; font-size: 13px; }
564      .cmp-row > span:first-child { font-size: 12px; }
565      .cmp-card .cc-thumb { width: 100%; height: auto; aspect-ratio: 1.586; }
566      .off-row { grid-template-columns: 72px minmax(0, 1fr) auto; column-gap: 14px; }
567      .off-row .cc-thumb { width: 72px; height: 45px; }
568      .off-checked { display: none; }
569      .lanes { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
570      .lane { padding: 16px; gap: 14px; }
571      .lane-art { height: 84px; }
572      .lane-art .cc-thumb { width: 112px; height: 71px; }
573      .lane-art .cc-thumb + .cc-thumb { display: none; }
574      .lane-ft { flex-direction: column; gap: 2px; }
575      .lane-ft strong { font-size: 18px; }
576      .g-lead h3 { font-size: 24px; letter-spacing: -.5px; }
577      .g-lead p { font-size: 15px; }
578    }
579    @media (max-width: 1024px) and (min-width: 641px) {
580      .hand { height: 430px; }
581      .hand-fan { top: 210px; transform: scale(.78); transform-origin: 50% 0; }
582      .hx-calc-hd { padding: 240px 24px 0; }
583    }
584    @media (prefers-reduced-motion: reduce) {
585      *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
586      .reveal { opacity: 1 !important; transform: none !important; }
587    }
588  </style>
589  
589<script src="nav-search.js?v=2" defer></script>
589
590</head>
591<body>
592<a href="#main-content" class="skip-link">Skip to main content</a>
593
594<!-- ═══════════ NAVBAR ═══════════ -->
595<nav class="navbar">
596  <div class="nav-inner">
597    <a href="/" class="logo">
598      <div class="logo-icon">
599        <img src="images/logo-icon.svg" alt="CardCompass compass logo" width="40" height="40">
600      </div>
601      <span class="logo-text">Card<span>Compass</span></span>
602    </a>
603
604    <div class="nav-links" id="nav-links">
605      <div class="nav-item">
606        <button class="nav-link" aria-expanded="false" aria-haspopup="true">Credit Cards <i class="fas fa-chevron-down"></i></button>
607        <div class="dropdown">
608          <a href="/all-cards" style="font-weight:700"><i class="fas fa-th-list"></i> All Credit Cards</a>
609          <div style="height:1px;background:#E2E8F0;margin:6px 0;"></div>
610          <a href="/bonus-offers"><i class="fas fa-gift"></i> Current Bonus Offers</a>
611          <a href="/cards?cat=cashback"><i class="fas fa-star"></i> Best Cash Back Cards</a>
612          <a href="/cards?cat=travel"><i class="fas fa-plane"></i> Best Travel Cards</a>
613          <a href="/cards?cat=hotel"><i class="fas fa-hotel"></i> Best Hotel Cards</a>
614          <a href="/cards?cat=airline"><i class="fas fa-plane-departure"></i> Best Airline Cards</a>
615          <a href="/cards?cat=gas"><i class="fas fa-gas-pump"></i> Best Gas Cards</a>
616          <a href="/cards?cat=dining"><i class="fas fa-utensils"></i> Best Dining Cards</a>
617          <a href="/cards?cat=nofee"><i class="fas fa-shield-alt"></i> No Annual Fee Cards</a>
618          <a href="/cards?cat=balancetransfer"><i class="fas fa-sync-alt"></i> Balance Transfer Cards</a>
619          <a href="/cards?cat=business"><i class="fas fa-briefcase"></i> Best Business Cards</a>
620          <a href="/cards?cat=student"><i class="fas fa-graduation-cap"></i> Student Cards</a>
621          <a href="/cards?cat=secured"><i class="fas fa-lock"></i> Build Credit / Secured</a>
622        </div>
623      </div>
624      <div class="nav-item">
625        <button class="nav-link" aria-expanded="false" aria-haspopup="true">Rewards <i class="fas fa-chevron-down"></i></button>
626        <div class="dropdown">
627          <a href="/rewards-calculator"><i class="fas fa-calculator"></i> Points Calculator</a>
628          <a href="/rewards-partners"><i class="fas fa-exchange-alt"></i> Transfer Partners</a>
629          <a href="/rewards-maximize"><i class="fas fa-chart-line"></i> Maximize Your Points</a>
630          <a href="/rewards-award-travel"><i class="fas fa-suitcase"></i> Award Travel Guide</a>
631          <a href="/rewards-optimizer"><i class="fas fa-dollar-sign"></i> Cash Back Optimizer</a>
632          <a href="/balance-transfer-calculator"><i class="fas fa-sync-alt"></i> Balance Transfer Calc</a>
633        </div>
634      </div>
635      <div class="nav-item">
636        <button class="nav-link" aria-expanded="false" aria-haspopup="true">Airlines <i class="fas fa-chevron-down"></i></button>
637        <div class="dropdown">
638          <a href="/airlines" style="font-weight:700"><i class="fas fa-plane"></i> All Airlines</a>
639          <div style="height:1px;background:#E2E8F0;margin:6px 0;"></div>
640                    <a href="/airline-transfer-matrix"><i class="fas fa-table"></i> Transfer Partner Matrix</a>
641          <a href="/airline-search-programs"><i class="fas fa-search"></i> Search All 23 Programs</a>
642          <div style="height:1px;background:#E2E8F0;margin:6px 0;"></div>
643          <a href="/airlines/united"><i class="fas fa-plane-departure"></i> United MileagePlus</a>
644          <a href="/airlines/delta"><i class="fas fa-plane-departure"></i> Delta SkyMiles</a>
645          <a href="/airlines/american"><i class="fas fa-plane-departure"></i> American AAdvantage</a>
646          <a href="/airlines/southwest"><i class="fas fa-plane-departure"></i> Southwest Rapid Rewards</a>
647          <a href="/airlines/alaska"><i class="fas fa-plane-departure"></i> Alaska Mileage Plan</a>
648          <a href="/airlines/jetblue"><i class="fas fa-plane-departure"></i> JetBlue TrueBlue</a>
649          <a href="/airlines/hawaiian"><i class="fas fa-plane-departure"></i> Hawaiian Miles</a>
650        </div>
651      </div>
652      <a href="/quiz" class="nav-link">Card Quiz</a>
653      <a href="/compare" class="nav-link">Compare Cards</a>
654      <a href="/my-current-wallet" class="nav-link">My Current Wallet</a>
655      <a href="/guides" class="nav-link">Guides</a>
656    </div>
657
658    <div class="nav-actions">
659      <a href="/login" class="btn-outline">Log In</a>
660      <a href="/get-started" class="btn-primary">Get Started Free</a>
661    </div>
662    <button class="hamburger" id="hamburger" aria-label="Toggle menu">
663      <span></span><span></span><span></span>
664    </button>
665  </div>
666</nav>
667
668<!-- ═══════════ HERO: SPEND CALCULATOR ═══════════ -->
669<section class="hx" id="main-content">
670  <div class="hand">
671    <!-- The photo is the page's LCP element. Phones get a portrait crop (rect = the 60%-40% window object-fit shows at ~0.86 aspect), so they stop downloading the full-width landscape. -->
672    <picture>
673      <source media="(max-width: 640px)" sizes="100vw" srcset="https://images.unsplash.com/photo-1556740738-b6a63e27c4df?rect=2084,0,4667,5427&w=480&q=75&auto=format&fit=crop&monochrome=071e4a&con=-35&exp=8&blend=071e4a&bm=normal&blend-alpha=40 480w, https://images.unsplash.com/photo-1556740738-b6a63e27c4df?rect=2084,0,4667,5427&w=860&q=75&auto=format&fit=crop&monochrome=071e4a&con=-35&exp=8&blend=071e4a&bm=normal&blend-alpha=40 860w, https://images.unsplash.com/photo-1556740738-b6a63e27c4df?rect=2084,0,4667,5427&w=1080&q=75&auto=format&fit=crop&monochrome=071e4a&con=-35&exp=8&blend=071e4a&bm=normal&blend-alpha=40 1080w">
674      <img class="hand-photo" src="https://images.unsplash.com/photo-1556740738-b6a63e27c4df?w=1600&q=75&auto=format&fit=crop&monochrome=071e4a&con=-35&exp=8&blend=071e4a&bm=normal&blend-alpha=40" srcset="https://images.unsplash.com/photo-1556740738-b6a63e27c4df?w=1200&q=75&auto=format&fit=crop&monochrome=071e4a&con=-35&exp=8&blend=071e4a&bm=normal&blend-alpha=40 1200w, https://images.unsplash.com/photo-1556740738-b6a63e27c4df?w=1600&q=75&auto=format&fit=crop&monochrome=071e4a&con=-35&exp=8&blend=071e4a&bm=normal&blend-alpha=40 1600w" sizes="100vw" alt="" aria-hidden="true" fetchpriority="high">
675    </picture>
676    <div class="hand-scrim" aria-hidden="true"></div>
677    <div class="hx-inner hand-copy">
678      <div class="hero-badge" id="hero-src-badge" hidden><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--accent-gold)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/></svg>Welcome to CardCompass. Start with a rough month of spending.</div>
679      
679<script>(function(){try{var q=new URLSearchParams(location.search),s=(q.get('utm_source')||(q.get('gclid')||q.get('gbraid')||q.get('wbraid')?'google':q.get('fbclid')?'facebook':q.get('ref'))||'').toLowerCase();if(s)sessionStorage.setItem('cc_src',s);if(sessionStorage.getItem('cc_src'))document.getElementById('hero-src-badge').hidden=false;}catch(e){}})();</script>
679
680      <h1 id="hx-h1">Find the card that fits how you spend.</h1>
681      <p class="hand-actions"><a class="hand-go" href="#calc">Start with your spending</a><a class="hand-all" href="/all-cards">All&nbsp;<span data-card-count>113</span>&nbsp;cards</a></p>
682    </div>
683  </div>
684  <div class="hand-fan" aria-hidden="true">
685    <span class="hf hf1"><img src="https://cdn.prodstatic.com/shared/images/cards/500x315/083f25c0-504f-11f0-962e-072d632f0110.png" alt="" loading="eager" decoding="async"></span>
686    <span class="hf hf2"><img src="https://cdn.prodstatic.com/shared/images/cards/500x315/8cd47490-949f-11f0-832a-f3f3309a1b0c.png" alt="" loading="eager" decoding="async"></span>
687    <span class="hf hf3"><img src="https://cdn.prodstatic.com/shared/images/cards/500x315/ae837480-ed19-11ee-b6aa-83dad90afdcd.png" alt="" loading="eager" decoding="async"></span>
688    <span class="hf hf4"><img src="https://cdn.prodstatic.com/shared/images/cards/500x315/a5c974a0-eb11-11f0-8b83-5b931725a212.png" alt="" loading="eager" decoding="async"></span>
689    <span class="hf hf5"><img src="https://cdn.prodstatic.com/shared/images/cards/500x315/4f675c90-7268-11e9-8bc5-4d4394516d65.png" alt="" loading="eager" decoding="async"></span>
690  </div>
691  <div class="hx-inner hx-calc-hd" id="calc">
692    <p class="hx-cue"><span><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M19 12l-7 7-7-7"/></svg></span>Continue to see</p>
693    <h2 id="hx-calc-h2">What <span id="hx-total">$1,650</span> a month earns on a credit card</h2>
694  </div>
695  <div class="hx-inner hx-body">
696    <div class="hx-inputs">
697      <div class="hx-head"><span>Per month, roughly</span><span>Earns per year</span></div>
698      <div id="hx-rows" role="group" aria-label="Monthly spending"></div>
699      <p class="hx-note">Points counted at 1 cent each. Welcome offers counted at a conservative value, and only when this spending meets the offer's requirement. This ranks general rewards and cash back cards. Hotel, airline, business and credit-building cards are in <a href="/quiz">the quiz</a>.</p>
700    </div>
701    <div class="hx-result">
702      <div class="hx-tabs" role="group" aria-label="Period">
703        <button type="button" data-mode="yearOne" aria-pressed="true">Year one</button>
704        <button type="button" data-mode="after" aria-pressed="false">Every year after</button>
705      </div>
706      <div class="hx-stage" id="hx-stage"></div>
707      <div id="hx-detail"></div>
708      <p class="hx-legal">Rates and fees apply. Offers can change. We may earn a commission when you apply through our links and are approved. <a href="/advertiser-disclosure">Advertiser disclosure</a></p>
709      <div class="sr-only" role="status" id="hx-status"></div>
710    </div>
711  </div>
712</section>
713
714<!-- ═══════════ ADVERTISER DISCLOSURE BAR ═══════════ -->
715<div class="adv-bar">
716  <div class="adv-bar-inner">
717    <i class="fas fa-info-circle"></i>
718    <span><a href="/advertiser-disclosure">Advertiser Disclosure</a> — CardCompass may earn a commission when you apply through our links. This does not affect our ratings or rankings.</span>
719  </div>
720</div>
721
722<!-- ═══════════ PERSONAL MATCH BANNER (logged-in) ═══════════ -->
723<div class="personal-banner hidden" id="personal-banner">
724  <div class="personal-banner-inner">
725    <div class="pb-greeting"><i class="fas fa-hand"></i> Hey, <b id="pb-name">there</b></div>
726    <div class="pb-sep"></div>
727    <div class="pb-label">Your top match</div>
728    <div class="pb-match">
729      <div class="pb-badge" id="pb-badge"></div>
730      <div>
731        <div class="pb-card-name" id="pb-card-name"></div>
732        <div class="pb-card-rate" id="pb-card-rate"></div>
733      </div>
734    </div>
735    <div class="pb-earn" id="pb-earn"><small>est. yearly value</small></div>
736    <div class="pb-actions">
737      <a href="/account" class="pb-acct-btn"><i class="fas fa-user"></i> My Account</a>
738      <button class="pb-retake" onclick="location.href='/quiz'"><i class="fas fa-bullseye"></i> Update Preferences</button>
739    </div>
740  </div>
741</div>
742
743<!-- ═══════════ TOOLS: COMPARE + QUIZ ═══════════ -->
744<section class="hp hp-first" id="quiz">
745  <div class="hp-inner tools">
746    <div>
747      <div class="hp-hd" id="compare"><h2>Compare Cards</h2><a href="/compare" id="cmp-link">Compare these three</a></div>
748      <div id="cmp-table" role="table" aria-label="Your top three cards, side by side"></div>
749    </div>
750    <div>
751      <div class="hp-hd"><h2>Card Quiz</h2><span>7 questions</span></div>
752      <p class="quiz-q">What's your primary goal with a new credit card?</p>
753      <div id="quiz-opts">
754        <a class="quiz-opt" href="/quiz?goal=travel" data-goal="travel">Earn travel rewards<i class="fas fa-chevron-right" aria-hidden="true" style="font-size:12px;color:var(--text-gray)"></i></a>
755        <a class="quiz-opt" href="/quiz?goal=cashback" data-goal="cashback">Maximize cash back<i class="fas fa-chevron-right" aria-hidden="true" style="font-size:12px;color:var(--text-gray)"></i></a>
756        <a class="quiz-opt" href="/quiz?goal=airline" data-goal="airline">Earn airline miles<i class="fas fa-chevron-right" aria-hidden="true" style="font-size:12px;color:var(--text-gray)"></i></a>
757        <a class="quiz-opt" href="/quiz?goal=hotel" data-goal="hotel">Earn hotel points<i class="fas fa-chevron-right" aria-hidden="true" style="font-size:12px;color:var(--text-gray)"></i></a>
758        <a class="quiz-opt" href="/quiz?goal=credit" data-goal="credit">Build or improve my credit<i class="fas fa-chevron-right" aria-hidden="true" style="font-size:12px;color:var(--text-gray)"></i></a>
759        <a class="quiz-opt" href="/quiz?goal=business" data-goal="business">Business card for my company<i class="fas fa-chevron-right" aria-hidden="true" style="font-size:12px;color:var(--text-gray)"></i></a>
760      </div>
761      <p class="quiz-foot">It picks up the spending you entered above.</p>
762    </div>
763  </div>
764</section>
765
766<!-- ═══════════ CURRENT BONUS OFFERS ═══════════ -->
767<section class="hp">
768  <div class="hp-inner">
769    <div class="hp-hd"><h2>Current Bonus Offers</h2><a href="/bonus-offers">All bonus offers</a></div>
770    <div id="off-table" role="table" aria-label="Welcome offers, by our value"></div>
771  </div>
772</section>
773
774<!-- ═══════════ CARDS BY TYPE ═══════════ -->
775<section class="hp" id="categories">
776  <div class="hp-inner">
777    <div class="hp-hd"><h2><span data-card-count>113</span> cards, by type</h2><a href="/all-cards">All cards</a></div>
778    <div class="lanes" id="lanes"></div>
779  </div>
780</section>
781
782<!-- ═══════════ ISSUERS ═══════════ -->
783<div class="issuers"><p id="issuers"></p></div>
784
785<!-- ═══════════ GUIDES ═══════════ -->
786<section class="hp hp-first">
787  <div class="hp-inner">
788    <div class="hp-hd"><h2>Guides</h2><a href="/guides">All guides</a></div>
789    <div class="guides">
790      <a class="g-lead" href="/guides/marriott-bonvoy-credit-cards-2026">
791        <span class="g-tag">Hotel Cards</span>
792        <h3>Marriott Bonvoy Credit Cards 2026: Bold vs. Boundless vs. Brilliant vs. Business</h3>
793        <p>Four cards, two issuers, a $0 to $650 fee range. Here's which Bonvoy card's Free Night Award actually covers its own annual fee.</p>
794        <span class="g-meta">September 4, 2026 &nbsp;&middot;&nbsp; 9 min read</span>
795      </a>
796      <div>
797        <a class="g-item" href="/guides/marriott-bonvoy-points-value-2026">
798          <span class="g-tag">Rewards Strategy</span>
799          <h3>Marriott Bonvoy Points Value in 2026: What Your Points Are Really Worth</h3>
800          <p>A 100,000-point balance sounds like $1,000. It's closer to $750. Here's the formula to check any redemption before you book it.</p>
801          <span class="g-meta">September 4, 2026 &nbsp;&middot;&nbsp; 7 min read</span>
802        </a>
803        <a class="g-item" href="/guides/best-business-travel-credit-cards-2026">
804          <span class="g-tag">Business Cards</span>
805          <h3>Best Business Credit Cards for Travel in 2026</h3>
806          <p>Free employee cards, higher limits, and spend that stays off your personal credit report. Here&rsquo;s which airline and hotel business co-brands are worth the fee.</p>
807          <span class="g-meta">September 3, 2026 &nbsp;&middot;&nbsp; 10 min read</span>
808        </a>
809        <a class="g-item" href="/guides/best-hotel-credit-cards-2026">
810          <span class="g-tag">Hotel Cards</span>
811          <h3>Best Hotel Credit Cards in 2026</h3>
812          <p>Most hotel cards are bought for the points and kept for the free night certificate. Here&rsquo;s which certificates actually cover their own annual fee.</p>
813          <span class="g-meta">August 31, 2026 &nbsp;&middot;&nbsp; 10 min read</span>
814        </a>
815      </div>
816    </div>
817  </div>
818</section>
819
820<!-- ═══════════ NEWSLETTER ═══════════ -->
821<section class="newsletter-section">
822  <div class="newsletter-inner reveal">
823    <span class="newsletter-icon"><i class="fas fa-envelope-open-text" style="color:var(--accent-gold)"></i></span>
824    <h2>Get the best card deals in your inbox</h2>
825    <p>An alert when we add a card or a welcome bonus jumps, and a roundup on the 1st of each month. No spam, ever.</p>
826    <div id="newsletter-wrap">
827      <div class="newsletter-form">
828        <input type="email" class="newsletter-input" id="nl-email" placeholder="[email protected]" autocomplete="email">
829        <button class="newsletter-submit" onclick="submitNewsletter()">Subscribe Free →</button>
830      </div>
831      <div class="cf-turnstile" data-sitekey="0x4AAAAAADiVOynXw_ZhYDP0" style="margin:14px 0;"></div>
832      <div class="newsletter-error" id="nl-error">Please enter a valid email address.</div>
833      <div class="newsletter-meta" style="justify-content:center;">
834        <span><i class="fas fa-check"></i> Unsubscribe anytime</span>
835        <span><i class="fas fa-check"></i> No spam, ever</span>
836      </div>
837    </div>
838    <div class="newsletter-success" id="nl-success">
839      <h3><i class="fas fa-champagne-glasses"></i> You're in!</h3>
840      <p>Check your inbox to confirm your subscription. Your first deal alert is on the way.</p>
841    </div>
842    <div class="newsletter-already" id="nl-already">
843      <i class="fas fa-check" style="color:#22c55e"></i> You're already subscribed — we'll keep sending you the best card deal
843s each month.
844    </div>
845  </div>
846</section>
847
848<!-- ═══════════ FOOTER ═══════════ -->
849<footer>
850  <div class="footer-inner">
851    <div class="footer-top">
852      <div class="footer-brand">
853        <a href="/" class="logo">
854          <div class="logo-icon">
855            <img src="images/logo-icon.svg" alt="CardCompass compass logo" width="40" height="40">
856          </div>
857          <span class="logo-text">Card<span>Compass</span></span>
858        </a>
859        <p>CardCompass helps you navigate the crowded world of credit cards with honest, data-driven recommendations — so you earn more on every dollar you spend.</p>
860      </div>
861      <div class="footer-col">
862        <h4>Credit Cards</h4>
863        <a href="/cards?cat=cashback">Best Cash Back</a><a href="/cards?cat=travel">Best Travel Cards</a><a href="/cards?cat=hotel">Best Hotel Cards</a><a href="/cards?cat=airline">Best Airline Cards</a><a href="/cards?cat=nofee">No Annual Fee</a><a href="/cards?cat=balancetransfer">Balance Transfer</a><a href="/cards?cat=business">Business Cards</a><a href="/cards?cat=student">Student Cards</a><a href="/cards?cat=secured">Build Credit</a>
864      </div>
865      <div class="footer-col">
866        <h4>Rewards</h4>
867        <a href="/rewards-calculator">Points Calculator</a><a href="/rewards-partners">Transfer Partners</a><a href="/rewards-award-travel">Award Travel Guide</a><a href="/rewards-maximize">Maximize Rewards</a><a href="/airlines">Airport Lounge Access</a><a href="/my-current-wallet">My Current Wallet</a>
868      </div>
869      <div class="footer-col">
870        <h4>Company</h4>
871        <a href="/guides">Guides</a><a href="/about">About Us</a><a href="/about#methodology">How We Rate Cards</a><a href="/editorial-policy">Editorial Policy</a><a href="/advertiser-disclosure">Advertiser Disclosure</a><a href="/contact">Contact Us</a>
872        <a href="/account" id="footer-account-link" style="display:none;">My Account</a>
873      </div>
874    </div>
875    <div class="footer-bottom">
876      <span>© 2026 CardCompass, Inc. CardCompass may receive compensation through credit card issuer partnerships. This does not influence our ratings or recommendations. <span class="footer-social" style="display:inline-flex;align-items:center;gap:14px;vertical-align:middle;margin-left:10px;"><a href="https://www.instagram.com/cardcompassapp/" target="_blank" rel="noopener" aria-label="CardCompass on Instagram" style="display:inline-flex;margin:0;"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="20" height="20" x="2" y="2" rx="5" ry="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" x2="17.51" y1="6.5" y2="6.5"/></svg></a><a href="https://www.facebook.com/cardcompassapp" target="_blank" rel="noopener" aria-label="CardCompass on Facebook" style="display:inline-flex;margin:0;"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/></svg></a></span></span>
877      <div class="foot-links"><a href="/privacy-policy">Privacy Policy</a><a href="/terms">Terms of Service</a><a href="/advertiser-disclosure">Advertiser Disclosure</a></div>
878    </div>
879  </div>
880</footer>
881
882<button class="scroll-top" onclick="window.scrollTo({top:0,behavior:'smooth'})" aria-label="Back to top">
883  <i class="fas fa-arrow-up"></i>
884</button>
885
886<!-- ═══════════ DRAWER ═══════════ -->
887<div class="drawer-overlay" id="drawer-overlay" onclick="closeDrawer()"></div>
888<div class="drawer" id="card-drawer">
889  <div class="drawer-header">
890    <span>Card Details</span>
891    <button class="drawer-close" onclick="closeDrawer()" aria-label="Close">&times;</button>
892  </div>
893  <div class="drawer-body">
894    <div class="drawer-card-hd">
895      <div class="drawer-card-img" id="d-img"></div>
896      <div>
896<div class="drawer-card-name" id="d-name"></div><div class="drawer-card-sub" id="d-sub"></div></div>
897    </div>
898    <div class="drawer-score-bar">
899      <span class="drawer-stars" id="d-stars"></span>
900      <span class="drawer-score-val" id="d-score"></span>
901      <span class="drawer-score-lbl">/ 5 — CardCompass Score</span>
902    </div>
903    <div class="drawer-divider"></div>
904    <div class="drawer-details" id="d-details"></div>
905    <div class="drawer-divider"></div>
906    <div class="drawer-proscons" id="d-proscons"></div>
907  </div>
908  <div class="drawer-actions">
909    <button class="drawer-apply" id="drawer-apply-btn">Apply Now →</button>
910    <button class="drawer-learn" onclick="closeDrawer()">Close</button>
911  </div>
912</div>
913
914<!-- ═══════════ COOKIE CONSENT BANNER ═══════════ -->
915<div class="cookie-banner hidden" id="cookie-banner" role="dialog" aria-label="Cookie consent">
916  <p>We use cookies to analyze site traffic and improve your experience. See our <a href="/privacy-policy">Privacy Policy</a> for details.</p>
917  <div class="cookie-actions">
918    <button class="cookie-accept" onclick="acceptCookies()">Accept</button>
919    <button class="cookie-decline" onclick="declineCookies()">Decline</button>
920  </div>
921</div>
922
923<script src="cards-data.js?v=15"></script>
vendor: 1 bytes, line 923
923
924<script src="recommend-engine.js?v=4"></script>
vendor: 1 bytes, line 924
924
925<script src="nav-auth.js?v=2"></script>
vendor: 1 bytes, line 925
925
926<script>
927/* ═══════════════════════════════════════════
928   DATA — full card pool now comes from cards-data.js /
929   recommend-engine.js (window.ALL_CARDS / window.CCEngine), not a
930   hardcoded 5-card subset. See cardBadgeHtml() below for the shared
931   image-or-issuer-badge renderer used by the quiz results, banner, and
932   drawer instead of the old per-card badgeClass/badgeText fields.
933═══════════════════════════════════════════ */
934// Fills whatever sized container it's placed in (.banner-card-img,
935// .drawer-card-img, .qrc-img all already set width/height via CSS).
936function cardBadgeHtml(c) {
937  if (c.cardImage) {
938    return '<img src="' + c.cardImage + '" alt="' + c.name + '" style="width:100%;height:100%;object-fit:cover;border-radius:6px;" onerror="this.style.display=\'none\'">';
939  }
940  const bg = (c.cardArt && c.cardArt.bg) || 'linear-gradient(135deg,#1A3F6F 0%,#0D2A4A 100%)';
941  const label = (c.issuer || '').substring(0, 4).toUpperCase();
942  return '<div style="width:100%;height:100%;background:' + bg + ';color:#fff;font-size:10px;font-weight:800;display:flex;align-items:center;justify-content:center;border-radius:6px;">' + label + '</div>';
943}
944
945/* ═══════════════════════════════════════════
946   AFFILIATE APPLY NOW
947═══════════════════════════════════════════ */
948let currentBannerCardId = null;
949
950function applyNow(cardId, source) {
951  const allCards = window.ALL_CARDS || window.CCEngine.getCardPool();
952  const c = allCards.find(x => x.id === cardId);
953  if (!c) return;
954  if (typeof gtag === 'function') {
955    gtag('event', 'apply_click', { card_name: c.name, card_issuer: c.issuer, source: source });
956  }
957  window.open(window.getApplyUrl ? window.getApplyUrl(c) : c.applyUrl, '_blank', 'noopener,noreferrer');
958}
959
960const REVIEWS = [
961  { name: 'Sarah K.', initials: 'SK', av: 'av-blue',  card: 'Chase Sapphire Preferred®', rating: 5, quote: 'Earned over $1,200 in travel rewards my first year. The sign-up bonus alone paid for a round-trip flight to Europe.' },
962  { name: 'Marcus T.', initials: 'MT', av: 'av-accent', card: 'Amex Gold Card', rating: 5, quote: 'Best dining card I\'ve ever had. The 4× points at restaurants adds up incredibly fast — I earned enough for a free flight in 6 months.' },
963  { name: 'Priya L.', initials: 'PL', av: 'av-gold',  card: 'Citi Double Cash®', rating: 4, quote: 'Perfect for someone who doesn\'t want to think about categories. Flat 2% on everything, no annual fee. Simple and effective.' },
964  { name: 'James R.', initials: 'JR', av: 'av-red',    card: 'Capital One Venture X',     rating: 5, quote: 'The lounge access alone justifies the fee. Free Priority Pass plus the $300 travel credit — it basically pays for itself year after year.' },
965  { name: 'Emily C.', initials: 'EC', av: 'av-green',  card: 'Discover it® Cash Back',    rating: 4, quote: 'Great starter card. The Cashback Match in year one was a game changer — I made $400 cash back my first year with zero annual fee.' },
966  { name: 'David M.', initials: 'DM', av: 'av-blue',   card: 'Chase Sapphire Preferred®', rating: 5, quote: 'CardCompass matched me to this card based on my spending. Projected $1,100/year — I\'m actually earning $1,350. Incredible tool.' },
967  { name: 'Rachel H.', initials: 'RH', av: 'av-accent', card: 'Amex Platinum',             rating: 5, quote: 'After the $200 airline fee credit, $200 hotel credit, and Centurion lounge access, I\'m paying around $300 net for benefits worth well over $2,000.' },
968  { name: 'Tyler B.', initials: 'TB', av: 'av-purple',  card: 'Chase Freedom Unlimited',   rating: 5, quote: 'No annual fee and I\'m earning $580 a year. CardCompass showed me I was leaving money on the table with my old card — switched immediately.' },
969  { name: 'Natalie W.', initials: 'NW', av: 'av-teal', card: 'World of Hyatt Card',        rating: 4, quote: 'Hyatt points are worth 2–3× more than Marriott. CardCompass\'s comparison made it obvious. I got a free 5-night stay in my first year.' },
970  { name: 'Kevin S.', initials: 'KS', av: 'av-gold',   card: 'Capital One Venture',        rating: 5, quote: 'Transferred 80,000 miles to Air Canada and flew business class to London. Would have been $4,800 to buy the seat outright. This card is unreal.' },
971  { name: 'Aisha J.', initials: 'AJ', av: 'av-green',  card: 'Amex Gold Card',             rating: 5, quote: 'I spend a lot on groceries and restaurants. The 4× categories earn me $900+ per year in points. The $120 dining credit covers Uber Eats monthly.' },
972  { name: 'Michael T.', initials: 'MT', av: 'av-red',  card: 'Citi Custom Cash®',          rating: 5, quote: 'Automatically earns 5% on my top spend category each month. I don\'t have to think about it — it just optimizes itself. Best no-fee card I\'ve had.' },
973  { name: 'Lauren P.', initials: 'LP', av: 'av-blue',  card: 'Chase Sapphire Reserve®',   rating: 5, quote: 'The $300 travel credit drops the effective fee to $250. Add Priority Pass, TSA PreCheck reimbursement, and 3× on travel — it pays for itself easily.' }
974];
975
976/* ═══════════════════════════════════════════
977   CARD DRAWER
978═══════════════════════════════════════════ */
979let drawerTriggerEl = null;
980let drawerCardId = null;
981
982const MIN_CREDIT_LABELS = {
983  poor: 'Poor – Fair (under 640)', fair: 'Fair – Good (640+)', building: 'Building Credit',
984  good: 'Good – Excellent (700+)', verygood: 'Very Good (740+)', excellent: 'Excellent (750+)'
985};
986
987function openDrawer(id) {
988  const allCards = window.ALL_CARDS || window.CCEngine.getCardPool();
989  const c = allCards.find(x => x.id === id);
990  if (!c) return;
991
992  drawerCardId = id;
993  drawerTriggerEl = document.activeElement;
994
995  document.getElementById('d-img').className = 'drawer-card-img';
996  document.getElementById('d-img').innerHTML = cardBadgeHtml(c);
997  document.getElementById('d-name').textContent = c.name;
998  document.getElementById('d-sub').textContent = 'Best for: ' + c.bestFor;
999
1000  const fullStars = Math.round(c.cardScore || 4);
1001  const dStarFull = '<i class="fas fa-star" style="color:#F5A623"></i>';
1002  const dStarEmpty = '<i class="fas fa-star" style="color:#F5A623;opacity:.25"></i>';
1003  document.getElementById('d-stars').innerHTML = dStarFull.repeat(fullStars) + dStarEmpty.repeat(5 - fullStars);
1004  document.getElementById('d-score').textContent = (c.cardScore || 4).toFixed(1);
1005
1006  document.getElementById('d-details').innerHTML = `
1007    <div class="drawer-row"><span class="drawer-lbl">Welcome Bonus</span><span class="drawer-val gold">${c.bonusDesc || c.bonusShort || 'See terms'}</span></div>
1008    <div class="drawer-row"><span class="drawer-lbl">Annual Fee</span><span class="drawer-val">${c.annualFee > 0 ? '$' + c.annualFee + ' / year' : 'None'}</span></div>
1009    <div class="drawer-row"><span class="drawer-lbl">Best Rewards Rate</span><span class="drawer-val">${c.rateLabel}</span></div>
1010    <div class="drawer-row"><span class="drawer-lbl">Foreign Transaction Fee</span><span class="drawer-val">${c.foreignFee}</span></div>
1011    <div class="drawer-row"><span class="drawer-lbl">Recommended Credit</span><span class="drawer-val">${MIN_CREDIT_LABELS[c.minScore] || 'Good – Excellent (700+)'}</span></div>
1012  `;
1013
1014  const pros = c.pros.map(p => `<li><span><i class="fas fa-check-circle" style="color:#22c55e"></i></span>${p}</li>`).join('');
1015  const cons = c.cons.map(p => `<li><span><i class="fas fa-times-circle" style="color:#ef4444"></i></span>${p}</li>`).join('');
1016  document.getElementById('d-proscons').innerHTML = `
1017    <div class="drawerpc pros-col"><h5 class="pros-title">Pros</h5><ul>${pros}</ul></div>
1018    <div class="drawerpc cons-col"><h5 class="cons-title">Cons</h5><ul>${cons}</ul></div>
1019  `;
1020
1021  // Wire Apply Now button
1022  const applyBtn = document.getElementById('drawer-apply-btn');
1023  applyBtn.onclick = () => applyNow(id, 'drawer');
1024
1025  document.getElementById('drawer-overlay').classList.add('open');
1026  const drawer = document.getElementById('card-drawer');
1027  drawer.classList.add('open');
1028  document.body.style.overflow = 'hidden';
1029
1030  // Focus the close button after transition
1031  setTimeout(() => {
1032    const closeBtn = drawer.querySelector('.drawer-close');
1033    if (closeBtn) closeBtn.focus();
1034  }, 350);
1035
1036  if (typeof gtag === 'function') {
1037    gtag('event', 'card_detail_view', { card_name: c.name, card_issuer: c.issuer });
1038  }
1039
1040  // Focus trap
1041  const FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
1042  drawer.addEventListener('keydown', trapFocus);
1043  function trapFocus(e) {
1044    if (e.key !== 'Tab') return;
1045    const focusable = Array.from(drawer.querySelectorAll(FOCUSABLE)).filter(el => !el.disabled);
1046    if (!focusable.length) { e.preventDefault(); return; }
1047    const first = focusable[0], last = focusable[focusable.length - 1];
1048    if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } }
1049    else            { if (document.activeElement === last)  { e.preventDefault(); first.focus(); } }
1050  }
1051  drawer._trapFocus = trapFocus;
1052}
1053
1054function closeDrawer() {
1055  const drawer = document.getElementById('card-drawer');
1056  if (drawer._trapFocus) { drawer.removeEventListener('keydown', drawer._trapFocus); delete drawer._trapFocus; }
1057  document.getElementById('drawer-overlay').classList.remove('open');
1058  drawer.classList.remove('open');
1059  document.body.style.overflow = '';
1060  if (drawerTriggerEl) { drawerTriggerEl.focus(); drawerTriggerEl = null; }
1061}
1062
1063document.addEventListener('keydown', e => { if (e.key === 'Escape') closeDrawer(); });
1064
1065/* ═══════════════════════════════════════════
1066   REVIEWS CAROUSEL
1067═══════════════════════════════════════════ */
1068let carouselIndex = 0;
1069let carouselTimer;
1070
1071function getPerPage() {
1072  return window.innerWidth < 768 ? 1 : window.innerWidth < 1024 ? 2 : 3;
1073}
1074
1075function renderTrack() {
1076  const track = document.getElementById('reviews-track');
1077  if (!track) return;
1078  const rvStarFull = '<i class="fas fa-star" style="color:#F5A623"></i>';
1079  const rvStarEmpty = '<i class="fas fa-star" style="color:#F5A623;opacity:.25"></i>';
1080  track.innerHTML = REVIEWS.map(r => {
1081    const stars = rvStarFull.repeat(r.rating) + rvStarEmpty.repeat(5 - r.rating);
1082    return `<div class="review-card">
1083      <div class="review-stars">${stars}</div>
1084      <div class="review-quote">${r.quote}</div>
1085      <div class="review-author">
1086        <div class="review-avatar ${r.av}">${r.initials}</div>
1087        <div><div class="review-name">${r.name}</div><div class="review-card-name">${r.card}</div></div>
1088      </div>
1089    </div>`;
1090  }).join('');
1091}
1092
1093function slideToPage(page) {
1094  const perPage = getPerPage();
1095  const track = document.getElementById('reviews-track');
1096  if (!track) return;
1097  const card = track.querySelector('.review-card');
1098  if (!card) return;
1099  const cardW = card.offsetWidth;
1100  const gap = 24;
1101  const maxPage = Math.ceil(REVIEWS.length / perPage) - 1;
1102  const safePage = Math.min(page, maxPage);
1103  track.style.transform = `translateX(-${safePage * perPage * (cardW + gap)}px)`;
1104}
1105
1106function updateDots() {
1107  const perPage = getPerPage();
1108  const pages = Math.ceil(REVIEWS.length / perPage);
1109  const dots = document.getElementById('carousel-dots');
1110  if (!dots) return;
1111  dots.innerHTML = '';
1112  for (let i = 0; i < pages; i++) {
1113    const d = document.createElement('button');
1114    d.className = 'carousel-dot' + (i === carouselIndex ? ' active' : '');
1115    d.setAttribute('aria-label', `Page ${i + 1}`);
1116    d.onclick = () => { carouselIndex = i; slideToPage(i); updateDots(); resetTimer(); };
1117    dots.appendChild(d);
1118  }
1119}
1120
1121function carousel(dir) {
1122  const perPage = getPerPage();
1123  const pages = Math.ceil(REVIEWS.length / perPage);
1124  carouselIndex = (carouselIndex + dir + pages) % pages;
1125  slideToPage(carouselIndex);
1126  updateDots();
1127  resetTimer();
1128}
1129
1130function resetTimer() {
1131  clearInterval(carouselTimer);
1132  carouselTimer = setInterval(() => carousel(1), 5000);
1133}
1134
1135
1136/* ═══════════════════════════════════════════
1137   SCROLL ANIMATIONS (IntersectionObserver)
1138═══════════════════════════════════════════ */
1139const revealObserver = new IntersectionObserver((entries) => {
1140  entries.forEach(el => {
1141    if (el.isIntersecting) { el.target.classList.add('visible'); revealObserver.unobserve(el.target); }
1142  });
1143}, { threshold: 0.12 });
1144
1145document.querySelectorAll('.reveal').forEach(el => revealObserver.observe(el));
1146
1147
1148/* ═══════════════════════════════════════════
1149   COUNTER ANIMATION
1150═══════════════════════════════════════════ */
1151function animateCount(el) {
1152  const target = parseInt(el.dataset.target);
1153  const divisor = parseInt(el.dataset.divisor) || 1;
1154  const suffix = el.dataset.suffix || '';
1155  const prefix = el.dataset.prefix || '';
1156  const duration = 1500;
1157  const start = performance.now();
1158
1159  function step(now) {
1160    const progress = Math.min((now - start) / duration, 1);
1161    const ease = 1 - Math.pow(1 - progress, 3);
1162    const val = Math.round(target * ease / divisor);
1163    el.textContent = prefix + val.toLocaleString() + suffix;
1164    if (progress < 1) requestAnimationFrame(step);
1165    else el.textContent = el.dataset.final;
1166  }
1167
1168  el.dataset.final = el.textContent;
1169  requestAnimationFrame(step);
1170}
1171
1172const counterObserver = new IntersectionObserver((entries) => {
1173  entries.forEach(e => {
1174    if (e.isIntersecting) { animateCount(e.target); counterObserver.unobserve(e.target); }
1175  });
1176}, { threshold: 0.5 });
1177
1178document.querySelectorAll('[data-card-count]').forEach(el => {
1179  el.textContent = window.ALL_CARDS.length;
1180  if (el.dataset.target) el.dataset.target = window.ALL_CARDS.length;
1181});
1182document.querySelectorAll('.trust-item .num[data-target]').forEach(el => counterObserver.observe(el));
1183
1184/* ═══════════════════════════════════════════
1185   HAMBURGER (MOBILE NAV)
1186═══════════════════════════════════════════ */
1187const hamburger = document.getElementById('hamburger');
1188const navLinks  = document.getElementById('nav-links');
1189
1190hamburger.addEventListener('click', () => {
1191  const open = hamburger.classList.toggle('open');
1192  navLinks.classList.toggle('mobile-open', open);
1193  hamburger.setAttribute('aria-expanded', open ? 'true' : 'false');
1194});
1195
1196document.addEventListener('click', e => {
1197  if (!hamburger.contains(e.target) && !navLinks.contains(e.target)) {
1198    hamburger.classList.remove('open');
1199    navLinks.classList.remove('mobile-open');
1200  }
1201});
1202
1203/* ═══════════════════════════════════════════
1204   NEWSLETTER
1205═══════════════════════════════════════════ */
1206async function submitNewsletter() {
1207  const emailEl = document.getElementById('nl-email');
1208  const errEl   = document.getElementById('nl-error');
1209  const btn     = document.querySelector('.newsletter-submit');
1210  const email   = emailEl.value.trim();
1211  errEl.style.display = 'none';
1212
1213  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
1214    errEl.style.display = 'block';
1215    emailEl.focus();
1216    return;
1217  }
1218
1219  const token = window.turnstile?.getResponse();
1220  if (!token) {
1221    errEl.textContent = 'Please complete the verification below.';
1222    errEl.style.display = 'block';
1223    return;
1224  }
1225
1226  btn.disabled = true;
1227  btn.textContent = 'Subscribing…';
1228
1229  try {
1230    const res  = await fetch('/api/subscribe', {
1231      method: 'POST',
1232      headers: { 'Content-Type': 'application/json' },
1233      body: JSON.stringify({ email, cfToken: token })
1234    });
1235    const data = await res.json();
1236
1237    if (data.status === 'subscribed') {
1238      localStorage.setItem('cc_subscribed', email);
1239      document.getElementById('newsletter-wrap').style.display = 'none';
1240      document.getElementById('nl-success').style.display = 'block';
1241      if (typeof gtag === 'function') gtag('event', 'newsletter_signup');
1242    } else {
1243      throw new Error('bad response');
1244    }
1245  } catch {
1246    errEl.textContent = 'Something went wrong — please try again.';
1247    errEl.style.display = 'block';
1248    btn.disabled = false;
1249    btn.textContent = 'Subscribe Free →';
1250    window.turnstile?.reset();
1251  }
1252}
1253
1254(function checkSubscribed() {
1255  if (localStorage.getItem('cc_subscribed')) {
1256    document.getElementById('newsletter-wrap').style.display = 'none';
1257    document.getElementById('nl-already').style.display = 'block';
1258  }
1259})();
1260
1261/* ═══════════════════════════════════════════
1262   NAV SCROLL HELPERS
1263═══════════════════════════════════════════ */
1264
1265/* ═══════════════════════════════════════════
1266   WELCOME TOAST (post-login redirect)
1267═══════════════════════════════════════════ */
1268(function () {
1269  const msg = sessionStorage.getItem('cc_welcome');
1270  if (!msg) return;
1271  sessionStorage.removeItem('cc_welcome');
1272  const t = document.createElement('div');
1273  t.style.cssText = 'position:fixed;bottom:28px;left:50%;transform:translateX(-50%) translateY(80px);background:#1A1A2E;color:#fff;padding:13px 22px;border-radius:12px;font-size:14px;font-weight:500;z-index:9999;opacity:0;transition:all .35s cubic-bezier(.4,0,.2,1);max-width:calc(100vw - 32px);width:max-content;text-align:center;box-shadow:0 8px 28px rgba(0,0,0,.25);font-family:Inter,sans-serif;';
1274  // msg carries the user's name, so it goes in as text, not HTML.
1275  t.innerHTML = '<i class="fas fa-hand" style="margin-right:6px"></i>';
1276  t.append(msg);
1277  document.body.appendChild(t);
1278  setTimeout(() => { t.style.opacity = '1'; t.style.transform = 'translateX(-50%) translateY(0)'; }, 100);
1279  setTimeout(() => { t.style.opacity = '0'; t.style.transform = 'translateX(-50%) translateY(80px)'; }, 4000);
1280  setTimeout(() => t.remove(), 4500);
1281})();
1282
1283/* ═══════════════════════════════════════════
1284   BEST CARDS TABLE — DYNAMIC CATEGORY TABS
1285═══════════════════════════════════════════ */
1286const ISSUER_BADGE = {
1287  'Chase':             'ci-chase',
1288  'American Express':  'ci-amex',
1289  'Citi':              'ci-citi',
1290  'Capital One':       'ci-cap',
1291  'Discover':          'ci-disc',
1292};
1293function issuerBadge(issuer) {
1294  return ISSUER_BADGE[issuer] || 'ci-chase';
1295}
1296
1297const COMP_PAGE_SIZE = 6;
1298let _compCat = 'bestoffers';
1299
1300function _getFilteredCards(cat) {
1301  const allCards = window.ALL_CARDS || window.CCEngine.getCardPool();
1302  let filtered = [...allCards]
1303    .filter(c => (c.categories || []).includes(cat))
1304    .sort((a, b) => (b.cardScore || 0) - (a.cardScore || 0));
1305  // Fallback: if no cards match, show top-rated overall
1306  if (!filtered.length) {
1307    filtered = [...allCards].sort((a, b) => (b.cardScore || 0) - (a.cardScore || 0));
1308  }
1309  return filtered;
1310}
1311
1312function renderCompTable(cat, page) {
1313  page = (page === undefined) ? 0 : page;
1314  _compCat = cat;
1315
1316  const filtered    = _getFilteredCards(cat);
1317  const totalPages  = Math.max(1, Math.ceil(filtered.length / COMP_PAGE_SIZE));
1318  const safePage    = Math.min(Math.max(0, page), totalPages - 1);
1319  const pageCards   = filtered.slice(safePage * COMP_PAGE_SIZE, (safePage + 1) * COMP_PAGE_SIZE);
1320
1321  const tbody = document.getElementById('comp-table-body');
1322  if (!tbody) return;
1323  const header = `<div class="comp-header">
1324    <span></span><span>Card</span><span>Welcome Bonus</span><span>Best Rate</span><span>Annual Fee</span><span></span>
1325  </div>`;
1326
1327  const rows = pageCards.map(c => {
1328    const badge = issuerBadge(c.issuer);
1329    const imgHtml = c.cardImage
1330      ? `<img src="${c.cardImage}" alt="${c.name}" loading="lazy" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:8px;" onerror="this.style.display='none'">`
1331      : '';
1332    const bonusShort = c.bonusShort || (c.bonusDesc ? c.bonusDesc.split(' after')[0] : '—');
1333    const bonusVal   = c.bonusValue ? `≈ $${c.bonusValue.toLocaleStr
1333ing()} value` : '&nbsp;';
1334    const feeLabel   = c.annualFee === 0
1335      ? '<span style="color:#10B981;font-weight:700;">$0/yr</span>'
1336      : `$${c.annualFee}/yr`;
1337    return `<div class="comp-row" onclick="openDrawer('${c.id}')">
1338      <div class="comp-info">
1339        <div class="comp-card-img ${badge}" style="position:relative;">${imgHtml}</div>
1340        <div>
1341          <div class="comp-name">${c.name}</div>
1342          <div class="comp-sub">${c.bestFor || ''}</div>
1343        </div>
1344      </div>
1345      <div class="comp-cell"><span class="hi">${bonusShort}</span><br><span class="tiny">${bonusVal}</span></div>
1346      <div class="comp-cell"><span class="badge">${c.rateLabel || '—'}</span></div>
1347      <div class="comp-cell">${feeLabel}</div>
1348      <div class="comp-cell"><button class="comp-btn" onclick="event.stopPropagation();applyNow('${c.id}','comp_table')">Apply Now</button></div>
1349    </div>`;
1350  }).join('');
1351
1352  tbody.innerHTML = header + rows;
1353  tbody.classList.add('visible');
1354
1355  // Render pagination controls
1356  const pg = document.getElementById('comp-pagination');
1357  if (pg) {
1358    if (totalPages <= 1) {
1359      pg.innerHTML = '';
1360    } else {
1361      let pgHtml = `<button class="comp-pager-btn" onclick="renderCompTable('${cat}',${safePage - 1})" ${safePage === 0 ? 'disabled' : ''}><i class="fas fa-chevron-left"></i></button>`;
1362      for (let i = 0; i < totalPages; i++) {
1363        pgHtml += `<button class="comp-pager-num${i === safePage ? ' active' : ''}" onclick="renderCompTable('${cat}',${i})">${i + 1}</button>`;
1364      }
1365      pgHtml += `<button class="comp-pager-btn" onclick="renderCompTable('${cat}',${safePage + 1})" ${safePage === totalPages - 1 ? 'disabled' : ''}><i class="fas fa-chevron-right"></i></button>`;
1366      pg.innerHTML = pgHtml;
1367    }
1368  }
1369}
1370
1371// Wire up the category tabs — resets to page 0 on every tab switch
1372(function initCompTabs() {
1373  const tabs = document.querySelectorAll('.comp-tab');
1374  renderCompTable('bestoffers', 0);
1375  tabs.forEach(btn => {
1376    btn.addEventListener('click', () => {
1377      tabs.forEach(t => t.classList.remove('active'));
1378      btn.classList.add('active');
1379      renderCompTable(btn.dataset.cat, 0);
1380    });
1381  });
1382})();
1383
1384/* ═══════════════════════════════════════════
1385   AUTH — PERSONAL BANNER (scores the full window.ALL_CARDS pool via
1386   window.CCEngine — this used to only ever consider 5 hardcoded cards)
1387═══════════════════════════════════════════ */
1388(function updateBannerForAuth() {
1389  if (!localStorage.getItem('cc_token')) return;
1390  let user = null;
1391  try { user = JSON.parse(localStorage.getItem('cc_user') || 'null'); } catch(e) {}
1392  if (!user || !user.prefs) return;
1393
1394  const prefs  = user.prefs;
1395  const profile = window.CCEngine.profileFromQuickForm(prefs.goal, prefs.spend, prefs.credit, prefs.maxFee);
1396  const maxFee = parseFloat(prefs.maxFee) || 9999;
1397
1398  const scored = window.CCEngine.rankCards(profile, { limit: 20 })
1399    .filter(c => c.annualFee <= maxFee)
1400    .map(c => {
1401      const yearly = Object.keys(profile.spend).reduce(
1402        (sum, cat) => sum + window.CCEngine.estimateEarn(c, cat, profile.spend[cat]) * 12, 0);
1403      const net = Math.round(yearly + (c.bonusValue / 2) - c.annualFee);
1404      return Object.assign({}, c, { yearly: Math.round(yearly), net: net });
1405    })
1406    .sort((a, b) => b.net - a.net);
1407
1408  if (!scored.length) return;
1409  const top = scored[0];
1410  const fname = user.fname || 'there';
1411  document.getElementById('pb-name').textContent      = fname;
1412  const badge = document.getElementById('pb-badge');
1413  badge.className = 'pb-badge';
1414  badge.innerHTML = cardBadgeHtml(top);
1415  document.getElementById('pb-card-name').textContent = top.name;
1416  document.getElementById('pb-card-rate').textContent = top.rateLabel + (top.annualFee > 0 ? ' · $' + top.annualFee + '/yr' : ' · No fee');
1417  document.getElementById('pb-earn').innerHTML = '$' + top.net.toLocaleString() + '<small>est. yearly value</small>';
1418  document.getElementById('personal-banner').classList.remove('hidden');
1419})();
1420
1421function toggleUserDropdown() {
1422  const nav = document.getElementById('user-nav');
1423  if (!nav) return;
1424  const open = nav.classList.toggle('open');
1425  document.getElementById('user-btn').setAttribute('aria-expanded', open ? 'true' : 'false');
1426}
1427
1428/* Close dropdown on outside click */
1429document.addEventListener('click', function(e) {
1430  const nav = document.getElementById('user-nav');
1431  if (nav && !nav.contains(e.target)) {
1432    nav.classList.remove('open');
1433    const btn = document.getElementById('user-btn');
1434    if (btn) btn.setAttribute('aria-expanded', 'false');
1435  }
1436});
1437
1438// signOut() is provided by nav-auth.js (window.signOut) — it clears cc_token/cc_user
1439// and calls /api/auth/logout. Not redefined here so that shared implementation wins.
1440
1441/* ═══════════════════════════════════════════
1442   COOKIE CONSENT
1443═══════════════════════════════════════════ */
1444function acceptCookies() {
1445  localStorage.setItem('cc_consent', 'granted');
1446  document.getElementById('cookie-banner').classList.add('hidden');
1447  if (typeof gtag === 'function') {
1448    gtag('consent', 'update', { analytics_storage: 'granted', ad_storage: 'granted', ad_user_data: 'granted', ad_personalization: 'granted' });
1449  }
1450}
1451
1452function declineCookies() {
1453  localStorage.setItem('cc_consent', 'denied');
1454  document.getElementById('cookie-banner').classList.add('hidden');
1455  if (typeof gtag === 'function') {
1456    gtag('consent', 'update', { analytics_storage: 'denied', ad_storage: 'denied', ad_user_data: 'denied', ad_personalization: 'denied' });
1457  }
1458}
1459
1460(function initCookieBanner() {
1461  if (localStorage.getItem('cc_consent')) return;
1462  const eu = /^Europe\//.test(Intl.DateTimeFormat().resolvedOptions().timeZone);
1463  const banner = document.getElementById('cookie-banner');
1464  if (!eu) {
1465    banner.querySelector('p').innerHTML = 'We use analytics cookies to understand site traffic and improve CardCompass. You can opt out anytime — see our <a href="/privacy-policy">Privacy Policy</a>.';
1466    banner.querySelector('.cookie-accept').textContent = 'OK';
1467    banner.querySelector('.cookie-decline').textContent = 'Opt out';
1468  }
1469  // Show banner after a short delay so page renders first
1470  setTimeout(() => banner.classList.remove('hidden'), 1500);
1471})();
1472
1473/* ═══════════════════════════════════════════
1474   HOMEPAGE: SPEND CALCULATOR, COMPARE, OFFERS, LANES
1475   All ranking is window.CCEngine.rankBySpend (recommend-engine.js).
1476   Spend options are quiz.html's own buckets, so /quiz?grocery=450...
1477   pre-fills its spending question exactly.
1478═══════════════════════════════════════════ */
1479(function homepage() {
1480  const cards = window.ALL_CARDS || [];
1481  const E = window.CCEngine;
1482  if (!cards.length || !E || !E.rankBySpend) return;
1483
1484  const CATS = [
1485    ['grocery', 'Groceries', [0, 200, 450, 800]],
1486    ['dining', 'Dining out', [0, 200, 450, 800]],
1487    ['travel', 'Travel', [0, 200, 450, 800], 'booked through the card’s portal'],
1488    ['gas', 'Gas', [0, 200, 450, 800]],
1489    ['other', 'Everything else', [200, 450, 800, 1500]]
1490  ];
1491  const spend = { grocery: 450, dining: 200, travel: 0, gas: 200, other: 800 };
1492
1493  // Ads land here promising a specific amount ($500/mo groceries, $400/mo dining,
1494  // $2,000/mo mixed for the debit call-out), so that amount is preselected;
1495  // ?grocery=500 etc. works too. An amount that isn't a button replaces the nearest
1496  // one, and the quiz accepts it as an exact amount.
1497  const DEBIT = { grocery: 500, dining: 300, travel: 0, gas: 200, other: 1000 };
1498  const AD_SPEND = { groceries: { grocery: 500 }, dining: { dining: 400 }, debit: DEBIT, 'debit-static': DEBIT };
1499  const params = new URLSearchParams(location.search);
1500  const preset = Object.assign({}, AD_SPEND[params.get('utm_content')]);
1501  CATS.forEach(([k]) => { if (/^\d{1,5}$/.test(params.get(k) || '')) preset[k] = +params.get(k); });
1502  Object.entries(preset).forEach(([k, v]) => {
1503    const opts = CATS.find(x => x[0] === k)[2];
1504    if (!opts.includes(v)) {
1505      opts[opts.reduce((best, o, i) => Math.abs(o - v) < Math.abs(opts[best] - v) ? i : best, 0)] = v;
1506      opts.sort((a, b) => a - b);
1507    }
1508    spend[k] = v;
1509  });
1510  // The debit call-out quotes the whole mix net of fees with welcome offers excluded,
1511  // which is the "every year after" figure, so its visitors start on that tab.
1512  let mode = /^debit/.test(params.get('utm_content') || '') ? 'after' : 'yearOne';
1513  let touched = false;
1514
1515  const $ = id => document.getElementById(id);
1516  const esc = s => String(s).replace(/[&<>"]/g, ch => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[ch]));
1517  const money = n => (n < 0 ? '−$' : '$') + Math.abs(Math.round(n)).toLocaleString('en-US');
1518  const short = c => c.name.replace(/[®™℠]/g, '').replace(/\s+(Credit )?Card( from .*)?$/i, '').trim();
1519  const rate = (r, k) => (r.rates[k] || 0) + ((r.card.categories || []).includes('cashback') ? '%' : '×');
1520  const day = iso => new Date(iso + 'T12:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
1521  const track = cta => { if (typeof gtag === 'function') gtag('event', 'hero_cta_click', { cta: cta }); };
1522
1523  // The site stores 219px issuer art; the same host serves 500px. Art from anywhere
1524  // else is shown as supplied (it is often angled or transparent).
1525  const COMPOSITE = ['amex-gold'];
1526  function art(c, cls, tag) {
1527    const src = (c.cardImage || '').replace('/cards/219x139/', '/cards/500x315/');
1528    const kind = COMPOSITE.includes(c.id) ? 'gold' : /cdn\.prodstatic\.com/.test(src) ? 'flat' : 'asis';
1529    const img = '<img src="' + esc(src) + '" alt="' + (tag === 'a' ? esc(c.name) : '') + '" loading="lazy">';
1530    return tag === 'a'
1531      ? '<a class="' + cls + ' ' + kind + '" href="/card/' + c.id + '" data-cta="card">' + img + '</a>'
1532      : '<span class="' + cls + ' ' + kind + '" aria-hidden="true">' + img + '</span>';
1533  }
1534
1535  function render() {
1536    const ranked = E.rankBySpend(spend, { mode: mode });
1537    const top = ranked[0], c = top.card;
1538    const total = CATS.reduce((s, x) => s + spend[x[0]], 0);
1539    const after = mode === 'after';
1540    $('hx-total').textContent = money(total);
1541
1542    $('hx-rows').innerHTML = CATS.map(([k, label, opts, sub]) =>
1543      '<div class="hx-row"><span class="hx-row-label">' + label + (sub ? '<small>' + sub + '</small>' : '') + '</span>' +
1544      '<div class="hx-seg" role="group" aria-label="' + label + ' per month">' +
1545      opts.map(v => '<button type="button" data-cat="' + k + '" data-val="' + v + '" aria-pressed="' + (spend[k] === v) + '">' + money(v) + '</button>').join('') +
1546      '</div><span class="hx-earn">' + rate(top, k) + ' <strong>' + money(top.perCat[k]) + '</strong></span></div>').join('');
1547
1548    // Two cards fan out behind the winner. Composite art cannot sit in a fan, so the
1549    // fan skips it; "Next best" below still names the true second and third.
1550    const fan = COMPOSITE.includes(c.id) ? [] : ranked.slice(1).filter(r => !COMPOSITE.includes(r.card.id)).slice(0, 2);
1551    $('hx-stage').innerHTML = art(c, 'hx-art hx-win', 'a') +
1552      (fan[0] ? art(fan[0].card, 'hx-art hx-r2') : '') + (fan[1] ? art(fan[1].card, 'hx-art hx-r3') : '');
1553
1554    const q = new URLSearchParams(spend).toString();
1555    const three = ranked.slice(0, 3).map(r => r.card);
1556    const cmpHref = '/compare?' + three.map((x, i) => 'abc'[i] + '=' + x.id).join('&');
1557    const welcome = top.met
1558      ? '<div><span>Welcome offer: ' + esc(c.bonusDesc || c.bonusShort || '') + '</span><strong>+' + money(top.welcome) + '</strong></div>'
1559      : '<div><span>Welcome offer needs ' + money(top.req.amount) + ' in ' + top.req.months + ' months, not counted at this spending</span><strong>$0</strong></div>';
1560    $('hx-detail').innerHTML =
1561      '<div class="hx-meta">' + esc(c.issuer) + (top.checked ? ' · Offer checked ' + day(top.checked) : '') + '</div>' +
1562      '<h2 class="hx-name"><a href="/card/' + c.id + '" data-cta="card">' + esc(c.name) + '</a></h2>' +
1563      '<div class="hx-figrow"><span class="hx-fig">' + money(after ? top.after : top.yearOne) + '</span>' +
1564      '<span class="hx-figlabel">' + (after ? 'every year after' : 'in year one') + '</span>' +
1565      '<span class="hx-recip"><strong>' + money(after ? top.yearOne : top.after) + '</strong> ' + (after ? 'in year one' : 'every year after') + '</span></div>' +
1566      '<div class="hx-actions"><a class="hx-cta" href="/quiz?' + q + '" data-cta="quiz">Continue in the quiz</a>' +
1567      '<span>Your numbers carry over. It ranks all ' + cards.length + ' cards.<
1567/span></div>' +
1568      '<div class="hx-ledger"><div><span>Rewards on this spending</span><strong>' + money(top.rewards) + '</strong></div>' +
1569      (after ? '' : welcome) +
1570      '<div><span>Annual fee</span><strong>' + (top.fee ? '−' + money(top.fee) : '$0') + '</strong></div></div>' +
1571      (c.note ? '<p class="hx-worth">' + esc(c.note) + '</p>' : '') +
1572      '<div class="hx-next"><span>Next best</span>' + ranked.slice(1, 3).map(r =>
1573        '<span>' + esc(short(r.card)) + ' <strong>' + money(after ? r.after : r.yearOne) + '</strong></span>').join('') + '</div>' +
1574      '<p class="hx-links"><a href="' + cmpHref + '" data-cta="compare">Compare these three side by side</a>' +
1575      '<a href="/card/' + c.id + '" data-cta="card">Card details</a>' +
1576      (c.guideUrl ? '<a href="' + esc(c.guideUrl) + '" data-cta="guide">Our guide</a>' : '') + '</p>';
1577    $('hx-status').textContent = c.name + ', ' + money(after ? top.after : top.yearOne) + (after ? ' every year after.' : ' in year one.');
1578
1579    // Compare Cards follows the same three.
1580    const row = (label, f) => '<div class="cmp-row" role="row"><span role="rowheader">' + label + '</span>' + three.map(x => '<span role="cell">' + f(x) + '</span>').join('') + '</div>';
1581    const floor = x => { const r = ranked.find(y => y.card === x); return r.offer ? money(r.offer) : 'None'; };
1582    $('cmp-table').innerHTML =
1583      '<div class="cmp-row cmp-head" role="row"><span role="columnheader">Your top three, from the numbers above</span>' +
1584      three.map(x => '<span class="cmp-card" role="columnheader">' + art(x, 'cc-thumb') + esc(short(x)) + '</span>').join('') + '</div>' +
1585      row('Welcome offer', x => esc(x.bonusShort || 'None')) + row('Our value', floor) +
1586      row('Annual fee', x => money(x.annualFee || 0)) +
1587      CATS.map(([k, label]) => row(label, x => rate(ranked.find(y => y.card === x), k))).join('');
1588    $('cmp-link').href = cmpHref;
1589    document.querySelectorAll('#quiz-opts a').forEach(a => { a.href = '/quiz?goal=' + a.dataset.goal + '&' + q; });
1590  }
1591
1592  $('hx-rows').addEventListener('click', e => {
1593    const b = e.target.closest('button[data-cat]');
1594    if (!b) return;
1595    spend[b.dataset.cat] = +b.dataset.val;
1596    if (!touched && typeof gtag === 'function') gtag('event', 'hero_calc_use', { category: b.dataset.cat });
1597    touched = true;
1598    render();
1599    const again = $('hx-rows').querySelector('[data-cat="' + b.dataset.cat + '"][data-val="' + b.dataset.val + '"]');
1600    if (again) again.focus();   // the rows re-render; keep the keyboard user's place
1601  });
1602  document.querySelectorAll('.hx-tabs button').forEach(b => b.addEventListener('click', () => {
1603    mode = b.dataset.mode;
1604    document.querySelectorAll('.hx-tabs button').forEach(x => x.setAttribute('aria-pressed', String(x === b)));
1605    render();
1606  }));
1607  document.querySelectorAll('.hx-tabs button').forEach(x => x.setAttribute('aria-pressed', String(x.dataset.mode === mode)));
1608  document.querySelector('.hx').addEventListener('click', e => {
1609    const a = e.target.closest('a[data-cta]');
1610    if (a) track(a.dataset.cta);
1611  });
1612  render();
1613
1614  /* ── Current Bonus Offers: the conservative floor, never the headline; stale offers are skipped ── */
1615  const F = window.CARD_FRESHNESS || {};
1616  const offers = cards
1617    .filter(x => F[x.id] && F[x.id].bonusFloor > 0 && !(window.getBonusDisplay && window.getBonusDisplay(x).stale))
1618    .sort((a, b) => F[b.id].bonusFloor - F[a.id].bonusFloor || (a.annualFee || 0) - (b.annualFee || 0))
1619    .slice(0, 7);
1620  $('off-table').innerHTML =
1621    '<div class="off-row off-head" role="row"><span></span><span role="columnheader">Card</span><span class="off-desc" role="columnheader">Welcome offer</span>' +
1622    '<span class="off-r" role="columnheader">Our value</span><span class="off-r off-fee" role="columnheader">Annual fee</span><span class="off-r off-checked" role="columnheader">Last checked</span></div>' +
1623    offers.map(x => '<a class="off-row" role="row" href="/card/' + x.id + '">' + art(x, 'cc-thumb') +
1624      '<span class="off-name" role="cell">' + esc(x.name) + '<span class="off-issuer">
1624' + esc(x.issuer) + '</span></span>' +
1625      '<span class="off-desc" role="cell">' + esc(x.bonusDesc || x.bonusShort || '') + '</span>' +
1626      '<span class="off-val off-r" role="cell">' + money(F[x.id].bonusFloor) + '</span>' +
1627      '<span class="off-r off-fee" role="cell">' + money(x.annualFee || 0) + '</span>' +
1628      '<span class="off-r off-checked" role="cell">' + day(F[x.id].bonusUpdated) + '</span></a>').join('');
1629
1630  /* ── Cards by type: live counts, the two best-scored cards of each lane as art ── */
1631  const LANES = [['travel', 'Travel'], ['cashback', 'Cash back'], ['dining', 'Dining'], ['nofee', 'No annual fee'], ['secured', 'Build credit'], ['business', 'Business']];
1632  const used = new Set();
1633  $('lanes').innerHTML = LANES.map(([k, label]) => {
1634    const inLane = cards.filter(x => (x.categories || []).includes(k));
1635    const pics = inLane.filter(x => /cdn\.prodstatic\.com/.test(x.cardImage || '') && !COMPOSITE.includes(x.id) && !used.has(x.id)
1636        && (k === 'business' || !(x.categories || []).includes('business')))   // consumer lanes show consumer cards
1637      .sort((a, b) => (b.cardScore || 0) - (a.cardScore || 0)).slice(0, 2);
1638    pics.forEach(x => used.add(x.id));
1639    return '<a class="lane" href="/cards?cat=' + k + '"><div class="lane-art">' + pics.map(x => art(x, 'cc-thumb')).join('') + '</div>' +
1640      '<div class="lane-ft"><strong>' + label + '</strong><span>' + inLane.length + ' cards</span></div></a>';
1641  }).join('');
1642
1643  /* ── Issuers ── */
1644  const byIssuer = {};
1645  cards.forEach(x => { byIssuer[x.issuer] = (byIssuer[x.issuer] || 0) + 1; });
1646  const names = Object.keys(byIssuer).sort((a, b) => byIssuer[b] - byIssuer[a]);
1647  $('issuers').innerHTML = '<span>' + cards.length + ' cards from ' + names.length + ' issuers</span>' + names.slice(0, 9).map(n => '<span>' + esc(n) + '</span>').join('');
1648})();
1649</script>
vendor: 1 bytes, line 1649
1649
1650<script src="/newsletter-popup.js" defer></script>
vendor: 1 bytes, line 1650
1650
1651<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"e79d8b0449fb411087aba9b1640e3938","r":1,"spa":2}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 1651
1651
1652<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a43fd73e19fa4e06',t:'MTc5MDkwMzU2Nw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
1652</body>
1653</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.