PageSourceSearch

https://www.simplystamps.com/

html simplystamps.com collected 2026-10-02 06:57:22 UTC 891,425 bytes, 11,612 lines download raw bytes

1<!DOCTYPE html><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>Custom Stamps &amp; Personalized Rubber Stamps | Simply Stamps</title><meta name="viewport" content="width=device-width, initial-scale=1" /> 
1<script> window.dataLayer = window.dataLayer || [];
2window.zGA4 = function(eventName, params) {
3if (params && params.ecommerce) {
4dataLayer.push({ ecommerce: null });
5}
6params = params || {};
7params.event = eventName;
8dataLayer.push(params);
9}; </script>
9 
9<script> (function(){
10try {
11var m = document.cookie.match(/(?:^|; )zephyr_ud=([^;]*)/);
12if (!m) return;
13var ud = JSON.parse(decodeURIComponent(m[1]));
14if (ud && Object.keys(ud).length) {
15window.dataLayer = window.dataLayer || [];
16window.dataLayer.push({ event: 'set_user_data', user_data: ud });
17}
18} catch (e) {}
19})(); </script>
19 <style> [x-cloak] { display: none !important; }
20/* Sticky header spacing - responsive estimates, refined by JS in header.phtml */
21:root {
22--header-height: 65px;
23}
24@media (min-width: 1024px) {
25:root {
26--header-height: 97px;
27}
28} </style><meta name="description" content="Shop personalized and custom rubber stamps at Simply Stamps. Since 1954, we have delivered trusted stamps, quick shipping, and designs for home and office." /><meta name="robots" content="INDEX,FOLLOW" /><link rel="icon" href="https://cdn.simplystamps.com/media/favicon/websites/5/SS-Icon-Logo.png" type="image/x-icon" /><link rel="shortcut icon" href="https://cdn.simplystamps.com/media/favicon/websites/5/SS-Icon-Logo.png" type="image/x-icon" /><link rel="dns-prefetch" href="//cdn.simplystamps.com"><meta property="og:title" content="Custom Stamps &amp; Personalized Rubber Stamps | Simply Stamps" /><meta property="og:type" content="website" /><meta property="og:url" content="https://www.simplystamps.com/" /><meta property="og:description" content="Shop personalized and custom rubber stamps at Simply Stamps. Since 1954, we have delivered trusted stamps, quick shipping, and designs for home and office." /><meta property="og:site_name" content="Simply Stamps" /><meta property="og:image" content="https://cdn.simplystamps.com/skin/frontend/zephyr/sstamps-modern/images/SS-Desktop-logo.svg" /><link rel="stylesheet" href="https://cdn.simplystamps.com/skin/frontend/zephyr/default/css/print.css?v1126" media="print" ><link rel="stylesheet" href="https://cdn.simplystamps.com/skin/frontend/base/default/css/dist/builder.css?v1126" media="all" ><link rel="canonical" href="https://www.simplystamps.com/">
28<script type="text/javascript"> //<![CDATA[
29// mage/cookies.js now loads via a deferred 
29<script> tag (see head.phtml), so Mage.Cookies
30// isn't guaranteed to exist yet at this point in parsing. Wait for DOMContentLoaded, which
31// fires only after deferred scripts have executed.
32document.addEventListener('DOMContentLoaded', function () {
33Mage.Cookies.path     = '/';
34Mage.Cookies.domain   = '.www.simplystamps.com';
35});
36//]]> </script>
36<link rel="stylesheet" href="https://cdn.simplystamps.com/skin/frontend/zephyr/default/css/dist/theme.css?v=1126"> 
36<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
37new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
38j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
39'https://www.simplystamps.com/d/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
40})(window,document,'script','dataLayer','GTM-TT325PX9');</script>
40 <noscript><iframe src="https://www.simplystamps.com/d/ns.html?id=GTM-TT325PX9"
41height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> 
41<script type="text/javascript" src="https://chimpstatic.com/mcjs-connected/js/users/de94d9b4a6aa148b052eb1fad/fac024488a3ce12a438eb7e65.js" defer></script>
41<style id="zephyr-brand-colors"> /* Zephyr Brand Colors - Store: sstamps_en */
42:root {
43--brand-primary: #002E49;
44--brand-primary-hover: #00456E;
45--brand-secondary: #55B5E3;
46--brand-secondary-hover: #22495C;
47--brand-success: #19A745;
48--brand-warning: #C27803;
49--brand-danger: #E02424;
50--brand-info: #3F83F8;
51--blog-bg: #FFFFFF;
52} </style><link rel="preload" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'" /><noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" /></noscript> 
52<script>if(typeof Translate==='undefined'){var Translate=function(d){this._data=d||{}};Translate.prototype={translate:function(t){return this._data[t]||t},add:function(){}}}</script>
52 
52<script type="text/javascript">//<![CDATA[
53var Translator = new Translate([]);
54//]]></script>
54<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7201785794839578"
55crossorigin="anonymous"></script>
55 
55<script src="https://cdn.simplystamps.com/js/mage/cookies.js" defer></script>
55 
55<script type="text/javascript" src="https://onsite.optimonk.com/script.js?account=25642" async> </script>
55</head><body class="cms-index-index cms-home"><div class="flex min-h-screen flex-col"> <div class="flex flex-1 flex-col"><div x-data="stickyHeader()" x-init="init()" class="relative"><div
56x-show="mobileMenuOpen"
57@click="mobileMenuOpen = false"
58x-transition:enter="transition-opacity ease-out duration-300"
59x-transition:enter-start="opacity-0"
60x-transition:enter-end="opacity-100"
61x-transition:leave="transition-opacity ease-in duration-200"
62x-transition:leave-start="opacity-100"
63x-transition:leave-end="opacity-0"
64class="fixed inset-0 bg-black bg-opacity-50 z-40"
65style="display: none;"
66></div><div
67x-show="$store.imagePreview.isOpen"
68x-cloak
69@keydown.escape.window="$store.imagePreview.close()"
70class="fixed inset-0 z-[100] flex items-center justify-center p-4"
71style="display: none;"
72><div
73@click.stop="$store.imagePreview.close()"
74x-transition:enter="transition-opacity ease-out duration-300"
75x-transition:enter-start="opacity-0"
76x-transition:enter-end="opacity-100"
77x-transition:leave="transition-opacity ease-in duration-200"
78x-transition:leave-start="opacity-100"
79x-transition:leave-end="opacity-0"
80class="absolute inset-0 bg-black bg-opacity-80"
81></div><div
82x-transition:enter="transition ease-out duration-300"
83x-transition:enter-start="opacity-0 scale-95"
84x-transition:enter-end="opacity-100 scale-100"
85x-transition:leave="transition ease-in duration-200"
86x-transition:leave-start="opacity-100 scale-100"
87x-transition:leave-end="opacity-0 scale-95"
88class="relative w-full max-w-3xl max-h-[90vh]"
89> <button
90@click.stop="$store.imagePreview.close()"
91class="absolute -top-10 right-0 p-2 text-white hover:text-gray-300 transition-colors"
92aria-label="Close preview"
93> <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> <img
94:src="$store.imagePreview.imageUrl"
95:alt="$store.imagePreview.imageAlt"
96class="max-w-full max-h-[85vh] mx-auto object-contain rounded-lg shadow-2xl bg-white"
97/> <template x-if="$store.imagePreview.imageAlt && $store.imagePreview.linkUrl"> <a
98:href="$store.imagePreview.linkUrl"
99x-text="$store.imagePreview.imageAlt"
100class="block text-center text-white text-sm mt-3 font-medium hover:underline transition-colors"
101></a> </template> <template x-if="$store.imagePreview.imageAlt && !$store.imagePreview.linkUrl"><p
102x-text="$store.imagePreview.imageAlt"
103class="text-center text-white text-sm mt-3 font-medium"
104></p> </template></div></div><div
105x-show="mobileMenuOpen"
106@click.away="mobileMenuOpen = false"
107x-transition:enter="transform transition ease-out duration-300"
108x-transition:enter-start="-translate-x-full"
109x-transition:enter-end="translate-x-0"
110x-transition:leave="transform transition ease-in duration-200"
111x-transition:leave-start="translate-x-0"
112x-transition:leave-end="-translate-x-full"
113class="fixed inset-0 bg-white z-[60] flex flex-col"
114style="display: none;"
115><div class="flex items-center justify-between px-4 py-3 border-b border-gray-200 shrink-0"> <a href="https://www.simplystamps.com/" class="block"> <img
116src="https://cdn.simplystamps.com/skin/frontend/zephyr/sstamps-modern/images/SS-Desktop-logo.svg"
117alt="Simply Stamps"
118loading="eager"
119class="h-8 w-auto"
120/> </a> <button
121@click="mobileMenuOpen = false"
122class="p-2 hover:bg-gray-100 rounded-lg transition-colors"
123aria-label="Close menu"
124> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-7 h-7"> <path d="M18 6L6 18" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M6 6L18 18" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button></div><div class="flex-1 overflow-y-auto py-4"> <button
125@click="if ($el._clickLock) return; $el._clickLock = true; setTimeout(() => $el._clickLock = false, 300); $store.search.open()"
126class="flex items-center gap-3 w-[calc(100%-2rem)] mx-4 px-4 py-3 border-2 border-gray-300 rounded-xl text-gray-400 hover:border-brand-primary hover:text-gray-500 transition-colors"
127> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 shrink-0"> <path d="M11 19C15.4183 19 19 15.4183 19 11C19 6.58172 15.4183 3 11 3C6.58172 3 3 6.58172 3 11C3 15.4183 6.58172 19 11 19Z"
128stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <span class="text-base">Search products...</span> </button><div x-init="loadMobileMenu()" x-html="mobileMenuContent"></div><div class="mt-4 pt-4" x-show="$store.auth.isLoggedIn" x-cloak style="display: none;"><h3 class="text-sm font-bold text-gray-700 uppercase tracking-wider px-4 mb-2">Account</h3> <a href="https://www.simplystamps.com/customer/account/"
129class="flex items-center gap-3 px-4 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-100 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/> </svg> My Account </a> <a href="https://www.simplystamps.com/sales/order/history/"
130class="flex items-center gap-3 px-4 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-100 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/> </svg> My Orders </a> <a x-show="$store.auth.companyStoreUrl"
131:href="$store.auth.companyStoreUrl"
132class="flex items-center gap-3 px-4 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-100 transition-colors"
133style="display: none;"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/> </svg> Company Store </a> <button @click="mobileMenuOpen = false;
134if (typeof window.zGA4 === 'function') { window.zGA4('logout', { method: 'site' }); }
135$store.auth.refreshMessage = 'Logging out...';
136$store.auth.showRefreshOverlay = true;
137window.location.href = 'https://www.simplystamps.com/customer/account/logout/';"
138class="flex items-center gap-3 px-4 py-2.5 text-base text-gray-500 hover:text-red-600 hover:bg-gray-100 transition-colors w-full mt-1"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
139d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/> </svg> Log Out </button></div><div class="mt-4 pt-4"><h3 class="text-sm font-bold text-gray-700 uppercase tracking-wider px-4 mb-2">Help</h3> <button x-show="$store.chat.available" x-cloak id="mobile-live-chat-now" class="flex items-center gap-3 px-4 py-2.5 text-base font-medium text-brand-primary hover:bg-gray-100 transition-colors w-full cursor-pointer" style="display: none;"> <svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/> </svg> <span class="text-left"> Chat with a Live Agent <span class="block text-sm text-brand-success font-medium">Available now</span> <span class="block text-sm text-gray-500" x-text="$store.chat.hoursLabel"></span> </span> </button> <a href="https://www.simplystamps.com/contacts"
140class="flex items-center gap-3 px-4 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-100 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/> </svg> Track My Order </a> <a href="https://www.simplystamps.com/contacts"
141class="flex items-center gap-3 px-4 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-100 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/> </svg> Contact Us </a> <a href="https://www.simplystamps.com/faq"
142class="flex items-center gap-3 px-4 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-100 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> </svg> FAQ </a></div></div><div class="p-4 border-t border-gray-200 shrink-0" x-show="$store.auth.ready && !$store.auth.isLoggedIn" style="display: none;"> <button @click="if ($el._clickLock) return; $el._clickLock = true; setTimeout(() => $el._clickLock = false, 300); mobileMenuOpen = false; setTimeout(() => { $store.auth._loginOpenedAt = Date.now(); $store.auth.showLoginModal = true; }, 100)"
143class="w-full flex items-center justify-center space-x-2 px-4 py-3 bg-brand-primary hover:bg-brand-primary-hover text-white font-medium rounded-lg transition-colors"> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5"> <path d="M20 21V19C20 17.9391 19.5786 16.9217 18.8284 16.1716C18.0783 15.4214 17.0609 15 16 15H8C6.93913 15 5.92172 15.4214 5.17157 16.1716C4.42143 16.9217 4 17.9391 4 19V21"
144stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 11C14.2091 11 16 9.20914 16 7C16 4.79086 14.2091 3 12 3C9.79086 3 8 4.79086 8 7C8 9.20914 9.79086 11 12 11Z"
145stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <span>Log In</span> </button></div></div> <header
146:class="{
147'-translate-y-full': !headerVisible || $store.ui.hideHeader
148}"
149class="fixed top-0 left-0 right-0 bg-white z-50 shadow-lg transition-all duration-300"
150><div class="lg:hidden border-b border-gray-200"><div class="container mx-auto px-4"><div class="flex items-center justify-between py-3"><div class="flex items-center"> <button
151@click="mobileMenuOpen = !mobileMenuOpen"
152class="p-2 hover:bg-gray-100 rounded-lg transition-colors"
153aria-label="Menu"
154> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6"> <path d="M3 12H21" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3 6H21" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3 18H21" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button></div><div class="flex-1 flex justify-center"> <a href="https://www.simplystamps.com/" class="block"> <img
155src="https://cdn.simplystamps.com/skin/frontend/zephyr/sstamps-modern/images/SS-Desktop-logo.svg"
156alt="Simply Stamps"
157loading="eager"
158class="h-10 w-auto"
159/> </a></div><div class="flex items-center"> <button
160@click="$store.cart.openDrawer()"
161@dblclick.prevent="window.location.href='/checkout/cart/'"
162class="relative p-2 hover:bg-gray-100 rounded-lg transition-colors"
163aria-label="Open cart"
164> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6"> <path d="M9 22C9.55228 22 10 21.5523 10 21C10 20.4477 9.55228 20 9 20C8.44772 20 8 20.4477 8 21C8 21.5523 8.44772 22 9 22Z" fill="#4F4F4F" stroke="#474747" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M20 22C20.5523 22 21 21.5523 21 21C21 20.4477 20.5523 20 20 20C19.4477 20 19 20.4477 19 21C19 21.5523 19.4477 22 20 22Z" stroke="#474747" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M1 1H5L7.68 14.39C7.77144 14.8504 8.02191 15.264 8.38755 15.5583C8.75318 15.8526 9.2107 16.009 9.68 16H19.4C19.8693 16.009 20.3268 15.8526 20.6925 15.5583C21.0581 15.264 21.3086 14.8504 21.4 14.39L23 6H6" stroke="#474747" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <span x-cloak
165x-show="$store.cart.itemCount > 0"
166x-text="$store.cart.itemCount"
167class="absolute -top-1 -right-1 bg-brand-danger text-white text-xs rounded-full h-5 flex items-center justify-center font-bold"
168:class="$store.cart.itemCount >= 10 ? 'min-w-[1.25rem] px-1' : 'w-5'"></span> </button></div></div><div
169x-show="mobileSearchOpen"
170x-transition:enter="transition ease-out duration-200"
171x-transition:enter-start="opacity-0 -translate-y-2"
172x-transition:enter-end="opacity-100 translate-y-0"
173x-transition:leave="transition ease-in duration-150"
174x-transition:leave-start="opacity-100 translate-y-0"
175x-transition:leave-end="opacity-0 -translate-y-2"
176class="pb-4"
177style="display: none;"
178><form class="searchautocomplete UI-SEARCHAUTOCOMPLETE" action="https://www.simplystamps.com/catalogsearch/result/" method="get"
179data-tip="Search entire store here..."
180data-url="//www.simplystamps.com/searchautocomplete/ajax/get/"
181data-minchars="3"
182data-delay="500"
183data-hide-delay="1000"><label for="search">Search</label><div class="nav"><div class="nav-search-in"> <span class="category-fake UI-CATEGORY-TEXT">All</span> <span class="nav-down-arrow"></span> <select name="cat" class="category UI-CATEGORY"><option value="0">All</option><option value="713" > Address Stamps</option><option value="958" > Custom Business & Office Stamps</option><option value="749" > Craft Stamps</option><option value="1387" > Custom Logo Stamps</option><option value="770" > Embossers</option><option value="802" > Stamp Ink & Pads</option><option value="811" > Teacher Stamps</option><option value="793" > Notary</option><option value="815" > Wedding Stamps</option><option value="1432" > Redeemable Personalized Address Stamp with Gift Box</option><option value="2291" > St. Patrick's Day & Irish Theme Stamps</option><option value="2391" > Stamp of the Week</option><option value="809" > Breast Cancer Awareness</option><option value="3035" > Gift Notes</option><option value="3073" > Real Estate Supplies</option><option value="3085" > Custom Rubber Stamps</option><option value="3094" > Custom Return Address Labels</option><option value="3109" > Here Tomorrow</option><option value="3118" > StampToons</option><option value="3327" > Custom Face Stamps</option><option value="3432" > Salt & Paper Collaboration</option> </select></div><div class="nav-input UI-NAV-INPUT"> <input class="input-text UI-SEARCH" type="text" autocomplete="off" name="q" value="" maxlength="128" /></div><div class="searchautocomplete-loader UI-LOADER"><div id="g01"></div><div id="g02"></div><div id="g03"></div><div id="g04"></div><div id="g05"></div><div id="g06"></div><div id="g07"></div><div id="g08"></div></div></div><div class="nav-submit-button"> <button type="submit" title="Go" class="button">Go</button></div> <style> .store-4-search-autocomplete {
184top: 108px !important;
185right: unset !important;
186}@media (min-width: 768px) {
187.store-4-search-autocomplete {
188top: 74px !important;
189right: unset !important;
190}
191} </style><div style="display:none" class="searchautocomplete-placeholder UI-PLACEHOLDER "></div></form></div></div></div><div class="hidden lg:block border-b border-gray-200"><div class="mx-8"><div class="flex justify-between py-4"><div class="flex items-center justify-center w-1/8"> <a href="https://www.simplystamps.com/"> <img
192src="https://cdn.simplystamps.com/skin/frontend/zephyr/sstamps-modern/images/SS-Desktop-logo.svg"
193alt="Simply Stamps"
194loading="eager"
195class="h-12 w-auto"
196/> </a></div> <nav class="relative flex-1 px-2 md:px-4 flex items-center justify-around"><div class="hidden md:block"> <nav class="relative"><ul class="flex items-center justify-center space-x-2 list-none p-0 m-0 flex-wrap"><li x-data="{ open: false, hoverTimeout: null }"
197@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
198@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
199@keydown.escape="open = false"
200class="static"> <a href="https://www.simplystamps.com/address-stamps/view-all"
201:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
202class="block font-medium text-gray-700 p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary"> Address Stamps </a><div x-show="open" style="display: none;"
203x-transition:enter="transition ease-out duration-150"
204x-transition:enter-start="opacity-0 -translate-y-1"
205x-transition:enter-end="opacity-100 translate-y-0"
206x-transition:leave="transition ease-in duration-100"
207x-transition:leave-start="opacity-100 translate-y-0"
208x-transition:leave-end="opacity-0 -translate-y-1"
209class="absolute top-full left-0 right-0 bg-white shadow-xl rounded-b-md z-50 border border-gray-100"><div class="mx-8 py-6"><div x-data="{
210defaultHref: '/address-stamps',
211defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS_AS_Love-Envelope_lifestyle_2_800.jpg',
212defaultLabel: 'Address Stamps Collection',
213activeHref: '/address-stamps',
214activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS_AS_Love-Envelope_lifestyle_2_800.jpg',
215activeLabel: 'Address Stamps Collection',
216set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
217reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
218}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/address-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS_AS_Love-Envelope_lifestyle_2_800.jpg" alt="Address Stamps Collection" :src="activeImage" :alt="activeLabel"
219loading="lazy" width="224" height="224"
220class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
221x-text="activeLabel">Address Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-4 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Style</h3><ul class="space-y-2.5"><li><a href="/address-stamps/script-handwritten"
222@mouseenter="set('/address-stamps/script-handwritten', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Script-AS-224x224.jpg', 'Script Address Stamps')"
223class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Script Address Stamps</a></li><li><a href="/address-stamps/custom-line"
224@mouseenter="set('/address-stamps/custom-line', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Multiple-Line-A
224S-224x224.jpg', 'Multiple Line Address Stamps')"
225class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Multiple Line Address Stamps</a></li><li><a href="/address-stamps/round-address-stamps"
226@mouseenter="set('/address-stamps/round-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Round-AS-224x224.jpg', 'Round Address Stamps')"
227class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Round Address Stamps</a></li><li><a href="/address-stamps/rectangle-address-stamps"
228@mouseenter="set('/address-stamps/rectangle-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Rectangle-AS-224x224.jpg', 'Rectangle Address Stamps')"
229class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Rectangle Address Stamps</a></li><li><a href="/business-office-stamps/custom-line-stamps"
230@mouseenter="set('/business-office-stamps/custom-line-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Custom-Text-AS-224x224.jpg', 'Line Address Stamps')"
231class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Line Address Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Design</h3><ul class="space-y-2.5"><li><a href="/address-stamps/floral"
232@mouseenter="set('/address-stamps/floral', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Floral-AS-224x224.jpg', 'Floral Address Stamps')"
233class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Floral Address Stamps</a></li><li><a href="/address-stamps/wedding-couple"
234@mouseenter="set('/address-stamps/wedding-couple', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Wedding-AS-224x224.jpg', 'Wedding Address Stamps')"
235class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Wedding Address Stamps</a></li><li><a href="/address-stamps/dog-address-stamps"
236@mouseenter="set('/address-stamps/dog-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Dog-AS-224x224.jpg', 'Dog Address Stamps')"
237class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Dog Address Stamps</a></li><li><a href="/address-stamps/animals"
238@mouseenter="set('/address-stamps/animals', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Animal-AS-224x224.jpg', 'Animal Address Stamps')"
239class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Animal Address Stamps</a></li><li><a href="/address-stamps/hearts"
240@mouseenter="set('/address-stamps/hearts', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Whimsical-AS-224x224.jpg', 'Whimsical Address Stamps')"
241class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Whimsical Address Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Business &amp; Seasonal</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/business-address-stamps"
242@mouseenter="set('/business-office-stamps/business-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Business-AS-224x224.jpg', 'Business Address Stamps')"
243class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Business Address Stamps</a></li><li><a href="/address-stamps/patriotic-address-stamps"
244@mouseenter="set('/address-stamps/patriotic-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Patriotic-AS-224x224.jpg', 'Patriotic Address Stamps')"
245class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Patriotic Address Stamps</a></li><li><a href="/address-stamps/holiday"
246@mouseenter="set('/address-stamps/holiday', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Holiday-AS-224x224.jpg', 'Holiday Address Stamps')"
247class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Holiday Address Stamps</a></li><li><a href="/address-stamps/christmas-address-stamps"
248@mouseenter="set('/address-stamps/christmas-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Christmas-AS-224x224.jpg', 'Christmas Address Stamps')"
249class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Christmas Address Stamps</a></li><li><a href="/custom-address-labels"
250@mouseenter="set('/custom-address-labels', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/address-stamps/SS-QCS-Address-Labels-AS-400x400.jpg', 'Custom Return Address Labels')"
251class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Return Address Labels</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Collabs</h3><ul class="space-y-2.5"><li><a href="/salt-and-paper"
252@mouseenter="set('/salt-and-paper', 'https://cdn.simplystamps.com/media/catalog/category/8-simply-stamps-spco-2026-AS-Salt-Wine-1-1-lifestyle-1-2000_1.jpg', 'Salt &amp; Paper Collaboration')"
253style='background-image:url(https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/CMSImages/SS_Nav-Salt-and-Paper-Swish.png); background-repeat:no-repeat; background-position:left center; background-size:100% 100%; padding:6px 10px; margin:0 -10px;'
254class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Salt &amp; Paper Collaboration</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/address-stamps/view-all" @mouseenter="reset()"
255class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Address Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
255<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div></div></div></li><li x-data="{ open: false, hoverTimeout: null }"
256@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
257@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
258@keydown.escape="open = false"
259class="static"> <a href="https://www.simplystamps.com/rubber-logo-stamps"
260:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
261class="block font-medium text-gray-700 p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary"> Logo Stamps </a><div x-show="open" style="display: none;"
262x-transition:enter="transition ease-out duration-150"
263x-transition:enter-start="opacity-0 -translate-y-1"
264x-transition:enter-end="opacity-100 translate-y-0"
265x-transition:leave="transition ease-in duration-100"
266x-transition:leave-start="opacity-100 translate-y-0"
267x-transition:leave-end="opacity-0 -translate-y-1"
268class="absolute top-full left-0 right-0 bg-white shadow-xl rounded-b-md z-50 border border-gray-100"><div class="mx-8 py-6"><div x-data="{
269defaultHref: '/rubber-logo-stamps',
270defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Custom-Logo-Stamps.jpg',
271defaultLabel: 'Logo Stamps Collection',
272activeHref: '/rubber-logo-stamps',
273activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Custom-Logo-Stamps.jpg',
274activeLabel: 'Logo Stamps Collection',
275set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
276reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
277}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/rubber-logo-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Custom-Logo-Stamps.jpg" alt="Logo Stamps Collection" :src="activeImage" :alt="activeLabel"
278loading="lazy" width="224" height="224"
279class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
280x-text="activeLabel">Logo Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-3 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Shape</h3><ul class="space-y-2.5"><li><a href="/rubber-logo-stamps/rectangle"
281@mouseenter="set('/rubber-logo-stamps/rectangle', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Rectangle-Logo.jpg', 'Rectangle Logo Stamp')"
282class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Rectangle Logo Stamp</a></li><li><a href="/rubber-logo-stamps/round"
283@mouseenter="set('/rubber-logo-stamps/round', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Round-Logo-2.jpg', 'Round Logo Stamp')"
284class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Round Logo Stamp</a></li><li><a href="/rubber-logo-stamps/square"
285@mouseenter="set('/rubber-logo-stamps/square', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Square-Logo.jpg', 'Square Logo Stamp')"
286class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Square Logo Stamp</a></li><li><a href="/rubber-logo-stamps/catalog-stamps"
287@mouseenter="set('/rubber-logo-stamps/catalog-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Catalog-Logo.jpg', 'Catalog Stamps')"
288class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Catalog Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Artwork</h3><ul class="space-y-2.5"><li><a href="/rubber-logo-stamps/with-text"
289@mouseenter="set('/rubber-logo-stamps/with-text', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Logo-and-Text.jpg', 'Logo & Text')"
290class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Logo &amp; Text</a></li><li><a href="/rubber-logo-stamps/upload-art"
291@mouseenter="set('/rubber-logo-stamps/upload-art', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Artwork-Logo.jpg', 'Personalized Art')"
292class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Personalized Art</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Stamp Type</h3><ul class="space-y-2.5"><li><a href="/rubber-logo-stamps/pre-inked-logo"
293@mouseenter="set('/rubber-logo-stamps/pre-inked-logo', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Pre-Inked-Logo.jpg', 'Pre-Inked')"
294class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Pre-Inked</a></li><li><a href="/rubber-logo-stamps/self-inking"
295@mouseenter="set('/rubber-logo-stamps/self-inking', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Self-Ink-Logo.jpg', 'Self-Inking')"
296class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Self-Inking</a></li><li><a href="/rubber-logo-stamps/traditional-wooden-handle"
297@mouseenter="set('/rubber-logo-stamps/traditional-wooden-handle', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Wooden-Logo.jpg', 'Wood Handle')"
298class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Wood Handle</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/rubber-logo-stamps" @mouseenter="reset()"
299class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Logo Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div></div></div></li><li x-data="{ open: false, hoverTimeout: null }"
300@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
301@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
302@keydown.escape="open = false"
303class="static"> <a href="https://www.simplystamps.com/business-office-stamps/signature-stamps"
304:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
305class="block font-medium text-gray-700 p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary"> Signature Stamps </a><div x-show="open" style="display: none;"
306x-transition:enter="transition ease-out duration-150"
307x-transition:enter-start="opacity-0 -translate-y-1"
308x-transition:enter-end="opacity-100 translate-y-0"
309x-transition:leave="transition ease-in duration-100"
310x-transition:leave-start="opacity-100 translate-y-0"
311x-transition:leave-end="opacity-0 -translate-y-1"
312class="absolute top-full left-0 right-0 bg-white shadow-xl rounded-b-md z-50 border border-gray-100"><div class="mx-8 py-6"><div x-data="{
313defaultHref: '/business-office-stamps/signature-stamps',
314defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-Category.jpg',
315defaultLabel: 'Signature Stamps Collection',
316activeHref: '/business-office-stamps/signature-stamps',
317activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-Category.jpg',
318activeLabel: 'Signature Stamps Collection',
319set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
320reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
321}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/business-office-stamps/signature-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-Category.jpg" alt="Signature Stamps Collection" :src="activeImage" :alt="activeLabel"
322loading="lazy" width="224" height="224"
323class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
324x-text="activeLabel">Signature Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-2 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Signature Stamp Types</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/signature-stamps/signature-only"
325@mouseenter="set('/business-office-stamps/signature-stamps/signature-only', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-only.jpg', 'Signature Only')"
326class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Signature Only</a></li><li><a href="/business-office-stamps/signature-stamps/with-date"
327@mouseenter="set('/business-office-stamps/signature-stamps/with-date', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-and-date.jpg', 'Signature with Date')"
328class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Signature with Date</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Signature &amp; More</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/signature-stamps/with-printed-name"
329@mouseenter="set('/business-office-stamps/signature-stamps/with-printed-name', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-and-name.jpg', 'Signature with Printed Name')"
330class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Signature with Printed Name</a></li><li><a href="/business-office-stamps/signature-stamps/with-logo"
331@mouseenter="set('/business-office-stamps/signature-stamps/with-logo', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-and-logo.jpg', 'Signature with Logo')"
332class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Signature with Logo</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/business-office-stamps/signature-stamps" @mouseenter="reset()"
333class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Signature Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div></div></div></li><li x-data="{ open: false, hoverTimeout: null }"
334@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
335@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
336@keydown.escape="open = false"
337class="static"> <a href="https://www.simplystamps.com/custom-rubber-stamps"
338:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
339class="block font-medium text-gray-700 p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary"> Custom Stamps </a><div x-show="open" style="display: none;"
340x-transition:enter="transition ease-out duration-150"
341x-transition:enter-start="opacity-0 -translate-y-1"
342x-transition:enter-end="opacity-100 translate-y-0"
343x-transition:leave="transition ease-in duration-100"
344x-transition:leave-start="opacity-100 translate-y-0"
345x-transition:leave-end="opacity-0 -translate-y-1"
346class="absolute top-full left-0 right-0 bg-white shadow-xl rounded-b-md z-50 border border-gray-100"><div class="mx-8 py-6"><div x-data="{
347defaultHref: '/custom-rubber-stamps',
348defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Stamp-Assortment.jpg',
349defaultLabel: 'Custom Stamps Collection',
350activeHref: '/custom-rubber-stamps',
351activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Stamp-Assortment.jpg',
352activeLabel: 'Custom Stamps Collection',
353set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
354reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
355}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/custom-rubber-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Stamp-Assortment.jpg" alt="Custom Stamps Collection" :src="activeImage" :alt="activeLabel"
356loading="lazy" width="224" height="224"
357class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
358x-text="activeLabel">Custom Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-2 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Stamp Type</h3><ul class="space-y-2.5"><li><a href="/custom-rubber-stamps/custom-self-inking-stamps"
359@mouseenter="set('/custom-rubber-stamps/custom-self-inking-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Self-Inking-Stamps-Custom-400x400.jpg', 'Custom Self-Inking Stamps')"
360class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Self-Inking Stamps</a></li><li><a href="/custom-rubber-stamps/custom-pre-inked-stamps"
361@mouseenter="set('/custom-rubber-stamps/custom-pre-inked-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Pre-Inked-Stamps-Custom-400x400.jpg', 'Custom Pre-Inked Stamps')"
362class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Pre-Inked Stamps</a></li><li><a href="/custom-rubber-stamps/custom-wood-handle-stamps"
363@mouseenter="set('/custom-rubber-stamps/custom-wood-handle-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Traditional-Hand-Stamps-Custom-400x400.jpg', 'Custom Wood Handle Stamps')"
364class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Wood Handle Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Specialty</h3><ul class="space-y-2.5"><li><a href="/custom-rubber-stamps/custom-european-hand-stamps"
365@mouseenter="set('/custom-rubber-stamps/custom-european-hand-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-European-Hand-Stamps-Custom-400x400.jpg', 'Custom European Hand Stamps')"
366class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom European Hand Stamps</a></li><li><a href="/custom-rubber-stamps/custom-professional-stamps"
367@mouseenter="set('/custom-rubber-stamps/custom-professional-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Professional-Stamps-Custom-400x400.jpg', 'Custom Professional Stamps')"
368class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Professional Stamps</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/custom-rubber-stamps" @mouseenter="reset()"
369class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Custom Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
369<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div></div></div></li><li x-data="{ open: false, hoverTimeout: null }"
370@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
371@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
372@keydown.escape="open = false"
373class="static"> <a href="https://www.simplystamps.com/business-office-stamps"
374:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
375class="block font-medium text-gray-700 p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary"> Office Stamps </a><div x-show="open" style="display: none;"
376x-transition:enter="transition ease-out duration-150"
377x-transition:enter-start="opacity-0 -translate-y-1"
378x-transition:enter-end="opacity-100 translate-y-0"
379x-transition:leave="transition ease-in duration-100"
380x-transition:leave-start="opacity-100 translate-y-0"
381x-transition:leave-end="opacity-0 -translate-y-1"
382class="absolute top-full left-0 right-0 bg-white shadow-xl rounded-b-md z-50 border border-gray-100"><div class="mx-8 py-6"><div x-data="{
383defaultHref: '/business-office-stamps',
384defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS_OfficeStamp-Cat_Bank_250x250.jpg',
385defaultLabel: 'Office Stamps Collection',
386activeHref: '/business-office-stamps',
387activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS_OfficeStamp-Cat_Bank_250x250.jpg',
388activeLabel: 'Office Stamps Collection',
389set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
390reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
391}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/business-office-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS_OfficeStamp-Cat_Bank_250x250.jpg" alt="Office Stamps Collection" :src="activeImage" :alt="activeLabel"
392loading="lazy" width="224" height="224"
393class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
394x-text="activeLabel">Office Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-3 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Business &amp; Banking</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/accounting-tax-stamps"
395@mouseenter="set('/business-office-stamps/accounting-tax-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Accounting-Stamps-OS-400x400.jpg', 'Accounting Stamps')"
396class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Accounting Stamps</a></li><li><a href="/business-office-stamps/business-address-stamps"
397@mouseenter="set('/business-office-stamps/business-address-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Business-Address-Stamps-OS-400x400.jpg', 'Business Address Stamps')"
398class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Business Address Stamps</a></li><li><a href="/business-office-stamps/banking-stamps/deposit-stamps/check-endorsement"
399@mouseenter="set('/business-office-stamps/banking-stamps/deposit-stamps/check-endorsement', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Check-Endorsement-Stamps-OS-400x400.jpg', 'Check Endorsement Stamps')"
400class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Check Endorsement Stamps</a></li><li><a href="/business-office-stamps/banking-stamps/deposit-stamps"
401@mouseenter="set('/business-office-stamps/banking-stamps/deposit-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Deposit-Stamps-OS-400x400.jpg', 'Deposit Stamps')"
402class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Deposit Stamps</a></li><li><a href="/business-office-stamps/accounting-tax-stamps/received"
403@mouseenter="set('/business-office-stamps/accounting-tax-stamps/received', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Received-stamps-OS-400x400.jpg', 'Received Stamps')"
404class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Received Stamps</a></li><li><a href="/business-office-stamps/custom-line-stamps"
405@mouseenter="set('/business-office-stamps/custom-line-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Custom-text-Stamps-OS-400x400.jpg', 'Custom Text Stamps')"
406class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Text Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Office Essentials</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/office-stamps"
407@mouseenter="set('/business-office-stamps/office-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Stock-office-stamps-OS-400x400.jpg', 'Stock Office Stamps')"
408class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Stock Office Stamps</a></li><li><a href="/business-office-stamps/daters"
409@mouseenter="set('/business-office-stamps/daters', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Date-Stamps-OS-400x400.jpg', 'Date Stamps')"
410class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Date Stamps</a></li><li><a href="/business-office-stamps/checkbox"
411@mouseenter="set('/business-office-stamps/checkbox', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Checkbox-Stamps-OS-400x400.jpg', 'Checkbox 
411Stamps')"
412class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Checkbox Stamps</a></li><li><a href="/business-office-stamps/secure-stamps"
413@mouseenter="set('/business-office-stamps/secure-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Security-Stamps-OS-400x400.jpg', 'Security Stamps')"
414class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Security Stamps</a></li><li><a href="/business-office-stamps/inspection-stamps"
415@mouseenter="set('/business-office-stamps/inspection-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-inspection-Stamps-OS-400x400.jpg', 'Inspection Stamps')"
416class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Inspection Stamps</a></li><li><a href="/business-office-stamps/medical-stamps"
417@mouseenter="set('/business-office-stamps/medical-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Medical-Stamps-OS-400x400.jpg', 'Medical Stamps')"
418class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Medical Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Professional Stamps</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/professional-stamps/llc-stamps"
419@mouseenter="set('/business-office-stamps/professional-stamps/llc-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-LLC-Stamps-OS-400x400.jpg', 'LLC Stamps')"
420class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">LLC Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/architect"
421@mouseenter="set('/business-office-stamps/professional-stamps/architect', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Architect-Stamps-OS-400x400.jpg', 'Architect Stamps')"
422class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Architect Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/engineer"
423@mouseenter="set('/business-office-stamps/professional-stamps/engineer', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Engineer-Stamps-OS-400x400.jpg', 'Engineer Stamps')"
424class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Engineer Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/land-surveyor"
425@mouseenter="set('/business-office-stamps/professional-stamps/land-surveyor', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Land-Surveyor-Stamps-OS-400x400.jpg', 'Land Surveyor Stamps')"
426class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Land Surveyor Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/real-estate-appraiser"
427@mouseenter="set('/business-office-stamps/professional-stamps/real-estate-appraiser', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Real-Estate-Appraiser-Stamps-OS-400x400.jpg', 'Real Estate Appraiser Stamps')"
428class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Real Estate Appraiser Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/geologist-stamps-other"
429@mouseenter="set('/business-office-stamps/professional-stamps/geologist-stamps-other', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Geologist-Stamps-OS-400x400.jpg', 'Geologist Stamps')"
430class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Geologist Stamps</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/business-office-stamps" @mouseenter="reset()"
431class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Office Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
431<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div></div></div></li><li x-data="{ open: false, hoverTimeout: null }"
432@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
433@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
434@keydown.escape="open = false"
435class="static"> <a href="https://www.simplystamps.com/embossers"
436:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
437class="block font-medium text-gray-700 p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary"> Embossers </a><div x-show="open" style="display: none;"
438x-transition:enter="transition ease-out duration-150"
439x-transition:enter-start="opacity-0 -translate-y-1"
440x-transition:enter-end="opacity-100 translate-y-0"
441x-transition:leave="transition ease-in duration-100"
442x-transition:leave-start="opacity-100 translate-y-0"
443x-transition:leave-end="opacity-0 -translate-y-1"
444class="absolute top-full left-0 right-0 bg-white shadow-xl rounded-b-md z-50 border border-gray-100"><div class="mx-8 py-6"><div x-data="{
445defaultHref: '/embossers',
446defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Shop-All-EMB-400x400.jpg',
447defaultLabel: 'Embossers Collection',
448activeHref: '/embossers',
449activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Shop-All-EMB-400x400.jpg',
450activeLabel: 'Embossers Collection',
451set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
452reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
453}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/embossers" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Shop-All-EMB-400x400.jpg" alt="Embossers Collection" :src="activeImage" :alt="activeLabel"
454loading="lazy" width="224" height="224"
455class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
456x-text="activeLabel">Embossers Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-3 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Personal &amp; Occasion</h3><ul class="space-y-2.5"><li><a href="/embossers/custom-address"
457@mouseenter="set('/embossers/custom-address', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-Address-EMB-400x400.jpg', 'Address Embossers')"
458class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Address Embossers</a></li><li><a href="/embossers/library-book-embossers"
459@mouseenter="set('/embossers/library-book-embossers', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-Library-Book-EMB-400x400.jpg', 'Library Book Embossers')"
460class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Library Book Embossers</a></li><li><a href="/embossers/wedding-embossers"
461@mouseenter="set('/embossers/wedding-embossers', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-wedding-embosser-cat-emboss.jpg', 'Wedding Embossers')"
462class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Wedding Embossers</a></li><li><a href="/embossers/luxury-embossers"
463@mouseenter="set('/embossers/luxury-embossers', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-Luxury-Embossers-QCS-Nav-Embosser-400.jpg', 'Luxury Embossers')"
464class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Luxury Embossers</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Business &amp; Legal</h3><ul class="space-y-2.5"><li><a href="/embossers/corporate-seal"
465@mouseenter="set('/embossers/corporate-seal', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-Corporate-Seals-EMB-400x400.jpg', 'Corporate Seals')"
466class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Corporate Seals</a></li><li><a href="/embossers/notary-public"
467@mouseenter="set('/embossers/notary-public', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-Notary-EMB-400x400.jpg', 'Notary Embossers')"
468class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Notary Embossers</a></li><li><a href="/embossers/custom-logo"
469@mouseenter="set('/embossers/custom-logo', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-LOGO-EMB-400x400.jpg', 'Logo Embossers')"
470class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Logo Embossers</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Accessories</h3><ul class="space-y-2.5"><li><a href="/stamp-ink-pads/embosser-accessories"
471@mouseenter="set('/stamp-ink-pads/embosser-accessories', 'https://cdn.simplystamps.com/media/catalog/category/SS_cat-ink_and_accessories_embosser-accesories-265
471x265.jpg', 'Embosser Accessories')"
472class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Embosser Accessories</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/embossers" @mouseenter="reset()"
473class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Embossers <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div></div></div></li><li x-data="{ open: false, hoverTimeout: null }"
474@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
475@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
476@keydown.escape="open = false"
477class="relative"> <a href="https://www.simplystamps.com/notary-public"
478:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
479class="block font-medium p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary text-gray-700"> Notary </a></li><li x-data="{ open: false, hoverTimeout: null }"
480@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
481@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
482@keydown.escape="open = false"
483class="static"> <a href="https://www.simplystamps.com/teacher-stamps"
484:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
485class="block font-medium text-gray-700 p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary"> Teacher Stamps </a><div x-show="open" style="display: none;"
486x-transition:enter="transition ease-out duration-150"
487x-transition:enter-start="opacity-0 -translate-y-1"
488x-transition:enter-end="opacity-100 translate-y-0"
489x-transition:leave="transition ease-in duration-100"
490x-transition:leave-start="opacity-100 translate-y-0"
491x-transition:leave-end="opacity-0 -translate-y-1"
492class="absolute top-full left-0 right-0 bg-white shadow-xl rounded-b-md z-50 border border-gray-100"><div class="mx-8 py-6"><div x-data="{
493defaultHref: '/teacher-stamps',
494defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-TS-400.jpg',
495defaultLabel: 'Teacher Stamps Collection',
496activeHref: '/teacher-stamps',
497activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-TS-400.jpg',
498activeLabel: 'Teacher Stamps Collection',
499set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
500reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
501}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/teacher-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-TS-400.jpg" alt="Teacher Stamps Collection" :src="activeImage" :alt="activeLabel"
502loading="lazy" width="224" height="224"
503class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
504x-text="activeLabel">Teacher Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-2 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Classroom &amp; Grading</h3><ul class="space-y-2.5"><li><a href="/teacher-stamps/grading"
505@mouseenter="set('/teacher-stamps/grading', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Grading-stamps-TS-400x400.jpg', 'Grading Stamps')"
506class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Grading Stamps</a></li><li><a href="/teacher-stamps/feedback"
507@mouseenter="set('/teacher-stamps/feedback', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Feedback-stamps-TS-400x400.jpg', 'Feedback Stamps')"
508class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Feedback Stamps</a></li><li><a href="/teacher-stamps/name-missing"
509@mouseenter="set('/teacher-stamps/name-missing', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Missing-Name-stamps-TS-400x400.jpg', 'Name Missing Stamps')"
510class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Name Missing Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Fun &amp; Motivation</h3><ul class="space-y-2.5"><li><a href="/teacher-stamps/motivational"
511@mouseenter="set('/teacher-stamps/motivational', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Motivational-stamps-TS-400x400.jpg', 'Motivational Stamps')"
512class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Motivational Stamps</a></li><li><a href="/teacher-stamps/fun-designs"
513@mouseenter="set('/teacher-stamps/fun-designs', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Fun-stamps-TS-400x400.jpg', 'Fun Stamps')"
514class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Fun Stamps</a></li><li><a href="/teacher-stamps/from-the-desk-of"
515@mouseenter="set('/teacher-stamps/from-the-desk-of', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Book-stamps-TS-400x400.jpg', 'Book Stamps')"
516class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Book Stamps</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/teacher-stamps" @mouseenter="reset()"
517class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Teacher Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div></div></div></li><li x-data="{ open: false, hoverTimeout: null }"
518@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
519@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
520@keydown.escape="open = false"
521class="static"> <a href="https://www.simplystamps.com/craft-stamps"
522:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
523class="block font-medium text-gray-700 p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary"> Craft Stamps </a><div x-show="open" style="display: none;"
524x-transition:enter="transition ease-out duration-150"
525x-transition:enter-start="opacity-0 -translate-y-1"
526x-transition:enter-end="opacity-100 translate-y-0"
527x-transition:leave="transition ease-in duration-100"
528x-transition:leave-start="opacity-100 translate-y-0"
529x-transition:leave-end="opacity-0 -translate-y-1"
530class="absolute top-full left-0 right-0 bg-white shadow-xl rounded-b-md z-50 border border-gray-100"><div class="mx-8 py-6"><div x-data="{
531defaultHref: '/craft-stamps',
532defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-CS-2000_1.jpg',
533defaultLabel: 'Craft Stamps Collection',
534activeHref: '/craft-stamps',
535activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-CS-2000_1.jpg',
536activeLabel: 'Craft Stamps Collection',
537set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
538reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
539}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/craft-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-CS-2000_1.jpg" alt="Craft Stamps Collection" :src="activeImage" :alt="activeLabel"
540loading="lazy" width="224" height="224"
541class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
542x-text="activeLabel">Craft Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-3 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Seasonal &amp; Holiday</h3><ul class="space-y-2.5"><li><a href="/craft-stamps/holiday-crafting"
543@mouseenter="set('/craft-stamps/holiday-crafting', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Holiday-Stamps-CS-400x400.jpg', 'Holiday Stamps')"
544class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Holiday Stamps</a></li><li><a href="/craft-stamps/halloween"
545@mouseenter="set('/craft-stamps/halloween', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Halloween-Stamps-CS-400x400.jpg', 'Halloween Stamps')"
546class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Halloween Stamps</a></li><li><a href="/craft-stamps/thanksgiving"
547@mouseenter="set('/craft-stamps/thanksgiving', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Thanksgiving-Stamps-CS-400x400.jpg', 'Thanksgiving Stamps')"
548class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Thanksgiving Stamps</a></li><li><a href="/craft-stamps/autumn-fall"
549@mouseenter="set('/craft-stamps/autumn-fall', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Autumn-Stamps-CS-400x400.jpg', 'Autumn Stamps')"
550class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Autumn Stamps</a></li><li><a href="/craft-stamps/summer"
551@mouseenter="set('/craft-stamps/summer', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Summer-Stamps-CS-400x400.jpg', 'Summer Stamps')"
552class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Summer Stamps</a></li><li><a href="/craft-stamps/valentine-s-day-craft-stamp"
553@mouseenter="set('/craft-stamps/valentine-s-day-craft-stamp', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Valentines-Stamps-CS-400x400.jpg', 'Valentine\'s Day Stamps')"
554class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Valentine's Day Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Occasions &amp; Gifts</h3><ul class="space-y-2.5"><li><a href="/wedding-stamps"
555@mouseenter="set('/wedding-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Wedding-Stamps-CS-400x400.jpg', 'Wedding Stamps')"
556class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Wedding Stamps</a></li><li><a href="/craft-stamps/we-have-moved"
557@mouseenter="set('/craft-stamps/we-have-moved', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Just-Moved-Stamps-CS-400x400.jpg', 'Just Moved Stamps')"
558class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Just Moved Stamps</a></li><li><a href="/craft-stamps/thank-you-stamps"
559@mouseenter="set('/craft-stamps/thank-you-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Thank-You-Stamps-CS-400x400.jpg', 'Thank You Stamps')"
560class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Thank You Stamps</a></li><li><a href="/craft-stamps/sentiment"
561@mouseenter="set('/craft-stamps/sentiment', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Sentiment-Stamps-CS-400x400.jpg', 'Sentiment Stamps')"
562class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Sentiment Stamps</a></li><li><a href="/teacher-stamps/clothing-stamps"
563@mouseenter="set('/teacher-stamps/clothing-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Name-Stamps-CS-400x400.jpg', 'Name Stamps')"
564class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Name Stamps</a></li><li><a href="/face-stamps"
565@mouseenter="set('/face-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Face-Stamps-CS-400x400.jpg', 'Face Stamps')"
566class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Face Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Crafting</h3><ul class="space-y-2.5"><li><a href="/craft-stamps/mini-accent-stamps"
567@mouseenter="set('/craft-stamps/mini-accent-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Mini-Accent-Stamps-CS-400x400.jpg', 'Mini Accent Stamps')"
568class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Mini Accent Stamps</a></li><li><a href="/craft-stamps/embellishment/bullet-journal"
569@mouseenter="set('/craft-stamps/embellishment/bullet-journal', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Bullet-Journal-Stamps-CS-400x400.jpg', 'Bullet Journal Stamps')"
570class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Bullet Journal Stamps</a></li><li><a href="/craft-stamps/embellishment"
571@mouseenter="set('/craft-stamps/embellishment', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Decorative-Stamps-CS-400x400.jpg', 'Decorative Stamps')"
572class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Decorative Stamps</a></li><li><a href="/rubber-flower-stamps"
573@mouseenter="set('/rubber-flower-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Flower-Stamps-CS-400x400.jpg', 'Flower Stamps')"
574class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Flower Stamps</a></li><li><a href="/craft-stamps/emoji-stamps"
575@mouseenter="set('/craft-stamps/emoji-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Emoji-Stamps-CS-400x400.jpg', 'Emoji Stamps')"
576class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Emoji Stamps</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/craft-stamps" @mouseenter="reset()"
577class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Craft Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
577<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div></div></div></li><li x-data="{ open: false, hoverTimeout: null }"
578@mouseenter="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = true, 300)"
579@mouseleave="clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => open = false, 150)"
580@keydown.escape="open = false"
581class="static"> <a href="https://www.simplystamps.com/stamp-ink-pads"
582:class="open ? 'underline underline-offset-8 text-brand-primary bg-brand-primary/15' : ''"
583class="block font-medium text-gray-700 p-2 text-sm lg:text-base text-center rounded-t-md transition-all duration-200 hover:underline hover:underline-offset-8 hover:text-brand-primary"> Ink &amp; Accessories </a><div x-show="open" style="display: none;"
584x-transition:enter="transition ease-out duration-150"
585x-transition:enter-start="opacity-0 -translate-y-1"
586x-transition:enter-end="opacity-100 translate-y-0"
587x-transition:leave="transition ease-in duration-100"
588x-transition:leave-start="opacity-100 translate-y-0"
589x-transition:leave-end="opacity-0 -translate-y-1"
590class="absolute top-full left-0 right-0 bg-white shadow-xl rounded-b-md z-50 border border-gray-100"><div class="mx-8 py-6"><div x-data="{
591defaultHref: '/stamp-ink-pads',
592defaultImage: 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Category-Craft-400.jpg',
593defaultLabel: 'Ink & Accessories Collection',
594activeHref: '/stamp-ink-pads',
595activeImage: 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Category-Craft-400.jpg',
596activeLabel: 'Ink & Accessories Collection',
597set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
598reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
599}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/stamp-ink-pads" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Category-Craft-400.jpg" alt="Ink & Accessories Collection" :src="activeImage" :alt="activeLabel"
600loading="lazy" width="224" height="224"
601class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
602x-text="activeLabel">Ink & Accessories Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-2 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Ink &amp; Refills</h3><ul class="space-y-2.5"><li><a href="/stamp-ink-pads/craft-stamp-ink"
603@mouseenter="set('/stamp-ink-pads/craft-stamp-ink', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Craft-Ink-Pads-Ink-And-Accessories-400.jpg', 'Craft Stamp Ink Pads')"
604class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Craft Stamp Ink Pads</a></li><li><a href="/stamp-ink-pads/ink-pad-replacements"
605@mouseenter="set('/stamp-ink-pads/ink-pad-replacements', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Ink-Pad-Refills-Ink-And-Accessories-400.jpg', 'Ink Pad Refills')"
606class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Ink Pad Refills</a></li><li><a href="/stamp-ink-pads/ink-bottle-refills"
607@mouseenter="set('/stamp-ink-pads/ink-bottle-refills', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Ink-Bottle-refills-Ink-And-Accessories-400.jpg', 'Ink Bottle Refills')"
608class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Ink Bottle Refills</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Accessories</h3><ul class="space-y-2.5"><li><a href="/stamp-ink-pads/embosser-accessories"
609@mouseenter="set('/stamp-ink-pads/embosser-accessories', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Embosser-Accessories-Ink-And-Accessories-400.jpg', 'Embosser Acce
609ssories')"
610class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Embosser Accessories</a></li><li><a href="/mail-accessories"
611@mouseenter="set('/mail-accessories', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Mail-Accessories-Ink-And-Accessories-400.jpg', 'Mail Accessories')"
612class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Mail Accessories</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/stamp-ink-pads" @mouseenter="reset()"
613class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Ink & Accessories <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div></div></div></li></ul> </nav></div><div id="mobile-menu-content" class="hidden"> <nav class="py-4"><ul class="space-y-1 list-none p-0 m-0 border-t border-gray-200"><li x-data="{ open: false }"> <button @click="open = !open" class="flex items-center w-full border-b border-gray-200 text-left"> <span class="flex-1 px-4 py-3 font-semibold text-lg transition-colors duration-200 text-gray-700"> Address Stamps </span> <span class="px-3 py-3 text-gray-600 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" :class="open ? 'rotate-180' : ''"
614class="w-5 h-5 transition-transform duration-200"
615fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </span> </button><div x-show="open" x-transition class="px-4 py-3 border-b border-gray-200" style="display: none;"><div x-data="{
616defaultHref: '/address-stamps',
617defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS_AS_Love-Envelope_lifestyle_2_800.jpg',
618defaultLabel: 'Address Stamps Collection',
619activeHref: '/address-stamps',
620activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS_AS_Love-Envelope_lifestyle_2_800.jpg',
621activeLabel: 'Address Stamps Collection',
622set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
623reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
624}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/address-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS_AS_Love-Envelope_lifestyle_2_800.jpg" alt="Address Stamps Collection" :src="activeImage" :alt="activeLabel"
625loading="lazy" width="224" height="224"
626class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
627x-text="activeLabel">Address Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-4 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Style</h3><ul class="space-y-2.5"><li><a href="/address-stamps/script-handwritten"
628@mouseenter="set('/address-stamps/script-handwritten', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Script-AS-224x224.jpg', 'Script Address Stamps')"
629class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Script Address Stamps</a></li><li><a href="/address-stamps/custom-line"
630@mouseenter="set('/address-stamps/custom-line', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Multiple-Line-A
630S-224x224.jpg', 'Multiple Line Address Stamps')"
631class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Multiple Line Address Stamps</a></li><li><a href="/address-stamps/round-address-stamps"
632@mouseenter="set('/address-stamps/round-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Round-AS-224x224.jpg', 'Round Address Stamps')"
633class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Round Address Stamps</a></li><li><a href="/address-stamps/rectangle-address-stamps"
634@mouseenter="set('/address-stamps/rectangle-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Rectangle-AS-224x224.jpg', 'Rectangle Address Stamps')"
635class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Rectangle Address Stamps</a></li><li><a href="/business-office-stamps/custom-line-stamps"
636@mouseenter="set('/business-office-stamps/custom-line-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Custom-Text-AS-224x224.jpg', 'Line Address Stamps')"
637class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Line Address Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Design</h3><ul class="space-y-2.5"><li><a href="/address-stamps/floral"
638@mouseenter="set('/address-stamps/floral', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Floral-AS-224x224.jpg', 'Floral Address Stamps')"
639class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Floral Address Stamps</a></li><li><a href="/address-stamps/wedding-couple"
640@mouseenter="set('/address-stamps/wedding-couple', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Wedding-AS-224x224.jpg', 'Wedding Address Stamps')"
641class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Wedding Address Stamps</a></li><li><a href="/address-stamps/dog-address-stamps"
642@mouseenter="set('/address-stamps/dog-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Dog-AS-224x224.jpg', 'Dog Address Stamps')"
643class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Dog Address Stamps</a></li><li><a href="/address-stamps/animals"
644@mouseenter="set('/address-stamps/animals', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Animal-AS-224x224.jpg', 'Animal Address Stamps')"
645class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Animal Address Stamps</a></li><li><a href="/address-stamps/hearts"
646@mouseenter="set('/address-stamps/hearts', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Whimsical-AS-224x224.jpg', 'Whimsical Address Stamps')"
647class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Whimsical Address Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Business &amp; Seasonal</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/business-address-stamps"
648@mouseenter="set('/business-office-stamps/business-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Business-AS-224x224.jpg', 'Business Address Stamps')"
649class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Business Address Stamps</a></li><li><a href="/address-stamps/patriotic-address-stamps"
650@mouseenter="set('/address-stamps/patriotic-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Patriotic-AS-224x224.jpg', 'Patriotic Address Stamps')"
651class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Patriotic Address Stamps</a></li><li><a href="/address-stamps/holiday"
652@mouseenter="set('/address-stamps/holiday', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Holiday-AS-224x224.jpg', 'Holiday Address Stamps')"
653class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Holiday Address Stamps</a></li><li><a href="/address-stamps/christmas-address-stamps"
654@mouseenter="set('/address-stamps/christmas-address-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Christmas-AS-224x224.jpg', 'Christmas Address Stamps')"
655class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Christmas Address Stamps</a></li><li><a href="/custom-address-labels"
656@mouseenter="set('/custom-address-labels', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/address-stamps/SS-QCS-Address-Labels-AS-400x400.jpg', 'Custom Return Address Labels')"
657class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Return Address Labels</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Collabs</h3><ul class="space-y-2.5"><li><a href="/salt-and-paper"
658@mouseenter="set('/salt-and-paper', 'https://cdn.simplystamps.com/media/catalog/category/8-simply-stamps-spco-2026-AS-Salt-Wine-1-1-lifestyle-1-2000_1.jpg', 'Salt &amp; Paper Collaboration')"
659style='background-image:url(https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/CMSImages/SS_Nav-Salt-and-Paper-Swish.png); background-repeat:no-repeat; background-position:left center; background-size:100% 100%; padding:6px 10px; margin:0 -10px;'
660class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Salt &amp; Paper Collaboration</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/address-stamps/view-all" @mouseenter="reset()"
661class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Address Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
661<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div> <a href="https://www.simplystamps.com/address-stamps/view-all"
662class="inline-block mt-3 text-sm font-semibold text-brand-primary hover:underline"> View Page &rarr; </a></div></li><li x-data="{ open: false }"> <button @click="open = !open" class="flex items-center w-full border-b border-gray-200 text-left"> <span class="flex-1 px-4 py-3 font-semibold text-lg transition-colors duration-200 text-gray-700"> Logo Stamps </span> <span class="px-3 py-3 text-gray-600 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" :class="open ? 'rotate-180' : ''"
663class="w-5 h-5 transition-transform duration-200"
664fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </span> </button><div x-show="open" x-transition class="px-4 py-3 border-b border-gray-200" style="display: none;"><div x-data="{
665defaultHref: '/rubber-logo-stamps',
666defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Custom-Logo-Stamps.jpg',
667defaultLabel: 'Logo Stamps Collection',
668activeHref: '/rubber-logo-stamps',
669activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Custom-Logo-Stamps.jpg',
670activeLabel: 'Logo Stamps Collection',
671set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
672reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
673}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/rubber-logo-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Custom-Logo-Stamps.jpg" alt="Logo Stamps Collection" :src="activeImage" :alt="activeLabel"
674loading="lazy" width="224" height="224"
675class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
676x-text="activeLabel">Logo Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-3 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Shape</h3><ul class="space-y-2.5"><li><a href="/rubber-logo-stamps/rectangle"
677@mouseenter="set('/rubber-logo-stamps/rectangle', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Rectangle-Logo.jpg', 'Rectangle Logo Stamp')"
678class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Rectangle Logo Stamp</a></li><li><a href="/rubber-logo-stamps/round"
679@mouseenter="set('/rubber-logo-stamps/round', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Round-Logo-2.jpg', 'Round Logo Stamp')"
680class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Round Logo Stamp</a></li><li><a href="/rubber-logo-stamps/square"
681@mouseenter="set('/rubber-logo-stamps/square', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Square-Logo.jpg', 'Square Logo Stamp')"
682class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Square Logo Stamp</a></li><li><a href="/rubber-logo-stamps/catalog-stamps"
683@mouseenter="set('/rubber-logo-stamps/catalog-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Catalog-Logo.jpg', 'Catalog Stamps')"
684class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Catalog Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Artwork</h3><ul class="space-y-2.5"><li><a href="/rubber-logo-stamps/with-text"
685@mouseenter="set('/rubber-logo-stamps/with-text', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Logo-and-Text.jpg', 'Logo & Text')"
686class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Logo &amp; Text</a></li><li><a href="/rubber-logo-stamps/upload-art"
687@mouseenter="set('/rubber-logo-stamps/upload-art', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Artwork-Logo.jpg', 'Personalized Art')"
688class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Personalized Art</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Stamp Type</h3><ul class="space-y-2.5"><li><a href="/rubber-logo-stamps/pre-inked-logo"
689@mouseenter="set('/rubber-logo-stamps/pre-inked-logo', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Pre-Inked-Logo.jpg', 'Pre-Inked')"
690class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Pre-Inked</a></li><li><a href="/rubber-logo-stamps/self-inking"
691@mouseenter="set('/rubber-logo-stamps/self-inking', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Self-Ink-Logo.jpg', 'Self-Inking')"
692class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Self-Inking</a></li><li><a href="/rubber-logo-stamps/traditional-wooden-handle"
693@mouseenter="set('/rubber-logo-stamps/traditional-wooden-handle', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Wooden-Logo.jpg', 'Wood Handle')"
694class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Wood Handle</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/rubber-logo-stamps" @mouseenter="reset()"
695class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Logo Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div> <a href="https://www.simplystamps.com/rubber-logo-stamps"
696class="inline-block mt-3 text-sm font-semibold text-brand-primary hover:underline"> View Page &rarr; </a></div></li><li x-data="{ open: false }"> <button @click="open = !open" class="flex items-center w-full border-b border-gray-200 text-left"> <span class="flex-1 px-4 py-3 font-semibold text-lg transition-colors duration-200 text-gray-700"> Signature Stamps </span> <span class="px-3 py-3 text-gray-600 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" :class="open ? 'rotate-180' : ''"
697class="w-5 h-5 transition-transform duration-200"
698fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </span> </button><div x-show="open" x-transition class="px-4 py-3 border-b border-gray-200" style="display: none;"><div x-data="{
699defaultHref: '/business-office-stamps/signature-stamps',
700defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-Category.jpg',
701defaultLabel: 'Signature Stamps Collection',
702activeHref: '/business-office-stamps/signature-stamps',
703activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-Category.jpg',
704activeLabel: 'Signature Stamps Collection',
705set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
706reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
707}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/business-office-stamps/signature-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-Category.jpg" alt="Signature Stamps Collection" :src="activeImage" :alt="activeLabel"
708loading="lazy" width="224" height="224"
709class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
710x-text="activeLabel">Signature Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-2 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Signature Stamp Types</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/signature-stamps/signature-only"
711@mouseenter="set('/business-office-stamps/signature-stamps/signature-only', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-only.jpg', 'Signature Only')"
712class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Signature Only</a></li><li><a href="/business-office-stamps/signature-stamps/with-date"
713@mouseenter="set('/business-office-stamps/signature-stamps/with-date', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-and-date.jpg', 'Signature with Date')"
714class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Signature with Date</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Signature &amp; More</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/signature-stamps/with-printed-name"
715@mouseenter="set('/business-office-stamps/signature-stamps/with-printed-name', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-and-name.jpg', 'Signature with Printed Name')"
716class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Signature with Printed Name</a></li><li><a href="/business-office-stamps/signature-stamps/with-logo"
717@mouseenter="set('/business-office-stamps/signature-stamps/with-logo', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Signature-and-logo.jpg', 'Signature with Logo')"
718class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Signature with Logo</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/business-office-stamps/signature-stamps" @mouseenter="reset()"
719class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Signature Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div> <a href="https://www.simplystamps.com/business-office-stamps/signature-stamps"
720class="inline-block mt-3 text-sm font-semibold text-brand-primary hover:underline"> View Page &rarr; </a></div></li><li x-data="{ open: false }"> <button @click="open = !open" class="flex items-center w-full border-b border-gray-200 text-left"> <span class="flex-1 px-4 py-3 font-semibold text-lg transition-colors duration-200 text-gray-700"> Custom Stamps </span> <span class="px-3 py-3 text-gray-600 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" :class="open ? 'rotate-180' : ''"
721class="w-5 h-5 transition-transform duration-200"
722fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </span> </button><div x-show="open" x-transition class="px-4 py-3 border-b border-gray-200" style="display: none;"><div x-data="{
723defaultHref: '/custom-rubber-stamps',
724defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Stamp-Assortment.jpg',
725defaultLabel: 'Custom Stamps Collection',
726activeHref: '/custom-rubber-stamps',
727activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Stamp-Assortment.jpg',
728activeLabel: 'Custom Stamps Collection',
729set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
730reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
731}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/custom-rubber-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Stamp-Assortment.jpg" alt="Custom Stamps Collection" :src="activeImage" :alt="activeLabel"
732loading="lazy" width="224" height="224"
733class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
734x-text="activeLabel">Custom Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-2 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Shop by Stamp Type</h3><ul class="space-y-2.5"><li><a href="/custom-rubber-stamps/custom-self-inking-stamps"
735@mouseenter="set('/custom-rubber-stamps/custom-self-inking-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Self-Inking-Stamps-Custom-400x400.jpg', 'Custom Self-Inking Stamps')"
736class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Self-Inking Stamps</a></li><li><a href="/custom-rubber-stamps/custom-pre-inked-stamps"
737@mouseenter="set('/custom-rubber-stamps/custom-pre-inked-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Pre-Inked-Stamps-Custom-400x400.jpg', 'Custom Pre-Inked Stamps')"
738class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Pre-Inked Stamps</a></li><li><a href="/custom-rubber-stamps/custom-wood-handle-stamps"
739@mouseenter="set('/custom-rubber-stamps/custom-wood-handle-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Traditional-Hand-Stamps-Custom-400x400.jpg', 'Custom Wood Handle Stamps')"
740class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Wood Handle Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Specialty</h3><ul class="space-y-2.5"><li><a href="/custom-rubber-stamps/custom-european-hand-stamps"
741@mouseenter="set('/custom-rubber-stamps/custom-european-hand-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-European-Hand-Stamps-Custom-400x400.jpg', 'Custom European Hand Stamps')"
742class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom European Hand Stamps</a></li><li><a href="/custom-rubber-stamps/custom-professional-stamps"
743@mouseenter="set('/custom-rubber-stamps/custom-professional-stamps', 'https://cdn.simplystamps.com/media/catalog/category/SS-QCustom-Professional-Stamps-Custom-400x400.jpg', 'Custom Professional Stamps')"
744class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Professional Stamps</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/custom-rubber-stamps" @mouseenter="reset()"
745class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Custom Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
745<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div> <a href="https://www.simplystamps.com/custom-rubber-stamps"
746class="inline-block mt-3 text-sm font-semibold text-brand-primary hover:underline"> View Page &rarr; </a></div></li><li x-data="{ open: false }"> <button @click="open = !open" class="flex items-center w-full border-b border-gray-200 text-left"> <span class="flex-1 px-4 py-3 font-semibold text-lg transition-colors duration-200 text-gray-700"> Office Stamps </span> <span class="px-3 py-3 text-gray-600 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" :class="open ? 'rotate-180' : ''"
747class="w-5 h-5 transition-transform duration-200"
748fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </span> </button><div x-show="open" x-transition class="px-4 py-3 border-b border-gray-200" style="display: none;"><div x-data="{
749defaultHref: '/business-office-stamps',
750defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS_OfficeStamp-Cat_Bank_250x250.jpg',
751defaultLabel: 'Office Stamps Collection',
752activeHref: '/business-office-stamps',
753activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS_OfficeStamp-Cat_Bank_250x250.jpg',
754activeLabel: 'Office Stamps Collection',
755set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
756reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
757}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/business-office-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS_OfficeStamp-Cat_Bank_250x250.jpg" alt="Office Stamps Collection" :src="activeImage" :alt="activeLabel"
758loading="lazy" width="224" height="224"
759class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
760x-text="activeLabel">Office Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-3 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Business &amp; Banking</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/accounting-tax-stamps"
761@mouseenter="set('/business-office-stamps/accounting-tax-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Accounting-Stamps-OS-400x400.jpg', 'Accounting Stamps')"
762class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Accounting Stamps</a></li><li><a href="/business-office-stamps/business-address-stamps"
763@mouseenter="set('/business-office-stamps/business-address-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Business-Address-Stamps-OS-400x400.jpg', 'Business Address Stamps')"
764class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Business Address Stamps</a></li><li><a href="/business-office-stamps/banking-stamps/deposit-stamps/check-endorsement"
765@mouseenter="set('/business-office-stamps/banking-stamps/deposit-stamps/check-endorsement', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Check-Endorsement-Stamps-OS-400x400.jpg', 'Check Endorsement Stamps')"
766class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Check Endorsement Stamps</a></li><li><a href="/business-office-stamps/banking-stamps/deposit-stamps"
767@mouseenter="set('/business-office-stamps/banking-stamps/deposit-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Deposit-Stamps-OS-400x400.jpg', 'Deposit Stamps')"
768class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Deposit Stamps</a></li><li><a href="/business-office-stamps/accounting-tax-stamps/received"
769@mouseenter="set('/business-office-stamps/accounting-tax-stamps/received', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Received-stamps-OS-400x400.jpg', 'Received Stamps')"
770class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Received Stamps</a></li><li><a href="/business-office-stamps/custom-line-stamps"
771@mouseenter="set('/business-office-stamps/custom-line-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Custom-text-Stamps-OS-400x400.jpg', 'Custom Text Stamps')"
772class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Custom Text Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Office Essentials</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/office-stamps"
773@mouseenter="set('/business-office-stamps/office-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Stock-office-stamps-OS-400x400.jpg', 'Stock Office Stamps')"
774class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Stock Office Stamps</a></li><li><a href="/business-office-stamps/daters"
775@mouseenter="set('/business-office-stamps/daters', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Date-Stamps-OS-400x400.jpg', 'Date Stamps')"
776class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Date Stamps</a></li><li><a href="/business-office-stamps/checkbox"
777@mouseenter="set('/business-office-stamps/checkbox', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Checkbox-Stamps-OS-400x400.jpg', 'Checkbox 
777Stamps')"
778class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Checkbox Stamps</a></li><li><a href="/business-office-stamps/secure-stamps"
779@mouseenter="set('/business-office-stamps/secure-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Security-Stamps-OS-400x400.jpg', 'Security Stamps')"
780class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Security Stamps</a></li><li><a href="/business-office-stamps/inspection-stamps"
781@mouseenter="set('/business-office-stamps/inspection-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-inspection-Stamps-OS-400x400.jpg', 'Inspection Stamps')"
782class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Inspection Stamps</a></li><li><a href="/business-office-stamps/medical-stamps"
783@mouseenter="set('/business-office-stamps/medical-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Medical-Stamps-OS-400x400.jpg', 'Medical Stamps')"
784class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Medical Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Professional Stamps</h3><ul class="space-y-2.5"><li><a href="/business-office-stamps/professional-stamps/llc-stamps"
785@mouseenter="set('/business-office-stamps/professional-stamps/llc-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-LLC-Stamps-OS-400x400.jpg', 'LLC Stamps')"
786class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">LLC Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/architect"
787@mouseenter="set('/business-office-stamps/professional-stamps/architect', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Architect-Stamps-OS-400x400.jpg', 'Architect Stamps')"
788class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Architect Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/engineer"
789@mouseenter="set('/business-office-stamps/professional-stamps/engineer', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Engineer-Stamps-OS-400x400.jpg', 'Engineer Stamps')"
790class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Engineer Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/land-surveyor"
791@mouseenter="set('/business-office-stamps/professional-stamps/land-surveyor', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Land-Surveyor-Stamps-OS-400x400.jpg', 'Land Surveyor Stamps')"
792class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Land Surveyor Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/real-estate-appraiser"
793@mouseenter="set('/business-office-stamps/professional-stamps/real-estate-appraiser', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Real-Estate-Appraiser-Stamps-OS-400x400.jpg', 'Real Estate Appraiser Stamps')"
794class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Real Estate Appraiser Stamps</a></li><li><a href="/business-office-stamps/professional-stamps/geologist-stamps-other"
795@mouseenter="set('/business-office-stamps/professional-stamps/geologist-stamps-other', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/OfficeStamps/SS-QCS-Geologist-Stamps-OS-400x400.jpg', 'Geologist Stamps')"
796class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Geologist Stamps</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/business-office-stamps" @mouseenter="reset()"
797class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Office Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
797<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div> <a href="https://www.simplystamps.com/business-office-stamps"
798class="inline-block mt-3 text-sm font-semibold text-brand-primary hover:underline"> View Page &rarr; </a></div></li><li x-data="{ open: false }"> <button @click="open = !open" class="flex items-center w-full border-b border-gray-200 text-left"> <span class="flex-1 px-4 py-3 font-semibold text-lg transition-colors duration-200 text-gray-700"> Embossers </span> <span class="px-3 py-3 text-gray-600 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" :class="open ? 'rotate-180' : ''"
799class="w-5 h-5 transition-transform duration-200"
800fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </span> </button><div x-show="open" x-transition class="px-4 py-3 border-b border-gray-200" style="display: none;"><div x-data="{
801defaultHref: '/embossers',
802defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Shop-All-EMB-400x400.jpg',
803defaultLabel: 'Embossers Collection',
804activeHref: '/embossers',
805activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Shop-All-EMB-400x400.jpg',
806activeLabel: 'Embossers Collection',
807set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
808reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
809}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/embossers" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-Shop-All-EMB-400x400.jpg" alt="Embossers Collection" :src="activeImage" :alt="activeLabel"
810loading="lazy" width="224" height="224"
811class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
812x-text="activeLabel">Embossers Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-3 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Personal &amp; Occasion</h3><ul class="space-y-2.5"><li><a href="/embossers/custom-address"
813@mouseenter="set('/embossers/custom-address', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-Address-EMB-400x400.jpg', 'Address Embossers')"
814class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Address Embossers</a></li><li><a href="/embossers/library-book-embossers"
815@mouseenter="set('/embossers/library-book-embossers', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-Library-Book-EMB-400x400.jpg', 'Library Book Embossers')"
816class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Library Book Embossers</a></li><li><a href="/embossers/wedding-embossers"
817@mouseenter="set('/embossers/wedding-embossers', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-wedding-embosser-cat-emboss.jpg', 'Wedding Embossers')"
818class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Wedding Embossers</a></li><li><a href="/embossers/luxury-embossers"
819@mouseenter="set('/embossers/luxury-embossers', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-Luxury-Embossers-QCS-Nav-Embosser-400.jpg', 'Luxury Embossers')"
820class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Luxury Embossers</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Business &amp; Legal</h3><ul class="space-y-2.5"><li><a href="/embossers/corporate-seal"
821@mouseenter="set('/embossers/corporate-seal', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-Corporate-Seals-EMB-400x400.jpg', 'Corporate Seals')"
822class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Corporate Seals</a></li><li><a href="/embossers/notary-public"
823@mouseenter="set('/embossers/notary-public', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-Notary-EMB-400x400.jpg', 'Notary Embossers')"
824class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Notary Embossers</a></li><li><a href="/embossers/custom-logo"
825@mouseenter="set('/embossers/custom-logo', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Embossers/SS-QCS-LOGO-EMB-400x400.jpg', 'Logo Embossers')"
826class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Logo Embossers</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Accessories</h3><ul class="space-y-2.5"><li><a href="/stamp-ink-pads/embosser-accessories"
827@mouseenter="set('/stamp-ink-pads/embosser-accessories', 'https://cdn.simplystamps.com/media/catalog/category/SS_cat-ink_and_accessories_embosser-accesories-265
827x265.jpg', 'Embosser Accessories')"
828class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Embosser Accessories</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/embossers" @mouseenter="reset()"
829class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Embossers <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div> <a href="https://www.simplystamps.com/embossers"
830class="inline-block mt-3 text-sm font-semibold text-brand-primary hover:underline"> View Page &rarr; </a></div></li><li > <a href="https://www.simplystamps.com/notary-public"
831class="block px-4 py-3 font-semibold text-lg border-b border-gray-200 transition-colors duration-200 hover:bg-gray-100 hover:text-brand-primary text-gray-700"> Notary </a></li><li x-data="{ open: false }"> <button @click="open = !open" class="flex items-center w-full border-b border-gray-200 text-left"> <span class="flex-1 px-4 py-3 font-semibold text-lg transition-colors duration-200 text-gray-700"> Teacher Stamps </span> <span class="px-3 py-3 text-gray-600 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" :class="open ? 'rotate-180' : ''"
832class="w-5 h-5 transition-transform duration-200"
833fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </span> </button><div x-show="open" x-transition class="px-4 py-3 border-b border-gray-200" style="display: none;"><div x-data="{
834defaultHref: '/teacher-stamps',
835defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-TS-400.jpg',
836defaultLabel: 'Teacher Stamps Collection',
837activeHref: '/teacher-stamps',
838activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-TS-400.jpg',
839activeLabel: 'Teacher Stamps Collection',
840set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
841reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
842}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/teacher-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-TS-400.jpg" alt="Teacher Stamps Collection" :src="activeImage" :alt="activeLabel"
843loading="lazy" width="224" height="224"
844class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
845x-text="activeLabel">Teacher Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-2 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Classroom &amp; Grading</h3><ul class="space-y-2.5"><li><a href="/teacher-stamps/grading"
846@mouseenter="set('/teacher-stamps/grading', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Grading-stamps-TS-400x400.jpg', 'Grading Stamps')"
847class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Grading Stamps</a></li><li><a href="/teacher-stamps/feedback"
848@mouseenter="set('/teacher-stamps/feedback', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Feedback-stamps-TS-400x400.jpg', 'Feedback Stamps')"
849class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Feedback Stamps</a></li><li><a href="/teacher-stamps/name-missing"
850@mouseenter="set('/teacher-stamps/name-missing', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Missing-Name-stamps-TS-400x400.jpg', 'Name Missing Stamps')"
851class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Name Missing Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Fun &amp; Motivation</h3><ul class="space-y-2.5"><li><a href="/teacher-stamps/motivational"
852@mouseenter="set('/teacher-stamps/motivational', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Motivational-stamps-TS-400x400.jpg', 'Motivational Stamps')"
853class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Motivational Stamps</a></li><li><a href="/teacher-stamps/fun-designs"
854@mouseenter="set('/teacher-stamps/fun-designs', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Fun-stamps-TS-400x400.jpg', 'Fun Stamps')"
855class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Fun Stamps</a></li><li><a href="/teacher-stamps/from-the-desk-of"
856@mouseenter="set('/teacher-stamps/from-the-desk-of', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Teacher/SS-QCS-Book-stamps-TS-400x400.jpg', 'Book Stamps')"
857class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Book Stamps</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/teacher-stamps" @mouseenter="reset()"
858class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Teacher Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div> <a href="https://www.simplystamps.com/teacher-stamps"
859class="inline-block mt-3 text-sm font-semibold text-brand-primary hover:underline"> View Page &rarr; </a></div></li><li x-data="{ open: false }"> <button @click="open = !open" class="flex items-center w-full border-b border-gray-200 text-left"> <span class="flex-1 px-4 py-3 font-semibold text-lg transition-colors duration-200 text-gray-700"> Craft Stamps </span> <span class="px-3 py-3 text-gray-600 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" :class="open ? 'rotate-180' : ''"
860class="w-5 h-5 transition-transform duration-200"
861fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </span> </button><div x-show="open" x-transition class="px-4 py-3 border-b border-gray-200" style="display: none;"><div x-data="{
862defaultHref: '/craft-stamps',
863defaultImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-CS-2000_1.jpg',
864defaultLabel: 'Craft Stamps Collection',
865activeHref: '/craft-stamps',
866activeImage: 'https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-CS-2000_1.jpg',
867activeLabel: 'Craft Stamps Collection',
868set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
869reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
870}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/craft-stamps" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/catalog/category/SS-QCS-View-All-CS-2000_1.jpg" alt="Craft Stamps Collection" :src="activeImage" :alt="activeLabel"
871loading="lazy" width="224" height="224"
872class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
873x-text="activeLabel">Craft Stamps Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-3 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Seasonal &amp; Holiday</h3><ul class="space-y-2.5"><li><a href="/craft-stamps/holiday-crafting"
874@mouseenter="set('/craft-stamps/holiday-crafting', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Holiday-Stamps-CS-400x400.jpg', 'Holiday Stamps')"
875class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Holiday Stamps</a></li><li><a href="/craft-stamps/halloween"
876@mouseenter="set('/craft-stamps/halloween', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Halloween-Stamps-CS-400x400.jpg', 'Halloween Stamps')"
877class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Halloween Stamps</a></li><li><a href="/craft-stamps/thanksgiving"
878@mouseenter="set('/craft-stamps/thanksgiving', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Thanksgiving-Stamps-CS-400x400.jpg', 'Thanksgiving Stamps')"
879class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Thanksgiving Stamps</a></li><li><a href="/craft-stamps/autumn-fall"
880@mouseenter="set('/craft-stamps/autumn-fall', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Autumn-Stamps-CS-400x400.jpg', 'Autumn Stamps')"
881class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Autumn Stamps</a></li><li><a href="/craft-stamps/summer"
882@mouseenter="set('/craft-stamps/summer', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Summer-Stamps-CS-400x400.jpg', 'Summer Stamps')"
883class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Summer Stamps</a></li><li><a href="/craft-stamps/valentine-s-day-craft-stamp"
884@mouseenter="set('/craft-stamps/valentine-s-day-craft-stamp', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Valentines-Stamps-CS-400x400.jpg', 'Valentine\'s Day Stamps')"
885class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Valentine's Day Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Occasions &amp; Gifts</h3><ul class="space-y-2.5"><li><a href="/wedding-stamps"
886@mouseenter="set('/wedding-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Wedding-Stamps-CS-400x400.jpg', 'Wedding Stamps')"
887class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Wedding Stamps</a></li><li><a href="/craft-stamps/we-have-moved"
888@mouseenter="set('/craft-stamps/we-have-moved', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Just-Moved-Stamps-CS-400x400.jpg', 'Just Moved Stamps')"
889class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Just Moved Stamps</a></li><li><a href="/craft-stamps/thank-you-stamps"
890@mouseenter="set('/craft-stamps/thank-you-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Thank-You-Stamps-CS-400x400.jpg', 'Thank You Stamps')"
891class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Thank You Stamps</a></li><li><a href="/craft-stamps/sentiment"
892@mouseenter="set('/craft-stamps/sentiment', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Sentiment-Stamps-CS-400x400.jpg', 'Sentiment Stamps')"
893class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Sentiment Stamps</a></li><li><a href="/teacher-stamps/clothing-stamps"
894@mouseenter="set('/teacher-stamps/clothing-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Name-Stamps-CS-400x400.jpg', 'Name Stamps')"
895class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Name Stamps</a></li><li><a href="/face-stamps"
896@mouseenter="set('/face-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Face-Stamps-CS-400x400.jpg', 'Face Stamps')"
897class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Face Stamps</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Crafting</h3><ul class="space-y-2.5"><li><a href="/craft-stamps/mini-accent-stamps"
898@mouseenter="set('/craft-stamps/mini-accent-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Mini-Accent-Stamps-CS-400x400.jpg', 'Mini Accent Stamps')"
899class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Mini Accent Stamps</a></li><li><a href="/craft-stamps/embellishment/bullet-journal"
900@mouseenter="set('/craft-stamps/embellishment/bullet-journal', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Bullet-Journal-Stamps-CS-400x400.jpg', 'Bullet Journal Stamps')"
901class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Bullet Journal Stamps</a></li><li><a href="/craft-stamps/embellishment"
902@mouseenter="set('/craft-stamps/embellishment', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Decorative-Stamps-CS-400x400.jpg', 'Decorative Stamps')"
903class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Decorative Stamps</a></li><li><a href="/rubber-flower-stamps"
904@mouseenter="set('/rubber-flower-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Flower-Stamps-CS-400x400.jpg', 'Flower Stamps')"
905class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Flower Stamps</a></li><li><a href="/craft-stamps/emoji-stamps"
906@mouseenter="set('/craft-stamps/emoji-stamps', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Craft/SS-QCS-Emoji-Stamps-CS-400x400.jpg', 'Emoji Stamps')"
907class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Emoji Stamps</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/craft-stamps" @mouseenter="reset()"
908class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Craft Stamps <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
908<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div> <a href="https://www.simplystamps.com/craft-stamps"
909class="inline-block mt-3 text-sm font-semibold text-brand-primary hover:underline"> View Page &rarr; </a></div></li><li x-data="{ open: false }"> <button @click="open = !open" class="flex items-center w-full border-b border-gray-200 text-left"> <span class="flex-1 px-4 py-3 font-semibold text-lg transition-colors duration-200 text-gray-700"> Ink &amp; Accessories </span> <span class="px-3 py-3 text-gray-600 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" :class="open ? 'rotate-180' : ''"
910class="w-5 h-5 transition-transform duration-200"
911fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </span> </button><div x-show="open" x-transition class="px-4 py-3 border-b border-gray-200" style="display: none;"><div x-data="{
912defaultHref: '/stamp-ink-pads',
913defaultImage: 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Category-Craft-400.jpg',
914defaultLabel: 'Ink & Accessories Collection',
915activeHref: '/stamp-ink-pads',
916activeImage: 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Category-Craft-400.jpg',
917activeLabel: 'Ink & Accessories Collection',
918set(h, i, l) { this.activeHref = h; this.activeImage = i; this.activeLabel = l; },
919reset() { this.activeHref = this.defaultHref; this.activeImage = this.defaultImage; this.activeLabel = this.defaultLabel; }
920}" @mouseleave="reset()"><div class="flex gap-8 items-start"><div class="hidden md:block w-56 flex-shrink-0"> <a :href="activeHref" href="/stamp-ink-pads" class="block group"><div class="aspect-square w-full bg-gray-100 rounded-lg overflow-hidden mb-2"> <img src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Category-Craft-400.jpg" alt="Ink & Accessories Collection" :src="activeImage" :alt="activeLabel"
921loading="lazy" width="224" height="224"
922class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div><p class="text-sm font-semibold text-gray-900 group-hover:text-brand-primary transition-colors"
923x-text="activeLabel">Ink & Accessories Collection</p> </a></div><div class="flex-1"><div class="grid grid-cols-1 md:grid-cols-2 gap-8"><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Ink &amp; Refills</h3><ul class="space-y-2.5"><li><a href="/stamp-ink-pads/craft-stamp-ink"
924@mouseenter="set('/stamp-ink-pads/craft-stamp-ink', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Craft-Ink-Pads-Ink-And-Accessories-400.jpg', 'Craft Stamp Ink Pads')"
925class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Craft Stamp Ink Pads</a></li><li><a href="/stamp-ink-pads/ink-pad-replacements"
926@mouseenter="set('/stamp-ink-pads/ink-pad-replacements', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Ink-Pad-Refills-Ink-And-Accessories-400.jpg', 'Ink Pad Refills')"
927class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Ink Pad Refills</a></li><li><a href="/stamp-ink-pads/ink-bottle-refills"
928@mouseenter="set('/stamp-ink-pads/ink-bottle-refills', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Ink-Bottle-refills-Ink-And-Accessories-400.jpg', 'Ink Bottle Refills')"
929class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Ink Bottle Refills</a></li></ul></div><div><h3 class="text-sm font-bold uppercase tracking-wider text-gray-900 mb-4 pb-2 border-b border-gray-200">Accessories</h3><ul class="space-y-2.5"><li><a href="/stamp-ink-pads/embosser-accessories"
930@mouseenter="set('/stamp-ink-pads/embosser-accessories', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Embosser-Accessories-Ink-And-Accessories-400.jpg', 'Embosser Acce
930ssories')"
931class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Embosser Accessories</a></li><li><a href="/mail-accessories"
932@mouseenter="set('/mail-accessories', 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Ink-Accessories/SS-QCS-Mail-Accessories-Ink-And-Accessories-400.jpg', 'Mail Accessories')"
933class="block text-base text-gray-700 no-underline hover:text-brand-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary focus-visible:rounded-sm transition-colors duration-150">Mail Accessories</a></li></ul></div></div><div class="mt-6 pt-4 border-t border-gray-200"> <a href="/stamp-ink-pads" @mouseenter="reset()"
934class="inline-flex items-center text-sm font-semibold text-brand-primary hover:text-brand-primary-hover no-underline hover:no-underline transition-colors duration-150"> View all Ink & Accessories <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> </a></div></div></div></div> <a href="https://www.simplystamps.com/stamp-ink-pads"
935class="inline-block mt-3 text-sm font-semibold text-brand-primary hover:underline"> View Page &rarr; </a></div></li></ul> </nav></div> </nav><div class="flex items-center justify-center space-x-1 w-1/8"><div x-data="{ accountOpen: false }" @click.away="accountOpen = false" class="relative"> <button @click="if ($el._clickLock) return; $el._clickLock = true; setTimeout(() => $el._clickLock = false, 300); if ($store.auth.ready && $store.auth.isLoggedIn) { accountOpen = !accountOpen } else if ($store.auth.ready) { $store.auth._loginOpenedAt = Date.now(); $store.auth.showLoginModal = true }"
936class="flex items-center flex-col space-y-1 p-2 w-[70px] hover:bg-gray-100 rounded-lg transition-colors"> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-7 h-7"> <path d="M20 21V19C20 17.9391 19.5786 16.9217 18.8284 16.1716C18.0783 15.4214 17.0609 15 16 15H8C6.93913 15 5.92172 15.4214 5.17157 16.1716C4.42143 16.9217 4 17.9391 4 19V21"
937stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 11C14.2091 11 16 9.20914 16 7C16 4.79086 14.2091 3 12 3C9.79086 3 8 4.79086 8 7C8 9.20914 9.79086 11 12 11Z"
938stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <span class="flex items-center justify-center gap-0.5"> <span class="text-sm font-medium text-gray-700"
939x-text="$store.auth.ready ? ($store.auth.isLoggedIn ? 'Account' : 'Log In') : 'Account'"></span> <svg x-show="$store.auth.ready && $store.auth.isLoggedIn"
940class="w-2.5 h-2.5 text-gray-400 shrink-0 transition-transform duration-200"
941:class="{ 'rotate-180': accountOpen }"
942fill="none" stroke="currentColor" viewBox="0 0 24 24"
943style="display: none;"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M19 9l-7 7-7-7"/> </svg> </span> </button><div x-show="accountOpen"
944x-transition:enter="transition ease-out duration-150"
945x-transition:enter-start="opacity-0 scale-95"
946x-transition:enter-end="opacity-100 scale-100"
947x-transition:leave="transition ease-in duration-100"
948x-transition:leave-start="opacity-100 scale-100"
949x-transition:leave-end="opacity-0 scale-95"
950class="absolute left-1/2 -translate-x-1/2 top-full mt-3 w-64 bg-white rounded-lg shadow-lg border border-gray-300 py-1.5 z-50"
951style="display: none;"><div class="absolute -top-2 left-1/2 -translate-x-1/2 w-3 h-3 bg-white border-l border-t border-gray-300 rotate-45"></div><div class="px-5 py-2.5 border-b border-gray-100 mb-1"><p class="text-sm text-gray-400 truncate" x-text="$store.auth.customerEmail"></p></div> <a href="https://www.simplystamps.com/customer/account/"
952class="flex items-center gap-3 px-5 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-50 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/> </svg> My Account </a> <a href="https://www.simplystamps.com/sales/order/history/"
953class="flex items-center gap-3 px-5 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-50 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/> </svg> My Orders </a> <a x-show="$store.auth.companyStoreUrl"
954:href="$store.auth.companyStoreUrl"
955class="flex items-center gap-3 px-5 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-50 transition-colors"
956style="display: none;"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/> </svg> Company Store </a><div class="border-t border-gray-100 my-1.5"></div> <button @click="accountOpen = false;
957if (typeof window.zGA4 === 'function') { window.zGA4('logout', { method: 'site' }); }
958$store.auth.refreshMessage = 'Logging out...';
959$store.auth.showRefreshOverlay = true;
960window.location.href = 'https://www.simplystamps.com/customer/account/logout/';"
961class="flex items-center gap-3 px-5 py-2.5 text-base text-gray-500 hover:text-red-600 hover:bg-gray-50 transition-colors w-full"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
962d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/> </svg> Log Out </button></div></div><button
963@click="if ($el._clickLock) return; $el._clickLock = true; setTimeout(() => $el._clickLock = false, 300); $store.search.open()"
964class="flex items-center flex-col space-y-1 p-2 w-[70px] hover:bg-gray-100 rounded-lg transition-colors"> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-7 h-7"> <circle cx="11" cy="11" r="6" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M16 16L21 21" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <span class="text-sm font-medium text-gray-700">Search</span> </button><div x-data="{ helpOpen: false }" @click.away="helpOpen = false" class="relative"> <button @click="if ($el._clickLock) return; $el._clickLock = true; setTimeout(() => $el._clickLock = false, 300); helpOpen = !helpOpen"
965class="flex items-center flex-col space-y-1 p-2 w-[70px] hover:bg-gray-100 rounded-lg transition-colors"> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-7 h-7"> <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M9.08984 8.99959C9.32495 8.33126 9.789 7.7677 10.3998 7.40873C11.0106 7.04975 11.7287 6.91853 12.427 7.0383C13.1253 7.15808 13.7587 7.52112 14.2149 8.06312C14.6712 8.60512 14.9209 9.29112 14.9198 9.99959C14.9198 11.9996 11.9198 12.9996 11.9198 12.9996" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 17H12.01" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <span class="flex items-center justify-center gap-0.5"> <span class="text-sm font-medium text-gray-700">Help</span> <svg class="w-2.5 h-2.5 text-gray-400 shrink-0 transition-transform duration-200"
966:class="{ 'rotate-180': helpOpen }"
967fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M19 9l-7 7-7-7"/> </svg> </span> </button><div x-show="helpOpen"
968x-transition:enter="transition ease-out duration-150"
969x-transition:enter-start="opacity-0 scale-95"
970x-transition:enter-end="opacity-100 scale-100"
971x-transition:leave="transition ease-in duration-100"
972x-transition:leave-start="opacity-100 scale-100"
973x-transition:leave-end="opacity-0 scale-95"
974class="absolute left-1/2 -translate-x-1/2 top-full mt-3 w-64 bg-white rounded-lg shadow-lg border border-gray-300 py-1.5 z-50"
975style="display: none;"><div class="absolute -top-2 left-1/2 -translate-x-1/2 w-3 h-3 bg-white border-l border-t border-gray-300 rotate-45"></div> <a href="https://www.simplystamps.com/contacts"
976class="flex items-center gap-3 px-5 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-50 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/> </svg> Track My Order </a> <a href="https://www.simplystamps.com/contacts"
977class="flex items-center gap-3 px-5 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-50 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/> </svg> Contact Us </a> <a href="https://www.simplystamps.com/faq"
978class="flex items-center gap-3 px-5 py-2.5 text-base text-gray-700 hover:text-brand-primary hover:bg-gray-50 transition-colors"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> </svg> FAQ </a> <template x-if="$store.chat.available"><div><div class="border-t border-gray-100 my-1.5"></div> <button id="live-chat-now" class="flex items-center gap-3 px-5 py-2.5 pb-1.5 text-base font-medium text-brand-primary hover:bg-gray-50 transition-colors w-full cursor-pointer"> <svg class="w-6 h-6 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.25
9785-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/> </svg> <span class="text-left"> Chat with a Live Agent <span class="block text-sm text-brand-success font-medium">Available now</span> <span class="block text-sm text-gray-400 font-normal" x-text="$store.chat.hoursLabel"></span> </span> </button></div> </template></div></div><button
979@click="$store.cart.openDrawer()"
980@dblclick.prevent="window.location.href='/checkout/cart/'"
981class="relative flex items-center flex-col space-y-1 p-2 w-[70px] hover:bg-gray-100 rounded-lg transition-colors"
982aria-label="Open cart"
983> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-7 h-7"> <path d="M9 22C9.55228 22 10 21.5523 10 21C10 20.4477 9.55228 20 9 20C8.44772 20 8 20.4477 8 21C8 21.5523 8.44772 22 9 22Z" fill="#4F4F4F" stroke="#474747" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M20 22C20.5523 22 21 21.5523 21 21C21 20.4477 20.5523 20 20 20C19.4477 20 19 20.4477 19 21C19 21.5523 19.4477 22 20 22Z" stroke="#474747" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M1 1H5L7.68 14.39C7.77144 14.8504 8.02191 15.264 8.38755 15.5583C8.75318 15.8526 9.2107 16.009 9.68 16H19.4C19.8693 16.009 20.3268 15.8526 20.6925 15.5583C21.0581 15.264 21.3086 14.8504 21.4 14.39L23 6H6" stroke="#474747" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <span class="text-sm font-medium text-gray-700">Cart</span> <span x-cloak
984x-show="$store.cart.itemCount > 0"
985x-text="$store.cart.itemCount"
986class="absolute -top-2 right-0 bg-brand-danger text-white text-xs rounded-full h-5 flex items-center justify-center"
987:class="$store.cart.itemCount >= 10 ? 'min-w-[1.25rem] px-1' : 'w-5'"></span> </button></div></div><div
988x-show="searchOpen"
989x-transition:enter="transition ease-out duration-200"
990x-transition:enter-start="opacity-0 -translate-y-2"
991x-transition:enter-end="opacity-100 translate-y-0"
992x-transition:leave="transition ease-in duration-150"
993x-transition:leave-start="opacity-100 translate-y-0"
994x-transition:leave-end="opacity-0 -translate-y-2"
995class="pb-4"
996style="display: none;"
997><form class="searchautocomplete UI-SEARCHAUTOCOMPLETE" action="https://www.simplystamps.com/catalogsearch/result/" method="get"
998data-tip="Search entire store here..."
999data-url="//www.simplystamps.com/searchautocomplete/ajax/get/"
1000data-minchars="3"
1001data-delay="500"
1002data-hide-delay="1000"><label for="search">Search</label><div class="nav"><div class="nav-search-in"> <span class="category-fake UI-CATEGORY-TEXT">All</span> <span class="nav-down-arrow"></span> <select name="cat" class="category UI-CATEGORY"><option value="0">All</option><option value="713" > Address Stamps</option><option value="958" > Custom Business & Office Stamps</option><option value="749" > Craft Stamps</option><option value="1387" > Custom Logo Stamps</option><option value="770" > Embossers</option><option value="802" > Stamp Ink & Pads</option><option value="811" > Teacher Stamps</option><option value="793" > Notary</option><option value="815" > Wedding Stamps</option><option value="1432" > Redeemable Personalized Address Stamp with Gift Box</option><option value="2291" > St. Patrick's Day & Irish Theme Stamps</option><option value="2391" > Stamp of the Week</option><option value="809" > Breast Cancer Awareness</option><option value="3035" > Gift Notes</option><option value="3073" > Real Estate Supplies</option><option value="3085" > Custom Rubber Stamps</option><option value="3094" > Custom Return Address Labels</option><option value="3109" > Here Tomorrow</option><option value="3118" > StampToons</option><option value="3327" > Custom Face Stamps</option><option value="3432" > Salt & Paper Collaboration</option> </select></div><div class="nav-input UI-NAV-INPUT"> <input class="input-text UI-SEARCH" type="text" autocomplete="off" name="q" value="" maxlength="128" /></div><div class="searchautocomplete-loader UI-LOADER"><div id="g01"></div><div id="g02"></div><div id="g03"></div><div id="g04"></div><div id="g05"></div><div id="g06"></div><div id="g07"></div><div id="g08"></div></div></div><div class="nav-submit-button"> <button type="submit" title="Go" class="button">Go</button></div> <style> .store-4-search-autocomplete {
1003top: 108px !important;
1004right: unset !important;
1005}@media (min-width: 768px) {
1006.store-4-search-autocomplete {
1007top: 74px !important;
1008right: unset !important;
1009}
1010} </style><div style="display:none" class="searchautocomplete-placeholder UI-PLACEHOLDER "></div></form></div></div></div><div id="zephyr-promo-banner"
1011role="region"
1012aria-label="Promotion"
1013data-key="19208366"
1014data-start="1790568000"
1015data-end="1793474220"
1016data-code=""
1017data-auto-apply="0"
1018data-countdown="0"
1019class="relative text-sm sm:text-base leading-snug "
1020style="background-color:#60B5E4;color:#012E4B;"     x-data="{
1021copied: false,
1022applied: false,
1023applying: false,
1024hovering: false,
1025countdown: '',
1026timer: null,init() {
1027if (this.$root.dataset.countdown === '1') {
1028this.tick();
1029this.timer = setInterval(() => this.tick(), 1000);
1030}
1031},// Mirrors HSC_Zephyr_Model_PromoBanner::formatCountdown() exactly.
1032formatCountdown(s) {
1033const pad = n => String(n).padStart(2, '0');
1034const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60), sec = s % 60;
1035return (d > 0 ? pad(d) + 'd ' : '') + pad(h) + 'h ' + pad(m) + 'm ' + pad(sec) + 's';
1036},tick() {
1037const end = parseInt(this.$root.dataset.end, 10) || 0;
1038const left = Math.max(0, end - Math.floor(Date.now() / 1000));
1039this.countdown = this.formatCountdown(left);
1040if (left <= 0) {
1041// Promo is over: take the band down the same way dismiss does.
1042clearInterval(this.timer);
1043this.$root.hidden = true;
1044window.dispatchEvent(new CustomEvent('zephyr:header-resize'));
1045}
1046},copy() {
1047// A tap fires mouseenter/focus with no matching leave, so clear the hover state here.
1048this.hovering = false;
1049const code = this.$root.dataset.code;
1050const write = navigator.clipboard
1051? navigator.clipboard.writeText(code)
1052: Promise.reject();
1053write.then(() => {
1054this.copied = true;
1055setTimeout(() => { this.copied = false; }, 2000);
1056}).catch(() => {});if (this.$root.dataset.autoApply === '1') {
1057this.apply(code);
1058}
1059},// Mirrors Alpine.store('promo').apply() in promo-code.js, minus the modal
1060// state. Only when the cart has items; a coupon on an empty quote does
1061// not validate reliably. Rejections stay silent: the shopper still has
1062// the code on the clipboard and the cart's own promo UI reports errors.
1063apply(code) {
1064const cart = Alpine.store('cart');
1065if (this.applying || this.applied || !cart || !cart.itemCount) return;
1066if (typeof promoCodeConfig === 'undefined' || !promoCodeConfig.applyCouponUrl) return;this.applying = true;
1067fetch(promoCodeConfig.applyCouponUrl, {
1068method: 'POST',
1069credentials: 'same-origin',
1070headers: {
1071'Content-Type': 'application/x-www-form-urlencoded',
1072'X-Requested-With': 'XMLHttpRequest'
1073},
1074body: 'coupon_code=' + encodeURIComponent(code) + '&form_key=' + encodeURIComponent(promoCodeConfig.formKey || '')
1075})
1076.then(r => r.json())
1077.then(data => {
1078if (!data.success) return;
1079this.applied = true;
1080cart.refresh();
1081if (Alpine.store('promo')) {
1082Alpine.store('promo').appliedCode = code.toUpperCase();
1083}
1084window.dispatchEvent(new CustomEvent('cart-updated'));
1085})
1086.catch(() => {})
1087.finally(() => { this.applying = false; });
1088},dismiss() {
1089try { sessionStorage.setItem('zephyr_promo_dismissed', this.$root.dataset.key); } catch (e) {}
1090this.$root.hidden = true;
1091window.dispatchEvent(new CustomEvent('zephyr:header-resize'));
1092}
1093}"><div class="container mx-auto px-9 sm:px-10 min-h-[36px] sm:min-h-[40px] py-1.5 sm:py-2 flex flex-col items-center justify-center gap-y-1.5 text-center sm:flex-row sm:flex-wrap sm:gap-x-3 sm:gap-y-1"> <span class="leading-tight sm:contents"> <span class="[&_a]:underline [&_a]:font-semibold [&_a]:text-inherit [&_img]:inline-block [&_img]:h-[1.3em] [&_img]:w-auto [&_img]:align-middle [&_img]:mx-0.5">Free shipping on orders $79+ | <b>FREE GIFT</b> with every logo stamp order</span> <a href="https://www.simplystamps.com/rubber-logo-stamps" class="ml-1 sm:ml-0 sm:order-last font-semibold underline underline-offset-2 whitespace-nowrap text-inherit hover:text-inherit hover:opacity-80 transition-opacity"> Shop logo stamps </a> </span></div><button type="button"
1094@click="dismiss()"
1095aria-label="Dismiss"
1096class="absolute right-1.5 sm:right-2 top-1/2 -translate-y-1/2 p-1.5 rounded transition-colors hover:bg-black/10"> <svg class="w-3.5 h-3.5 sm:w-4 sm:h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/> </svg> </button> <a x-show="$store.auth.isStaff" x-cloak
1097:href="$store.auth.adminEditBase ? $store.auth.adminEditBase + 'type/config/id/hsc_zephyr/store/sstamps_en/' : '#'"
1098target="_blank" rel="noopener"
1099title="Edit Promo Banner"
1100class="absolute left-2 top-1/2 -translate-y-1/2 flex w-6 h-6 bg-blue-600 hover:bg-blue-700 text-white rounded-full items-center justify-center shadow transition-colors staff-edit-icon"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/> </svg> </a></div> 
1100<script> // Pre-paint gate: runs before header.phtml measures --header-height.
1101(function () {
1102var el = document.getElementById('zephyr-promo-banner');
1103if (!el) return;
1104var now = Date.now() / 1000;
1105var start = parseInt(el.dataset.start, 10) || 0;
1106var end = parseInt(el.dataset.end, 10) || 0;
1107var hide = (start && now < start) || (end && now > end);
1108if (!hide) {
1109try { hide = sessionStorage.getItem('zephyr_promo_dismissed') === el.dataset.key; } catch (e) {}
1110}
1111if (hide) el.hidden = true;
1112})(); </script>
1112 </header></div>
1112<script> // Alpine.js global stores
1113document.addEventListener('alpine:init', () => {
1114// Chat Availability Store (client-side check to bypass Fastly cache)
1115Alpine.store('chat', {
1116available: false,
1117hoursLabel: '',
1118schedule: {"1":{"from":"09:00:00","to":"20:00:00"},"2":{"from":"09:00:00","to":"20:00:00"},"3":{"from":"09:00:00","to":"20:00:00"},"4":{"from":"09:00:00","to":"20:00:00"},"5":{"from":"09:00:00","to":"20:00:00"},"6":{"from":"00:00:00","to":"00:00:00"},"7":{"from":"00:00:00","to":"00:00:00"}},init() {
1119this.check();
1120setInterval(() => this.check(), 60000);
1121},check() {
1122if (!Object.keys(this.schedule).length) return;
1123try {
1124var etDate = new Date(new Date().toLocaleString('en-US', { timeZone: 'America/New_York' }));
1125var dow = etDate.getDay();
1126var isoDay = dow === 0 ? 7 : dow;
1127var daySchedule = this.schedule[isoDay];
1128if (!daySchedule) { this.available = false; this.hoursLabel = ''; return; }var h = etDate.getHours(), m = etDate.getMinutes(), s = etDate.getSeconds();
1129var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
1130var currentTime = pad(h) + ':' + pad(m) + ':' + pad(s);
1131this.available = currentTime >= daySchedule.from && currentTime < daySchedule.to;
1132this.hoursLabel = this.formatTime(daySchedule.from) + '-' + this.formatTime(daySchedule.to) + ' ET';
1133} catch (e) {
1134this.available = false;
1135}
1136},// "09:00:00" → "9AM", "13:30:00" → "1:30PM"
1137formatTime(timeStr) {
1138var p = timeStr.split(':');
1139var h = parseInt(p[0], 10), m = parseInt(p[1], 10);
1140var suffix = h >= 12 ? 'PM' : 'AM';
1141var h12 = h % 12 || 12;
1142return m > 0 ? h12 + ':' + (m < 10 ? '0' + m : m) + suffix : h12 + suffix;
1143}
1144});// Image Preview Store
1145Alpine.store('imagePreview', {
1146isOpen: false,
1147imageUrl: '',
1148imageAlt: '',
1149linkUrl: '',open(url, alt = '', link = '') {
1150this.imageUrl = url;
1151this.imageAlt = alt;
1152this.linkUrl = link;
1153this.isOpen = true;
1154document.body.style.overflow = 'hidden';
1155},close() {
1156this.isOpen = false;
1157document.body.style.overflow = '';
1158}
1159});
1160});// Alpine.js component for sticky header and mobile menu
1161function stickyHeader() {
1162return {
1163headerVisible: true,
1164searchOpen: false,
1165mobileSearchOpen: false,
1166mobileMenuOpen: false,
1167mobileMenuContent: '',
1168headerHeight: 0,
1169scrollThreshold: 100,
1170lastScrollY: 0,
1171scrollTimeout: null,
1172// Minimum pixels to scroll before detecting direction change
1173scrollDelta: 5,init() {
1174this.lastScrollY = this.getScrollY();
1175this.updateHeaderHeight();
1176window.addEventListener('scroll', () => this.handleScroll());
1177window.addEventListener('resize', () => this.updateHeaderHeight());
1178// Fired by children (promo banner dismiss) whose size change moves the page top
1179window.addEventListener('zephyr:header-resize', () => this.updateHeaderHeight());
1180this.$watch('mobileMenuOpen', value => {
1181document.body.style.overflow = value ? 'hidden' : '';
1182});
1183},getScrollY() {
1184// Use multiple methods for compatibility (window.scrollY may be overridden by legacy JS)
1185return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
1186},handleScroll() {
1187const currentScrollY = this.getScrollY();
1188const scrollDiff = currentScrollY - this.lastScrollY;// Clear existing timeout
1189if (this.scrollTimeout) {
1190clearTimeout(this.scrollTimeout);
1191}// Determine scroll direction and header visibility
1192if (currentScrollY <= this.scrollThreshold) {
1193// At top of page - always show header
1194this.setHeaderVisible(true);
1195this.lastScrollY = currentScrollY;
1196} else if (scrollDiff < -this.scrollDelta) {
1197// Scrolling up - show header immediately
1198this.setHeaderVisible(true);
1199this.lastScrollY = currentScrollY;
1200} else if (scrollDiff > this.scrollDelta) {
1201// Scrolling down - hide header immediately
1202this.setHeaderVisible(false);
1203this.lastScrollY = currentScrollY;// Show header again after user stops scrolling for 200ms
1204this.scrollTimeout = setTimeout(() => {
1205this.setHeaderVisible(true);
1206}, 200);
1207}
1208// If scrollDiff is between -scrollDelta and +scrollDelta, ignore (too small)
1209},setHeaderVisible(visible) {
1210this.headerVisible = visible;
1211// Sync with global store for other components (cart drawer, etc.)
1212Alpine.store('ui').headerVisible = visible;
1213},updateHeaderHeight() {
1214this.$nextTick(() => {
1215const header = this.$el.querySelector('header');
1216if (header) {
1217this.headerHeight = header.offsetHeight;
1218// Update CSS variable for main content spacing
1219document.documentElement.style.setProperty('--header-height', this.headerHeight + 'px');
1220// Sync with global store for other components
1221Alpine.store('ui').headerHeight = this.headerHeight;
1222}
1223});
1224}
1224,loadMobileMenu() {
1225// Load mobile menu content from the navigation template
1226const mobileMenuElement = document.getElementById('mobile-menu-content');
1227if (mobileMenuElement) {
1228this.mobileMenuContent = mobileMenuElement.innerHTML;
1229}
1230}
1231}
1232}</script>
1232 
1232<script> (function() {
1233var header = document.querySelector('header.fixed');
1234if (header) {
1235document.documentElement.style.setProperty('--header-height', header.offsetHeight + 'px');
1236}
1237})(); </script>
1237 <main class="flex-1" style="padding-top: var(--header-height);"><div class="w-full"><div class="w-full"><div x-data x-show="$store.flash.messages.length > 0" x-cloak class="space-y-3 mb-4 px-4"> <template x-for="msg in $store.flash.messages" :key="msg.id"><div x-show="msg.visible"
1238x-transition:enter="transition ease-out duration-300"
1239x-transition:enter-start="opacity-0 -translate-y-2"
1240x-transition:enter-end="opacity-100 translate-y-0"
1241x-transition:leave="transition ease-in duration-200"
1242x-transition:leave-start="opacity-100"
1243x-transition:leave-end="opacity-0"
1244role="alert"
1245:class="{
1246'bg-green-50 border-green-400 text-green-800': msg.type === 'success',
1247'bg-red-50 border-red-400 text-red-800': msg.type === 'error',
1248'bg-yellow-50 border-yellow-400 text-yellow-800': msg.type === 'warning' || msg.type === 'notice',
1249'bg-blue-50 border-blue-400 text-blue-800': msg.type !== 'success' && msg.type !== 'error' && msg.type !== 'warning' && msg.type !== 'notice'
1250}"
1251class="border-l-4 rounded-r-lg p-4 flex items-start gap-3"><div class="shrink-0 mt-0.5"> <template x-if="msg.type === 'success'"> <svg class="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg> </template> <template x-if="msg.type === 'error'"> <svg class="w-5 h-5 text-red-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg> </template> <template x-if="msg.type === 'warning' || msg.type === 'notice'"> <svg class="w-5 h-5 text-yellow-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg> </template></div><div class="flex-1 text-sm" x-html="msg.text"></div> <button @click="$store.flash.dismiss(msg.id)" class="shrink-0 ml-auto -mr-1 -mt-1 p-1 rounded hover:bg-black/5 transition-colors" aria-label="Dismiss"> <svg class="w-4 h-4 opacity-50" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg> </button></div> </template></div>
1251<script type="application/ld+json"> {
1252"@context": "http://schema.org",
1253"@type": "WebSite",
1254"url": "https://www.simplystamps.com/",
1255"potentialAction": {
1256"@type": "SearchAction",
1257"target": "https://www.simplystamps.com/catalogsearch/result/index/?q={search_term_string}",
1258"query-input": "required name=search_term_string"
1259}
1260} </script>
1260<div class="std"><style> /* --- Hero (section 0) --- */
1261.zephyr-builder-row[data-section-index="0"] section.min-h-\[350px\] {
1262min-height: 360px;
1263}
1264/* Right-aligned so the subject stays in frame; eased in on mobile */
1265.zephyr-builder-row[data-section-index="0"] .absolute.inset-0 {
1266background-position: right center !important;
1267}
1268@media (max-width: 767px) {
1269.zephyr-builder-row[data-section-index="0"] .absolute.inset-0 {
1270background-position: 78% center !important;
1271}
1272}
1273/* White wash, phased in as the crop tightens (none above 1400px) */
1274@media (max-width: 1400px) {
1275.zephyr-builder-row[data-section-index="0"] .absolute.inset-0::after {
1276content: "";
1277position: absolute;
1278inset: 0;
1279background: rgba(255, 255, 255, 0.2);
1280pointer-events: none;
1281}
1282}
1283@media (max-width: 1200px) {
1284.zephyr-builder-row[data-section-index="0"] .absolute.inset-0::after { background: rgba(255, 255, 255, 0.3); }
1285}
1286@media (max-width: 1024px) {
1287.zephyr-builder-row[data-section-index="0"] .absolute.inset-0::after { background: rgba(255, 255, 255, 0.4); }
1288}
1289@media (max-width: 767px) {
1290.zephyr-builder-row[data-section-index="0"] .absolute.inset-0::after { background: rgba(255, 255, 255, 0.5); }
1291}
1292/* --- Category tiles (section 1) --- */
1293.zephyr-builder-row[data-section-index="1"][data-row-index="1"] section.py-8 {
1294padding-top: 0.75rem;
1295padding-bottom: 0.75rem;
1296}
1297@media (min-width: 768px) {
1298.zephyr-builder-row[data-section-index="1"][data-row-index="1"] section.py-8 {
1299padding-top: 1.25rem;
1300padding-bottom: 1.25rem;
1301}
1302}
1303/* --- Media images: Salt & Paper (3) and Expert Tips (8) --- */
1304/* Halved: 2rem -> 1rem mobile, 3rem -> 1.5rem desktop */
1305.zephyr-builder-row[data-section-index="3"] [data-library-block="media_image"] section.py-8,
1306.zephyr-builder-row[data-section-index="8"] [data-library-block="media_image"] section.py-8 {
1307padding-top: 1rem;
1308padding-bottom: 1rem;
1309}
1310@media (min-width: 768px) {
1311.zephyr-builder-row[data-section-index="3"] [data-library-block="media_image"] section.py-8,
1312.zephyr-builder-row[data-section-index="8"] [data-library-block="media_image"] section.py-8 {
1313padding-top: 1.5rem;
1314padding-bottom: 1.5rem;
1315}
1316}
1317/* --- Salt & Paper (section 3) only --- */
1318.zephyr-builder-row[data-section-index="3"] [data-library-block="media_image"] section.py-8 {
1319padding-top: 0;
1320}
1321/* Stacked view: drop the spacer and put the GIF above the text */
1322@media (max-width: 639px) {
1323.zephyr-builder-row[data-section-index="3"] [data-library-block="content_spacer"] { display: none; }
1324.zephyr-builder-row[data-section-index="3"] .zephyr-builder-zone:first-child { order: 2; }
1325.zephyr-builder-row[data-section-index="3"] .zephyr-builder-zone:last-child { order: 1; }
1326}
1327/* --- Face Stamps banner (section 5): widen as the screen narrows --- */
1328/* 60% on wide screens, easing to 95% by ~600px */
1329.zephyr-builder-row[data-section-index="5"] [data-library-block="media_image"] section > div {
1330max-width: min(95%, max(60%, 570px)) !important;
1331} </style><div class="zephyr-builder-layout"><div class="zephyr-builder-section"
1332data-section-index="0"
1333data-section-id="sec_11897d41"
1334data-layout-id="44"
1335data-bg-color=""
1336data-bg-image=""
1337data-overlay-color=""
1338data-overlay-opacity="0"
1339data-full-width="yes"
1340><div class="zephyr-builder-row"
1341data-section-index="0"
1342data-row-index="0"><div class="zephyr-builder-block"
1343data-placement-id="3187"
1344data-layout-id="44"
1345data-library-block="hero_centered"> <section class="relative min-h-[350px] md:min-h-[400px] flex items-center justify-start"><div class="absolute inset-0" style="background-color: ; background-image: url('https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage_banner/SS-Homepage-Banner_Fall-2026-1900x360.jpg'); background-size: cover; background-position: center;"></div><div class="relative z-10 text-left px-5 sm:px-8 py-8 md:py-0 max-w-4xl mr-auto"> <span class="block text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold mb-6 tracking-tight leading-tight" style="color: #012e4a;"> Your Mark Matters </span><span class="block text-xl sm:text-2xl md:text-3xl mb-8 max-w-2xl mr-auto leading-relaxed" style="color: #012e4a; opacity: 0.9;"> Designed by You. Delivered Fast. </span><a href="/address-stamps/view-all"
1346class="inline-flex items-center px-6 py-3 sm:px-8 sm:py-4 bg-white text-brand-primary font-semibold rounded-lg
1347shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200 zb-btn-tinted" style="background-color: #012e4a; color: #FFFFFF;"> Make a Custom Address Stamp </a></div></section></div></div></div>
1347<div class="zephyr-builder-section"
1348data-section-index="1"
1349data-section-id="sec_b22d0c6b"
1350data-layout-id="44"
1351data-bg-color=""
1352data-bg-image=""
1353data-overlay-color=""
1354data-overlay-opacity="0"
1355data-full-width="xl"
1356><div class="mx-auto max-w-screen-xl"><div class="zephyr-builder-row"
1357data-section-index="1"
1358data-row-index="0"><div class="zephyr-builder-block"
1359data-placement-id="3188"
1360data-layout-id="44"
1361data-library-block="content_seo_header"> <section class="pt-4 pb-0 border-0"><div class="w-full px-4 sm:px-0 justify-center text-center"><div class="text-center"><h1 class=" text-xl sm:text-2xl md:text-3xl font-bold  mb-4" style="color: #012e4a;"> Shop All Rubber Stamps and Design Your Own Custom Stamps</h1></div></div> </section></div></div><div class="zephyr-builder-row"
1362data-section-index="1"
1363data-row-index="1"><div class="grid grid-cols-12 gap-6"><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1364data-placement-id="3189"
1365data-layout-id="44"
1366data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 75%"> <a href="/address-stamps/view-all" class="group block"><div class="flex justify-center"> <img
1367src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_address_250_1.jpg"
1368alt="close up of an address stamp impression on a white envelope"
1369class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1370/></div><div class="text-center mt-3 md:mt-4"><h2 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Address Stamps</h2></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1371data-placement-id="3190"
1372data-layout-id="44"
1373data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 75%"> <a href="/rubber-logo-stamps" class="group block"><div class="flex justify-center"> <img
1374src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_logo_v2_250.jpg"
1375alt="Image"
1376class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1377/></div><div class="text-center mt-3 md:mt-4"><h2 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Logo Stamps</h2></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1378data-placement-id="3191"
1379data-layout-id="44"
1380data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 75%"> <a href="/business-office-stamps/signature-stamps" class="group block"><div class="flex justify-center"> <img
1381src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_signature_250_1.jpg"
1382alt="close up of a signature stamp impression in the bottom right corner of a check"
1383class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1384/></div><div class="text-center mt-3 md:mt-4"><h2 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Signature Stamps</h2></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1385data-placement-id="3192"
1386data-layout-id="44"
1387data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 75%"> <a href="/custom-rubber-stamps" class="group block"><div class="flex justify-center"> <img
1388src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_custom_v2_250.jpg"
1389alt="impression area of a design tool used to create custom stamps"
1390class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1391/></div><div class="text-center mt-3 md:mt-4"><h2 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Custom Stamps</h2></div> </a></div> </section></div></div></div></div></div></div>
1391<div class="zephyr-builder-section"
1392data-section-index="2"
1393data-section-id="sec_f6966fe6"
1394data-layout-id="44"
1395data-bg-color=""
1396data-bg-image=""
1397data-overlay-color=""
1398data-overlay-opacity="0"
1399data-full-width="2xl"
1400><div class="mx-auto max-w-screen-2xl"><div class="zephyr-builder-row"
1401data-section-index="2"
1402data-row-index="0"><div class="zephyr-builder-block"
1403data-placement-id="3193"
1404data-layout-id="44"
1405data-library-block="content_seo_header"> <section class="pt-4 pb-4 border-0"><div class="px-4 sm:px-0"><div class="flex items-center gap-4 text-center"><div class="flex-1 h-px bg-brand-secondary"></div><h2 class=" text-xl sm:text-2xl font-bold  mb-0 min-w-0" style="color: #012e4a;"> New at Simply Stamps</h2><div class="flex-1 h-px bg-brand-secondary"></div></div></div><div class="w-full px-4 sm:px-0 justify-center text-center"><div class="text-center"></div></div> </section></div></div></div></div><div class="zephyr-builder-section"
1406data-section-index="3"
1407data-section-id="sec_0dda0584"
1408data-layout-id="44"
1409data-bg-color=""
1410data-bg-image=""
1411data-overlay-color=""
1412data-overlay-opacity="0"
1413data-full-width="xl"
1414><div class="mx-auto max-w-screen-xl"><div class="zephyr-builder-row"
1415data-section-index="3"
1416data-row-index="0"><div class="grid grid-cols-12 gap-6"><div class="zephyr-builder-zone col-span-12 sm:col-span-6"><div class="zephyr-builder-block"
1417data-placement-id="3194"
1418data-layout-id="44"
1419data-library-block="content_spacer"><div class="h-16" aria-hidden="true"></div></div><div class="zephyr-builder-block"
1420data-placement-id="3195"
1421data-layout-id="44"
1422data-library-block="content_seo_header"> <section class="pt-8 pb-4 border-0"><div class="w-full px-4 sm:px-0 justify-center text-center"><div class="text-center"><h3 class=" text-2xl sm:text-3xl md:text-4xl font-bold  mb-4" style="color: #012e4a;"> Simply Stamps × Salt &amp; Paper Collaboration</h3></div></div> </section></div><div class="zephyr-builder-block"
1423data-placement-id="3196"
1424data-layout-id="44"
1425data-library-block="content_seo_header"> <section class="pt-0 pb-4 border-0"><div class="w-full px-4 sm:px-0 justify-center text-center"><div class="text-center"> <span class="block text-lg sm:text-xl font-normal  mt-2" style="color: #012e4a; opacity: 0.8;"> Our new Salt &amp; Paper collection turns the return address into the best part of the envelope. Each design is hand-drawn by illustrator Monika, personalized with your details, and made to give every envelope something extra before it gets opened. </span></div></div> </section></div><div class="zephyr-builder-block"
1426data-placement-id="3197"
1427data-layout-id="44"
1428data-library-block="cta_button"><div class="text-center mt-8 mb-8"> <a href="/salt-and-paper"       class="inline-flex items-center justify-center font-semibold rounded-lg
1429shadow-md hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200
1430px-8 py-4 text-lg"
1431style="background-color: #f2bbc4; color: #111827;"> Shop the Collection </a></div></div></div><div class="zephyr-builder-zone col-span-12 sm:col-span-6"><div class="zephyr-builder-block"
1432data-placement-id="3232"
1433data-layout-id="44"
1434data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/salt-and-paper" class="group block"><div class="flex justify-center"> <img
1435src="https://cdn.simplystamps.com/media/wysiwyg/salt-paper-gif-homepage-square.gif"
1436alt="Image"
1437class="max-w-full h-auto rounded-none transition-all duration-300 group-hover:scale-105"
1438/></div> </a></div> </section></div></div></div></div></div></div><div class="zephyr-builder-section"
1439data-section-index="4"
1440data-section-id="sec_e2067f1e"
1441data-layout-id="44"
1442data-bg-color=""
1443data-bg-image=""
1444data-overlay-color=""
1445data-overlay-opacity="0"
1446data-full-width="2xl"
1447><div class="mx-auto max-w-screen-2xl"><div class="zephyr-builder-row"
1448data-section-index="4"
1449data-row-index="0"><div class="zephyr-builder-block"
1450data-placement-id="3199"
1451data-layout-id="44"
1452data-library-block="hero_split_category_detail"> <style> [x-cloak] { display: none !important; }
1453/* Plain CSS on purpose. builder.css omits @tailwind base, so Tailwind's
1454`border` never paints (no border-style: solid) and `ring-*`/`shadow-*`
1455compose from --tw-* properties that only preflight defines. These few
1456cosmetics must not depend on the theme shipping that. */
1457/* One column on mobile; at lg the copy column (which carries the tile row)
1458takes the larger track so 5 tiles and their labels are not squeezed. */
1459.zb-split-grid {
1460display: grid;
1461grid-template-columns: minmax(0, 1fr);
1462}
1463@media (min-width: 1024px) {
1464.zb-split-grid.zb-copy-right { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); }
1465.zb-split-grid.zb-copy-left  { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); }
1466}
1467/* An h3 for semantics, but its UA margin/size must be pinned by hand: this
1468stylesheet cannot lean on Tailwind's preflight to reset headings. */
1469.zb-tile-label {
1470margin: 0.5rem 0 0;
1471text-decoration: none;
1472}
1473/* The grow is driven by the cell, not by the anchor's `group`: the ? badge and
1474its popup are siblings of the link, so group-hover never reached them and
1475hovering the ? left the image at rest. Anchoring it here also keeps the image
1476grown while the pointer is inside the popup. Plain CSS because Tailwind's
1477scale-110 composes from --tw-* properties that only preflight defines. */
1478.zb-tile-img {
1479transition: transform 0.2s ease-out;
1480}
1481.zb-tile-cell:hover .zb-tile-img {
1482transform: scale(1.18);
1483}
1484.zb-tile-cell:hover .zb-tile-label {
1485text-decoration: underline;
1486}
1487.zb-tip-card {
1488background: #fff;
1489border: 1px solid #e5e7eb;
1490border-radius: 0.5rem;
1491box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
1492}
1493/* Both states are owned here rather than by Tailwind's focus:outline-none,
1494which is `outline: 2px solid transparent` at a higher specificity and would
1495win over the :focus-visible rule below, leaving no visible focus at all. */
1496/* 26px, up from 20px - the old badge was hard to pick out against the card.
1497Size and colour are both here because a .zb-tip-badge rule cannot outrank
1498Tailwind's background utilities, which carry the scope ancestor and so win
1499on specificity. (Careful: never write a Tailwind glob like bg-<star><slash> in a CSS comment - it closes the comment early and eats the next rule.) */
1500.zb-tip-badge {
1501display: flex;
1502align-items: center;
1503justify-content: center;
1504width: 1.625rem;
1505height: 1.625rem;
1506border-radius: 9999px;
1507background: #6b7280;
1508color: #fff;
1509font-size: 0.875rem;
1510font-weight: 700;
1511line-height: 1;
1512cursor: pointer;
1513border: 1px solid rgba(255, 255, 255, 0.85);
1514box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
1515transition: background-color 0.15s ease-out;
1516}
1517.zb-tip-badge:hover {
1518background: #374151;
1519}
1520.zb-tile:focus,
1521.zb-tip-badge:focus {
1522outline: none;
1523}
1524.zb-tile:focus-visible,
1525.zb-tip-badge:focus-visible {
1526outline: 2px solid #9ca3af;
1527outline-offset: 2px;
1528} </style><section class="relative min-h-[350px] md:min-h-[400px] flex items-center py-8 sm:py-10 md:py-14"><div class="relative w-full max-w-[90rem] mx-auto px-5 sm:px-6 lg:px-8"><div class="zb-split-grid zb-copy-right gap-8 lg:gap-16 items-center"><div class="order-2 lg:order-2"><h2 class=" text-xl sm:text-2xl md:text-3xl font-bold mb-5 leading-tight " style="color: #012e4a;"> Design Your Custom Rubber Stamps With Our Design Tool</h2><p class=" text-base sm:text-lg mb-8 leading-relaxed " style="color: #012e4a; opacity: 0.85;"> <span class="block">Create personalized stamps in just a few clicks with our simple design tool. Our stamps are available in a variety of sizes, types, and ink colors. Simply start by choosing the stamp that best suits your needs and personalize your impression in the design tool.</span></p><div class="bg-white/80 rounded-xl shadow-sm px-5 py-6 sm:px-8 sm:py-7"
1529x-data="{
1530open: null,
1531timer: null,
1532hoverable: window.matchMedia('(hover: hover)').matches,
1533clearTimer() {
1534if (this.timer) { clearTimeout(this.timer); this.timer = null; }
1535},
1536show(i, el) { this.clearTimer(); this.open = i; this.place(el); },
1537hide() { this.clearTimer(); this.open = null; },
1538enter(i, el) {
1539if (!this.hoverable) { return; }
1540this.clearTimer();
1541/* Already showing one? Track the pointer immediately - the
1542user has waited out the delay once already. */
1543if (this.open !== null) { this.show(i, el); return; }
1544var self = this;
1545this.timer = setTimeout(function () { self.show(i, el); }, 250);
1546},
1547leave() {
1548if (!this.hoverable) { return; }
1549this.clearTimer();
1550/* Short grace period so crossing the gap to the card, or
1551slipping off an edge for a frame, does not dismiss it. */
1552var self = this;
1553this.timer = setTimeout(function () { self.open = null; }, 140);
1554},
1555toggle(i, el) {
1556/* Where hover governs, a click must not close the popup the
1557pointer is still sitting on. Escape and mouseleave close it. */
1558if (this.hoverable) { this.show(i, el); return; }
1559if (this.open === i) { this.hide(); } else { this.show(i, el); }
1560},
1561place(el) {
1562if (!el) { return; }
1563el.style.transform = 'translateX(-50%)';
1564this.$nextTick(function () {
1565var r = el.getBoundingClientRect();
1566var pad = 8;
1567var shift = 0;
1568if (pad > r.left) {
1569shift = pad - r.left;
1570} else if (r.right > window.innerWidth - pad) {
1571shift = window.innerWidth - pad - r.right;
1572}
1573el.style.transform = 'translateX(calc(-50% + ' + Math.round(shift) + 'px))';
1574});
1575}
1576}"
1577@keydown.escape.window="hide()"
1578@click.outside="hide()"><p class="text-sm sm:text-base font-medium mb-4 " style="color: #012e4a;"> Now, choose the stamp that's best for you:</p>
1578<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-x-4 gap-y-7"><div class="zb-tile-cell relative"> <a class="zb-tile group no-underline flex flex-col items-center text-center rounded-md cursor-pointer" href="/custom-rubber-stamps/custom-self-inking-stamps"> <span class="flex items-end justify-center w-full h-20 sm:h-24"> <img src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/Simply-Stamps-Self-Inking-Stamp-Transparent-105x105.png"
1579alt="Self-Inking"
1580loading="lazy"
1581class="zb-tile-img max-h-20 sm:max-h-24 w-auto object-contain"> </span><h3 class="zb-tile-label text-base font-medium text-brand-primary"> Self-Inking</h3> </a><div class="absolute top-0 right-0"
1582@mouseenter="enter(0, $refs.tip0)"
1583@mouseleave="leave()"> <button type="button"
1584class="zb-tip-badge"
1585:aria-expanded="open === 0 ? 'true' : 'false'"
1586aria-label="More about Self-Inking"
1587@click="toggle(0, $refs.tip0)">?</button><div x-ref="tip0"
1588x-cloak
1589x-show="open === 0"
1590x-transition:enter="transition ease-out duration-150"
1591x-transition:enter-start="opacity-0"
1592x-transition:enter-end="opacity-100"
1593x-transition:leave="transition ease-in duration-100"
1594x-transition:leave-start="opacity-100"
1595x-transition:leave-end="opacity-0"
1596class="absolute bottom-full left-1/2 pb-2 z-50"
1597style="transform: translateX(-50%); width: 17rem; max-width: calc(100vw - 1.5rem);"><div class="zb-tip-card px-4 py-3 text-center"
1598role="tooltip"><p class="text-sm font-bold text-gray-900 mb-1 text-left"> Self-Inking Stamps</p><div class="prose prose-sm max-w-none text-sm text-gray-600
1599prose-p:my-1.5 prose-p:text-gray-600 prose-p:leading-snug
1600prose-strong:text-gray-900 prose-strong:font-semibold
1601prose-a:text-brand-primary prose-a:underline
1602prose-ul:my-1.5 prose-ul:pl-4 prose-ol:my-1.5 prose-ol:pl-4
1603prose-li:my-0.5 prose-li:text-gray-600"><p style="text-align: left"><span style="font-size: 14px">Self-inking rubber stamps are a convenient option for those who need to stamp frequently. These stamps come with a replaceable ink pad that is built into the stamp body.</span></p><p style="text-align: left"><strong>Benefits:</strong> <span style="font-size: 14px">Quick and easy to use, separate ink pads are not needed, and it's unnecessary to pause between making stamp impressions.</span></p></div></div></div></div></div><div class="zb-tile-cell relative"> <a class="zb-tile group no-underline flex flex-col items-center text-center rounded-md cursor-pointer" href="/custom-rubber-stamps/custom-pre-inked-stamps"> <span class="flex items-end justify-center w-full h-20 sm:h-24"> <img src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_Custom-Stamp-Type_Pre-Inked_105.png"
1604alt="Pre-Inked"
1605loading="lazy"
1606class="zb-tile-img max-h-20 sm:max-h-24 w-auto object-contain"> </span><h3 class="zb-tile-label text-base font-medium text-brand-primary"> Pre-Inked</h3> </a><div class="absolute top-0 right-0"
1607@mouseenter="enter(1, $refs.tip1)"
1608@mouseleave="leave()"> <button type="button"
1609class="zb-tip-badge"
1610:aria-expanded="open === 1 ? 'true' : 'false'"
1611aria-label="More about Pre-Inked"
1612@click="toggle(1, $refs.tip1)">?</button><div x-ref="tip1"
1613x-cloak
1614x-show="open === 1"
1615x-transition:enter="transition ease-out duration-150"
1616x-transition:enter-start="opacity-0"
1617x-transition:enter-end="opacity-100"
1618x-transition:leave="transition ease-in duration-100"
1619x-transition:leave-start="opacity-100"
1620x-transition:leave-end="opacity-0"
1621class="absolute bottom-full left-1/2 pb-2 z-50"
1622style="transform: translateX(-50%); width: 17rem; max-width: calc(100vw - 1.5rem);"><div class="zb-tip-card px-4 py-3 text-center"
1623role="tooltip"><p class="text-sm font-bold text-gray-900 mb-1 text-left"> Pre-Inked Stamps</p><div class="prose prose-sm max-w-none text-sm text-gray-600
1624prose-p:my-1.5 prose-p:text-gray-600 prose-p:leading-snug
1625prose-strong:text-gray-900 prose-strong:font-semibold
1626prose-a:text-brand-primary prose-a:underline
1627prose-ul:my-1.5 prose-ul:pl-4 prose-ol:my-1.5 prose-ol:pl-4
1628prose-li:my-0.5 prose-li:text-gray-600"><p style="text-align: left"><span style="font-size: 14px">Pre-inked stamps produce the highest quality impressions. They are designed with a built-in ink reservoir which can be refilled. The ink pad needs a bit of time to recover between several repeated impressions.</span></p><p style="text-align: left"><strong>Benefits:</strong> <span style="font-size: 14px">Long-lasting, clear impressions, and no need for separate ink pads. Can stamp detailed art.</span></p></div></div></div></div></div><div class="zb-tile-cell relative"> <a class="zb-tile group no-underline flex flex-col items-center text-center rounded-md cursor-pointer" href="/custom-rubber-stamps/custom-wood-handle-stamps"> <span class="flex items-end justify-center w-full h-20 sm:h-24"> <img src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_Custom-Stamp-Type_Wood-Handle_105.png"
1629alt="Wood Handle"
1630loading="lazy"
1631class="zb-tile-img max-h-20 sm:max-h-24 w-auto object-contain"> </span>
1631<h3 class="zb-tile-label text-base font-medium text-brand-primary"> Wood Handle</h3> </a><div class="absolute top-0 right-0"
1632@mouseenter="enter(2, $refs.tip2)"
1633@mouseleave="leave()"> <button type="button"
1634class="zb-tip-badge"
1635:aria-expanded="open === 2 ? 'true' : 'false'"
1636aria-label="More about Wood Handle"
1637@click="toggle(2, $refs.tip2)">?</button><div x-ref="tip2"
1638x-cloak
1639x-show="open === 2"
1640x-transition:enter="transition ease-out duration-150"
1641x-transition:enter-start="opacity-0"
1642x-transition:enter-end="opacity-100"
1643x-transition:leave="transition ease-in duration-100"
1644x-transition:leave-start="opacity-100"
1645x-transition:leave-end="opacity-0"
1646class="absolute bottom-full left-1/2 pb-2 z-50"
1647style="transform: translateX(-50%); width: 17rem; max-width: calc(100vw - 1.5rem);"><div class="zb-tip-card px-4 py-3 text-center"
1648role="tooltip"><p class="text-sm font-bold text-gray-900 mb-1 text-left"> Wood Handle Stamps</p><div class="prose prose-sm max-w-none text-sm text-gray-600
1649prose-p:my-1.5 prose-p:text-gray-600 prose-p:leading-snug
1650prose-strong:text-gray-900 prose-strong:font-semibold
1651prose-a:text-brand-primary prose-a:underline
1652prose-ul:my-1.5 prose-ul:pl-4 prose-ol:my-1.5 prose-ol:pl-4
1653prose-li:my-0.5 prose-li:text-gray-600"><p style="text-align: left"><span style="font-size: 14px">Wood handle rubber stamps are a classic choice for those who prefer a traditional option and feel. Wood Hand Stamps are durable and can be used with any ink pad, including super marking ink.</span></p><p style="text-align: left"><strong>Benefits:</strong> <span style="font-size: 14px">Durable, classic design, and you can switch between which ink pad is used.</span></p></div></div></div></div></div><div class="zb-tile-cell relative"> <a class="zb-tile group no-underline flex flex-col items-center text-center rounded-md cursor-pointer" href="/custom-rubber-stamps/custom-european-hand-stamps"> <span class="flex items-end justify-center w-full h-20 sm:h-24"> <img src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_Custom-Stamp-Type_European_105.png"
1654alt="European"
1655loading="lazy"
1656class="zb-tile-img max-h-20 sm:max-h-24 w-auto object-contain"> </span><h3 class="zb-tile-label text-base font-medium text-brand-primary"> European</h3> </a><div class="absolute top-0 right-0"
1657@mouseenter="enter(3, $refs.tip3)"
1658@mouseleave="leave()"> <button type="button"
1659class="zb-tip-badge"
1660:aria-expanded="open === 3 ? 'true' : 'false'"
1661aria-label="More about European"
1662@click="toggle(3, $refs.tip3)">?</button><div x-ref="tip3"
1663x-cloak
1664x-show="open === 3"
1665x-transition:enter="transition ease-out duration-150"
1666x-transition:enter-start="opacity-0"
1667x-transition:enter-end="opacity-100"
1668x-transition:leave="transition ease-in duration-100"
1669x-transition:leave-start="opacity-100"
1670x-transition:leave-end="opacity-0"
1671class="absolute bottom-full left-1/2 pb-2 z-50"
1672style="transform: translateX(-50%); width: 17rem; max-width: calc(100vw - 1.5rem);"><div class="zb-tip-card px-4 py-3 text-center"
1673role="tooltip"><p class="text-sm font-bold text-gray-900 mb-1 text-left"> European Rubber Stamps</p><div class="prose prose-sm max-w-none text-sm text-gray-600
1674prose-p:my-1.5 prose-p:text-gray-600 prose-p:leading-snug
1675prose-strong:text-gray-900 prose-strong:font-semibold
1676prose-a:text-brand-primary prose-a:underline
1677prose-ul:my-1.5 prose-ul:pl-4 prose-ol:my-1.5 prose-ol:pl-4
1678prose-li:my-0.5 prose-li:text-gray-600"><p style="text-align: left"><span style="font-size: 14px">European rubber stamps are traditional but also stylish. They are perfect for smaller impressions and come in various designs. Like traditional hand stamps, any ink pad can be used.</span></p><p style="text-align: left"><strong>Benefits: </strong><span style="font-size: 14px">Compact design, stylish options, and ideal for repeated, smaller impressions.</span></p></div></div></div></div></div><div class="zb-tile-cell relative"> <a class="zb-tile group no-underline flex flex-col items-center text-center rounded-md cursor-pointer" href="/custom-rubber-stamps/custom-professional-stamps"> <span class="flex items-end justify-center w-full h-20 sm:h-24"> <img src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_Custom-Stamp-Type_Professional_105.png"
1679alt="Professional"
1680loading="lazy"
1681class="zb-tile-img max-h-20 sm:max-h-24 w-auto object-contain"> </span>
1681<h3 class="zb-tile-label text-base font-medium text-brand-primary"> Professional</h3> </a><div class="absolute top-0 right-0"
1682@mouseenter="enter(4, $refs.tip4)"
1683@mouseleave="leave()"> <button type="button"
1684class="zb-tip-badge"
1685:aria-expanded="open === 4 ? 'true' : 'false'"
1686aria-label="More about Professional"
1687@click="toggle(4, $refs.tip4)">?</button><div x-ref="tip4"
1688x-cloak
1689x-show="open === 4"
1690x-transition:enter="transition ease-out duration-150"
1691x-transition:enter-start="opacity-0"
1692x-transition:enter-end="opacity-100"
1693x-transition:leave="transition ease-in duration-100"
1694x-transition:leave-start="opacity-100"
1695x-transition:leave-end="opacity-0"
1696class="absolute bottom-full left-1/2 pb-2 z-50"
1697style="transform: translateX(-50%); width: 17rem; max-width: calc(100vw - 1.5rem);"><div class="zb-tip-card px-4 py-3 text-center"
1698role="tooltip"><p class="text-sm font-bold text-gray-900 mb-1 text-left"> Professional Stamps</p><div class="prose prose-sm max-w-none text-sm text-gray-600
1699prose-p:my-1.5 prose-p:text-gray-600 prose-p:leading-snug
1700prose-strong:text-gray-900 prose-strong:font-semibold
1701prose-a:text-brand-primary prose-a:underline
1702prose-ul:my-1.5 prose-ul:pl-4 prose-ol:my-1.5 prose-ol:pl-4
1703prose-li:my-0.5 prose-li:text-gray-600"><p style="text-align: left"><span style="font-size: 14px">Professional self-inking stamps are designed for businesses and organizations that need repeat, quality impressions. They are durable and can handle heavy use, making them ideal for office environments.</span></p><p style="text-align: left"><strong>Benefits: </strong><span style="font-size: 14px">Durable, high-quality impressions, and suitable for heavy use.</span></p></div></div></div></div></div></div></div></div><div class="order-1 lg:order-1"> <img src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS-Design-Wizard-HP-final.gif"
1704alt="Design Your Custom Rubber Stamps With Our Design Tool"
1705class="w-full h-auto rounded-2xl shadow-xl"></div></div></div> </section></div></div></div></div><div class="zephyr-builder-section"
1706data-section-index="5"
1707data-section-id="sec_edb6a2dd"
1708data-layout-id="44"
1709data-bg-color="#f0f7fd"
1710data-bg-image=""
1711data-overlay-color=""
1712data-overlay-opacity="0"
1713data-full-width="yes"
1714style="background-color: #f0f7fd;"><div class="zephyr-builder-row"
1715data-section-index="5"
1716data-row-index="0"><div class="zephyr-builder-block"
1717data-placement-id="3517"
1718data-layout-id="44"
1719data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 60%"> <a href="/face-stamps" class="group block" title="Shop Custom Face Stamps"><div class="flex justify-center"> <img
1720src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/simply-stamps-face-stamp-category-banner-600.gif"
1721alt="Turn any face into a custom stamp at Simply Stamps"
1722class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1723/></div> </a></div> </section></div></div></div><div class="zephyr-builder-section"
1724data-section-index="6"
1725data-section-id="sec_32a8e372"
1726data-layout-id="44"
1727data-bg-color=""
1728data-bg-image=""
1729data-overlay-color=""
1730data-overlay-opacity="0"
1731data-full-width="xl"
1732><div class="mx-auto max-w-screen-xl"><div class="zephyr-builder-row"
1733data-section-index="6"
1734data-row-index="0"><div class="zephyr-builder-block"
1735data-placement-id="3200"
1736data-layout-id="44"
1737data-library-block="content_seo_header"> <section class="pt-12 pb-4 border-0"><div class="px-4 sm:px-0"><div class="flex items-center gap-4 text-center"><div class="flex-1 h-px bg-brand-secondary"></div><h2 class=" text-xl sm:text-2xl md:text-3xl font-bold  mb-0 min-w-0" style="color: #012e4a;"> Customers' Favorite Stamps by Use</h2><div class="flex-1 h-px bg-brand-secondary"></div></div></div><div class="w-full px-4 sm:px-0 justify-center text-center"><div class="text-center"></div></div> </section></div></div><div class="zephyr-builder-row"
1738data-section-index="6"
1739data-row-index="1"><div class="grid grid-cols-12 gap-6"><div class="zephyr-builder-zone col-span-6 md:col-span-3">
1739<div class="zephyr-builder-block"
1740data-placement-id="3201"
1741data-layout-id="44"
1742data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="https://www.simplystamps.com/embossers" class="group block"><div class="flex justify-center"> <img
1743src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_custom-embosser_250.jpg"
1744alt="one black embosser and one gold embosser against white background"
1745class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1746/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Custom Embossers</h3></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1747data-placement-id="3202"
1748data-layout-id="44"
1749data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/notary-public" class="group block"><div class="flex justify-center"> <img
1750src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_notary_250_1.jpg"
1751alt="close up of notary stamp impression on the top right corner of a document"
1752class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1753/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Notary Stamps &amp; Seals</h3></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1754data-placement-id="3203"
1755data-layout-id="44"
1756data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/teacher-stamps" class="group block"><div class="flex justify-center"> <img
1757src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_teacher_250.jpg"
1758alt="close up of a stamp impression reading Mrs. Walsh Thinks You're SUPER on lined paper"
1759class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1760/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Teacher Stamps</h3></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1761data-placement-id="3204"
1762data-layout-id="44"
1763data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/business-office-stamps" class="group block"><div class="flex justify-center"> <img
1764src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_business_250.jpg"
1765alt="close up of a business stamp impression on a manila file folder"
1766class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1767/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Business Stamps</h3></div> </a></div> </section></div></div></div></div><div class="zephyr-builder-row"
1768data-section-index="6"
1769data-row-index="2"><div class="grid grid-cols-12 gap-6"><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1770data-placement-id="3205"
1771data-layout-id="44"
1772data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/business-office-stamps/custom-line-stamps" class="group block"><div class="flex justify-center"> <img
1773src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_line-stamp_v1_250.jpg"
1774alt="close up of a three-line stamp impression on a white paper on a wooden clipboard"
1775class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1776/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Line Stamps</h3></div> </a></div> </section></div></div>
1776<div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1777data-placement-id="3206"
1778data-layout-id="44"
1779data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/craft-stamps" class="group block"><div class="flex justify-center"> <img
1780src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_craft_250_1.jpg"
1781alt="Craft stamp impression of a leaf on a brown box"
1782class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1783/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Craft Stamps</h3></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1784data-placement-id="3207"
1785data-layout-id="44"
1786data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/teacher-stamps/clothing-stamps" class="group block"><div class="flex justify-center"> <img
1787src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_name_250.jpg"
1788alt="A clothing stamp impression of a name shown on a gray shirt"
1789class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1790/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Name Stamps</h3></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1791data-placement-id="3208"
1792data-layout-id="44"
1793data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/wedding-stamps" class="group block"><div class="flex justify-center"> <img
1794src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_save-the-date_v1_250.jpg"
1795alt="a save the date wedding stamp impression on a white paper inside a gray envelope"
1796class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1797/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Wedding Stamps</h3></div> </a></div> </section></div></div></div></div><div class="zephyr-builder-row"
1798data-section-index="6"
1799data-row-index="3"><div class="grid grid-cols-12 gap-6"><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1800data-placement-id="3209"
1801data-layout-id="44"
1802data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/business-office-stamps/banking-stamps" class="group block"><div class="flex justify-center"> <img
1803src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_bank_250.jpg"
1804alt="Image of a deposit stamp impression on a lined paper"
1805class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1806/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Bank Deposit Stamps</h3></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1807data-placement-id="3210"
1808data-layout-id="44"
1809data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/business-office-stamps/medical-stamps" class="group block"><div class="flex justify-center"> <img
1810src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_medical_250_1.jpg"
1811alt="Image of a close up of a doctor's signature stamp impression on a white paper on a black clipboard"
1812class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1813/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Medical Stamps</h3></div> </a></div> </section></div></div>
1813<div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1814data-placement-id="3211"
1815data-layout-id="44"
1816data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/business-office-stamps/daters" class="group block"><div class="flex justify-center"> <img
1817src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_dater_250_1.jpg"
1818alt="Image of a date stamp impression on a document"
1819class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1820/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Date Stamps</h3></div> </a></div> </section></div></div><div class="zephyr-builder-zone col-span-6 md:col-span-3"><div class="zephyr-builder-block"
1821data-placement-id="3212"
1822data-layout-id="44"
1823data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/address-stamps/christmas-address-stamps" class="group block"><div class="flex justify-center"> <img
1824src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/Homepage/SS_HP-Category_christmas_250_1.jpg"
1825alt="close up of Christmas address stamp impression on a white paper"
1826class="max-w-full h-auto rounded-lg shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1827/></div><div class="text-center mt-3 md:mt-4"><h3 class=" mt-2 text-lg sm:text-xl text-gray-600 transition-all duration-300 group-hover:scale-105"> Christmas Address Stamps</h3></div> </a></div> </section></div></div></div></div></div></div><div class="zephyr-builder-section"
1828data-section-index="7"
1829data-section-id="sec_cb234680"
1830data-layout-id="44"
1831data-bg-color="#f0f7fd"
1832data-bg-image=""
1833data-overlay-color=""
1834data-overlay-opacity="0"
1835data-full-width="2xl"
1836style="background-color: #f0f7fd;"><div class="mx-auto max-w-screen-2xl"><div class="zephyr-builder-row"
1837data-section-index="7"
1838data-row-index="0"><div class="zephyr-builder-block"
1839data-placement-id="3213"
1840data-layout-id="44"
1841data-library-block="product_carousel"> <section class="py-12 md:py-16" aria-labelledby="product-carousel-6abf484daba2b-heading"><div class="w-full px-4 sm:px-6 lg:px-8 mb-8 md:mb-12"><div class="text-center"><h2 id="product-carousel-6abf484daba2b-heading" class=" text-xl sm:text-2xl md:text-3xl font-bold " style="color: #012e4a;"> Best Selling Rubber Stamps and Embossers</h2></div></div> <style> #product-carousel-6abf484daba2b:not(.swiper-initialized) .swiper-wrapper {
1842display: flex;
1843flex-wrap: nowrap;
1844overflow: hidden;
1845}
1846#product-carousel-6abf484daba2b:not(.swiper-initialized) .swiper-slide {
1847flex-shrink: 0;
1848width: calc((100% - 16px) / 2);
1849margin-right: 16px;
1850}
1851@media (min-width: 640px) {
1852#product-carousel-6abf484daba2b:not(.swiper-initialized) .swiper-slide {
1853width: calc((100% - 60px) / 4);
1854margin-right: 20px;
1855}
1856}
1857@media (min-width: 1024px) {
1858#product-carousel-6abf484daba2b:not(.swiper-initialized) .swiper-slide {
1859width: calc((100% - 120px) / 6);
1860margin-right: 24px;
1861}
1862} </style><div class="relative"> <button class="swiper-prev-product-carousel-6abf484daba2b zb-swiper-nav absolute z-10 left-2 sm:left-3 lg:left-4 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-white border border-gray-300 flex items-center justify-center shadow-md hover:bg-gray-100 transition-colors" aria-label="Previous products"> <svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path> </svg> </button> <button class="swiper-next-product-carousel-6abf484daba2b zb-swiper-nav absolute z-10 right-2 sm:right-3 lg:right-4 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-white border border-gray-300 flex items-center justify-center shadow-md hover:bg-gray-100 transition-colors" aria-label="Next products"> <svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> </svg> </button>
1862<div class="swiper px-12 sm:px-14 lg:px-16" id="product-carousel-6abf484daba2b"><div class="swiper-wrapper" role="list"><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/walker-letter-return-address-stamp" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/1/0/10.-_1012627-address-stamp-impression_800.jpg?width=480&quality=60"
1863srcset="https://cdn.simplystamps.com/media/catalog/product/1/0/10.-_1012627-address-stamp-impression_800.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/1/0/10.-_1012627-address-stamp-impression_800.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/1/0/10.-_1012627-address-stamp-impression_800.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Walker Letter Self-Inking Return Address Stamp"
1864class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1865decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/walker-letter-return-address-stamp"
1866class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Walker Letter Self-Inking Return Address Stamp</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1866 </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 90%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div></div> <a href="https://www.simplystamps.com/walker-letter-return-address-stamp#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(206)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> About this item:
1867Create a lasting impression with a custom address stamp f...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-7581-zb-carousel-7581"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $23.95 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/signature-script-family-name-address-stamp" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/s/s/ss_as_modernline-glyph_4914_lead_plp_design-on-envelope_2000.jpg?width=480&quality=60"
1868srcset="https://cdn.simplystamps.com/media/catalog/product/s/s/ss_as_modernline-glyph_4914_lead_plp_design-on-envelope_2000.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss_as_modernline-glyph_4914_lead_plp_design-on-envelope_2000.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss_as_modernline-glyph_4914_lead_plp_design-on-envelope_2000.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Signature Script Family Name Address Stamp"
1869class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1870decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/signature-script-family-name-address-stamp"
1871class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Signature Script Family Name Address Stamp</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1871 </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 95%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1871 </svg></div></div></div> <a href="https://www.simplystamps.com/signature-script-family-name-address-stamp#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(7)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> Product Features
1872Make your mark in seconds. This signature script return add...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-24434-zb-carousel-24434"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $23.95 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/reindeer-with-stars-2-5-holiday-address-stamp" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/s/s/ss_as_holiday_reindeerstars_4914_lead_plp_design-on-envelope_2000.jpg?width=480&quality=60"
1873srcset="https://cdn.simplystamps.com/media/catalog/product/s/s/ss_as_holiday_reindeerstars_4914_lead_plp_design-on-envelope_2000.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss_as_holiday_reindeerstars_4914_lead_plp_design-on-envelope_2000.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss_as_holiday_reindeerstars_4914_lead_plp_design-on-envelope_2000.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Reindeer with Stars 2.5&quot; Holiday Return Address Stamp"
1874class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1875decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/reindeer-with-stars-2-5-holiday-address-stamp"
1876class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Reindeer with Stars 2.5&quot; Holiday Return Address Stamp</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1876 </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div></div> <a href="https://www.simplystamps.com/reindeer-with-stars-2-5-holiday-address-stamp#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(1)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> Add some festivity to your outgoing mail with our Reindeer with Stars Holiday...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-21391-zb-carousel-21391"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $23.95 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/traditional-hand-stamp-rs30-3" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/s/s/ss-3x3-wood-handle-top-angle-with-design-wbg_800.jpg?width=480&quality=60"
1877srcset="https://cdn.simplystamps.com/media/catalog/product/s/s/ss-3x3-wood-handle-top-angle-with-design-wbg_800.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss-3x3-wood-handle-top-angle-with-design-wbg_800.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss-3x3-wood-handle-top-angle-with-design-wbg_800.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Traditional 3&quot; x 3&quot; Hand Stamp"
1878class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1879decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/traditional-hand-stamp-rs30-3"
1880class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Traditional 3&quot; x 3&quot; Hand Stamp</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1880 </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 90%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1880 </svg></div></div></div> <a href="https://www.simplystamps.com/traditional-hand-stamp-rs30-3#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(5)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> Laser engraved rubber stamp attached to solid wood base
1881Choose from severa...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-4125-zb-carousel-4125"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $29.99 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/s/s/ss_secure-id-protector_main-on-white_800.jpg?width=480&quality=60"
1882srcset="https://cdn.simplystamps.com/media/catalog/product/s/s/ss_secure-id-protector_main-on-white_800.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss_secure-id-protector_main-on-white_800.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss_secure-id-protector_main-on-white_800.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Guard Your ID with the Trodat ID Protector Roller"
1883class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1884decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller"
1885class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Guard Your ID with the Trodat ID Protector Roller</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1885 </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div></div> <a href="https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(1)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> Quick & Easy Identity Protection: Conceal personal details on mail, lab...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-24221-zb-carousel-24221"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $9.95 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/duncan-script-address-stamp" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/m/a/main_11751-duncan-script-address-stamp-800.jpg?width=480&quality=60"
1886srcset="https://cdn.simplystamps.com/media/catalog/product/m/a/main_11751-duncan-script-address-stamp-800.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/m/a/main_11751-duncan-script-address-stamp-800.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/m/a/main_11751-duncan-script-address-stamp-800.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Duncan Custom Self-Inking Address Stamp"
1887class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1888decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/duncan-script-address-stamp"
1889class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Duncan Custom Self-Inking Address Stamp</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1889 </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 90%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1889 </svg></div></div></div> <a href="https://www.simplystamps.com/duncan-script-address-stamp#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(142)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> About this item:
1890Create a lasting impression with our most popular custom ...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-8557-zb-carousel-8557"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $23.95 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/stamp-n-stick-kids-clothing-stamp-kit-custom-name-stamp-labels" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/1/a/1a_ss-stamp-n-stick-box-and-body-wbg_800.jpg?width=480&quality=60"
1891srcset="https://cdn.simplystamps.com/media/catalog/product/1/a/1a_ss-stamp-n-stick-box-and-body-wbg_800.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/1/a/1a_ss-stamp-n-stick-box-and-body-wbg_800.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/1/a/1a_ss-stamp-n-stick-box-and-body-wbg_800.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Stamp 'n Stick Clothing Stamp Kit – Labeler"
1892class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1893decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/stamp-n-stick-kids-clothing-stamp-kit-custom-name-stamp-labels"
1894class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Stamp 'n Stick Clothing Stamp Kit – Labeler</h3> </a><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> About the Stamp 'n Stick Kids Clothing Stamp Kit:
1895Kit Includes: 1 colorful...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-24271-zb-carousel-24271"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $19.99 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/1-line-self-inking-clothing-marker-stamp" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/4/9/4911-clothing-stamp-sanders-1-800.jpg?width=480&quality=60"
1896srcset="https://cdn.simplystamps.com/media/catalog/product/4/9/4911-clothing-stamp-sanders-1-800.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/4/9/4911-clothing-stamp-sanders-1-800.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/4/9/4911-clothing-stamp-sanders-1-800.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="1 line Self-Inking Clothing Marker Stamp"
1897class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1898decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/1-line-self-inking-clothing-marker-stamp"
1899class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">1 line Self-Inking Clothing Marker Stamp</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1899 </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1899 </svg></div></div></div> <a href="https://www.simplystamps.com/1-line-self-inking-clothing-marker-stamp#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(5)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> Keep track of your favorite clothing pieces with the help of our specially fo...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-4403-zb-carousel-4403"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $14.99 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/dupont-brush-return-address-stamp" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/m/a/main_10957-dupont-brush-return-address-stamp-hcb.png?width=480&quality=60"
1900srcset="https://cdn.simplystamps.com/media/catalog/product/m/a/main_10957-dupont-brush-return-address-stamp-hcb.png?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/m/a/main_10957-dupont-brush-return-address-stamp-hcb.png?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/m/a/main_10957-dupont-brush-return-address-stamp-hcb.png?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Dupont Brush Return Address Stamp"
1901class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1902decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/dupont-brush-return-address-stamp"
1903class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Dupont Brush Return Address Stamp</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1903 </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 90%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div></div> <a href="https://www.simplystamps.com/dupont-brush-return-address-stamp#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(99)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> About this item:
1904Create a lasting impression with our custom address stamp...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-7382-zb-carousel-7382"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $23.95 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/book-with-flowers-custom-book-embosser-choose-your-design" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/s/s/ss-emb-flo-embosser-design-on-paper-lifestyle-book-flowers-5_800.jpg?width=480&quality=60"
1905srcset="https://cdn.simplystamps.com/media/catalog/product/s/s/ss-emb-flo-embosser-design-on-paper-lifestyle-book-flowers-5_800.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss-emb-flo-embosser-design-on-paper-lifestyle-book-flowers-5_800.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/s/s/ss-emb-flo-embosser-design-on-paper-lifestyle-book-flowers-5_800.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Custom Book Embosser - Choose From 5 Floral Designs!"
1906class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1907decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/book-with-flowers-custom-book-embosser-choose-your-design"
1908class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Custom Book Embosser - Choose From 5 Floral Designs!</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1908 </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 95%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1908 </svg></div></div></div> <a href="https://www.simplystamps.com/book-with-flowers-custom-book-embosser-choose-your-design#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(10)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> Technical Details:
1909Choose from 5 unique custom book embosser designs
1910Se...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-23248-zb-carousel-23248"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $30.99 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/logo-embosser-seal" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/1/-/1-1010001-m1p-wbg-with-design-2000.jpg?width=480&quality=60"
1911srcset="https://cdn.simplystamps.com/media/catalog/product/1/-/1-1010001-m1p-wbg-with-design-2000.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/1/-/1-1010001-m1p-wbg-with-design-2000.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/1/-/1-1010001-m1p-wbg-with-design-2000.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Seal Logo Embosser"
1912class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1913decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/logo-embosser-seal"
1914class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Seal Logo Embosser</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1914 </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 95%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div></div> <a href="https://www.simplystamps.com/logo-embosser-seal#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(127)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> Product Features
1915Elevate your branding with the Custom Logo Embosser Seal. I...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-5401-zb-carousel-5401"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $30.99 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/3-line-address-stamp" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/1/0/1019233_address-stamp_lifestyle_imprint-on-paper_800.jpg?width=480&quality=60"
1916srcset="https://cdn.simplystamps.com/media/catalog/product/1/0/1019233_address-stamp_lifestyle_imprint-on-paper_800.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/1/0/1019233_address-stamp_lifestyle_imprint-on-paper_800.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/1/0/1019233_address-stamp_lifestyle_imprint-on-paper_800.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="3 Line Business Address Stamp"
1917class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1918decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/3-line-address-stamp"
1919class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">3 Line Business Address Stamp</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1919 </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 95%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1919 </svg></div></div></div> <a href="https://www.simplystamps.com/3-line-address-stamp#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(13)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> Features up to 3 lines of your custom text
1920Choose from three stamp typ...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-14769-zb-carousel-14769"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $12.99 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/medium-round-logo-stamp" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/1/-/1-1013804-r-50-wbg-with-design-2000.jpg?width=480&quality=60"
1921srcset="https://cdn.simplystamps.com/media/catalog/product/1/-/1-1013804-r-50-wbg-with-design-2000.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/1/-/1-1013804-r-50-wbg-with-design-2000.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/1/-/1-1013804-r-50-wbg-with-design-2000.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Medium Round Self-Inking Logo Stamp"
1922class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1923decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/medium-round-logo-stamp"
1924class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">Medium Round Self-Inking Logo Stamp</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1924 </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 90%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div></div> <a href="https://www.simplystamps.com/medium-round-logo-stamp#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(61)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> Product Features
1925The Medium Round Logo Stamp is perfect for creating high-qu...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-8939-zb-carousel-8939"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $29.49 </span> </span></div></div></div></div></div><div class="swiper-slide h-auto" role="listitem"><div class="group bg-white rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all overflow-hidden flex flex-col h-full"> <a href="https://www.simplystamps.com/corporate-seal-embosser-with-year" class="block bg-gray-50 flex-shrink-0 p-3 md:p-4"><div class="relative aspect-square"> <img src="https://cdn.simplystamps.com/media/catalog/product/1/0/1009368-embosser-impression_on_white_800.jpg?width=480&quality=60"
1926srcset="https://cdn.simplystamps.com/media/catalog/product/1/0/1009368-embosser-impression_on_white_800.jpg?width=320&quality=60 320w, https://cdn.simplystamps.com/media/catalog/product/1/0/1009368-embosser-impression_on_white_800.jpg?width=480&quality=60 480w, https://cdn.simplystamps.com/media/catalog/product/1/0/1009368-embosser-impression_on_white_800.jpg?width=600&quality=60 600w" sizes="(min-width: 1024px) 15vw, (min-width: 640px) 23vw, 46vw"                                     alt="Corporate Seal Embosser with Year"
1927class="absolute inset-0 w-full h-full object-contain rounded-lg group-hover:scale-[1.16] transition-transform duration-300"
1928decoding="async" loading="lazy"></div> </a><div class="p-4 md:p-5 flex flex-col flex-grow"> <a href="https://www.simplystamps.com/corporate-seal-embosser-with-year"
1929class="block text-gray-900 hover:text-brand-primary mb-2"><h3 class="text-base md:text-lg font-semibold truncate">
1929Corporate Seal Embosser with Year</h3> </a><div class="flex items-center gap-1 mb-2"><div class="flex items-center gap-0.5"><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg><div class="absolute inset-0 overflow-hidden" style="width: 100%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div><div class="relative w-4 h-4"> <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
1929 </svg><div class="absolute inset-0 overflow-hidden" style="width: 95%"> <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/> </svg></div></div></div> <a href="https://www.simplystamps.com/corporate-seal-embosser-with-year#reviews-accordion" class="text-xs md:text-sm text-gray-500 hover:text-brand-primary transition-colors">(7)</a></div><p class="text-sm md:text-base text-gray-500 mb-3 line-clamp-2 min-h-[2.5rem] flex-grow"> About this item:
1930For use on 20 lb. paper or stationery only
1931Features yo...</p><div class="flex items-center justify-between gap-2 mt-auto"><div class="price-box flex flex-wrap items-baseline gap-x-2" id="product-price-4308-zb-carousel-4308"> <span class="regular-price"> <span class="price text-lg md:text-xl font-bold text-brand-primary"> $44.99 </span> </span></div></div></div></div></div></div></div><div class="swiper-pagination-product-carousel-6abf484daba2b zb-swiper-pagination flex justify-center items-center gap-1.5 mt-4"></div></div> </section>
1931<script> document.addEventListener('DOMContentLoaded', function() {
1932// Initialize Swiper
1933if (typeof Swiper !== 'undefined') {
1934new Swiper('#product-carousel-6abf484daba2b', {
1935// slidesPerView and slidesPerGroup are held equal at every width so one
1936// click advances exactly one screenful, never skipping cards the shopper
1937// has not seen. Swiper only groups reliably when slidesPerView is a
1938// number - with 'auto' the snap grid is built per slide, grouping
1939// collapses, and a single click jumps to the end of the carousel.
1940slidesPerView: 2,
1941slidesPerGroup: 2,
1942spaceBetween: 16,
1943breakpoints: {
1944640: { slidesPerView: 4, slidesPerGroup: 4, spaceBetween: 20 },
19451024: { slidesPerView: 6, slidesPerGroup: 6, spaceBetween: 24 }
1946},
1947// Blank padding slides are refused: Swiper would otherwise pad the final
1948// group out to a whole page when the product count does not divide by it,
1949// showing empty cards.
1950loop: true,
1951loopAddBlankSlides: false,
1952pagination: {
1953el: '.swiper-pagination-product-carousel-6abf484daba2b',
1954clickable: true
1955},
1956// Let Swiper own the arrows so it maintains their disabled state; the
1957// buttons keep their own styling because they use custom class names.
1958navigation: {
1959prevEl: '.swiper-prev-product-carousel-6abf484daba2b',
1960nextEl: '.swiper-next-product-carousel-6abf484daba2b'
1961}
1962});
1963}}); </script>
1963</div></div></div></div><div class="zephyr-builder-section"
1964data-section-index="8"
1965data-section-id="sec_89f4f4e7"
1966data-layout-id="44"
1967data-bg-color=""
1968data-bg-image=""
1969data-overlay-color=""
1970data-overlay-opacity="0"
1971data-full-width="2xl"
1972><div class="mx-auto max-w-screen-2xl"><div class="zephyr-builder-row"
1973data-section-index="8"
1974data-row-index="0"><div class="zephyr-builder-block"
1975data-placement-id="3214"
1976data-layout-id="44"
1977data-library-block="content_seo_header"> <section class="pt-8 pb-4 border-0"><div class="px-4 sm:px-0"><div class="flex items-center gap-4 text-center"><div class="flex-1 h-px bg-brand-secondary"></div><h2 class=" text-xl sm:text-2xl md:text-3xl font-bold  mb-0 min-w-0" style="color: #012e4a;"> Expert Tips for Creating Your Perfect Custom Stamps</h2><div class="flex-1 h-px bg-brand-secondary"></div></div></div><div class="w-full px-4 sm:px-0 justify-center text-center"><div class="text-center"></div></div> </section></div></div><div class="zephyr-builder-row"
1978data-section-index="8"
1979data-row-index="1"><div class="grid grid-cols-12 gap-6"><div class="zephyr-builder-zone col-span-12 sm:col-span-6 md:col-span-4"><div class="zephyr-builder-block"
1980data-placement-id="3215"
1981data-layout-id="44"
1982data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/blog/how-custom-rubber-stamps-are-made" class="group block" title="Read our Blog on how custom rubber stamps are made!"><div class="flex justify-center"> <img
1983src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/CMSImages/blogs/how-custom-rubber-stamps-are-made.jpg"
1984alt="A custom rubber wood handled stamp and a self-inking body on top of papers"
1985class="max-w-full h-auto rounded-xl shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1986/></div><div class="text-center mt-3 md:mt-4"><h3 class=" text-xl sm:text-2xl font-bold text-gray-900 transition-all duration-300 group-hover:scale-105"> How Custom Rubber Stamps Are Made</h3> <span class="block mt-2 text-base sm:text-lg text-gray-600 transition-all duration-300 group-hover:scale-105"> Get a closer look at the fascinating process of creating custom rubber stamps. </span></div> </a></div> </section></div><div class="zephyr-builder-block"
1987data-placement-id="3216"
1988data-layout-id="44"
1989data-library-block="cta_button"><div class="text-center mt-0 mb-2"> <a href="/blog/how-custom-rubber-stamps-are-made"       class="inline-flex items-center justify-center font-semibold rounded-lg
1990shadow-md hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200
1991px-4 py-2 text-sm"
1992style="background-color: #012e4b; color: #FFFFFF;"> Read Full Article Now </a></div></div></div><div class="zephyr-builder-zone col-span-12 sm:col-span-6 md:col-span-4"><div class="zephyr-builder-block"
1993data-placement-id="3217"
1994data-layout-id="44"
1995data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/blog/how-to-choose-the-right-stamp-for-you" class="group block" title="Learn more about choosing the correct custom rubber stamp"><div class="flex justify-center"> <img
1996src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/CMSImages/blogs/how-to-choose-the-right-rubber-stamp-for-you.jpg"
1997alt="Collection of simply stamps branded self-inking and wood handled stamps prominently displayed"
1998class="max-w-full h-auto rounded-xl shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
1999/></div><div class="text-center mt-3 md:mt-4"><h3 class=" text-xl sm:text-2xl font-bold text-gray-900 transition-all duration-300 group-hover:scale-105"> Choose the Right Rubber Stamp for You</h3> <span class="block mt-2 text-base sm:text-lg text-gray-600 transition-all duration-300 group-hover:scale-105"> Learn more about each of our stamp types so you can select the best stamp for your needs. </span></div> </a></div> </section></div><div class="zephyr-builder-block"
2000data-placement-id="3218"
2001data-layout-id="44"
2002data-library-block="cta_button"><div class="text-center mt-0 mb-2"> <a href="/blog/how-to-choose-the-right-stamp-for-you"       class="inline-flex items-center justify-center font-semibold rounded-lg
2003shadow-md hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200
2004px-4 py-2 text-sm"
2005style="background-color: #012e4b; color: #FFFFFF;"> Read Full Article Now </a></div></div></div>
2005<div class="zephyr-builder-zone col-span-12 sm:col-span-6 md:col-span-4"><div class="zephyr-builder-block"
2006data-placement-id="3219"
2007data-layout-id="44"
2008data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 100%"> <a href="/blog/the-imprint-of-time-a-brief-history-of-custom-rubber-stamps" class="group block" title="Read more about the history of custom rubber stamps"><div class="flex justify-center"> <img
2009src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/CMSImages/blogs/the-history-of-custom-rubber-stamps.jpg"
2010alt="Customized stamp impressions on mail and other creative assets"
2011class="max-w-full h-auto rounded-xl shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:shadow-xl"
2012/></div><div class="text-center mt-3 md:mt-4"><h3 class=" text-xl sm:text-2xl font-bold text-gray-900 transition-all duration-300 group-hover:scale-105"> The History of Custom Rubber Stamps</h3> <span class="block mt-2 text-base sm:text-lg text-gray-600 transition-all duration-300 group-hover:scale-105"> Uncover the stories behind how rubber stamps evolved into the mighty tools we use today. </span></div> </a></div> </section></div><div class="zephyr-builder-block"
2013data-placement-id="3220"
2014data-layout-id="44"
2015data-library-block="cta_button"><div class="text-center mt-0 mb-2"> <a href="/blog/the-imprint-of-time-a-brief-history-of-custom-rubber-stamps"       class="inline-flex items-center justify-center font-semibold rounded-lg
2016shadow-md hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200
2017px-4 py-2 text-sm"
2018style="background-color: #012e4b; color: #FFFFFF;"> Read Full Article Now </a></div></div></div></div></div><div class="zephyr-builder-row"
2019data-section-index="8"
2020data-row-index="2"><div class="zephyr-builder-block"
2021data-placement-id="3221"
2022data-layout-id="44"
2023data-library-block="cta_button"><div class="text-center mt-8 mb-8"> <a href="/blog"       class="inline-flex items-center justify-center font-semibold rounded-lg
2024shadow-md hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200
2025px-8 py-4 text-lg"
2026style="background-color: #60b5e4; color: #111827;">
2026 Find More Rubber Stamps Resources </a></div></div></div></div></div><div class="zephyr-builder-section"
2027data-section-index="9"
2028data-section-id="sec_19127884"
2029data-layout-id="44"
2030data-bg-color=""
2031data-bg-image=""
2032data-overlay-color=""
2033data-overlay-opacity="0"
2034data-full-width="2xl"
2035><div class="mx-auto max-w-screen-2xl"><div class="zephyr-builder-row"
2036data-section-index="9"
2037data-row-index="0"><div class="zephyr-builder-block"
2038data-placement-id="3222"
2039data-layout-id="44"
2040data-library-block="content_seo_header"> <section class="pt-12 pb-0 border-0"><div class="px-4 sm:px-0"><div class="flex items-center gap-4 text-center"><div class="flex-1 h-px bg-brand-secondary"></div><h2 class=" text-xl sm:text-2xl md:text-3xl font-bold  mb-0 min-w-0" style="color: #012e4a;"> Custom Stamps Shipped Quickly Since 1954</h2><div class="flex-1 h-px bg-brand-secondary"></div></div></div><div class="w-full px-4 sm:px-0 justify-center text-center"><div class="text-center"></div></div> </section></div></div><div class="zephyr-builder-row"
2041data-section-index="9"
2042data-row-index="1"><div class="grid grid-cols-12 gap-6"><div class="zephyr-builder-zone col-span-12 sm:col-span-4"><div class="zephyr-builder-block"
2043data-placement-id="3223"
2044data-layout-id="44"
2045data-library-block="media_youtube"> <section class="py-12 md:py-16"><div class="mx-auto px-4 sm:px-6 lg:px-8" style="max-width: 896px"><div class="relative rounded-xl overflow-hidden shadow-lg bg-black" style="padding-bottom: 56.25%;"> <iframe
2046class="absolute inset-0 w-full h-full"
2047src="https://www.youtube.com/embed/lpHefLk4fkI?rel=0&modestbranding=1"
2048title="YouTube video player"
2049frameborder="0"
2050allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
2051allowfullscreen
2052></iframe></div></div> </section></div></div><div class="zephyr-builder-zone col-span-12 sm:col-span-8"><div class="zephyr-builder-block"
2053data-placement-id="3224"
2054data-layout-id="44"
2055data-library-block="content_markdown"> <section class="pt-8 pb-16 border-0"><div class="prose prose-lg max-w-none px-4 sm:px-0
2056prose-headings:text-gray-900 prose-headings:font-bold
2057prose-h2:text-2xl prose-h2:md:text-3xl prose-h2:mt-8 prose-h2:mb-4
2058prose-h3:text-xl prose-h3:md:text-2xl prose-h3:mt-6 prose-h3:mb-3
2059prose-p:text-gray-600 prose-p:leading-relaxed prose-p:mb-4
2060prose-a:text-brand-primary hover:prose-a:text-brand-primary-hover prose-a:underline
2061prose-strong:text-gray-900 prose-strong:font-semibold
2062prose-ul:list-disc prose-ul:pl-6 prose-ul:mb-4
2063prose-ol:list-decimal prose-ol:pl-6 prose-ol:mb-4
2064prose-li:text-gray-600 prose-li:mb-2
2065prose-blockquote:border-l-4 prose-blockquote:border-brand-primary prose-blockquote:pl-4 prose-blockquote:italic prose-blockquote:text-gray-500
2066prose-code:bg-gray-100 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono
2067prose-pre:bg-gray-900 prose-pre:text-gray-100 prose-pre:p-4 prose-pre:rounded-lg prose-pre:overflow-x-auto"
2068><p><span style="font-size: 16px">At Simply Stamps, we blend over <strong>70 years of expertise</strong> with unwavering dedication to <strong>high-quality</strong> personalized stamp products, and <strong>fast service</strong>. Specializing in the art of crafting top-notch rubber stamps, we offer an <strong>extensive selection</strong> that includes everything from versatile self-inking stamps and precision pre-inked stamps to traditional wooden handle stamps, elegant European hand stamps, professional-grade options, and convenient pocket stamps.<br><br>In partnership with leading manufacturers such as Trodat, Shiny, and Colop, we offer unparalleled quality and innovation in every stamp we create. Discover the ideal stamp for your home, business, or creative projects. Choose from our unique address stamp designs, enhance your branding with custom logo stamps, streamline your work with signature stamps, or our wide assortment of business stamps. </span><a target="_blank" rel="noopener noreferrer" href="/custom-rubber-stamps"><span style="font-size: 16px">Design your own custom stamp today!</span></a></p></div> </section></div></div></div></div>
2068<div class="zephyr-builder-row"
2069data-section-index="9"
2070data-row-index="2"><div class="grid grid-cols-12 gap-6"><div class="zephyr-builder-zone col-span-12 sm:col-span-6 md:col-span-4"><div class="zephyr-builder-block"
2071data-placement-id="3225"
2072data-layout-id="44"
2073data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 25%"><div class="flex justify-center"> <img
2074src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/SS_Homepage-Icon_Stamps_120x120.png"
2075alt="Image of a stamp icon"
2076class="max-w-full h-auto rounded-lg"
2077/></div></div> </section></div><div class="zephyr-builder-block"
2078data-placement-id="3226"
2079data-layout-id="44"
2080data-library-block="content_markdown"> <section class="pt-0 pb-2 border-0"><div class="prose prose-lg max-w-none px-4 sm:px-0
2081prose-headings:text-gray-900 prose-headings:font-bold
2082prose-h2:text-2xl prose-h2:md:text-3xl prose-h2:mt-8 prose-h2:mb-4
2083prose-h3:text-xl prose-h3:md:text-2xl prose-h3:mt-6 prose-h3:mb-3
2084prose-p:text-gray-600 prose-p:leading-relaxed prose-p:mb-4
2085prose-a:text-brand-primary hover:prose-a:text-brand-primary-hover prose-a:underline
2086prose-strong:text-gray-900 prose-strong:font-semibold
2087prose-ul:list-disc prose-ul:pl-6 prose-ul:mb-4
2088prose-ol:list-decimal prose-ol:pl-6 prose-ol:mb-4
2089prose-li:text-gray-600 prose-li:mb-2
2090prose-blockquote:border-l-4 prose-blockquote:border-brand-primary prose-blockquote:pl-4 prose-blockquote:italic prose-blockquote:text-gray-500
2091prose-code:bg-gray-100 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono
2092prose-pre:bg-gray-900 prose-pre:text-gray-100 prose-pre:p-4 prose-pre:rounded-lg prose-pre:overflow-x-auto"
2093><p style="text-align: center"><strong>Get All Your Stamps In One Place</strong></p><p style="text-align: center">With the largest online selection of rubber stamps, you can easily shop all the stamps you or your business need.</p></div> </section></div></div><div class="zephyr-builder-zone col-span-12 sm:col-span-6 md:col-span-4"><div class="zephyr-builder-block"
2094data-placement-id="3227"
2095data-layout-id="44"
2096data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 25%"><div class="flex justify-center"> <img
2097src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/SS_Homepage-Icon_Shipping_120x120.png"
2098alt="Image"
2099class="max-w-full h-auto rounded-lg"
2100/></div></div> </section></div><div class="zephyr-builder-block"
2101data-placement-id="3228"
2102data-layout-id="44"
2103data-library-block="content_markdown"> <section class="pt-0 pb-2 border-0"><div class="prose prose-lg max-w-none px-4 sm:px-0
2104prose-headings:text-gray-900 prose-headings:font-bold
2105prose-h2:text-2xl prose-h2:md:text-3xl prose-h2:mt-8 prose-h2:mb-4
2106prose-h3:text-xl prose-h3:md:text-2xl prose-h3:mt-6 prose-h3:mb-3
2107prose-p:text-gray-600 prose-p:leading-relaxed prose-p:mb-4
2108prose-a:text-brand-primary hover:prose-a:text-brand-primary-hover prose-a:underline
2109prose-strong:text-gray-900 prose-strong:font-semibold
2110prose-ul:list-disc prose-ul:pl-6 prose-ul:mb-4
2111prose-ol:list-decimal prose-ol:pl-6 prose-ol:mb-4
2112prose-li:text-gray-600 prose-li:mb-2
2113prose-blockquote:border-l-4 prose-blockquote:border-brand-primary prose-blockquote:pl-4 prose-blockquote:italic prose-blockquote:text-gray-500
2114prose-code:bg-gray-100 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono
2115prose-pre:bg-gray-900 prose-pre:text-gray-100 prose-pre:p-4 prose-pre:rounded-lg prose-pre:overflow-x-auto"
2116><p style="text-align: center"><strong>Your Order Ships Next Business Day</strong></p><p style="text-align: center">With 3 US production sites we ensure that you don't need to wait to get great custom products.</p></div> </section></div></div><div class="zephyr-builder-zone col-span-12 sm:col-span-6 md:col-span-4"><div class="zephyr-builder-block"
2117data-placement-id="3229"
2118data-layout-id="44"
2119data-library-block="media_image"> <section class="py-8 md:py-12"><div class="mx-auto px-1 sm:px-2" style="max-width: 25%"><div class="flex justify-center"> <img
2120src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/SS_Homepage-Icon_Established-Quality_120x120.png"
2121alt="Image"
2122class="max-w-full h-auto rounded-lg"
2123/></div></div> </section></div>
2123<div class="zephyr-builder-block"
2124data-placement-id="3230"
2125data-layout-id="44"
2126data-library-block="content_markdown"> <section class="pt-0 pb-2 border-0"><div class="prose prose-lg max-w-none px-4 sm:px-0
2127prose-headings:text-gray-900 prose-headings:font-bold
2128prose-h2:text-2xl prose-h2:md:text-3xl prose-h2:mt-8 prose-h2:mb-4
2129prose-h3:text-xl prose-h3:md:text-2xl prose-h3:mt-6 prose-h3:mb-3
2130prose-p:text-gray-600 prose-p:leading-relaxed prose-p:mb-4
2131prose-a:text-brand-primary hover:prose-a:text-brand-primary-hover prose-a:underline
2132prose-strong:text-gray-900 prose-strong:font-semibold
2133prose-ul:list-disc prose-ul:pl-6 prose-ul:mb-4
2134prose-ol:list-decimal prose-ol:pl-6 prose-ol:mb-4
2135prose-li:text-gray-600 prose-li:mb-2
2136prose-blockquote:border-l-4 prose-blockquote:border-brand-primary prose-blockquote:pl-4 prose-blockquote:italic prose-blockquote:text-gray-500
2137prose-code:bg-gray-100 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono
2138prose-pre:bg-gray-900 prose-pre:text-gray-100 prose-pre:p-4 prose-pre:rounded-lg prose-pre:overflow-x-auto"
2139><p style="text-align: center"><strong>Established In 1954: Quality You Can Trust</strong></p><p style="text-align: center">How we make our stamps here in the US has changed over 70 years, but our high quality standards have not.</p></div> </section></div></div></div></div><div class="zephyr-builder-row"
2140data-section-index="9"
2141data-row-index="3"><div class="zephyr-builder-block"
2142data-placement-id="3231"
2143data-layout-id="44"
2144data-library-block="content_spacer"><div class="h-16" aria-hidden="true"></div></div></div></div></div></div></div></div></div> </main> <footer id="site-footer" class="w-full"><div class="relative"><div class="widget widget-static-block"><style> /* ── SimplyStamps Shipping ETA Timer ── */
2145/* v4.0 — Unified copy, color does the talking */
2146.ss-ship-eta {
2147display: none;
2148flex-direction: column;
2149gap: 5px;
2150padding: 12px 16px;
2151border-radius: 8px 8px 0px 0px;
2152background: #eef7ff;
2153width: 100%;
2154box-sizing: border-box;
2155margin-top: 0;
2156margin-bottom: 0px;
2157border: 1px solid #d1e9ff;
2158transition: background 0.35s ease, border-color 0.35s ease;
2159font-family: inherit;
2160}
2161.ss-ship-eta__topline {
2162font-size: 14px;
2163font-weight: 600;
2164color: #374151;
2165line-height: 1.4;
2166margin: 0;
2167}
2168.ss-ship-eta__hero {
2169font-size: 14px;
2170color: #374151;
2171line-height: 1.5;
2172}
2173.ss-ship-eta__hero-date {
2174font-weight: 700;
2175}
2176.ss-ship-eta__hero-tier {
2177font-size: 12px;
2178color: #9ca3af;
2179margin-left: 6px;
2180}
2181.ss-ship-eta__toggle {
2182background: none;
2183border: none;
2184padding: 0;
2185margin-top: 3px;
2186font-size: 12px;
2187font-weight: 600;
2188color: #1d4ed8;
2189cursor: pointer;
2190display: inline-flex;
2191align-items: center;
2192gap: 3px;
2193font-family: inherit;
2194}
2195.ss-ship-eta__toggle:hover { text-decoration: underline; }
2196.ss-ship-eta__toggle-arrow {
2197font-size: 10px;
2198transition: transform 0.2s ease;
2199display: inline-block;
2200}
2201.ss-ship-eta__toggle-arrow.ss-open {
2202transform: rotate(180deg);
2203}
2204.ss-ship-eta__more {
2205display: none;
2206flex-direction: column;
2207gap: 1px;
2208margin-top: 4px;
2209font-size: 14px;
2210color: #374151;
2211line-height: 1.5;
2212}
2213.ss-ship-eta__more.ss-visible {
2214display: flex;
2215}
2216.ss-ship-eta__more-row {
2217display: flex;
2218align-items: baseline;
2219gap: 6px;
2220}
2221.ss-ship-eta__more-label {
2222font-size: 12px;
2223font-weight: 500;
2224color: #9ca3af;
2225min-width: 52px;
2226}
2227.ss-ship-eta__more-date {
2228font-weight: 500;
2229}
2230.ss-eta-cd {
2231font-weight: 800;
2232color: #1d4ed8;
2233}
2234/* ── Warn state ── */
2235.ss-ship-eta.ss-warn {
2236background: #fffbeb;
2237border-color: #fde68a;
2238}
2239.ss-ship-eta.ss-warn .ss-ship-eta__topline,
2240.ss-ship-eta.ss-warn .ss-ship-eta__hero { color: #78350f; }
2241.ss-ship-eta.ss-warn .ss-ship-eta__more { color: #78350f; }
2242.ss-ship-eta.ss-warn .ss-eta-cd { color: #d97706; }
2243.ss-ship-eta.ss-warn .ss-ship-eta__toggle { color: #b45309; }
2244/* ── Urgent state ── */
2245.ss-ship-eta.ss-urgent {
2246background: #fff1f2;
2247border-color: #fecdd3;
2248}
2249.ss-ship-eta.ss-urgent .ss-ship-eta__topline,
2250.ss-ship-eta.ss-urgent .ss-ship-eta__hero { color: #7f1d1d; }
2251.ss-ship-eta.ss-urgent .ss-ship-eta__more { color: #7f1d1d; }
2252.ss-ship-eta.ss-urgent .ss-eta-cd {
2253color: #dc2626;
2254animation: ss-pulse 1.1s ease-in-out 5;
2255}
2256.ss-ship-eta.ss-urgent .ss-ship-eta__toggle { color: #dc2626; }
2257@keyframes ss-pulse {
22580%, 100% { opacity: 1; }
225950%       { opacity: 0.5; }
2260}
2261/* ── After-cutoff state ── */
2262.ss-ship-eta.ss-after {
2263background: #f9fafb;
2264border-color: #e5e7eb;
2265}
2266.ss-ship-eta.ss-after .ss-ship-eta__topline,
2267.ss-ship-eta.ss-after .ss-ship-eta__hero { color: #6b7280; }
2268.ss-ship-eta.ss-after .ss-ship-eta__more { color: #6b7280; }
2269.ss-ship-eta.ss-after .ss-ship-eta__toggle { color: #6b7280; } </style> 
2269<script> (() => {
2270var TIMEZONE         = "America/New_York";
2271var CUTOFF_HOUR_ET   = 17;
2272var CUTOFF_MINUTE_ET = 0;
2273var HOLIDAYS_MMDD = new Set([
2274"01/01","01/19","02/16","05/25","07/03","09/07",
2275"11/26","11/27","12/24","12/25","12/31"
2276]);
2277var OFFSETS = { rush: 2, express: 3, standard: 5, economy: 6 };
2278var STD_LABEL = "Preferred";
2279var SHIPPING_CONTAINER_SELECTOR = '.ships-and-made-in-usa-container';
2280var FALLBACK_PARENT_SELECTOR    = '.vpt-product';
2281var PFX = "ss";
2282function createTimerEl() {
2283var div = document.createElement("div");
2284div.className = PFX + "-ship-eta";
2285div.setAttribute("aria-label", "Delivery estimates");
2286div.innerHTML =
2287'<div class="' + PFX + '-ship-eta__topline"></div>' +
2288'<div class="' + PFX + '-ship-eta__hero"></div>' +
2289'<button class="' + PFX + '-ship-eta__toggle" type="button" aria-expanded="false">' +
2290'All shipping options <span class="' + PFX + '-ship-eta__toggle-arrow">\u25BC</span>' +
2291'</button>' +
2292'<div class="' + PFX + '-ship-eta__more">' +
2293'<div class="' + PFX + '-ship-eta__more-row" data-tier="express"></div>' +
2294'<div class="' + PFX + '-ship-eta__more-row" data-tier="standard"></div>' +
2295'<div class="' + PFX + '-ship-eta__more-row" data-tier="economy"></div>' +
2296'</div>';
2297var toggleEl = div.querySelector('.' + PFX + '-ship-eta__toggle');
2298var arrowEl = div.querySelector('.' + PFX + '-ship-eta__toggle-arrow');
2299var moreEl = div.querySelector('.' + PFX + '-ship-eta__more');
2300var expanded = false;
2301toggleEl.addEventListener("click", function() {
2302expanded = !expanded;
2303moreEl.classList.toggle(PFX + "-visible", expanded);
2304arrowEl.classList.toggle(PFX + "-open", expanded);
2305toggleEl.setAttribute("aria-expanded", expanded);
2306toggleEl.firstChild.textContent = expanded ? "Hide options " : "All shipping options ";
2307});
2308return div;
2309}
2310function getEasternParts(date) {
2311date = date || new Date();
2312var fmt = new Intl.DateTimeFormat("en-US", {
2313timeZone: TIMEZONE,
2314year: "numeric", month: "2-digit", day: "2-digit",
2315hour: "2-digit", minute: "2-digit", second: "2-digit",
2316hour12: false
2317});
2318var out = {};
2319var parts = fmt.formatToParts(date);
2320for (var i = 0; i < parts.length; i++) out[parts[i].type] = parts[i].value;
2321return out;
2322}
2323function getNowET() {
2324var p = getEasternParts();
2325return new Date(+p.year, +p.month - 1, +p.day, +p.hour, +p.minute, +p.second);
2326}
2327function mmdd(d) {
2328return String(d.getMonth()+1).padStart(2,"0") + "/" + String(d.getDate()).padStart(2,"0");
2329}
2330function isWeekend(d)     { var w = d.getDay(); return w === 0 || w === 6; }
2331function isHoliday(d)     { return HOLIDAYS_MMDD.has(mmdd(d)); }
2332function isBusinessDay(d) { return !isWeekend(d) && !isHoliday(d); }
2333function startOfDay(d)    { var x = new Date(d); x.setHours(0,0,0,0); return x; }
2334function nextDay(d)       { var x = new Date(d); x.setDate(x.getDate()+1); return x; }
2335function nextBusinessDay(from) {
2336var x = nextDay(from);
2337while (!isBusinessDay(x)) x = nextDay(x);
2338return startOfDay(x);
2339}
2340function addBusinessDays(from, n) {
2341var x = startOfDay(from);
2342var added = 0;
2343while (added < n) { x = nextDay(x); if (isBusinessDay(x)) added++; }
2344return x;
2345}
2346function formatDate(d) {
2347return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "
2347numeric" });
2348}
2349function countdownStr(diffMs) {
2350var totalSecs = Math.max(0, Math.floor(diffMs / 1000));
2351var totalMins = Math.floor(totalSecs / 60);
2352var hrs  = Math.floor(totalSecs / 3600);
2353var mins = Math.floor((totalSecs % 3600) / 60);
2354var secs = totalSecs % 60;
2355if (totalMins < 5) {
2356return hrs + "h " + String(mins).padStart(2,"0") + "m " + String(secs).padStart(2,"0") + "s";
2357}
2358return hrs + " hr" + (hrs !== 1 ? "s" : "") + " " + String(mins).padStart(2,"0") + " min";
2359}
2360var instances = [];
2361function ensureInstances() {
2362var containers = document.querySelectorAll(SHIPPING_CONTAINER_SELECTOR);
2363var placed = false;
2364for (var i = 0; i < containers.length; i++) {
2365var container = containers[i];
2366var prev = container.previousElementSibling;
2367if (prev && prev.classList.contains(PFX + "-ship-eta")) {
2368placed = true;
2369continue;
2370}
2371var timer = createTimerEl();
2372container.insertAdjacentElement("beforebegin", timer);
2373instances.push(timer);
2374placed = true;
2375}
2376if (!placed) {
2377var fallback = document.querySelector(FALLBACK_PARENT_SELECTOR);
2378if (fallback) {
2379var first = fallback.firstElementChild;
2380if (!first || !first.classList.contains(PFX + "-ship-eta")) {
2381var timer = createTimerEl();
2382fallback.insertAdjacentElement("afterbegin", timer);
2383instances.push(timer);
2384placed = true;
2385}
2386}
2387}
2388return placed;
2389}
2390function renderAll() {
2391var nowET  = getNowET();
2392var cutoff = new Date(nowET);
2393cutoff.setHours(CUTOFF_HOUR_ET, CUTOFF_MINUTE_ET, 0, 0);
2394var diffMs       = cutoff - nowET;
2395var totalMins    = Math.floor(diffMs / 60000);
2396var isPastCutoff = nowET >= cutoff;
2397var isOffDay     = !isBusinessDay(nowET);
2398var urgencyClass = null;
2399var toplineHTML  = "";
2400var base;
2401if (isPastCutoff || isOffDay) {
2402urgencyClass = PFX + "-after";
2403base = nextBusinessDay(nowET);
2404toplineHTML = 'Get as early as:';
2405} else {
2406var cdStr = countdownStr(diffMs);
2407base = startOfDay(nowET);
2408toplineHTML = 'Order in <span class="' + PFX + '-eta-cd">' + cdStr + '</span> to get as early as:';
2409if (totalMins <= 120) {
2410urgencyClass = PFX + "-urgent";
2411} else if (totalMins <= 240) {
2412urgencyClass = PFX + "-warn";
2413}
2414}
2415var rushDate    = addBusinessDays(base, OFFSETS.rush);
2416var expressDate = addBusinessDays(base, OFFSETS.express);
2417var stdDate     = addBusinessDays(base, OFFSETS.standard);
2418var ecoDate     = addBusinessDays(base, OFFSETS.economy);
2419var heroHTML    = '<span class="' + PFX + '-ship-eta__hero-date">' + formatDate(rushDate) + '</span><span class="' + PFX + '-ship-eta__hero-tier">via Rush</span>';
2420var expressHTML = '<span class="' + PFX + '-ship-eta__more-label">Express</span><span class="' + PFX + '-ship-eta__more-date">' + formatDate(expressDate) + '</span>';
2421var stdHTML     = '<span class="' + PFX + '-ship-eta__more-label">' + STD_LABEL + '</span><span class="' + PFX + '-ship-eta__more-date">' + formatDate(stdDate) + '</span>';
2422var ecoHTML     = '<span class="' + PFX + '-ship-eta__more-label">Economy</span><span class="' + PFX + '-ship-eta__more-date">' + formatDate(ecoDate) + '</span>';
2423for (var i = 0; i < instances.length; i++) {
2424var el = instances[i];
2425el.classList.remove(PFX + "-warn", PFX + "-urgent", PFX + "-after");
2426if (urgencyClass) el.classList.add(urgencyClass);
2427el.querySelector('.' + PFX + '-ship-eta__topline').innerHTML = toplineHTML;
2428el.querySelector('.' + PFX + '-ship-eta__hero').innerHTML = heroHTML;
2429el.querySelector('[data-tier="express"]').innerHTML = expressHTML;
2430el.querySelector('[data-tier="standard"]').innerHTML = stdHTML;
2431el.querySelector('[data-tier="economy"]').innerHTML = ecoHTML;
2432el.style.display = "flex";
2433}
2434}
2435function tick() {
2436var placed = ensureInstances();
2437if (placed && instances.length > 0) renderAll();
2438var nowET  = getNowET();
2439var cutoff = new Date(nowET);
2440cutoff.setHours(CUTOFF_HOUR_ET, CUTOFF_MINUTE_ET, 0, 0);
2441var minsLeft = Math.floor((cutoff - nowET) / 60000);
2442var interval = !placed ? 250
2443: (minsLeft >= 0 && minsLeft < 5) ? 1000
2444: 60000;
2445setTimeout(tick, interval);
2446}
2447tick();
2448if (window.MutationObserver) {
2449var mo = new MutationObserver(function() { ensureInstances(); });
2450mo.observe(document.body, { childList: true, subtree: true });
2451}
2452})();
2453</script>
2453</div> <style> .full-size-container.footer-marketing {
2454background-color: #60b5e5;
2455width: 100vw;
2456margin-top: 30px;
2457} </style> <style> .CustomerServiceNotice img {
2458width: 100%;
2459margin-bottom: 2%;
2460}
2461.CustomerServiceNotice .modal-footer {
2462display: none;
2463} </style></div> <section id="zephyr-signup-band"
2464class="relative w-full py-10 sm:py-12 bg-brand-primary text-white"
2465x-data="zephyrSignupBand()"
2466aria-labelledby="zephyr-signup-headline"><div class="relative max-w-5xl mx-auto px-4 sm:px-6 lg:px-8"><div x-ref="stage"
2467class="relative flex flex-col justify-center"><div x-ref="panelOffers"
2468class="transition-opacity duration-200 ease-out motion-reduce:transition-none"
2469:class="view === 'offers' ? 'relative opacity-100 visible' : 'absolute inset-x-0 top-0 opacity-0 invisible'"
2470:aria-hidden="view !== 'offers'"><p id="zephyr-signup-headline" class="text-3xl sm:text-4xl font-bold text-center"> Save On Your Order Today &amp; On Future Deals!</p><div class="mt-8 sm:mt-10 grid gap-4 sm:gap-8 sm:grid-cols-2 max-w-5xl mx-auto items-stretch"><div class="group relative bg-white rounded-2xl shadow-xl p-6 pt-10 sm:p-8 sm:pt-12 flex flex-col text-center transition-transform duration-200 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0 ring-4 ring-brand-success lg:scale-[1.03]"> <span class="absolute -top-4 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-full bg-brand-success px-5 py-1.5 text-sm font-bold uppercase tracking-widest text-white shadow-md"> Most Popular! </span><div class="flex flex-col-reverse items-center gap-4 text-center sm:block sm:gap-0"><p class="text-sm font-semibold uppercase tracking-wider text-gray-500"> Subscribe To Email + Text and get</p><p class="text-4xl sm:text-6xl font-extrabold leading-none sm:mt-4 text-brand-success"> 15% Off</p></div><ul class="mt-5 sm:mt-6 space-y-2 w-fit max-w-full mx-auto text-left text-sm text-gray-700"><li class="flex items-start gap-2"> <svg class="h-5 w-5 shrink-0 text-brand-success" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M16.7 5.3a1 1 0 010 1.4l-7.5 7.5a1 1 0 01-1.4 0l-3.5-3.5a1 1 0 011.4-1.4l2.8 2.79 6.8-6.79a1 1 0 011.4 0z" /></svg> <span>Everything in the email list</span></li><li class="flex items-start gap-2"> <svg class="h-5 w-5 shrink-0 text-brand-success" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M16.7 5.3a1 1 0 010 1.4l-7.5 7.5a1 1 0 01-1.4 0l-3.5-3.5a1 1 0 011.4-1.4l2.8 2.79 6.8-6.79a1 1 0 011.4 0z" /></svg> <span>Early access to promotions</span></li><li class="flex items-start gap-2"> <svg class="h-5 w-5 shrink-0 text-brand-success" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M16.7 5.3a1 1 0 010 1.4l-7.5 7.5a1 1 0 01-1.4 0l-3.5-3.5a1 1 0 011.4-1.4l2.8 2.79 6.8-6.79a1 1 0 011.4 0z" /></svg> <span>Text-only flash sales</span></li></ul><div class="mt-auto pt-5 sm:pt-6"> <button type="button"
2471@click="choose('email_sms')"
2472class="w-full min-h-[56px] bg-brand-primary hover:bg-brand-primary-hover text-white text-base font-semibold px-4 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-brand-primary"> Subscribe To Email + Text </button></div></div><div class="group bg-white rounded-2xl border border-gray-200 shadow-sm p-6 sm:p-8 flex flex-col text-center transition-transform duration-200 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0"><div class="flex flex-col-reverse items-center gap-4 text-center sm:block sm:gap-0"><p class="text-sm font-semibold uppercase tracking-wider text-gray-500"> Subscribe to Email Only and get</p><p class="text-4xl sm:text-6xl font-extrabold leading-none text-brand-primary sm:mt-4"> 10% Off</p></div><ul class="mt-5 sm:mt-6 space-y-2 w-fit max-w-full mx-auto text-left text-sm text-gray-700"><li class="flex items-start gap-2"> <svg class="h-5 w-5 shrink-0 text-brand-success" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M16.7 5.3a1 1 0 010 1.4l-7.5 7.5a1 1 0 01-1.4 0l-3.5-3.5a1 1 0 011.4-1.4l2.8 2.79 6.8-6.79a1 1 0 011.4 0z" /></svg> <span>Exclusive subscriber-only deals</span></li><li class="flex items-start gap-2">
2472 <svg class="h-5 w-5 shrink-0 text-brand-success" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M16.7 5.3a1 1 0 010 1.4l-7.5 7.5a1 1 0 01-1.4 0l-3.5-3.5a1 1 0 011.4-1.4l2.8 2.79 6.8-6.79a1 1 0 011.4 0z" /></svg> <span>Early access to promotions</span></li></ul><div class="mt-auto pt-5 sm:pt-6"> <button type="button"
2473@click="choose('email')"
2474class="w-full min-h-[56px] border-2 border-brand-primary text-brand-primary hover:bg-brand-primary hover:text-white text-base font-semibold px-4 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-brand-primary"> Subscribe To Email List </button></div></div></div><p class="mt-10 text-center text-xs leading-relaxed opacity-90 max-w-2xl mx-auto"> One use per email address/cell-phone number.                                                    By signing up, you agree to our <a href="/terms-and-conditions" class="text-inherit underline hover:no-underline">Terms and Conditions</a> &amp; <a href="/privacy-policy" class="text-inherit underline hover:no-underline">Privacy Policy</a></p></div><div x-ref="panelForm"
2475x-cloak
2476class="transition-opacity duration-200 ease-out motion-reduce:transition-none"
2477:class="view === 'email' || view === 'email_sms'
2478? 'relative opacity-100 visible'
2479: 'absolute inset-x-0 top-0 opacity-0 invisible'"
2480:aria-hidden="!(view === 'email' || view === 'email_sms')"><div class="max-w-2xl mx-auto"><div class="bg-white rounded-xl shadow-sm p-6 sm:p-8 text-gray-900"><p class="text-xl sm:text-2xl font-bold text-center" x-text="headingText()"></p><p class="mt-2 text-sm text-gray-600 text-center" x-text="subheadingText()"></p><div x-show="errorMessage"
2481x-transition
2482role="alert"
2483aria-live="polite"
2484class="mt-5 p-4 bg-red-50 border border-red-200 rounded-lg"><p class="text-sm text-brand-danger" x-text="errorMessage"></p></div><form @submit.prevent="submit()" class="mt-6 space-y-5" novalidate><div> <label for="zephyr-signup-email" class="block text-sm font-medium text-gray-700 mb-1"> Email Address <span class="text-brand-danger">*</span> </label> <input type="email"
2485id="zephyr-signup-email"
2486x-model="email"
2487required
2488autocomplete="email"
2489placeholder="[email protected]"
2490class="w-full px-4 py-2 min-h-[44px] border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div><div x-show="view === 'email_sms' || measuring" x-cloak> <label for="zephyr-signup-phone" class="block text-sm font-medium text-gray-700 mb-1"> Mobile Number <span class="text-brand-danger">*</span> </label><div class="flex"> <span class="inline-flex items-center px-3 rounded-l-lg border border-r-0 border-gray-300 bg-gray-50 text-sm text-gray-600 select-none whitespace-nowrap"
2491aria-hidden="true">US +1</span> <input type="tel"
2492id="zephyr-signup-phone"
2493x-model="phone"
2494autocomplete="tel-national"
2495inputmode="tel"
2496placeholder="(555) 555-5555"
2497aria-describedby="zephyr-signup-phone-hint"
2498class="w-full px-4 py-2 min-h-[44px] border border-gray-300 rounded-r-lg focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div><p id="zephyr-signup-phone-hint" class="mt-1 text-xs text-gray-500"> US mobile numbers only. Landlines cannot receive texts.</p></div><div x-show="view === 'email_sms' || measuring" x-cloak class="flex gap-3"> <input type="checkbox"
2499id="zephyr-signup-consent"
2500x-model="consent"
2501class="mt-0.5 h-5 w-5 shrink-0 rounded border-gray-300 text-brand-primary focus:ring-brand-primary"> <label for="zephyr-signup-consent" class="text-xs leading-relaxed text-gray-600"> By checking this box, you agree to receive recurring automated promotional and personalized marketing text messages (e.g., cart reminders) from Simply Stamps at the mobile number provided at signup. Consent is not a condition of purchase. Reply HELP for help and STOP to cancel. Message frequency may vary. Message and data rates may apply. <a href="/terms-and-conditions" class="underline hover:no-underline">Terms and Conditions</a> &amp; <a href="/privacy-policy" class="underline hover:no-underline">Privacy Policy</a> </label></div><div class="flex flex-col sm:flex-row sm:items-center gap-4 pt-1"> <button type="submit"
2502:disabled="loading"
2503class="w-full sm:w-auto min-h-[48px] px-6 bg-brand-primary hover:bg-brand-primary-hover disabled:opacity-60 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-brand-primary inline-flex items-center justify-center"> <span x-show="!loading" x-text="submitText()"></span> <span x-show="loading" x-cloak class="inline-flex items-center gap-2"> <svg class="animate-spin h-5 w-5" viewBox="0 0 24 24" fill="none" aria-hidden="true"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path> </svg> Signing up... </span> </button><button type="button"
2504@click="back()"
2505class="text-sm text-gray-600 underline hover:no-underline hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-brand-primary rounded"> Back to both offers </button></div></form></div></div></div><div x-ref="panelDone"
2506x-cloak
2507class="transition-opacity duration-200 ease-out motion-reduce:transition-none"
2508:class="view === 'done' ? 'relative opacity-100 visible' : 'absolute inset-x-0 top-0 opacity-0 invisible'"
2509:aria-hidden="view !== 'done'"><div class="bg-white rounded-xl shadow-sm p-8 text-center text-gray-900 max-w-2xl mx-auto" role="status" aria-live="polite"> <svg class="mx-auto h-12 w-12 text-brand-success" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> </svg><p class="mt-4 text-xl font-bold">You're in!</p><p class="mt-2 text-sm text-gray-600" x-text="successMessage"></p></div></div></div></div> </section>
2509<script> /*
2510* Hide the band synchronously, before paint, for anyone who has already signed
2511* up. This runs inline rather than through Alpine on purpose: the footer is
2512* served from the Fastly cache, so the cookie is the only signal available, and
2513* Alpine boots after first paint — gating on x-show alone would render the band
2514* and then yank it, shifting the footer.
2515*/
2516(function () {
2517if (document.cookie.indexOf('zephyr_signup=') !== -1) {
2518var band = document.getElementById('zephyr-signup-band');
2519if (band) {
2520band.parentNode.removeChild(band);
2521}
2522}
2523})();function zephyrSignupBand() {
2524return {
2525view: 'offers',
2526email: '',
2527phone: '',
2528consent: false,
2529loading: false,
2530errorMessage: '',
2531successMessage: '',
2532postUrl: 'https://www.simplystamps.com/zephyr/signup/post/',
2533hideDays: 365,/* Per-offer copy, so the two form states share one set of markup. */
2534copy: {
2535email: {
2536heading: 'Save 10% - Subscribe to Email Only',
2537subheading: 'Please enter your email below to receive your discount code!',
2538submit: 'Get My Discount Code'
2539},
2540email_sms: {
2541heading: 'Save 15% - Subscribe to Email + Text',
2542subheading: 'Please enter your email address and your phone number to receive the 20% off discount code.',
2543submit: 'Get my 15% Off Now!'
2544}
2545},/*
2546* While `measuring` is on, the form panel renders in its tallest
2547* configuration so it can be sized. See measureTallest().
2548*/
2549formCopy: function () {
2550return this.copy[this.view]
2551|| (this.measuring ? this.copy.email_sms : null);
2552},headingText: function () {
2553var copy = this.formCopy();
2554return copy ? copy.heading : '';
2555},subheadingText: function () {
2556var copy = this.formCopy();
2557return copy ? copy.subheading : '';
2558},submitText: function () {
2559var copy = this.formCopy();
2560return copy ? copy.submit : '';
2561},measuring: false,init: function () {
2562this.measureTallest();// Panel heights change with the breakpoint, so re-pin on resize.
2563var self = this;
2564var timer = null;
2565window.addEventListener('resize', function () {
2566clearTimeout(timer);
2567timer = setTimeout(function () { self.measureTallest(); }, 150);
2568});
2569},/*
2570* Pin the stage to the tallest panel so switching states moves nothing
2571* on the page at all.
2572*
2573* The form panel has to be measured in its email+SMS configuration —
2574* its phone and consent blocks are x-show'd off in the email-only view,
2575* and its heading is bound to the current view, so it otherwise reports
2576* a short height. `measuring` forces that configuration on, and because
2577* an inactive panel is `absolute` + `invisible` (never display:none) it
2578* is measurable without ever being seen.
2579*
2580* Skipped while a form is on screen: `measuring` would briefly reveal
2581* the phone and consent fields in the email-only view. The pin just
2582* stays as-is until the visitor returns to the offers.
2583*/
2584measureTallest: function () {
2585var stage = this.$refs.stage;
2586if (!stage || this.view === 'email' || this.view === 'email_sms') {
2587return;
2588}var self = this;
2589this.measuring = true;this.$nextTick(function () {
2590var tallest = 0;
2591['panelOffers', 'panelForm', 'panelDone'].forEach(function (ref) {
2592var panel = self.$refs[ref];
2593if (panel) {
2594tallest = Math.max(tallest, panel.offsetHeight);
2595}
2596});self.measuring = false;
2597if (tallest) {
2598stage.style.minHeight = tallest + 'px';
2599}
2600});
2601},choose: function (which) {
2602this.errorMessage = '';
2603this.view = which;// After the DOM updates, not before: the field sits in a panel that
2604// is still `invisible` at this point, and focus() is a silent no-op
2605// on a hidden element.
2606var self = this;
2607this.$nextTick(function () {
2608// Both offers open the same panel, so email is always first.
2609self.focusLater('zephyr-signup-email');
2610});
2611},back: function () {
2612this.errorMessage = '';
2613this.view = 'offers';
2614},/* Move focus to the first field without yanking the page around. */
2615focusLater: function (focusId) {
2616if (!focusId) {
2617return;
2618}
2619var el = document.getElementById(focusId);
2620if (!el) {
2621return;
2622}
2623try {
2624el.focus({ preventScroll: true });
2625} catch (e) {
2626el.focus();
2627}
2628},submit: function () {
2629var self = this;
2630if (this.loading) {
2631return;
2632}// Client-side checks mirror the server's; the server is still the
2633// authority, these just save a round trip.
2634if (!this.email || this.email.indexOf('@') === -1) {
2635this.errorMessage = 'Enter a valid email address.';
2636return;
2637}
2638if (this.view === 'email_sms') {
2639if (this.phone.replace(/[^0-9]/g, '').length < 10) {
2640this.errorMessage = 'Enter a valid 10-digit US mobile number.';
2641return;
2642}
2643if (!this.consent) {
2644this.errorMessage = 'Check the consent box to receive text messages.';
2645return;
2646}
2647}this.loading = true;
2648this.errorMessage = '';var body = new FormData();
2649body.append('email', this.email);
2650body.append('signup_type', this.view);
2651if (this.view === 'email_sms') {
2652body.append('phone', this.phone);
2653body.append('sms_consent', this.consent ? '1' : '0');
2654}var send = function (token) {
2655if (token) {
2656body.append('recaptcha_response', token);
2657}fetch(self.postUrl, {
2658method: 'POST',
2659body: body,
2660credentials: 'same-origin'
2661}).then(function (response) {
2662return response.json();
2663}).then(function (data) {
2664if (data && data.success) {
2665self.successMessage = data.message || '';
2666self.view = 'done';
2667self.setHideCookie(data.hide_days);
2668if (typeof zGA4 === 'function') {
2669zGA4('generate_lead', {});
2670}
2671} else {
2672self.errorMessage = (data && data.error)
2673? data.error
2674: 'We could not complete your signup. Please try again in a moment.';
2675}
2676}).catch(function () {
2677self.errorMessage = 'We could not complete your signup. Please try again in a moment.';
2678}).then(function () {
2679self.loading = false;
2680});
2681};if (typeof window.getZephyrRecaptchaToken === 'function') {
2682window.getZephyrRecaptchaToken('signup').then(send, function () { send(null); });
2683} else {
2684send(null);
2685}
2686},/* Keeps the band hidden on later visits. 0 days means always show it. */
2687setHideCookie: function (days) {
2688var lifetime = typeof days === 'number' ? days : this.hideDays;
2689if (!lifetime || lifetime < 1) {
2690return;
2691}
2692var expires = new Date();
2693expires.setTime(expires.getTime() + (lifetime * 24 * 60 * 60 * 1000));
2694document.cookie = 'zephyr_signup=1; expires=' + expires.toUTCString()
2695+ '; path=/; SameSite=Lax'
2696+ (location.protocol === 'https:' ? '; Secure' : '');
2697}
2698};
2699}
2700</script>
2700<div class="relative"><div class="widget widget-static-block"></div> <!-On PDP FAQ Accordion Compatibility Shim--><div class="widget widget-static-block"><style> /* ---------------------------------------------------------------------------
2701FAQ blocks — standalone styles for the legacy Bootstrap panel markup.
2702Pairs with faq-collapse.js. No Bootstrap, no dependencies.
2703Scoped to .faq_block / .faq_table so nothing else on the page inherits
2704these rules. Retune the custom properties below to match the theme.
2705--------------------------------------------------------------------------- */
2706.faq_block {
2707--faq-border: #dcdcdc;
2708--faq-radius: 6px;
2709--faq-gap: 10px;
2710--faq-surface: #fff;
2711--faq-heading: #f7f7f7;
2712--faq-heading-hover: #efefef;
2713--faq-focus: #2563eb;
2714--faq-mark: #ffe9a8;
2715--faq-muted: #616161;
2716line-height: 1.55;
2717}
2718/* --- neutralize the layout table ---------------------------------------- */
2719.faq_block .faq_table,
2720.faq_block .faq_table > tbody,
2721.faq_block .faq_table > tbody > tr,
2722.faq_block .faq_table > tbody > tr > td {
2723display: block;
2724width: 100%;
2725max-width: 100%;
2726margin: 0;
2727padding: 0;
2728border: 0;
2729background: none;
2730}
2731.faq_block .faq_table > tbody > tr > td + td {
2732margin-top: var(--faq-gap);
2733}
2734/* the intro row holds the section heading, not a panel */
2735.faq_block .faq_table h3 {
2736margin: 0 0 calc(var(--faq-gap) * 1.5);
2737font-size: 1.5em;
2738line-height: 1.25;
2739}
2740/* --- panel ---------------------------------------------------------------- */
2741.faq_block .panel {
2742margin: 0 0 var(--faq-gap);
2743background: var(--faq-surface);
2744border: 1px solid var(--faq-border);
2745border-radius: var(--faq-radius);
2746overflow: hidden;
2747}
2748.faq_block .panel:last-child {
2749margin-bottom: 0;
2750}
2751.faq_block .panel-heading {
2752padding: 0;
2753background: var(--faq-heading);
2754border-bottom: 1px solid transparent;
2755transition: background-color .15s ease;
2756}
2757.faq_block .panel-heading:hover {
2758background: var(--faq-heading-hover);
2759}
2760.faq_block .panel-title {
2761margin: 0;
2762font-size: 1em;
2763font-weight: 600;
2764line-height: inherit;
2765}
2766/* the trigger fills the whole heading so the entire bar is clickable */
2767.faq_block .panel-title a {
2768display: flex;
2769align-items: center;
2770justify-content: space-between;
2771gap: 1em;
2772padding: 14px 16px;
2773color: inherit;
2774text-decoration: none;
2775cursor: pointer;
2776}
2777.faq_block .panel-title a:hover,
2778.faq_block .panel-title a:focus {
2779color: inherit;
2780text-decoration: none;
2781}
2782.faq_block .panel-title a:focus-visible {
2783outline: 2px solid var(--faq-focus);
2784outline-offset: -2px;
2785}
2786/* chevron — driven by aria-expanded, which the shim keeps in sync */
2787.faq_block .panel-title a::after {
2788content: '';
2789flex: 0 0 auto;
2790width: .45em;
2791height: .45em;
2792margin-top: -.22em;
2793border-right: 2px solid currentColor;
2794border-bottom: 2px solid currentColor;
2795transform: rotate(45deg);
2796transition: transform .2s ease, margin-top .2s ease;
2797opacity: .65;
2798}
2799.faq_block .panel-title a[aria-expanded="true"]::after {
2800margin-top: .18em;
2801transform: rotate(-135deg);
2802}
2803/* --- answer body ---------------------------------------------------------- */
2804/* Fallback for a JS failure: the browser still reveals a panel when its id is
2805in the URL, so the answers stay reachable. */
2806.faq_block .panel-collapse:target {
2807display: block !important;
2808}
2809/* divider only appears once a panel is open (:has() — no-op on old browsers) */
2810.faq_block .panel:has(.panel-collapse.in) .panel-heading {
2811border-bottom-color: var(--faq-border);
2812}
2813/* Tailwind ships a `.collapse` utility (visibility: collapse) that collides with
2814the legacy Bootstrap class of the same name in this markup. Two class-level
2815selectors here so this wins on specificity regardless of stylesheet order. */
2816.faq_block .panel-collapse,
2817.faq_table .panel-collapse {
2818visibility: visible;
2819}
2820/* padding lives on .panel-body, never margins — the height animation measures
2821scrollHeight on .panel-collapse and collapsing margins would break it */
2822.faq_block .panel-body {
2823padding: 14px 16px 16px;
2824}
2825.faq_block .panel-body > :first-child {
2826margin-top: 0;
2827}
2828.faq_block .panel-body > :last-child {
2829margin-bottom: 0;
2830}
2831.faq_block .panel-body ol,
2832.faq_block .panel-body ul {
2833margin: .75em 0;
2834padding-left: 1.4em;
2835}
2836.faq_block .panel-body li {
2837margin-bottom: .35em;
2838}
2839.faq_block .panel-body em {
2840display: block;
2841margin-top: .9em;
2842color: var(--faq-muted);
2843}
2844/* legacy inline font sizes inside answers (span style="font-size: medium")
2845fight the 14px wrapper — normalize them back to the block's scale */
2846.faq_block .panel-body [style*="font-size"] {
2847font-size: inherit !important;
2848}
2849/* --- search --------------------------------------------------------------- */
2850.faq_block mark,
2851.faq_table mark {
2852padding: 0 1px;
2853background: var(--faq-mark);
2854color: inherit;
2855border-radius: 2px;
2856}
2857#search_faq,
2858[data-faq-search] {
2859width: 100%;
2860max-width: 420px;
2861margin-bottom: calc(var(--faq-gap) * 1.5);
2862padding: 10px 12px;
2863font: inherit;
2864color: inherit;
2865background: #fff;
2866border: 1px solid #dcdcdc;
2867border-radius: 6px;
2868}
2869#search_faq:focus,
2870[data-faq-search]:focus {
2871border-color: #2563eb;
2872outline: 2px solid rgba(37, 99, 235, .25);
2873outline-offset: 0;
2874}
2875/* --- small screens -------------------------------------------------------- */
2876@media (max-width: 480px) {
2877.faq_block .panel-title a {
2878padding: 12px 13px;
2879}
2880.faq_block .panel-body {
2881padding: 12px 13px 14px;
2882}
2883} </style> 
2883<script> /**
2884* FAQ collapse + search shim (no jQuery, no Bootstrap JS, no mark.js)
2885*
2886* Restores the behavior of the legacy CMS FAQ blocks:
2887*   - a[data-toggle="collapse"][href="#id"]  ->  toggles .panel-collapse
2888*   - #search_faq                            ->  filters panels, highlights matches
2889*   - ?search=term in the URL                ->  pre-fills and runs the filter
2890*
2891* Deliberately does NOT define window.jQuery / window.$ — a global jQuery stub
2892* makes third-party widgets take the jQuery code path and break.
2893*
2894* Public API: window.FaqShim.refresh() — re-scan after injecting markup (ESI,
2895* Alpine, AJAX). Not needed for clicks; those are delegated on document.
2896*/
2897(function (window, document) {
2898'use strict';
2899var TRIGGER_SEL = '[data-toggle="collapse"]';
2900var PANEL_SEL = '.panel';
2901var TITLE_SEL = '.panel-title a';
2902var SEARCH_SEL = '#search_faq, [data-faq-search]';
2903var SCOPE_SEL = '.faq_block, .faq_table, .panel-group';
2904var DURATION = 250;
2905var originalTitles = new WeakMap();
2906var ready = false;
2907/* ---------------------------------------------------------- styles ---- */
2908function injectStyles() {
2909if (document.getElementById('faq-shim-css')) return;
2910var style = document.createElement('style');
2911style.id = 'faq-shim-css';
2912style.textContent = [
2913/* structural fallback so panels are closed even if Bootstrap CSS is gone */
2914'.faq_block .panel-collapse:not(.in),',
2915'.faq_table .panel-collapse:not(.in),',
2916'[data-faq-collapse]:not(.in){display:none}',
2917'[data-faq-collapse].in{display:block}',
2918'[data-faq-collapse][data-faq-collapse]{overflow:hidden;visibility:visible}',
2919'[data-faq-collapse].faq-animating{transition:height ' + DURATION + 'ms ease}',
2920'@media (prefers-reduced-motion:reduce){',
2921'[data-faq-collapse].faq-animating{transition:none}}'
2922].join('');
2923(document.head || document.documentElement).appendChild(style);
2924}
2925/* ------------------------------------------------------- targeting ---- */
2926function targetOf(trigger) {
2927var sel = trigger.getAttribute('data-target') || trigger.getAttribute('href') || '';
2928if (!sel || sel === '#') return null;
2929// Duplicate ids across repeated CMS blocks are likely, so look inside the
2930// trigger's own FAQ scope first and only then fall back to the document.
2931var scope = trigger.closest(SCOPE_SEL);
2932var el = null;
2933try {
2934if (scope) el = scope.querySelector(sel);
2935if (!el) el = document.querySelector(sel);
2936} catch (e) {
2937return null;
2938}
2939return el;
2940}
2941function claim(el) {
2942if (!el || el.hasAttribute('data-faq-collapse')) return el;
2943el.setAttribute('data-faq-collapse', '');
2944// `collapse` is a Tailwind utility (visibility: collapse) as well as a
2945// legacy Bootstrap hook. Nothing needs it now — .in and the data attribute
2946// carry the state — so drop it rather than fight it on specificity.
2947el.classList.remove('collapse');
2948if (!el.id) el.id = 'faq-c-' + Math.random().toString(36).slice(2, 9);
2949return el;
2950}
2951function syncAria(target) {
2952var open = target.classList.contains('in');
2953var triggers = document.querySelectorAll(
2954TRIGGER_SEL + '[href="#' + target.id + '"], ' +
2955TRIGGER_SEL + '[data-target="#' + target.id + '"]'
2956);
2957for (var i = 0; i < triggers.length; i++) {
2958triggers[i].setAttribute('aria-expanded', open ? 'true' : 'false');
2959triggers[i].setAttribute('aria-controls', target.id);
2960if (triggers[i].tagName === 'A') triggers[i].setAttribute('role', 'button');
2961}
2962}
2963/* ------------------------------------------------------ open/close ---- */
2964function settle(el) {
2965if (el._faqTimer) { clearTimeout(el._faqTimer); el._faqTimer = null; }
2966if (el._faqEnd) { el.removeEventListener('transitionend', el._faqEnd); el._faqEnd = null; }
2967el.classList.remove('faq-animating');
2968el.style.height = '';
2969}
2970function finish(el, cb) {
2971var done = function (e) {
2972if (e && e.target !== el) return;
2973settle(el);
2974if (cb) cb();
2975};
2976el._faqEnd = done;
2977el.addEventListener('transitionend', done);
2978el._faqTimer = setTimeout(done, DURATION + 60);
2979}
2980function open(el, animate) {
2981claim(el);
2982settle(el);
2983el.classList.add('in');
2984if (animate === false) { syncAria(el); return; }
2985el.style.height = '0px';
2986el.classList.add('faq-animating');
2987void el.offsetHeight;                       // force reflow
2988el.style.height = el.scrollHeight + 'px';
2989finish(el);
2990syncAria(el);
2991}
2992function close(el, animate) {
2993claim(el);
2994settle(el);
2995if (animate === false) {
2996el.classList.remove('in');
2997syncAria(el);
2998return;
2999}
3000el.style.height = el.scrollHeight + 'px';
3001el.classList.add('faq-animating');
3002void el.offsetHeight;
3003el.style.height = '0px';
3004finish(el, function () { el.classList.remove('in'); syncAria(el); });
3005}
3006function toggle(el) {
3007if (el.classList.contains('in')) close(el); else open(el);
3008}
3009/* --------------------------------------------------------- marking ---- */
3010function unmark(link) {
3011var original = originalTitles.get(link);
3012if (original !== undefined && link.innerHTML !== original) link.innerHTML = original;
3013}
3014function mark(link, term) {
3015if (!originalTitles.has(link)) originalTitles.set(link, link.innerHTML);
3016unmark(link);
3017if (!term) return false;
3018var needle = term.toLowerCase();
3019var walker = document.createTreeWalker(link, NodeFilter.SHOW_TEXT, null, false);
3020var nodes = [];
3021var node;
3022while ((node = walker.nextNode())) nodes.push(node);
3023var found = false;
3024for (var i = 0; i < nodes.length; i++) {
3025var text = nodes[i].nodeValue;
3026var hay = text.toLowerCase();
3027var at = hay.indexOf(needle);
3028if (at === -1) continue;
3029var frag = document.createDocumentFragment();
3030var pos = 0;
3031while (at !== -1) {
3032if (at > pos) frag.appendChild(document.createTextNode(text.slice(pos, at)));
3033var m = document.createElement('mark');
3034m.textContent = text.slice(at, at + needle.length);
3035frag.appendChild(m);
3036pos = at + needle.length;
3037at = hay.indexOf(needle, pos);
3038found = true;
3039}
3040if (pos < text.length) frag.appendChild(document.createTextNode(text.slice(pos)));
3041nodes[i].parentNode.replaceChild(frag, nodes[i]);
3042}
3043return found;
3044}
3045/* ---------------------------------------------------------- search ---- */
3046function panels() {
3047var scoped = document.querySelectorAll(
3048'.faq_block ' + PANEL_SEL + ', .faq_table ' + PANEL_SEL + ', .panel-group ' + PANEL_SEL
3049);
3050return scoped.length ? scoped : document.querySelectorAll(PANEL_SEL);
3051}
3052function search(term) {
3053term = (term || '').trim();
3054var list = panels();
3055for (var i = 0; i < list.length; i++) {
3056var panel = list[i];
3057var link = panel.querySelector(TITLE_SEL);
3058var body = panel.querySelector('.panel-collapse');
3059panel.style.display = '';
3060if (link) mark(link, '');
3061if (body) close(claim(body), false);
3062if (!term) continue;
3063var hit = link ? mark(link, term) : false;
3064if (hit) {
3065if (body) open(body, false);
3066} else {
3067panel.style.display = 'none';
3068}
3069}
3070}
3071function paramValue(name) {
3072var match = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.search);
3073if (!match) return '';
3074try {
3075return decodeURIComponent(match[1].replace(/\+/g, ' '));
3076} catch (e) {
3077return match[1];
3078}
3079}
3080/* ------------------------------------------------------------ init ---- */
3081function refresh() {
3082injectStyles();
3083var triggers = document.querySelectorAll(TRIGGER_SEL);
3084for (var i = 0; i < triggers.length; i++) {
3085var target = targetOf(triggers[i]);
3086if (!target) continue;
3087claim(target);
3088syncAria(target);
3089}
3090// deep link: /page?...#collapse84
3091if (window.location.hash.length > 1) {
3092var hashed = null;
3093try { hashed = document.querySelector(window.location.hash); } catch (e) { hashed = null; }
3094if (hashed && hashed.hasAttribute('data-faq-collapse')) open(hashed, false);
3095}
3096}
3097function init() {
3098if (ready) return;
3099ready = true;
3100refresh();
3101var seeded = paramValue('search');
3102var input = document.querySelector(SEARCH_SEL);
3103if (input && seeded) {
3104input.value = seeded;
3105search(seeded);
3106}
3107}
3108// Delegated — survives ESI fragments and anything injected after load.
3109document.addEventListener('click', function (e) {
3110var trigger = e.target.closest ? e.target.closest(TRIGGER_SEL) : null;
3111if (!trigger) return;
3112var target = targetOf(trigger);
3113if (!target) return;
3114e.preventDefault();
3115toggle(claim(target));
3116});
3117document.addEventListener('keydown', function (e) {
3118if (e.key !== 'Enter' && e.key !== ' ') return;
3119var trigger = e.target.closest ? e.target.closest(TRIGGER_SEL) : null;
3120if (!trigger || trigger.tagName === 'A') return;   // anchors fire click natively
3121var target = targetOf(trigger);
3122if (!target) return;
3123e.preventDefault();
3124toggle(claim(target));
3125});
3126document.addEventListener('input', function (e) {
3127if (!e.target.matches || !e.target.matches(SEARCH_SEL)) return;
3128search(e.target.value);
3129});
3130if (document.readyState === 'loading') {
3131document.addEventListener('DOMContentLoaded', init);
3132} else {
3133init();
3134}
3135window.addEventListener('load', refresh);
3136window.FaqShim = { refresh: refresh, open: open, close: close, search: search };
3137})(window, document); </script>
3137</div> <!-promo_free_logo_gift_badge--><div class="widget widget-static-block"><style> /* SimplyStamps | Free Logo Stamp Gift promo badge.
3138Renders in 4 places: homepage Logo Stamps tile, the /rubber-logo-stamps PLP + its 10 sibling logo-stamp sub-category PLPs
3139(banner right of the intro heading, wraps under on mobile), and the title of every PDP carrying the promo option (select_7643). Nowhere else.
3140To end the promo: disable this static block. */
3141.zephyr-builder-block a[href$="/rubber-logo-stamps"] > div:first-child::after,.ss-fg-badge::before{background:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 120 120%22%3E%3Cpath d=%22M60.00 8.00 Q70.51 0.42 77.79 11.14 Q90.25 7.61 93.42 20.17 Q106.35 21.11 105.03 34.00 Q116.85 39.31 111.21 50.97 Q120.50 60.00 111.21 69.03 Q116.85 80.69 105.03 86.00 Q106.35 98.89 93.42 99.83 Q90.25 112.39 77.79 108.86 Q70.51 119.58 60.00 112.00 Q49.49 119.58 42.21 108.86 Q29.75 112.39 26.58 99.83 Q13.65 98.89 14.97 86.00 Q3.15 80.69 8.79 69.03 Q-0.50 60.00 8.79 50.97 Q3.15 39.31 14.97 34.00 Q13.65 21.11 26.58 20.17 Q29.75 7.61 42.21 11.14 Q49.49 0.42 60.00 8.00 Z%22 fill=%22%23002E49%22/%3E%3Ccircle cx=%2260%22 cy=%2260%22 r=%2245.5%22 fill=%22none%22 stroke=%22%23FFFFFF%22 stroke-opacity=%22.55%22 stroke-width=%221.6%22 stroke-dasharray=%223.2 3.2%22/%3E%3Cg transform=%22translate(50.4 23) scale(.8)%22 fill=%22%2355B5E3%22%3E%3Cpath d=%22M12 8C9 8 7.4 6.6 8.3 5.1 9.3 3.4 11.4 4.8 12 8Z%22/%3E%3Cpath d=%22M12 8C15 8 16.6 6.6 15.7 5.1 14.7 3.4 12.6 4.8 12 8Z%22/%3E%3Crect x=%223%22 y=%228%22 width=%2218%22 height=%224.2%22 rx=%221%22/%3E%3Crect x=%224.6%22 y=%2212.2%22 width=%2214.8%22 height=%229.3%22 rx=%221%22/%3E%3Crect x=%2211%22 y=%228%22 width=%222%22 height=%2213.5%22 fill=%22%23002E49%22/%3E%3C/g%3E%3Ctext x=%2260%22 y=%2270%22 text-anchor=%22middle%22 font-family=%22ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Arial,sans-serif%22 font-size=%2228%22 font-weight=%22800%22 fill=%22%23FFFFFF%22 letter-spacing=%22.5%22%3EFREE%3C/text%3E%3Ctext x=%2260%22 y=%2290%22 text-anchor=%22middle%22 font-family=%22ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Arial,sans-serif%22 font-size=%2217%22 font-weight=%22800%22 fill=%22%2355B5E3%22 letter-spacing=%222.5%22%3EGIFT%3C/text%3E%3C/svg%3E") center/contain no-repeat}
3142.zephyr-builder-block a[href$="/rubber-logo-stamps"] > div:first-child{position:relative;width:fit-content;margin-left:auto;margin-right:auto}
3143.zephyr-builder-block a[href$="/rubber-logo-stamps"] > div:first-child::after{content:"";position:absolute;top:-7%;right:-7%;width:38%;height:38%;transform:rotate(-12deg);filter:drop-shadow(0 2px 3px rgba(0,0,0,.28));pointer-events:none;z-index:2}
3144@media (max-width:639px){.zephyr-builder-block a[href$="/rubber-logo-stamps"] > div:first-child::after{top:-8%;right:-8%;width:42%;height:42%}}
3145.ss-fg-badge{position:relative;display:block;cursor:help;outline:none}.ss-fg-badge::before{content:"";position:absolute;inset:0;transform:rotate(-8deg);filter:drop-shadow(0 2px 3px rgba(0,0,0,.25))}
3146.ss-fg-badge:focus-visible{outline:2px solid #55B5E3;outline-offset:3px;border-radius:50%}
3147.ss-fg-h2row{display:flex;flex-wrap:wrap;align-items:center;gap:10px 20px;margin:0 0 12px}
3148.ss-fg-h2row>h2,.ss-fg-h2row>h1{margin:0!important;flex:0 1 auto}
3149.ss-fg-banner{position:relative;display:inline-flex;align-items:center;gap:10px;margin-left:auto;padding:4px 16px 4px 4px;border:1px solid rgba(85,181,227,.6);border-radius:9999px;background:#EAF6FC;color:#002E49;font-size:14px;line-height:20px;cursor:help;outline:none}
3150.ss-fg-banner strong{font-weight:700;text-transform:uppercase;letter-spacing:.04em}
3151.ss-fg-banner:focus-visible{outline:2px solid #55B5E3;outline-offset:2px}
3152.ss-fg-bicon{flex:0 0 30px;width:30px;height:30px;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='16' fill='%23002E49'/%3E%3Cg transform='translate(3.4 2.9) scale(1.05)' fill='%2355B5E3'%3E%3Cpath d='M12 8C9 8 7.4 6.6 8.3 5.1 9.3 3.4 11.4 4.8 12 8Z'/%3E%3Cpath d='M12 8C15 8 16.6 6.6 15.7 5.1 14.7 3.4 12.6 4.8 12 8Z'/%3E%3Crect x='3' y='8' width='18' height='4.2' rx='1'/%3E%3Crect x='4.6' y='12.2' width='14.8' height='9.3' rx='1'/%3E%3Crect x='11' y='8' width='2' height='13.5' fill='%23002E49'/%3E%3C/g%3E%3C/svg%3E") center/contain no-repeat}
3153.ss-fg-banner .ss-fg-tip{right:0}
3154.ss-fg-banner.is-wrapped{margin-left:0;width:100%;border-radius:12px}
3155.ss-fg-title-row{position:relative}
3156.ss-fg-title-row>h1{padding-right:68px}
3157.ss-fg-pdp{position:absolute;top:-10px;right:0;width:58px;height:58px}
3158.ss-fg-tip{position:absolute;top:calc(100% + 10px);z-index:50;width:max-content;max-width:min(320px,calc(100vw - 32px));padding:8px 12px;border-radius:8px;background:#111827;color:#fff;font-size:12px;font-weight:500;line-height:16px;text-align:left;white-space:normal;box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);opacity:0;visibility:hidden;transition:opacity .15s;pointer-events:none}
3159.ss-fg-pdp .ss-fg-tip{right:0}
3160@media (hover:hover){.ss-fg-badge:hover .ss-fg-tip,.ss-fg-banner:hover .ss-fg-tip{opacity:1;visibility:visible}}
3161.ss-fg-badge:focus-visible .ss-fg-tip,.ss-fg-badge.is-open .ss-fg-tip,.ss-fg-banner:focus-visible .ss-fg-tip,.ss-fg-banner.is-open .ss-fg-tip{opacity:1;visibility:visible}
3162@media (max-width:639px){.ss-fg-banner{margin-left:0;width:100%;border-radius:12px}.ss-fg-pdp{width:48px;height:48px;top:-6px}.ss-fg-title-row>h1{padding-right:56px}}
3163@media (prefers-reduced-motion:reduce){.ss-fg-tip{transition:none}} </style> 
3163<script> (function(){
3164var TIP="Free with your logo stamp order. Pick any design and we’ll make it and ship it alongside your custom logo stamp at no extra charge.";
3165function make(extra,banner){
3166var b=document.createElement('span');
3167b.tabIndex=0;
3168if(banner){b.className='ss-fg-banner';b.innerHTML='<span class="ss-fg-bicon" aria-hidden="true"></span><span><strong>Free gift</strong> with every logo stamp order: choose your bonus stamp design <span style="font-weight:700;font-style:italic">for a limited time</span></span>';}
3169else{b.className='ss-fg-badge '+extra;b.setAttribute('role','img');b.setAttribute('aria-label','Free gift with any logo stamp. '+TIP);}
3170var t=document.createElement('span');
3171t.className='ss-fg-tip';
3172t.setAttribute('aria-hidden','true');
3173t.textContent=TIP;
3174b.appendChild(t);
3175b.addEventListener('click',function(e){e.stopPropagation();b.classList.toggle('is-open');});
3176b.addEventListener('keydown',function(e){if(e.key==='Escape'){b.classList.remove('is-open');b.blur();}});
3177document.addEventListener('click',function(){b.classList.remove('is-open');});
3178return b;
3179}
3180function run(){
3181var c=document.body.classList;
3182if(c.contains('catalog-category-view')&&c.contains('category-rubber-logo-stamps')){
3183var h2=document.querySelector('.ss-logo-top > h2');
3184if(h2&&!document.querySelector('.ss-fg-banner')){var row=document.createElement('div');row.className='ss-fg-h2row';h2.parentNode.insertBefore(row,h2);row.appendChild(h2);var bn=make('',true);row.appendChild(bn);
3185var fit=function(){bn.classList.remove('is-wrapped');if(bn.offsetTop>h2.offsetTop+h2.offsetHeight/2)bn.classList.add('is-wrapped');};
3186fit();window.addEventListener('resize',fit);if(window.ResizeObserver)new ResizeObserver(fit).observe(row);}
3187}
3188var SIB=['category-rectangle','category-round','category-square','category-with-text','category-upload-art','category-self-inking','category-traditional-wooden-handle','category-pre-inked-logo','category-loyalty-card-stamps','category-catalog-stamps'];
3189if(c.contains('catalog-category-view')&&SIB.some(function(k){return c.contains(k);})){
3190var h1c=document.querySelector('.flex-1 > h1');
3191if(h1c&&!document.querySelector('.ss-fg-banner')){
3192var rowc=document.createElement('div');rowc.className='ss-fg-h2row';h1c.parentNode.insertBefore(rowc,h1c);rowc.appendChild(h1c);
3193var bnc=make('',true);rowc.appendChild(bnc);
3194var fitc=function(){bnc.classList.remove('is-wrapped');if(bnc.offsetTop>h1c.offsetTop+h1c.offsetHeight/2)bnc.classList.add('is-wrapped');};
3195fitc();window.addEventListener('resize',fitc);if(window.ResizeObserver)new ResizeObserver(fitc).observe(rowc);
3196}
3197}
3198if(c.contains('catalog-product-view')&&document.getElementById('select_7643')){
3199var h1=document.querySelector('.product-header h1');
3200if(h1&&!document.querySelector('.ss-fg-pdp')){h1.parentNode.classList.add('ss-fg-title-row');h1.parentNode.appendChild(make('ss-fg-pdp'));}
3201}
3202}
3203if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',run);else run();
3204})(); </script>
3204</div><div class="widget widget-static-block"><div id="hcb-promo-root" hidden></div> 
3204<script> (function () {
3205/* ======================================================================
3206>>>>>>>>>>>>>>>>>>>>>>  EDIT ZONE START <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3207One list of promo phases. Each runs FROM its start UNTIL its end (Eastern).
3208MESSAGE = banner text (HTML ok — <strong> etc.)   CODE = coupon
3209THRESHOLD = free-ship order minimum   REWARD = what they unlock
3210Dates are Eastern: -04:00 summer, -05:00 winter. Day-end = 23:59:59.
3211====================================================================== */
3212var PROMO = {
3213PHASES: [
3214{ FROM: "2026-08-10T00:00:00-04:00", UNTIL: "2026-08-16T23:59:59-04:00", MESSAGE: "<strong>Free Shipping</strong> on orders $99+", CODE: "FreeShip99", THRESHOLD: 99, REWARD: "free shipping" },
3215{ FROM: "2026-08-17T00:00:00-04:00", UNTIL: "2026-08-23T23:59:59-04:00", MESSAGE: "<strong>Free Shipping</strong> on orders $89+", CODE: "FreeShip89", THRESHOLD: 89, REWARD: "free shipping" },
3216{ FROM: "2026-08-24T00:00:00-04:00", UNTIL: "2026-08-30T23:59:59-04:00", MESSAGE: "<strong>Free Shipping</strong> on orders $75+", CODE: "FreeShip75", THRESHOLD: 75, REWARD: "free shipping" },
3217{ FROM: "2026-08-31T00:00:00-04:00", UNTIL: "2026-09-07T23:59:59-04:00", MESSAGE: "<strong>Free Shipping</strong> on orders $69+", CODE: "FreeShip69", THRESHOLD: 69, REWARD: "free shipping" }
3218],
3219COUNTDOWN_DAYS_BEFORE: 0,          // 0 = NO timer (banner shows message only)
3220LINK_URL: "",                     // no link — banner is not clickable
3221SHOW_CODE: false,                 // native free shipping = no coupon; hide the code chip
3222// Small ⓘ bubble after the banner message. Leave "" to hide it. (Was used to explain
3223// the old coupon deducting $9.99; native free shipping needs no explainer.)
3224INFO_TIP: "",
3225LOAD_GOOGLE_FONT: true,
3226// --- ZEPHYR header banner mount ---
3227HEADER_SELECTOR: "header",         // the fixed Zephyr <header> MOUNT_POSITION: "bottom",          // "bottom" = below the nav (default) · "top" = above the nav rows
3228HEADER_HEIGHT_VAR: "--header-height", // Zephyr offsets <main> by padding-top:var(--header-height)
3229// --- PDP progress bar mount + subtotal source ---
3230GOAL_ENABLED: true,
3231GOAL_MOUNTS: [
3232{ selector: ".price-pipe-container", position: "after" }   // product pages (under the price block)
3233],
3234SUBTOTAL_URL: "/zephyr/cart/getItems",   // cart subtotal endpoint; fetched ONCE per load (heavy)
3235POLL_MS: 1500,
3236COLORS: {
3237banner: { background: "#60B5E5", text: "#0A3550", chipBg: "#FFFFFF", chipText: "#0A3550" },
3238goal:   { text: "#0A3550", track: "#E6EEF3", fill: "#60B5E5", done: "#1A7F37" }
3239}
3240};
3241/* ======================================================================
3242>>>>>>>>>>>>>>>>>>>>>>>  EDIT ZONE END <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3243Do not edit below this line.
3244====================================================================== */
3245if (window.__hcbPromoLoaded) return;
3246window.__hcbPromoLoaded = true;
3247var DAY = 86400000;
3248var P = PROMO.PHASES.map(function (p){ return { from:new Date(p.FROM).getTime(), ts:new Date(p.UNTIL).getTime(), MESSAGE:p.MESSAGE, CODE:p.CODE, THRESHOLD:+p.THRESHOLD, REWARD:p.REWARD }; })
3249.filter(function (p, i){ var ok = !isNaN(p.from) && !isNaN(p.ts) && p.ts > p.from; if (!ok) console.warn("[HCB Promo] skipping phase " + (i+1) + " — bad FROM/UNTIL:", PROMO.PHASES[i]); return ok; })
3250.sort(function (a, b){ return a.from - b.from; });
3251if (!P.length){ console.warn("[HCB Promo] no valid phases — disabled."); return; }
3252var endTs = P.reduce(function (m, p){ return Math.max(m, p.ts); }, 0);
3253var dismissKey = "hcbPromoDismissed:" + endTs;
3254try { if (sessionStorage.getItem(dismissKey)) window.__hcbDismissed = true; } catch (e) {}
3255if (Date.now() >= endTs) return;
3256function esc(s){ return String(s).replace(/[&<>"']/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]; }); }
3257function phaseIndexAt(now){ for (var i=0;i<P.length;i++) if (now >= P[i].from && now <= P[i].ts) return i; return -1; }
3258function pad(n){ return (n < 10 ? '0' : '') + n; }
3259function money(n){ return "$" + (Math.round(n*100)/100).toFixed(2).replace(/\.00$/, ""); }
3260var currentCode = P[Math.max(0, phaseIndexAt(Date.now()))].CODE;
3261var bannerInstances = [], goals = [], cachedUrlVal = null, interval = null;
3262function readSubtotal(){ return cachedUrlVal; }   // populated by pollUrl()
3263function pollUrl(){
3264if (!PROMO.SUBTOTAL_URL) return;
3265fetch(PROMO.SUBTOTAL_URL, {credentials:"include", headers:{'X-Requested-With':'XMLHttpRequest'}}).then(function(r){return r.json();}).then(function(d){
3266if (!d) return;
3267var v = (typeof d.subtotal_numeric !== "undefined") ? parseFloat(d.subtotal_numeric)
3268: (typeof d.subtotal !== "undefined") ? parseFloat(String(d.subtotal).replace(/[^0-9.]/g,"")) : NaN;
3269if (!isNaN(v)){ cachedUrlVal = v; tick(); }
3270}).catch(function(){});
3271}
3272if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start);
3273else start();
3274function start(){
3275if (PROMO.LOAD_GOOGLE_FONT && !document.getElementById('hcb-roboto-font')) {
3276var lk = document.createElement('link'); lk.id='hcb-roboto-font'; lk.rel='stylesheet';
3277lk.href='https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap';
3278document.head.appendChild(lk);
3279}
3280var Cb = PROMO.COLORS.banner, Cg = PROMO.COLORS.goal;
3281var css = ''
3282/* banner — position:relative because it lives INSIDE the fixed Zephyr <header> */
3283+ '.hcb-promo{position:relative;display:flex;align-items:center;justify-content:center;'
3284+   'gap:.6em;padding:9px 40px;font:600 14px/1.2 "Roboto",-apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif;'
3285+   'white-space:nowrap;overflow:hidden;box-sizing:border-box;width:100%}'
3286+ '.hcb-promo *{box-sizing:border-box}'
3287+ '.hcb-promo__main{display:flex;align-items:center;justify-content:center;gap:.6em;min-width:0;color:inherit;text-decoration:none}'
3288+ '.hcb-promo__msg{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;font-weight:400}'
3289+ '.hcb-promo__code{flex:none;cursor:pointer;border:0;border-radius:4px;padding:3px 9px;font:inherit;font-weight:700;letter-spacing:.03em}'
3290+ '.hcb-promo__timer{flex:none;font-variant-numeric:tabular-nums;font-weight:400}'
3291+ '.hcb-promo__timer b{font-weight:400}'
3292+ '.hcb-promo__timer--min{display:none}'
3293/* info ⓘ bubble — inline, height-safe; tooltip is portaled to<body> + fixed so nothing clips/covers it */
3294+ '.hcb-promo__info{position:relative;flex:none;display:inline-flex;align-items:center}'
3295+ '.hcb-promo__ibtn{width:16px;height:16px;border-radius:50%;border:1.5px solid currentColor;color:inherit;background:transparent;font:700 11px/1 "Roboto",Arial,sans-serif;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}'
3296+ '.hcb-promo__itip{position:fixed;transform:translateX(-50%);width:240px;max-width:72vw;background:' + Cb.text + ';color:' + Cb.background + ';font:400 12px/1.4 "Roboto",Arial,sans-serif;padding:8px 10px;border-radius:6px;box-shadow:0 2px 10px rgba(0,0,0,.25);opacity:0;visibility:hidden;transition:opacity .12s;z-index:2147483000;text-align:center;white-space:normal;pointer-events:none}'
3297+ '.hcb-promo__itip::after{content:"";position:absolute;bottom:100%;left:50%;transform:translateX(-50%);border:6px solid transparent;border-bottom-color:' + Cb.text + '}'
3298+ '.hcb-promo__itip.is-open{opacity:1;visibility:visible}'
3299+ '.hcb-promo__x{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:0;color:inherit;opacity:.75;cursor:pointer;font-size:20px;line-height:1;padding:4px 6px}'
3300+ '.hcb-promo__x:hover{opacity:1}'
3301+ '@media (max-width:640px){.hcb-promo{font-size:13px;padding:9px 24px;gap:.35em}.hcb-promo__timer--full{display:none}.hcb-promo__timer--min{display:inline}}'
3302/* goal bar */
3303+ '.hcb-goal{font:600 14px/1.35 "Roboto",-apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif;margin:10px 0;max-width:420px}'
3304+ '.hcb-goal__msg{color:' + Cg.text + ';margin:0 0 6px}.hcb-goal__msg b{font-weight:800}'
3305+ '.hcb-goal__track{height:10px;background:' + Cg.track + ';border-radius:999px;overflow:hidden}'
3306+ '.hcb-goal__fill{height:100%;width:0;background:' + Cg.fill + ';border-radius:999px;transition:width .4s ease}'
3307+ '.hcb-goal.is-done .hcb-goal__msg{color:' + Cg.done + '}.hcb-goal.is-done .hcb-goal__fill{background:' + Cg.done + '}';
3308var style=document.createElement('style'); style.textContent=css; document.head.appendChild(style);
3309/* ---------- build header banner ---------- */
3310var seed = P[Math.max(0, phaseIndexAt(Date.now()))];
3311var linkOpen  = PROMO.LINK_URL ? 'a href="' + esc(PROMO.LINK_URL) + '"' : 'span';
3312var linkClose = PROMO.LINK_URL ? 'a' : 'span';
3313var INFO_TIP  = PROMO.INFO_TIP || "";
3314function bannerMarkup(ph){
3315return '<div class="hcb-promo" role="region" aria-label="Promotion" style="background:' + Cb.background + ';color:' + Cb.text + '">'
3316+ '<' + linkOpen + ' class="hcb-promo__main">'
3317+   '<span class="hcb-promo__msg">' + ph.MESSAGE + '</span>'
3318+   '<span class="hcb-promo__timer hcb-promo__timer--full" aria-hidden="true" style="display:none">Ends in <b data-u="d">00</b>d : <b data-u="h">00</b>h : <b data-u="m">00</b>m : <b data-u="s">00</b>s</span>'
3319+   '<span class="hcb-promo__timer hcb-promo__timer--min" aria-hidden="true" style="display:none"><b data-u="dm">0</b>d <b data-u="h">00</b>:<b data-u="m">00</b>:<b data-u="s">00</b></span>'
3320+ '</' + linkClose + '>'
3321+ (INFO_TIP ? '<span class="hcb-promo__info"><button type="button" class="hcb-promo__ibtn" aria-label="More info">i</button><span class="hcb-promo__itip" role="tooltip">' + esc(INFO_TIP) + '</span></span>' : '')
3322+ (PROMO.SHOW_CODE ? '<button type="button" class="hcb-promo__code" style="background:' + Cb.chipBg + ';color:' + Cb.chipText + '" aria-label="Copy promo code ' + esc(ph.CODE) + '">' + esc(ph.CODE) + '</button>' : '')
3323+ '<button type="button" class="hcb-promo__x" aria-label="Dismiss promotion">&times;</button>'
3324+ '</div>';
3325}
3326var header = document.querySelector(PROMO.HEADER_SELECTOR);
3327var mountHost = header || document.querySelector('main') || document.body;
3328var root = document.createElement('div'); root.className='hcb-promo-root'; root.innerHTML = bannerMarkup(seed);
3329if (PROMO.MOUNT_POSITION === 'top') mountHost.insertBefore(root, mountHost.firstChild);  // above the nav rows
3330else mountHost.appendChild(root);                                                        // below the nav (default)
3331syncHeaderHeight();   // set --header-height now (anti-jump); Zephyr's own observer also maintains it
3332var inst={ root:root, msg:root.querySelector('.hcb-promo__msg'), units:root.querySelectorAll('[data-u]'),
3333fulls:root.querySelectorAll('.hcb-promo__timer--full'), mins:root.querySelectorAll('.hcb-promo__timer--min'),
3334code:root.querySelector('.hcb-promo__code'), x:root.querySelector('.hcb-promo__x') };
3335if (inst.code) inst.code.addEventListener('click', function(e){ e.preventDefault(); e.stopPropagation();
3336var b=inst.code, done=function(){ b.textContent='Copied!'; setTimeout(function(){ b.textContent=currentCode; },1500); };
3337if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(currentCode).then(done,fb);}else{fb();}
3338function fb(){var ta=document.createElement('textarea');ta.value=currentCode;ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.select();try{document.execCommand('copy');done();}catch(err){}document.body.removeChild(ta);} });
3339inst.x.addEventListener('click', function(e){ e.preventDefault(); e.stopPropagation();
3340try{sessionStorage.setItem(dismissKey,'1');}catch(err){}
3341window.__hcbDismissed=true; root.style.display='none'; syncHeaderHeight(); });
3342// info ⓘ tooltip — portal to<body>, position:fixed under the icon
3343var info=root.querySelector('.hcb-promo__info');
3344if(info){
3345var ib=info.querySelector('.hcb-promo__ibtn');
3346var tip=info.querySelector('.hcb-promo__itip');
3347document.body.appendChild(tip);
3348var place=function(){ var r=ib.getBoundingClientRect(); tip.style.left=(r.left+r.width/2)+'px'; tip.style.top=(r.bottom+9)+'px'; };
3349var openTip=function(){ place(); tip.classList.add('is-open'); };
3350var closeTip=function(){ tip.classList.remove('is-open'); };
3351ib.addEventListener('mouseenter', openTip);
3352ib.addEventListener('mouseleave', closeTip);
3353ib.addEventListener('focus', openTip);
3354ib.addEventListener('blur', closeTip);
3355ib.addEventListener('click', function(e){ e.preventDefault(); e.stopPropagation(); if(tip.classList.contains('is-open')) closeTip(); else openTip(); });
3356document.addEventListener('click', function(e){ if(e.target!==ib) closeTip(); });
3357window.addEventListener('scroll', function(){ if(tip.classList.contains('is-open')) place(); }, {passive:true});
3358window.addEventListener('resize', function(){ if(tip.classList.contains('is-open')) place(); });
3359}
3360bannerInstances.push(inst);
3361if (window.__hcbDismissed) root.style.display='none';
3362/* ---------- keep --header-height in sync (includes this banner) ---------- */
3363function syncHeaderHeight(){
3364if (!header) return;
3365var h = Math.round(header.getBoundingClientRect().height);
3366if (h) document.documentElement.style.setProperty(PROMO.HEADER_HEIGHT_VAR, h + 'px');
3367}
3368if (header){
3369if (window.ResizeObserver){ try { new ResizeObserver(syncHeaderHeight).observe(header); } catch(e){} }
3370window.addEventListener('resize', syncHeaderHeight);
3371setTimeout(syncHeaderHeight,300); setTimeout(syncHeaderHeight,1200);
3372}
3373/* ---------- build PDP goal bar (only where the price container exists) ---------- */
3374if (PROMO.GOAL_ENABLED){
3375(PROMO.GOAL_MOUNTS||[]).forEach(function(mnt){
3376var ghost=document.querySelector(mnt.selector); if(!ghost) return;
3377var groot=document.createElement('div'); groot.className='hcb-goal';
3378groot.innerHTML='<p class="hcb-goal__msg"></p><div class="hcb-goal__track"><div class="hcb-goal__fill"></div></div>';
3379var gp=mnt.position;
3380if(gp==='before') ghost.parentNode.insertBefore(groot, ghost);
3381else if(gp==='prepend') ghost.insertBefore(groot, ghost.firstChild);
3382else if(gp==='append') ghost.appendChild(groot);
3383else ghost.parentNode.insertBefore(groot, ghost.nextSibling);
3384goals.push({ root:groot, msg:groot.querySelector('.hcb-goal__msg'), fill:groot.querySelector('.hcb-goal__fill') });
3385});
3386}
3387if (goals.length) pollUrl();       // fetch cart subtotal ONCE (heavy endpoint) — only where a goal bar renders
3388interval=setInterval(tick, 1000);  // 1s clock only (no re-fetch)
3389tick();
3390}
3391/* ---------- single clock drives banner + goal ---------- */
3392var lastIdx = -2;
3393function setUnit(inst,u,val){ for(var i=0;i<inst.units.length;i++) if(inst.units[i].getAttribute('data-u')===u) inst.units[i].textContent=val; }
3394function showTimer(inst,on){ for(var i=0;i<inst.fulls.length;i++) inst.fulls[i].style.display=on?'':'none'; for(var j=0;j<inst.mins.length;j++) inst.mins[j].style.display=on?'':'none'; }
3395function tick(){
3396var now = Date.now();
3397var idx = phaseIndexAt(now);
3398if (now >= endTs || idx === -1){
3399if (interval) clearInterval(interval);
3400bannerInstances.forEach(function(o){ o.root.style.display='none'; });
3401goals.forEach(function(g){ g.root.style.display='none'; });
3402return;
3403}
3404var ph = P[idx];
3405if (idx !== lastIdx){
3406lastIdx = idx; currentCode = ph.CODE;
3407bannerInstances.forEach(function(o){ o.msg.innerHTML=ph.MESSAGE; if(o.code){ o.code.textContent=currentCode; o.code.setAttribute('aria-label','Copy promo code '+currentCode); } });
3408}
3409var ms = ph.ts - now, showT = ms <= PROMO.COUNTDOWN_DAYS_BEFORE*DAY;
3410var d=Math.floor(ms/DAY), h=Math.floor((ms%DAY)/3600000), m=Math.floor((ms%3600000)/60000), s=Math.floor((ms%60000)/1000);
3411bannerInstances.forEach(function(o){
3412if (window.__hcbDismissed) return;
3413if (showT){ setUnit(o,'d',pad(d)); setUnit(o,'dm',d); setUnit(o,'h',pad(h)); setUnit(o,'m',pad(m)); setUnit(o,'s',pad(s)); showTimer(o,true); }
3414else showTimer(o,false);
3415});
3416if (goals.length){
3417var sub=readSubtotal(), pct, html, done=false;
3418var useCode=PROMO.SHOW_CODE;
3419if (sub===null){ pct=0; html='Spend '+money(ph.THRESHOLD)+'+ for <b>'+ph.REWARD+'</b>'+(useCode?' — use code <b>'+ph.CODE+'</b>':''); }
3420else if (sub>=ph.THRESHOLD){ pct=100; done=true; html="You've unlocked <b>"+ph.REWARD+"</b>!"+(useCode?" Use code <b>"+ph.CODE+"</b> at checkout.":""); }
3421else { pct=Math.max(0,Math.min(100, sub/ph.THRESHOLD*100)); html="You're <b>"+money(ph.THRESHOLD-sub)+" away</b> from <b>"+ph.REWARD+"</b>"+(useCode?" (code "+ph.CODE+")":""); }
3422goals.forEach(function(gEl){ gEl.msg.innerHTML=html; gEl.fill.style.width=pct+'%'; gEl.root.classList.toggle('is-done',done); gEl.root.style.display=''; });
3423}
3424}
3425})(); </script>
3425</div><div class="widget widget-static-block"> <style> /* v1.31 -- Roboto promoted to first in the stack to match OpenMage brand.
3426All child elements inherit this via font-family: inherit or unspecified. */
3427.ss-upsell-overlay { position: fixed; inset: 0; background: rgba(15,23,42,0.55); display: flex; align-items: center; justify-content: center; z-index: 99999; padding: 16px; animation: ss-fade-in 180ms ease-out; font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; }
3428@keyframes ss-fade-in { from { opacity: 0; } to { opacity: 1; } }
3429.ss-upsell-modal { background: #fff; border-radius: 12px; max-width: 480px; width: 100%; padding: 32px 28px 24px; position: relative; box-shadow: 0 20px 60px rgba(0,0,0,0.25); animation: ss-slide-up 220ms ease-out; box-sizing: border-box; }
3430@keyframes ss-slide-up { from { transform: translateY(16px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
3431.ss-upsell-close { position: absolute; top: 12px; right: 14px; background: transparent; border: 0; font-size: 28px; line-height: 1; color: #94a3b8; cursor: pointer; padding: 4px 8px; border-radius: 4px; }
3432.ss-upsell-close:hover { color: #475569; background: #f1f5f9; }
3433.ss-upsell-badge { display: inline-block; background: #fef3c7; color: #92400e; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; padding: 4px 10px; border-radius: 999px; margin-bottom: 12px; }
3434/* v1.31 -- title case + Roboto Bold. Letter-spacing removed (was for all-caps). */
3435.ss-upsell-modal h2 { font-family: inherit; font-size: 22px; line-height: 1.25; margin: 0 0 8px; color: #0f172a; font-weight: 700; text-align: center; }
3436/* v1.31 -- subhead sits directly under the headline; smaller, softer color */
3437.ss-upsell-subhead { font-size: 14px; line-height: 1.5; margin: 0 0 16px; color: #475569; font-weight: 400; text-align: center; }
3438/* v1.31 -- 300px image slot, padding removed so GIF fills edge-to-edge.
3439overflow:hidden clips anything spilling outside the rounded corners. */
3440.ss-upsell-image { margin: 0 0 18px; background: #f8fafc; border-radius: 8px; padding: 0; display: flex; align-items: center; justify-content: center; height: 300px; box-sizing: border-box; overflow: hidden; }
3441.ss-upsell-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
3442.ss-upsell-bullets { list-style: none; margin: 0 0 20px; padding: 0; }
3443.ss-upsell-bullets li { position: relative; padding-left: 24px; margin-bottom: 10px; color: #334155; line-height: 1.5; font-size: 14px; }
3444.ss-upsell-bullets li:before { content: "\2713"; position: absolute; left: 0; color: #16a34a; font-weight: 700; }
3445.ss-upsell-price { background: #f8fafc; border-left: 3px solid #16a34a; padding: 12px 14px; margin: 0 0 20px; font-size: 15px; color: #0f172a; border-radius: 4px; }
3446.ss-upsell-price strong { color: #16a34a; font-size: 17px; }
3447.ss-upsell-actions { display: flex; flex-direction: column; gap: 4px; align-items: center; }
3448.ss-upsell-accept { width: 100%; padding: 13px 16px; border-radius: 6px; border: 0; font-size: 15px; font-weight: 600; cursor: pointer; transition: background 120ms ease; font-family: inherit; background: #16a34a; color: #fff; }
3449.ss-upsell-accept:hover { background: #15803d; }
3450.ss-upsell-accept:focus { outline: 2px solid #15803d; outline-offset: 2px; }
3451/* v1.31 -- "No thanks" is now a small centered text link, not a ghost button.
3452Reduces visual competition with the primary Upgrade CTA. */
3453.ss-upsell-decline { background: transparent; color: #64748b; border: 0; padding: 8px 4px; font-size: 13px; font-weight: 500; text-decoration: underline; cursor: pointer; font-family: inherit; }
3454.ss-upsell-decline:hover { color: #334155; text-decoration: underline; }
3455.ss-upsell-decline:focus { outline: 2px solid #64748b; outline-offset: 2px; }
3456/* v1.31 -- config-step Cancel now inherits the small-link style from
3457.ss-upsell-decline to match Step 1's philosophy. Ghost-button CSS
3458override deliberately removed. .ss-config-cancel class kept purely
3459as a JS querySelector hook. */
3460@media (max-width: 480px) { .ss-upsell-modal { padding: 24px 20px 20px; border-radius: 10px; } .ss-upsell-modal h2 { font-size: 18px; } }
3461/* Success state */
3462.ss-upsell-success { text-align: center; padding-top: 40px; }
3463.ss-upsell-success-icon { width: 64px; height: 64px; border-radius: 50%; background: #16a34a; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 36px; line-height: 1; margin: 0 auto 16px; animation: ss-pop 320ms cubic-bezier(0.34, 1.56, 0.64, 1); }
3464@keyframes ss-pop { 0% { transform: scale(0.3); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
3465.ss-upsell-success h2 { color: #15803d; margin-bottom: 8px; }
3466.ss-upsell-success-tagline { color: #334155; font-size: 15px; line-height: 1.5; margin: 0 0 18px; }
3467.ss-upsell-success-cta { color: #94a3b8; font-size: 13px; margin: 0; display: flex; align-items: center; justify-content: center; gap: 8px; }
3468.ss-upsell-spinner { width: 14px; height: 14px; border: 2px solid #cbd5e1; border-top-color: #16a34a; border-radius: 50%;
3468 animation: ss-spin 0.7s linear infinite; }
3469@keyframes ss-spin { to { transform: rotate(360deg); } }
3470.ss-upsell-success-skip { background: transparent; border: 0; color: #16a34a; font-size: 13px; cursor: pointer; padding: 6px 12px; margin-top: 8px; font-weight: 600; text-decoration: underline; }
3471/* Config step (step 2) */
3472.ss-config-intro { color: #475569; font-size: 14px; line-height: 1.5; margin: 0 0 18px; }
3473.ss-config-fields { max-height: 50vh; overflow-y: auto; margin: 0 0 20px; padding-right: 4px; }
3474.ss-opt-field { margin-bottom: 14px; }
3475.ss-opt-field label { display: block; font-weight: 600; font-size: 13px; color: #334155; margin-bottom: 6px; }
3476.ss-opt-field select { width: 100%; padding: 10px 12px; font-size: 14px; border: 1px solid #cbd5e1; border-radius: 6px; background: #fff; font-family: inherit; color: #0f172a; box-sizing: border-box; }
3477.ss-opt-field select:focus { outline: 2px solid #16a34a; outline-offset: 1px; border-color: #16a34a; }
3478.ss-opt-field input[type="text"], .ss-opt-field textarea { width: 100%; padding: 10px 12px; font-size: 14px; border: 1px solid #cbd5e1; border-radius: 6px; font-family: inherit; box-sizing: border-box; color: #0f172a; }
3479.ss-opt-field input[type="text"]:focus, .ss-opt-field textarea:focus { outline: 2px solid #16a34a; outline-offset: 1px; border-color: #16a34a; }
3480.ss-opt-field textarea { resize: vertical; min-height: 60px; }
3481.ss-opt-group-label { display: block; font-weight: 600; font-size: 13px; color: #334155; margin-bottom: 8px; }
3482.ss-opt-radio { display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 14px; color: #334155; cursor: pointer; font-weight: 400; }
3483.ss-opt-radio input { margin: 0; }
3484.ss-config-required { color: #dc2626; margin-left: 3px; }
3485.ss-opt-field-error select,
3486.ss-opt-field-error input[type="text"],
3487.ss-opt-field-error textarea { border-color: #dc2626; box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.12); }
3488.ss-opt-field-error > label,
3489.ss-opt-field-error > .ss-opt-group-label { color: #dc2626; }
3490.ss-opt-error { color: #dc2626; font-size: 12px; margin-top: 6px; font-weight: 500; }
3491/* Variant B / C -- value messaging enhancements */
3492.ss-upsell-badge.ss-upsell-badge-popular { background: #dcfce7; color: #15803d; }
3493.ss-upsell-badge.ss-upsell-badge-discount { background: #fef3c7; color: #92400e; }
3494.ss-upsell-cost-per-use { font-size: 12px; color: #64748b; margin-top: 4px; }
3495.ss-upsell-trust { font-size: 12px; color: #64748b; margin: 0 0 16px; text-align: center; line-height: 1.4; }
3496.ss-upsell-trust .ss-stars { color: #f59e0b; letter-spacing: 1px; }
3497.ss-upsell-discount-banner { background: #fff7ed; border: 1px dashed #f59e0b; color: #92400e; padding: 10px 12px; border-radius: 6px; margin: 0 0 16px; font-size: 13px; font-weight: 600; text-align: center; }
3498.ss-upsell-price-strike { text-decoration: line-through; color: #94a3b8; font-weight: 400; margin-right: 6px; } </style> 
3498<script> (function () {
3499'use strict';
3500var TIER_MAP = [
3501{ tier: 1, name: 'Wood Handle',  match: ['wood handle', 'wood'] },
3502{ tier: 2, name: 'Self-Inking',  match: ['self-inking', 'self inking', 'selfinking'] },
3503{ tier: 3, name: 'Pre-Inked',    match: ['pre-inked', 'pre inked', 'preinked'], subtype: 'pre_inked' },
3504{ tier: 3, name: 'Professional', match: ['professional'],                        subtype: 'professional' }
3505];
3506var STAMP_TYPE_LABEL_RE = /stamp\s*(type|style)/i;
3507var SESSION_ACCEPTED_KEY = 'ss_upsell_accepted_session';
3508var SESSION_DECLINE_KEY  = 'ss_upsell_decline_count';
3509var SUCCESS_DWELL_MS = 1500; // how long the success state stays before submit
3510// After this many declines in a session, the modal stops firing -- respects
3511// a clear "no thanks" signal so we don't pester. Cancels at the config
3512// step count as declines too. Set to 0 for unlimited (no decline cap).
3513var MAX_DECLINES_PER_SESSION = 3;
3514// ============================================================
3515// A/B VARIANT FRAMEWORK -- discount test
3516// ------------------------------------------------------------
3517// Visitors are randomly assigned to A or B (50/50) on first PDP
3518// visit and persisted in localStorage. Same visitor always sees
3519// the same variant. URL override: ?ss_variant=B for QA.
3520//
3521//   A = control: current production modal, no discount.
3522//   B = treatment: same modal + 10% off coupon (UPSELL10) auto-applied.
3523//
3524// Variant content lives in VARIANT_CONFIG -- edit copy/discount here
3525// without touching the rendering logic below.
3526// ============================================================
3527var VARIANT_STORAGE_KEY   = 'ss_upsell_variant';
3528var VARIANT_OVERRIDE_PARAM = 'ss_variant';
3529var VARIANTS = ['A', 'B'];
3530var VARIANT_CONFIG = {
3531// A = CONTROL -- current production modal exactly as-is, no overrides.
3532A: {
3533// Empty = use defaults. Same as live production behavior.
3534},
3535// B = DISCOUNT -- current modal + 10% discount on accept. The ONLY
3536// variable changing vs A is the discount; everything else (badge,
3537// copy, image, trust elements) stays identical so the lift can be
3538// attributed cleanly to the discount.
3539//
3540// Coupon codes are per-transition (keyed by upgrade target tier) so
3541// each transition can have its own Magento Cart Price Rule with its
3542// own conditions/scope. If a code leaks, only that specific upgrade
3543// path is exposed.
3544B: {
3545discountBanner: 'Save 10% on this upgrade -- today only',
3546couponCodes: {
3547'self-inking':  'UPSELL-SI10',   // Wood Handle -> Self-Inking
3548'pre_inked':    'UPSELL-PI10',   // Self-Inking -> Pre-Inked
3549'professional': 'UPSELL-PRO10'   // Self-Inking -> Professional
3550},
3551discountPct: 10
3552}
3553};
3554// If false, the config step skips text inputs and textareas (stamp text
3555// lines, etc). Their values still carry over to the cart -- they just
3556// aren't re-shown for editing in the modal.
3557var INCLUDE_TEXT_FIELDS_IN_CONFIG = false;
3558// DEV TOGGLE -- when true, the modal fires on EVERY eligible ATC, ignoring
3559// the no-badger session lock. Flip to false before production launch so
3560// the lock kicks in after a user accepts an upgrade.
3561var DEV_MODE_ALWAYS_SHOW = false;
3562// ============================================================
3563// CATEGORY TARGETING -- controlled rollout
3564// ------------------------------------------------------------
3565// Use this to limit which product categories show the upsell modal.
3566// Substring match (case-insensitive) against the breadcrumb path.
3567//
3568// TARGET_CATEGORIES -- if non-empty, modal ONLY fires when the
3569//   product's category breadcrumb contains one of these strings.
3570//   Empty array = fire on all categories (default).
3571//
3572// EXCLUDED_CATEGORIES -- modal NEVER fires when the breadcrumb
3573//   contains one of these strings, even if it also matches TARGET.
3574//   Exclusions always win over targets.
3575//
3576// Examples:
3577//   Target Address Stamps only:
3578//     TARGET_CATEGORIES = ['Address Stamps'];
3579//   Target everything EXCEPT custom/specialty stamps:
3580//     EXCLUDED_CATEGORIES = ['Custom', 'Specialty'];
3581//   Target Logo + Notary, exclude any "Bulk" sub-category:
3582//     TARGET_CATEGORIES = ['Logo Stamps', 'Notary Stamps'];
3583//     EXCLUDED_CATEGORIES = ['Bulk'];
3584//
3585// v1.32 ACTION NEEDED: these names came from the pre-Zephyr category
3586// landing page. Zephyr breadcrumbs read differently (a Duncan address
3587// stamp now shows "Address Stamps", not "Business Address Stamps"), so
3588// this whitelist must be re-mapped to the LIVE Zephyr breadcrumb strings
3589// or the modal won't fire on the categories you intend. Verify each on
3590// the live site and update below.
3591// ============================================================
3592var TARGET_CATEGORIES = [
3593'Office Stamps',
3594'Business Address Stamps',
3595'Signature Stamps',
3596'Date Stamps',
3597'Banking Stamps',
3598'Accounting & Tax',
3599'Postage Paid',
3600'Medical',
3601'Inspection',
3602'QR Code',
3603'Secure',
3604'Eco-Friendly',
3605'Checkbox',
3606'Custom Text'
3607];
3608var EXCLUDED_CATEGORIES = [
3609'Architects',
3610'Engineer',
3611'Land Surveyor',
3612'licensed Professional Stamps',
3613'Interior Design',
3614'LLC Stamps',
3615'Real Estate Appraisers'
3616]; // e.g. ['Custom', 'Specialty']
3617// ============================================================
3618// TIER IMAGES -- generic product image shown in the upsell modal
3619// ------------------------------------------------------------
3620// Drop in a URL per upgrade tier. Image appears above the value
3621// checkmarks in step 1 of the modal. Leave a key empty to skip
3622// the image section for that tier (graceful fallback).
3623//
3624// Recommended: ~600x300px, transparent or white background, the
3625// bare stamp photographed straight-on. Hosted anywhere reachable
3626// by the site (Magento media folder, CDN, etc).
3627// ============================================================
3628// v1.31 -- GIF assets, TEMPORARY LOCAL PATHS.
3629// Currently uploaded flat to wysiwyg/SimplyStamps/ with the "upsell-" prefix.
3630// TODO before prod push: move files to wysiwyg/SimplyStamps/upsell/ and rename
3631// to {tier}.gif (dropping the "upsell-" prefix), then update these paths to:
3632//   'self-inking':  'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/upsell/self-inking.gif',
3633//   'pre_inked':    'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/upsell/pre-inked.gif',
3634//   'professional': 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/upsell/professional.gif'
3635var TIER_IMAGES = {
3636'self-inking':  'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/upsell/upsell-self-inking.gif',
3637'pre_inked':    'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/upsell/upsell-pre-inked-v2.gif',
3638'professional': 'https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/upsell/upsell-professional-v2.gif'
3639};
3640// ============================================================
3641// CONFIG STEP -- tier-relevant option filtering
3642// ------------------------------------------------------------
3643// PRIMARY MECHANISM: auto-detect tier-tagged labels.
3644//   For each option label on the form, scan it for any tier name
3645//   ('Pre-Inked', 'Self-Inking', 'Wood Handle', 'Professional').
3646//     - Mentions UPGRADE TARGET tier name -> SHOW
3647//     - Mentions a DIFFERENT tier name    -> HIDE
3648//     - Mentions no tier name             -> SHOW (generic option)
3649//
3650// This handles SimplyStamps's convention of naming options like
3651// "Pre-Inked Sizes", "Self-Inking Sizes", "Ink Color - Pre-Inked"
3652// with no manual config required.
3653//
3654// ESCAPE HATCH: TIER_OPTION_FILTERS below lets you override per tier
3655// for edge cases (labels that don't follow the convention).
3656//   show: []  -- if non-empty, ONLY GENERIC options matching these
3657//               keywords are kept. Tier-tagged auto-detection still runs.
3658//   hide: []  -- additional labels to drop (substring match).
3659//
3660// Inspect what survives for a given product:
3661//   window.SS_UPSELL_DEBUG = true; window.SSUpsell.inspect();
3662// ============================================================
3663var TIER_OPTION_FILTERS = {
3664// All upgrade tiers (Self-Inking, Pre-Inked, Professional) have
3665// built-in pads -- separate ink pad option is Wood-Handle-only.
3666'self_inking':  { hide: ['ink pad', 'pad color'] },
3667// Pre-Inked also has pre-loaded ink, so the extra-bottle option doesn't apply.
3668'pre_inked':    { hide: ['ink pad', 'pad color', 'extra bottle', 'bottle of ink'] },
3669'professional': { hide: ['ink pad', 'pad color'] }
3670};
3671// v1.31 -- Routing flipped: default to Professional, override to Pre-Inked
3672// only if the product name/breadcrumb signals a logo/brand focus.
3673// Rationale: nearly all TARGET_CATEGORIES are professional-use stamps
3674// (office, notary, banking, medical, etc), so Professional is the norm.
3675// Pre-Inked's sweet spot is high-fidelity logo/brand reproduction --
3676// that's the exception, so we key off those signals specifically.
3677var PRE_INKED_KEYWORDS = [
3678'logo', 'brand', 'branding', 'monogram', 'crest', 'emblem'
3679];
3680// v1.31 -- copy refresh per transition. New pattern:
3681//   headline: short, all-caps, question-style attention grabber
3682//   subhead:  one-sentence value prop that reinforces the tier
3683//   bullets:  detail points (kept from v1.30, still useful proof)
3684// If headline is empty string, the<h2> is skipped and subhead renders alone.
3685var COPY = {
3686'1->self-inking': {
3687// Wood Handle -> Self-Inking (teacher buyup). Heading pairs with the
3688// subhead's "ink is already inside" promise -- calls out the ink pad
3689// as the pain point being solved.
3690headline: 'Skip The Ink Pad?',
3691subhead:  'The ink is already inside. Just press down and you get a clean, perfect mark.',
3692bullets: [
3693'No separate ink pad needed -- built right in',
3694'10,000+ impressions before re-inking'
3695]
3696},
3697'2->pre_inked': {
3698headline: 'Serious About Your Brand?',
3699subhead:  'Pre-inked stamps print sharper detail and cleaner lines. Your logo, exactly right every time.',
3700bullets: [
3701'Sharper, crisper detail -- ideal for logos & branding',
3702'Up to 50,000 impressions per fill'
3703]
3704},
3705'2->professional': {
3706headline: 'Are You A Professional?',
3707subhead:  'For desks that stamp all day. Heavier build, thousands of crisp impressions.',
3708bullets: [
3709'Heavy-duty frame engineered for high-volume stamping',
3710'Durable mechanism backed by a long-term warranty'
3711]
3712}
3713};
3714// Success state copy -- keyed the same way as COPY
3715var SUCCESS_COPY = {
3716'1->self-inking': {
3717headline: 'Upgraded to Self-Inking',
3718tagline: 'You\'ll get 10,000+ cleaner impressions without juggling a separate ink pad.'
3719},
3720'2->pre_inked': {
3721headline: 'Upgraded to Pre-Inked',
3722tagline: 'Premium, sharper impressions -- up to 50,000 per fill. A smart pick for branding.'
3723},
3724'2->professional': {
3725headline: 'Upgraded to Professional',
3726tagline: 'Built for daily volume -- durable mechanism backed by a long-term warranty.'
3727}
3728};
3729// Session helpers -- once a user accepts an upgrade, don't badger them again.
3730// DEV_MODE_ALWAYS_SHOW bypasses the lock so the modal fires every ATC.
3731function hasUpgradedThisSession() {
3732if (DEV_MODE_ALWAYS_SHOW) return false;
3733try { return sessionStorage.getItem(SESSION_ACCEPTED_KEY) === '1'; } catch (e) { return false; }
3734}
3735function markUpgradedThisSession() {
3736try { sessionStorage.setItem(SESSION_ACCEPTED_KEY, '1'); } catch (e) {}
3737}
3738// Decline-cap helpers -- count declines (modal dismiss OR cancel at config)
3739// so the modal stops firing once the user has clearly opted out.
3740function getDeclineCount() {
3741try { return parseInt(sessionStorage.getItem(SESSION_DECLINE_KEY) || '0', 10) || 0; }
3742catch (e) { return 0; }
3743}
3744function incrementDeclineCount() {
3745try { sessionStorage.setItem(SESSION_DECLINE_KEY, String(getDeclineCount() + 1)); } catch (e) {}
3746}
3747function hasDeclinedTooMany() {
3748if (DEV_MODE_ALWAYS_SHOW) return false;
3749if (!MAX_DECLINES_PER_SESSION) return false; // 0 = unlimited
3750return getDeclineCount() >= MAX_DECLINES_PER_SESSION;
3751}
3752// ----------------------------------------------------------
3753// Variant helpers (A/B/C framework)
3754// ----------------------------------------------------------
3755function getAssignedVariant() {
3756// URL override wins (for QA)
3757try {
3758var url = new URL(window.location.href);
3759var override = (url.searchParams.get(VARIANT_OVERRIDE_PARAM) || '').toUpperCase();
3760if (VARIANTS.indexOf(override) !== -1) return override;
3761} catch (e) {}
3762// Persisted in localStorage
3763try {
3764var stored = localStorage.getItem(VARIANT_STORAGE_KEY);
3765if (stored && VARIANTS.indexOf(stored) !== -1) return stored;
3766} catch (e) {}
3767// First visit -- random assignment
3768var idx = Math.floor(Math.random() * VARIANTS.length);
3769var variant = VARIANTS[idx];
3770try { localStorage.setItem(VARIANT_STORAGE_KEY, variant); } catch (e) {}
3771log('Assigned new variant:', variant);
3772return variant;
3773}
3774function getVariantConfig(variant) {
3775return VARIANT_CONFIG[variant] || VARIANT_CONFIG.A || {};
3776}
3777// Pick the right coupon code for this upgrade transition. Variant B uses
3778// a couponCodes map keyed by upgrade target ("self-inking", "pre_inked",
3779// "professional"). Legacy single couponCode also supported as fallback.
3780function resolveCouponCode(variantCfg, targetTier) {
3781if (!variantCfg) return null;
3782if (variantCfg.couponCodes && targetTier) {
3783var key = targetTier.subtype ? targetTier.subtype
3784: (targetTier.tier === 2 ? 'self-inking' : null);
3785if (key && variantCfg.couponCodes[key]) return variantCfg.couponCodes[key];
3786}
3787return variantCfg.couponCode || null;
3788}
3789// Small local merge helper -- inline to avoid scope issues with the
3790// init()-scoped mergeParams. Used by both applyVariantCDiscount
3791// (stash phase) and applyDeferredCoupon (apply phase).
3792function _mergeParams(a, b) {
3793var out = {};
3794for (var k in a)  if (Object.prototype.hasOwnProperty.call(a, k))  out[k]  = a[k];
3795for (var k2 in b) if (Object.prototype.hasOwnProperty.call(b, k2)) out[k2] = b[k2];
3796return out;
3797}
3798// v1.30 -- STASH the coupon code (do NOT POST from PDP).
3799// Previously we POSTed to /checkout/cart/couponPost/ from the PDP right
3800// before form.submit. That raced with the concurrent add-to-cart POST
3801// and hit an empty cart in Magento's session, so the coupon never stuck.
3802// Now we save the code + tracking context to sessionStorage; the actual
3803// apply happens on the cart page once the item is in cart (see
3804// applyDeferredCoupon below).
3805//
3806// v1.32 -- On Zephyr the AJAX add opens the cart DRAWER instead of
3807// navigating to /checkout/cart, so the stashed coupon applies the next
3808// time the shopper opens the full cart / checkout (still before purchase).
3809var PENDING_COUPON_KEY = 'ss_upsell_pending_coupon';
3810function applyVariantCDiscount(couponCode, trackingParams) {
3811if (!couponCode) return;
3812try {
3813var payload = {
3814code: couponCode,
3815tracking: trackingParams || {},
3816ts: Date.now()
3817};
3818sessionStorage.setItem(PENDING_COUPON_KEY, JSON.stringify(payload));
3819log('Coupon deferred for cart-page application:', couponCode);
3820} catch (e) {
3821log('Failed to defer coupon:', e);
3822}
3823}
3824// v1.30 -- Apply any pending upsell coupon on the cart page.
3825// Runs at module scope so it fires on every page load, cart or not.
3826// Bails immediately if not on the cart page or no pending coupon.
3827// Clears sessionStorage BEFORE posting to prevent reload loops if
3828// the POST fails or reload happens for an unrelated reason.
3829function applyDeferredCoupon() {
3830// Only run on the cart page
3831if (!/\/checkout\/cart(\/|\?|$)/.test(window.location.pathname + window.location.search)) return;
3832var raw;
3833try { raw = sessionStorage.getItem(PENDING_COUPON_KEY); } catch (e) { return; }
3834if (!raw) return;
3835var data;
3836try { data = JSON.parse(raw); } catch (e) { data = null; }
3837if (!data || !data.code) {
3838try { sessionStorage.removeItem(PENDING_COUPON_KEY); } catch (e) {}
3839return;
3840}
3841// Clear FIRST so a failed apply doesn't cause a reload loop
3842try { sessionStorage.removeItem(PENDING_COUPON_KEY); } catch (e) {}
3843log('Applying deferred coupon on cart page:', data.code);
3844var params = _mergeParams(data.tracking || {}, { coupon_code: data.code });
3845try {
3846var fd = new FormData();
3847fd.append('form_key', getFormKey());
3848fd.append('coupon_code', data.code);
3849fd.append('remove', '0');
3850fetch('/checkout/cart/couponPost/', {
3851method: 'POST',
3852body: fd,
3853credentials: 'same-origin',
3854headers: { 'X-Requested-With': 'XMLHttpRequest' }
3855}).then(function (r) {
3856log('Deferred coupon response:', r.status);
3857if (r.status >= 200 && r.status < 400) {
3858track('upsell_discount_applied', _mergeParams(params, { http_status: r.status }));
3859// Reload cart so the discount appears in the totals.
3860// Small delay so the event has time to fire before navigation.
3861setTimeout(function () {
3862// Guard against loop -- only reload if sessionStorage is now empty
3863var still;
3864try { still = sessionStorage.getItem(PENDING_COUPON_KEY); } catch (e) { still = null; }
3865if (!still) window.location.reload();
3866}, 150);
3867} else {
3868track('upsell_discount_failed', _mergeParams(params, { http_status: r.status, error: 'non-2xx-response' }));
3869}
3870}).catch(function (e) {
3871log('Deferred coupon POST failed:', e);
3872track('upsell_discount_failed', _mergeParams(params, { error: String(e && e.message || e) }));
3873});
3874} catch (e) {
3875log('Deferred coupon apply threw:', e);
3876track('upsell_discount_failed', _mergeParams(params, { error: String(e && e.message || e) }));
3877}
3878}
3879// v1.32b -- Apply a stashed coupon IMMEDIATELY (no page gate, no reload).
3880// On Zephyr the AJAX add opens the cart drawer and never loads /checkout/cart,
3881// so applyDeferredCoupon (cart-page only) might never run -- the shopper could
3882// reach checkout via the drawer and be charged full price on a promised
3883// discount. We call this right after the add's promise resolves, so the cart
3884// already has the item (no v1.30 race) and the coupon lands on the quote.
3885// On success we clear the stash; on failure we LEAVE it so the cart-page
3886// applyDeferredCoupon can still retry as a fallback.
3887function applyPendingCouponNow() {
3888var raw;
3889try { raw = sessionStorage.getItem(PENDING_COUPON_KEY); } catch (e) { return; }
3890if (!raw) return;
3891var data;
3892try { data = JSON.parse(raw); } catch (e) { data = null; }
3893if (!data || !data.code) { try { sessionStorage.removeItem(PENDING_COUPON_KEY); } catch (e) {} return; }
3894log('Applying pending coupon immediately (post-add):', data.code);
3895var params = _mergeParams(data.tracking || {}, { coupon_code: data.code });
3896try {
3897var fd = new FormData();
3898fd.append('form_key', getFormKey());
3899fd.append('coupon_code', data.code);
3900fd.append('remove', '0');
3901fetch('/checkout/cart/couponPost/', {
3902method: 'POST',
3903body: fd,
3904credentials: 'same-origin',
3905headers: { 'X-Requested-With': 'XMLHttpRequest' }
3906}).then(function (r) {
3907log('Immediate coupon response:', r.status);
3908if (r.status >= 200 && r.status < 400) {
3909// Applied -- clear so the cart page doesn't reload to re-apply.
3910try { sessionStorage.removeItem(PENDING_COUPON_KEY); } catch (e) {}
3911track('upsell_discount_applied', _mergeParams(params, { http_status: r.status, applied_from: 'pdp_post_add' }));
3912} else {
3913// Leave stashed -- cart-page applyDeferredCoupon will retry.
3914track('upsell_discount_failed', _mergeParams(params, { http_status: r.status, error: 'non-2xx-response', applied_from: 'pdp_post_add' }));
3915}
3916}).catch(function (e) {
3917log('Immediate coupon POST failed (will retry on cart page):', e);
3918track('upsell_discount_failed', _mergeParams(params, { error: String(e && e.message || e), applied_from: 'pdp_post_add' }));
3919});
3920} catch (e) {
3921log('Immediate coupon apply threw:', e);
3922}
3923}
3924// Run deferred coupon apply at module scope (fires on every page load,
3925// including cart page -- bails if not cart / no pending coupon). This is the
3926// fallback for when the immediate post-add apply (applyPendingCouponNow)
3927// didn't run or didn't succeed.
3928try { applyDeferredCoupon(); } catch (e) { /* never break page load */ }
3929// Record each upgrade so the order-success page can fire upsell_purchased
3930// with order_id + revenue_delta. Stored as an array in sessionStorage;
3931// the success-page snippet reads, dispatches, and clears it.
3932function recordUpgradeForPurchaseTracking(params, qty) {
3933try {
3934var key = 'ss_upsell_pending_purchases';
3935var existing = sessionStorage.getItem(key);
3936var arr = existing ? JSON.parse(existing) : [];
3937var rec = { qty: parseInt(qty, 10) || 1, ts: Date.now() };
3938for (var k in params) if (Object.prototype.hasOwnProperty.call(params, k)) rec[k] = params[k];
3939arr.push(rec);
3940sessionStorage.setItem(key, JSON.stringify(arr));
3941log('Recorded pending upgrade for purchase tracking:', rec);
3942} catch (e) {}
3943}
3944function log() {
3945if (window.SS_UPSELL_DEBUG && window.console) {
3946var args = ['[TierUpsell]'].concat([].slice.call(arguments));
3947console.log.apply(console, args);
3948}
3949}
3950function norm(s) { return (s || '').toLowerCase().trim(); }
3951function findTierForText(text) {
3952var t = norm(text);
3953for (var i = 0; i < TIER_MAP.length; i++) {
3954var tier = TIER_MAP[i];
3955for (var j = 0; j < tier.match.length; j++) {
3956if (t.indexOf(tier.match[j]) !== -1) return tier;
3957}
3958}
3959return null;
3960}
3961// Like findTierForText but returns ALL tiers whose name appears in the text.
3962// Used to detect which tier an option label is tagged for.
3963function findAllTiersInText(text) {
3964var t = norm(text);
3965var matches = [];
3966for (var i = 0; i < TIER_MAP.length; i++) {
3967var tier = TIER_MAP[i];
3968for (var j = 0; j < tier.match.length; j++) {
3969if (t.indexOf(tier.match[j]) !== -1) { matches.push(tier); break; }
3970}
3971}
3972return matches;
3973}
3974function findStampTypeSelect() {
3975var labels = document.querySelectorAll('.product-options label, .product-options-wrapper label, dt label, .field label, label');
3976for (var i = 0; i < labels.length; i++) {
3977var lbl = labels[i];
3978if (!STAMP_TYPE_LABEL_RE.test(lbl.textContent)) continue;
3979var forId = lbl.getAttribute('for');
3980if (forId) {
3981var byId = document.getElementById(forId);
3982if (byId && byId.tagName === 'SELECT') return byId;
3983}
3984var container = lbl.closest && (lbl.closest('.field, dd, dt, li, .input-box, .product-options li')) || lbl.parentElement;
3985if (container) {
3986var sel = container.querySelector('select');
3987if (sel) return sel;
3988}
3989}
3990var selects = document.querySelectorAll('.product-options select, .product-options-wrapper select');
3991for (var k = 0; k < selects.length; k++) {
3992for (var m = 0; m < selects[k].options.length; m++) {
3993if (findTierForText(selects[k].options[m].text)) return selects[k];
3994}
3995}
3996return null;
3997}
3998function getCurrentTier(selectEl) {
3999if (!selectEl || selectEl.selectedIndex < 0) return null;
4000var opt = selectEl.options[selectEl.selectedIndex];
4001var tier = findTierForText(opt.text);
4002return tier ? { tier: tier, optionEl: opt } : null;
4003}
4004// Scan the dropdown -- return which tiers are actually on THIS product.
4005// Keys: tier1, tier2, pre_inked, professional
4006function getAvailableTiers(selectEl) {
4007var available = {};
4008for (var i = 0; i < selectEl.options.length; i++) {
4009var t = findTierForText(selectEl.options[i].text);
4010if (!t) continue;
4011if (t.subtype) available[t.subtype] = t;
4012else available['tier' + t.tier] = t;
4013}
4014return available;
4015}
4016// Available options trump path preference. Preferred T3 first; fall back
4017// to the other T3 before bailing.
4018function getNextTier(currentTier, upsellPath, availableTiers) {
4019if (!currentTier) return null;
4020if (currentTier.tier >= 3) return null;
4021if (currentTier.tier === 1) {
4022return availableTiers.tier2 || null;
4023}
4024if (currentTier.tier === 2) {
4025var preferred = upsellPath === 'professional' ? 'professional' : 'pre_inked';
4026var fallback  = upsellPath === 'professional' ? 'pre_inked'    : 'professional';
4027return availableTiers[preferred] || availableTiers[fallback] || null;
4028}
4029return null;
4030}
4031function findOptionInSelect(selectEl, tierDef) {
4032for (var i = 0; i < selectEl.options.length; i++) {
4033if (findTierForText(selectEl.options[i].text) === tierDef) return selectEl.options[i];
4034}
4035return null;
4036}
4037function parsePriceModifier(text) {
4038if (!text) return 0;
4039var m = text.match(/[+\-]?\s*\$\s*([\d,]+\.\d{2})/);
4040return m ? parseFloat(m[1].replace(/,/g, '')) : 0;
4041}
4042function calcPriceDelta(currentOpt, upgradeOpt) {
4043var cur = parsePriceModifier(currentOpt.text);
4044var up  = parsePriceModifier(upgradeOpt.text);
4045return Math.max(0, up - cur);
4046}
4047function getFormKey() {
4048if (window.FORM_KEY) return window.FORM_KEY;
4049var input = document.querySelector('input[name="form_key"]');
4050return input ? input.value : '';
4051}
4052function getOptionIdFromSelectName(selectName) {
4053var m = (selectName || '').match(/options\[(\d+)\]/);
4054return m ? m[1] : null;
4055}
4056function detectUpsellPath() {
4057var explicit = document.querySelector('[data-upsell-path]');
4058if (explicit) {
4059var val = explicit.getAttribute('data-upsell-path');
4060log('upsell_path from data attribute:', val);
4061return val;
4062}
4063var hay = '';
4064hay += ' ' + (document.body.className || '');
4065var crumbs = document.querySelectorAll('.breadcrumbs a, .breadcrumbs li, nav[aria-label*="readcrumb"] a, nav[aria-label*="readcrumb"] li');
4066for (var i = 0; i < crumbs.length; i++) hay += ' ' + (crumbs[i].textContent || '');
4067var h1 = document.querySelector('.product-name h1, h1.product-name, h1[itemprop="name"], h1');
4068if (h1) hay += ' ' + (h1.textContent || '');
4069hay = hay.toLowerCase();
4070// v1.31 -- flipped: check for pre-inked signals (logo/brand focus).
4071// If any pre-inked keyword matches, route to pre_inked. Otherwise
4072// default to professional (aligns with our target category mix).
4073for (var k = 0; k < PRE_INKED_KEYWORDS.length; k++) {
4074if (hay.indexOf(PRE_INKED_KEYWORDS[k]) !== -1) {
4075log('upsell_path = pre_inked (matched keyword:', PRE_INKED_KEYWORDS[k], ')');
4076return 'pre_inked';
4077}
4078}
4079log('upsell_path = professional (default)');
4080return 'professional';
4081}
4082// Check if the current product's category breadcrumb passes the
4083// TARGET_CATEGORIES / EXCLUDED_CATEGORIES filters. Returns true if the
4084// upsell modal should fire for this product.
4085function isCategoryAllowed(categoryBreadcrumb) {
4086var crumb = String(categoryBreadcrumb || '').toLowerCase();
4087// Exclusions always win
4088for (var i = 0; i < EXCLUDED_CATEGORIES.length; i++) {
4089var ex = String(EXCLUDED_CATEGORIES[i]).toLowerCase();
4090if (ex && crumb.indexOf(ex) !== -1) {
4091log('Category excluded by rule:', EXCLUDED_CATEGORIES[i]);
4092return false;
4093}
4094}
4095// If no targets defined, allow all
4096if (TARGET_CATEGORIES.length === 0) return true;
4097// Targets defined -- must match at least one
4098for (var j = 0; j < TARGET_CATEGORIES.length; j++) {
4099var tg = String(TARGET_CATEGORIES[j]).toLowerCase();
4100if (tg && crumb.indexOf(tg) !== -1) return true;
4101}
4102log('Category not in TARGET_CATEGORIES list -- skipping');
4103return false;
4104}
4105function detectCategory() {
4106// v1.32 -- Zephyr renders breadcrumbs as<li><a>...</a></li>. The old
4107// query (".breadcrumbs a, .breadcrumbs li") double-counted every crumb
4108// (e.g. "Address Stamps > Address Stamps > View All > View All"). Collect
4109// ONE entry per crumb (prefer the<li> text, which contains its anchor),
4110// drop Home, and collapse consecutive duplicates.
4111var items = document.querySelectorAll('.breadcrumbs li');
4112if (!items.length) items = document.querySelectorAll('.breadcrumbs a');
4113var trail = [];
4114for (var i = 0; i < items.length; i++) {
4115var t = (items[i].textContent || '').replace(/\s+/g, ' ').trim();
4116if (!t || t.toLowerCase() === 'home') continue;
4117if (trail.length && trail[trail.length - 1].toLowerCase() === t.toLowerCase()) continue;
4118trail.push(t);
4119}
4120return trail.join(' > ');
4121}
4122function track(eventName, payload) {
4123window.dataLayer = window.dataLayer || [];
4124var data = { event: eventName };
4125for (var k in payload) if (Object.prototype.hasOwnProperty.call(payload, k)) data[k] = payload[k];
4126window.dataLayer.push(data);
4127log('Event:', eventName, payload);
4128}
4129// Resolve the image URL for an upgrade tier. Uses subtype for tier 3
4130// (pre_inked / professional) and 'self-inking' for tier 2.
4131function getTierImage(tierDef) {
4132if (!tierDef) return '';
4133var key = tierDef.subtype ? tierDef.subtype
4134: (tierDef.tier === 2 ? 'self-inking' : null);
4135if (!key) return '';
4136return TIER_IMAGES[key] || '';
4137}
4138function buildModal(ctx) {
4139var key = ctx.current.tier.tier === 1 ? '1->self-inking' : '2->' + ctx.next.subtype;
4140var copy = COPY[key];
4141if (!copy) return null;
4142// Resolve variant config (passed via ctx so buildModal stays pure)
4143var vCfg = ctx.variantCfg || {};
4144var overlay = document.createElement('div');
4145overlay.className = 'ss-upsell-overlay';
4146overlay.setAttribute('role', 'presentation');
4147var bulletHtml = copy.bullets.map(function (b) { return '<li>' + b + '</li>'; }).join('');
4148var imgUrl = getTierImage(ctx.next);
4149var imgHtml = imgUrl
4150? '<div class="ss-upsell-image"><img src="' + imgUrl + '" alt="' + escHtml(ctx.next.name) + ' stamp"></div>'
4151: '';
4152// === Variant-aware elements ===
4153// v1.31 -- "Recommended upgrade" chip/badge removed from Step 1 render.
4154// The badge variables + CSS classes remain in the file (dead code) in
4155// case a future variant wants to bring back a chip. To re-enable, add
4156// badgeHtml back into the innerHTML template below.
4157var badgeText  = vCfg.badgeText || 'Recommended upgrade';
4158var badgeClass = vCfg.badgeClass || '';
4159var badgeHtml  = '<div class="ss-upsell-badge ' + badgeClass + '">' + escHtml(badgeText) + '</div>';
4160void badgeHtml; // suppress unused warning; kept for potential future re-enable
4161// Discount banner (variant B only)
4162var discountBannerHtml = vCfg.discountBanner
4163? '<div class="ss-upsell-discount-banner">' + vCfg.discountBanner + '</div>'
4164: '';
4165// Price line -- strikethrough + discounted price for variant C
4166var priceLineHtml;
4167if (vCfg.discountPct && ctx.priceDelta > 0) {
4168var discounted = (ctx.priceDelta * (1 - vCfg.discountPct / 100)).toFixed(2);
4169priceLineHtml =
4170'<p class="ss-upsell-price">' +
4171'Upgrade for ' +
4172'<span class="ss-upsell-price-strike">+$' + ctx.priceDelta.toFixed(2) + '</span>' +
4173'<strong>+$' + discounted + '</strong>' +
4174'</p>';
4175} else {
4176priceLineHtml = '<p class="ss-upsell-price">Upgrade for <strong>+$' + ctx.priceDelta.toFixed(2) + '</strong></p>';
4177}
4178// Cost-per-use line (variant B + C)
4179var costPerUseHtml = '';
4180if (vCfg.showCostPerUse && ctx.priceDelta > 0) {
4181var impressions = ctx.next.subtype === 'pre_inked' ? 50000 : (ctx.next.tier === 2 ? 10000 : 100000);
4182var costPerUse = (ctx.priceDelta / impressions).toFixed(4);
4183costPerUseHtml = '<div class="ss-upsell-cost-per-use">~ $' + costPerUse + ' per impression over the life of this stamp</div>';
4184}
4185// Trust signal (variant B + C)
4186var trustHtml = '';
4187if (vCfg.trustSignal) {
4188var t = vCfg.trustSignal;
4189var parts = [];
4190if (t.ratingValue && t.ratingCount) {
4191parts.push('<span class="ss-stars">&#9733;&#9733;&#9733;&#9733;&#9733;</span> ' + escHtml(t.ratingValue) + ' from ' + escHtml(t.ratingCount) + ' reviews');
4192}
4193if (t.returnPolicy) parts.push(escHtml(t.returnPolicy));
4194if (parts.length) trustHtml = '<div class="ss-upsell-trust">' + parts.join(' &nbsp;|&nbsp; ') + '</div>';
4195}
4196// v1.31 -- optional headline + subhead. If headline is empty (e.g., the
4197// Wood Handle -> Self-Inking transition until copy is finalized),
4198// skip the<h2> so the subhead reads clean on its own.
4199var headlineHtml = copy.headline
4200? '<h2 id="ss-upsell-h">' + escHtml(copy.headline) + '</h2>'
4201: '';
4202var subheadHtml = copy.subhead
4203? '<p class="ss-upsell-subhead">' + escHtml(copy.subhead) + '</p>'
4204: '';
4205overlay.innerHTML =
4206'<div class="ss-upsell-modal" role="dialog" aria-modal="true"' +
4207(copy.headline ? ' aria-labelledby="ss-upsell-h"' : '') + '>' +
4208'<button class="ss-upsell-close" aria-label="Close">&times;</button>' +
4209// v1.31 -- badge removed from render
4210headlineHtml +
4211subheadHtml +
4212imgHtml +
4213discountBannerHtml +
4214'<ul class="ss-upsell-bullets">' + bulletHtml + '</ul>' +
4215priceLineHtml +
4216costPerUseHtml +
4217trustHtml +
4218'<div class="ss-upsell-actions">' +
4219'<button class="ss-upsell-accept" type="button">Upgrade my stamp</button>' +
4220'<button class="ss-upsell-decline" type="button">No thanks</button>' +
4221'</div>' +
4222'</div>';
4223return overlay;
4224}
4225function showModal(ctx) {
4226var overlay = buildModal(ctx);
4227if (!overlay) { log('No copy template for transition -- skipping'); return; }
4228document.body.appendChild(overlay);
4229var accept  = overlay.querySelector('.ss-upsell-accept');
4230var decline = overlay.querySelector('.ss-upsell-decline');
4231var close   = overlay.querySelector('.ss-upsell-close');
4232// v1.32b -- once the user has Accepted, step-1 dismissal must be dead.
4233// Otherwise (Zephyr AJAX path, no navigation) an outside/backdrop click
4234// during the success-state dwell would fire onDecline -> doSubmit and add
4235// the item a SECOND time alongside the success timer's submit.
4236var accepted = false;
4237function dismiss(method) {
4238if (accepted) return;
4239if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
4240document.removeEventListener('keydown', escHandler);
4241ctx.onDecline(method);
4242}
4243function escHandler(e) { if (e.key === 'Escape') dismiss('escape'); }
4244document.addEventListener('keydown', escHandler);
4245accept.addEventListener('click', function () {
4246// Hand the overlay to onAccept -- it decides whether to chain into the
4247// config step, the success state, or simply close.
4248if (accepted) return;              // v1.32b -- ignore double-click on Upgrade
4249accepted = true;
4250document.removeEventListener('keydown', escHandler);
4251ctx.onAccept(overlay);
4252});
4253decline.addEventListener('click', function () { dismiss('decline_button'); });
4254close.addEventListener('click',   function () { dismiss('close_button'); });
4255overlay.addEventListener('click', function (e) { if (e.target === overlay) dismiss('outside_click'); });
4256setTimeout(function () { accept.focus(); }, 50);
4257}
4258// Pick a sane default value for a required field that has no current
4259// value selected. Prefers options labeled "No" (avoids accidentally
4260// adding paid extras like "Yes - +$5.00") over the first non-empty.
4261function autoDefaultFor(field) {
4262if (!field || !field.choices) return '';
4263if (field.currentValue && String(field.currentValue).trim() !== '') return field.currentValue;
4264if (!field.required) return field.currentValue || '';
4265// Pass 1: prefer an option whose text starts with "no"
4266for (var i = 0; i < field.choices.length; i++) {
4267var c = field.choices[i];
4268if (!c.value || c.disabled) continue;
4269if (/^\s*no\b/i.test(c.text)) return c.value;
4270}
4271// Pass 2: first non-empty, non-disabled option
4272for (var j = 0; j < field.choices.length; j++) {
4273var c2 = field.choices[j];
4274if (c2.value && !c2.disabled) return c2.value;
4275}
4276return '';
4277}
4278// Step 2: render the config step inside the existing overlay so the
4279// user can review/adjust their other custom options for the upgrade.
4280function showConfigStep(overlay, ctx, fields, onConfirm, onCancel) {
4281var fieldsHtml = fields.map(function (field, i) {
4282var req = field.required ? ' <span class="ss-config-required">*</span>' : '';
4283var effectiveValue = autoDefaultFor(field);
4284if (field.type === 'select') {
4285var optHtml = field.choices.map(function (c) {
4286var sel = c.value === effectiveValue ? ' selected' : '';
4287var dis = c.disabled ? ' disabled' : '';
4288return '<option value="' + escHtml(c.value) + '"' + sel + dis + '>' + escHtml(c.text) + '</option>';
4289}).join('');
4290return '<div class="ss-opt-field">' +
4291'<label>' + escHtml(field.label) + req + '</label>' +
4292'<select data-field-idx="' + i + '">' + optHtml + '</select>' +
4293'</div>';
4294}
4295if (field.type === 'radio') {
4296var radioHtml = field.choices.map(function (c) {
4297var ch = c.value === effectiveValue ? ' checked' : '';
4298return '<label class="ss-opt-radio">' +
4299'<input type="radio" name="ss-radio-' + i + '" value="' + escHtml(c.value) + '"' + ch + '> ' +
4300escHtml(c.text) +
4301'</label>';
4302}).join('');
4303return '<div class="ss-opt-field" data-field-idx="' + i + '">' +
4304'<div class="ss-opt-group-label">' + escHtml(field.label) + req + '</div>' +
4305radioHtml +
4306'</div>';
4307}
4308if (field.type === 'text') {
4309var maxAttr = field.maxlength ? ' maxlength="' + field.maxlength + '"' : '';
4310return '<div class="ss-opt-field">' +
4311'<label>' + escHtml(field.label) + req + '</label>' +
4312'<input type="text" data-field-idx="' + i + '" value="' + escHtml(field.currentValue) + '"' + maxAttr + '>' +
4313'</div>';
4314}
4315if (field.type === 'textarea') {
4316return '<div class="ss-opt-field">' +
4317'<label>' + escHtml(field.label) + req + '</label>' +
4318'<textarea data-field-idx="' + i + '" rows="3">' + escHtml(field.currentValue) + '</textarea>' +
4319'</div>';
4320}
4321return '';
4322}).join('');
4323// v1.31 -- config-step badge removed to match Step 1 chip removal.
4324// Cancel button now inherits the small-link style (ss-config-cancel
4325// modifier retained for JS querySelector but no longer overrides CSS).
4326overlay.innerHTML =
4327'<div class="ss-upsell-modal" role="dialog" aria-modal="true" aria-labelledby="ss-config-h">' +
4328'<button class="ss-upsell-close" aria-label="Cancel">&times;</button>' +
4329'<h2 id="ss-config-h">Review your options</h2>' +
4330'<p class="ss-config-intro">Confirm the selections for your upgraded stamp.</p>' +
4331'<div class="ss-config-fields">' + fieldsHtml + '</div>' +
4332'<div class="ss-upsell-actions">' +
4333'<button class="ss-upsell-accept" type="button">Confirm upgrade</button>' +
4334'<button class="ss-upsell-decline ss-config-cancel" type="button">Cancel upgrade</button>' +
4335'</div>' +
4336'</div>';
4337var confirmBtn = overlay.querySelector('.ss-upsell-accept');
4338var cancelBtn  = overlay.querySelector('.ss-config-cancel');
4339var closeBtn   = overlay.querySelector('.ss-upsell-close');
4340// v1.32b -- guard against a double-click on Confirm firing two submits
4341// (the Zephyr AJAX add doesn't navigate, so a second one really adds twice).
4342var confirmed = false;
4343confirmBtn.addEventListener('click', function () {
4344if (confirmed) return;
4345var values = {};
4346var missing = [];
4347fields.forEach(function (field, i) {
4348var val = null;
4349if (field.type === 'select') {
4350var s = overlay.querySelector('select[data-field-idx="' + i + '"]');
4351if (s) val = s.value;
4352} else if (field.type === 'radio') {
4353var r = overlay.querySelector('[data-field-idx="' + i + '"] input[type="radio"]:checked');
4354if (r) val = r.value;
4355} else if (field.type === 'text' || field.type === 'textarea') {
4356var ti = overlay.querySelector('[data-field-idx="' + i + '"]');
4357if (ti) val = ti.value;
4358}
4359if (val != null) values[field.name] = val;
4360if (field.required && (val == null || String(val).trim() === '')) {
4361missing.push(i);
4362}
4363});
4364// Clear prior error styling
4365var oldErrs = overlay.querySelectorAll('.ss-opt-field-error');
4366for (var e = 0; e < oldErrs.length; e++) oldErrs[e].classList.remove('ss-opt-field-error');
4367var oldMsgs = overlay.querySelectorAll('.ss-opt-error');
4368for (var m = 0; m < oldMsgs.length; m++) oldMsgs[m].parentNode && oldMsgs[m].parentNode.removeChild(oldMsgs[m]);
4369// Mark missing fields and bail if any
4370if (missing.length > 0) {
4371missing.forEach(function (idx, n) {
4372var el = overlay.querySelector('[data-field-idx="' + idx + '"]');
4373if (!el) return;
4374var fieldEl = el.closest('.ss-opt-field') || el.parentElement;
4375if (!fieldEl) return;
4376fieldEl.classList.add('ss-opt-field-error');
4377var msg = document.createElement('div');
4378msg.className = 'ss-opt-error';
4379msg.textContent = 'Please make a selection';
4380fieldEl.appendChild(msg);
4381if (n === 0) {
4382try { fieldEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch (ex) { fieldEl.scrollIntoView(); }
4383}
4384});
4385track('upsell_config_validation_failed', { missing_count: missing.length });
4386return;
4387}
4388confirmed = true;                  // v1.32b -- lock after a valid confirm
4389onConfirm(values);
4390});
4391cancelBtn.addEventListener('click', function () { if (confirmed) return; onCancel('cancel_button'); });
4392closeBtn.addEventListener('click',  function () { if (confirmed) return; onCancel('close_button'); });
4393setTimeout(function () { confirmBtn.focus(); }, 50);
4394}
4395// Replace the modal contents with a celebratory success state. After
4396// SUCCESS_DWELL_MS, run ctx.onSuccessComplete (which triggers the submit).
4397function showSuccessState(overlay, ctx) {
4398var key = ctx.current.tier.tier === 1 ? '1->self-inking' : '2->' + ctx.next.subtype;
4399var copy = SUCCESS_COPY[key] || { headline: 'Upgrade applied', tagline: 'Your stamp has been upgraded.' };
4400overlay.innerHTML =
4401// v1.31 -- "Premium upgrade applied" chip removed to match Step 1 + Step 2.
4402// Green checkmark icon carries the celebration signal on its own now.
4403'<div class="ss-upsell-modal ss-upsell-success" role="dialog" aria-modal="true" aria-labelledby="ss-upsell-success-h">' +
4404'<div class="ss-upsell-success-icon" aria-hidden="true">&#10003;</div>' +
4405'<h2 id="ss-upsell-success-h">' + copy.headline + '</h2>' +
4406'<p class="ss-upsell-success-tagline">
4406' + copy.tagline + '</p>' +
4407'<p class="ss-upsell-success-cta"><span class="ss-upsell-spinner"></span> Continuing to your cart...</p>' +
4408'<button class="ss-upsell-success-skip" type="button">Continue now</button>' +
4409'</div>';
4410var skipBtn = overlay.querySelector('.ss-upsell-success-skip');
4411var done = false;
4412function finalize() {
4413if (done) return;
4414done = true;
4415// Intentionally DO NOT remove the overlay here. We want the success
4416// state to remain visible while the browser navigates to the cart.
4417// The page transition will replace the modal naturally -- no flicker
4418// of bare PDP between success state and cart load.
4419//
4420// v1.32 note: on Zephyr the AJAX add opens the cart drawer rather than
4421// navigating away, so the success state is removed by handleAjaxAddToCart
4422// /the drawer taking over. It still dwells for SUCCESS_DWELL_MS first.
4423if (typeof ctx.onSuccessComplete === 'function') ctx.onSuccessComplete();
4424}
4425skipBtn.addEventListener('click', finalize);
4426setTimeout(finalize, SUCCESS_DWELL_MS);
4427}
4428// ============================================================
4429// CONFIG STEP -- extract / sync custom options
4430// ============================================================
4431function escHtml(s) {
4432return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
4433}
4434function isElementHidden(el) {
4435if (!el) return true;
4436if (el.type === 'hidden') return true;
4437// Walk up the ancestor chain checking each computed style.
4438// Catches display:none, visibility:hidden, opacity:0 on the element
4439// OR any of its ancestors up to the form.
4440var node = el;
4441while (node && node !== document && node !== document.body) {
4442var s;
4443try { s = window.getComputedStyle(node); } catch (e) { s = null; }
4444if (s) {
4445if (s.display === 'none')        return true;
4446if (s.visibility === 'hidden')   return true;
4447if (parseFloat(s.opacity) === 0) return true;
4448}
4449node = node.parentElement;
4450}
4451// Final fallback -- offsetParent null catches some edge cases
4452if (el.offsetParent === null && el.tagName !== 'BODY') {
4453var rect = el.getBoundingClientRect();
4454if (rect.width === 0 && rect.height === 0) return true;
4455}
4456return false;
4457}
4458function cleanLabel(s) {
4459return String(s || '').replace(/\*/g, '').replace(/:\s*$/, '').replace(/\s+/g, ' ').trim();
4460}
4461function findLabelForFormField(el) {
4462if (!el) return 'Option';
4463if (el.id) {
4464var byFor = document.querySelector('label[for="' + el.id + '"]');
4465if (byFor) return cleanLabel(byFor.textContent);
4466}
4467// Magento default: previous DT contains the label
4468var dd = el.closest && el.closest('dd');
4469if (dd) {
4470var prevDt = dd.previousElementSibling;
4471if (prevDt && prevDt.tagName === 'DT') {
4472var lblInDt = prevDt.querySelector('label') || prevDt;
4473return cleanLabel(lblInDt.textContent);
4474}
4475}
4476var field = el.closest && el.closest('.field, .input-box, li');
4477if (field) {
4478var inLbl = field.querySelector('label.label, label');
4479if (inLbl) return cleanLabel(inLbl.textContent);
4480}
4481return 'Option';
4482}
4483// Pull the form's other custom option fields so we can render them in
4484// the config step. Skips the Stamp Type dropdown and any hidden fields.
4485function extractCustomOptions(form, excludeSelectEl) {
4486var container = form.querySelector('.product-options, .product-options-wrapper, dl.product-options') || form;
4487var fields = [];
4488// Selects
4489var selects = container.querySelectorAll('select[name^="options"]');
4490for (var i = 0; i < selects.length; i++) {
4491var sel = selects[i];
4492if (sel === excludeSelectEl) continue;
4493if (isElementHidden(sel)) continue;
4494var choices = [];
4495for (var j = 0; j < sel.options.length; j++) {
4496var opt = sel.options[j];
4497choices.push({ value: opt.value, text: opt.text, disabled: opt.disabled });
4498}
4499fields.push({
4500type: 'select',
4501name: sel.name,
4502label: findLabelForFormField(sel),
4503currentValue: sel.value,
4504required: sel.hasAttribute('required') || sel.classList.contains('required-entry') || sel.classList.contains('validate-select'),
4505choices: choices
4506});
4507}
4508// Text inputs + textareas (stamp text, line 1, line 2, etc.)
4509// Gated by INCLUDE_TEXT_FIELDS_IN_CONFIG so the modal stays focused on
4510// tier-relevant choices. Text values still carry to the cart untouched.
4511if (INCLUDE_TEXT_FIELDS_IN_CONFIG) {
4512var texts = container.querySelectorAll('input[type="text"][name^="options"], textarea[name^="options"]');
4513for (var t = 0; t < texts.length; t++) {
4514var inp = texts[t];
4515if (isElementHidden(inp)) continue;
4516fields.push({
4517type: inp.tagName === 'TEXTAREA' ? 'textarea' : 'text',
4518name: inp.name,
4519label: findLabelForFormField(inp),
4520currentValue: inp.value,
4521required: inp.hasAttribute('required') || inp.classList.contains('required-entry'),
4522maxlength: inp.maxLength > 0 ? inp.maxLength : null
4523});
4524}
4525}
4526// Radio groups
4527var radios = container.querySelectorAll('input[type="radio"][name^="options"]');
4528var groups = {};
4529for (var k = 0; k < radios.length; k++) {
4530var r = radios[k];
4531if (isElementHidden(r)) continue;
4532if (!groups[r.name]) groups[r.name] = [];
4533groups[r.name].push(r);
4534}
4535for (var name in groups) {
4536if (!Object.prototype.hasOwnProperty.call(groups, name)) continue;
4537var grp = groups[name];
4538var checked = null;
4539var rChoices = [];
4540for (var m = 0; m < grp.length; m++) {
4541if (grp[m].checked) checked = grp[m];
4542rChoices.push({ value: grp[m].value, text: findLabelForFormField(grp[m]) || grp[m].value });
4543}
4544fields.push({
4545type: 'radio',
4546name: name,
4547label: findLabelForFormField(grp[0]),
4548currentValue: checked ? checked.value : '',
4549required: grp[0].classList.contains('validate-one-required-by-name'),
4550choices: rChoices
4551});
4552}
4553return fields;
4554}
4555// Normalize a tier definition into a key for TIER_OPTION_FILTERS.
4556//   Self-Inking -> 'self_inking'
4557//   Pre-Inked   -> 'pre_inked'   (from subtype)
4558//   Professional-> 'professional'
4559function tierFilterKey(tierDef) {
4560if (!tierDef) return null;
4561if (tierDef.subtype) return tierDef.subtype;
4562return String(tierDef.name || '').toLowerCase().replace(/[\s\-]+/g, '_');
4563}
4564// Filter custom option fields to those that apply to the upgrade target.
4565// Logic:
4566//   1. Override `hide` list (if any) drops matches first.
4567//   2. Auto-detect tier mentions in label:
4568//        - mentions target tier name        -> SHOW
4569//        - mentions a different tier's name -> HIDE
4570//        - mentions no tier                 -> fall through to step 3
4571//   3. Generic (no tier in label) options:
4572//        - if override `show` is set, only labels matching survive
4573//        - otherwise, show
4574function filterFieldsForTier(fields, tierDef) {
4575if (!tierDef) return fields;
4576var override = TIER_OPTION_FILTERS[tierFilterKey(tierDef)] || {};
4577var overrideShow = (override.show || []).map(function (s) { return String(s).toLowerCase(); });
4578var overrideHide = (override.hide || []).map(function (s) { return String(s).toLowerCase(); });
4579return fields.filter(function (field) {
4580var label = String(field.label || '').toLowerCase();
4581// Manual hide override wins
4582if (overrideHide.length && overrideHide.some(function (kw) { return label.indexOf(kw) !== -1; })) {
4583return false;
4584}
4585// Auto-detect tier tags in the label
4586var tiersInLabel = findAllTiersInText(label);
4587var mentionsTarget = tiersInLabel.indexOf(tierDef) !== -1;
4588var mentionsOther  = tiersInLabel.some(function (t) { return t !== tierDef; });
4589if (mentionsTarget) return true;          // explicitly tagged for our tier
4590if (mentionsOther)  return false;         // explicitly tagged for a different tier
4591// Generic (no tier tag): apply manual show list if present
4592if (overrideShow.length > 0) {
4593return overrideShow.some(function (kw) { return label.indexOf(kw) !== -1; });
4594}
4595return true;
4596});
4597}
4598// Set the value of a single select-or-multiselect element. For multi-
4599// selects (name ends with "[]"), we must iterate options and set
4600// .selected explicitly -- assigning .value alone is unreliable across
4601// browsers and may leave stale selections active.
4602function setSelectValue(selectEl, targetValue) {
4603if (!selectEl) return;
4604if (selectEl.multiple) {
4605for (var i = 0; i < selectEl.options.length; i++) {
4606selectEl.options[i].selected = (selectEl.options[i].value === targetValue);
4607}
4608} else {
4609selectEl.value = targetValue;
4610}
4611}
4612// Cascade auto-fill: themes that use progressive reveal only show the
4613// next dropdown after the previous one has a value. After we swap
4614// Stamp Type, we may need to fill Size before Ink Color appears, fill
4615// Ink Color before Extra Bottle appears, etc.
4616//
4617// Strategy: find any required dropdown that is visible AND empty,
4618// pick a sane default ("No" if present, else first non-empty option),
4619// dispatch change, wait, repeat until DOM stops revealing new fields.
4620function cascadeAutoFill(form, stampSelect, callback, iter, maxIter) {
4621iter = iter || 0;
4622maxIter = maxIter || 10;
4623if (iter >= maxIter) { log('Cascade hit max iterations (' + maxIter + ')'); callback(); return; }
4624var container = form.querySelector('.product-options, .product-options-wrapper') || form;
4625var selects = container.querySelectorAll('select[name^="options"]');
4626var changedAny = false;
4627for (var i = 0; i < selects.length; i++) {
4628var sel = selects[i];
4629if (sel === stampSelect) continue;
4630if (sel.offsetParent === null) continue;          // skip currently-hidden
4631if (sel.value && String(sel.value).trim() !== '') continue; // already filled
4632var isRequired = sel.hasAttribute('required') ||
4633sel.classList.contains('required-entry') ||
4634sel.classList.contains('validate-select');
4635if (!isRequired) continue;
4636// Pick a sane default -- "No" preferred to avoid paid extras
4637var defaultVal = '';
4638for (var j = 0; j < sel.options.length; j++) {
4639var opt = sel.options[j];
4640if (!opt.value || opt.disabled) continue;
4641if (/^\s*no\b/i.test(opt.text)) { defaultVal = opt.value; break; }
4642if (!defaultVal) defaultVal = opt.value;
4643}
4644if (defaultVal) {
4645log('Cascade fill: ' + sel.name + ' = ' + defaultVal);
4646sel.value = defaultVal;
4647try { sel.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
4648changedAny = true;
4649}
4650}
4651if (!changedAny) { log('Cascade stable at iter ' + iter); callback(); return; }
4652setTimeout(function () { cascadeAutoFill(form, stampSelect, callback, iter + 1, maxIter); }, 300);
4653}
4654// Safety-net pass -- runs RIGHT BEFORE form submit. Iterates one final time
4655// through every visible required empty select/radio and fills with defaults.
4656// Catches any field that cascade missed because the theme revealed it after
4657// cascade exited (chained progressive reveals with long settle times).
4658// Returns the count of fields it filled (for logging).
4659function safetyNetFill(form, stampSelect) {
4660var filled = 0;
4661var container = form.querySelector('.product-options, .product-options-wrapper') || form;
4662// Selects
4663var selects = container.querySelectorAll('select[name^="options"]');
4664for (var i = 0; i < selects.length; i++) {
4665var sel = selects[i];
4666if (sel === stampSelect) continue;
4667if (sel.offsetParent === null) continue;
4668if (sel.value && String(sel.value).trim() !== '') continue;
4669var isRequired = sel.hasAttribute('required') ||
4670sel.classList.contains('required-entry') ||
4671sel.classList.contains('validate-select');
4672if (!isRequired) continue;
4673var defaultVal = '';
4674for (var j = 0; j < sel.options.length; j++) {
4675var opt = sel.options[j];
4676if (!opt.value || opt.disabled) continue;
4677if (/^\s*no\b/i.test(opt.text)) { defaultVal = opt.value; break; }
4678if (!defaultVal) defaultVal = opt.value;
4679}
4680if (defaultVal) {
4681log('Safety-net fill: ' + sel.name + ' = ' + defaultVal);
4682sel.value = defaultVal;
4683try { sel.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
4684filled++;
4685}
4686}
4687// Radio groups
4688var radios = container.querySelectorAll('input[type="radio"][name^="options"]');
4689var groups = {};
4690for (var k = 0; k < radios.length; k++) {
4691var r = radios[k];
4692if (r.offsetParent === null) continue;
4693if (!groups[r.name]) groups[r.name] = [];
4694groups[r.name].push(r);
4695}
4696for (var name in groups) {
4697if (!Object.prototype.hasOwnProperty.call(groups, name)) continue;
4698var grp = groups[name];
4699var hasChecked = false;
4700for (var m = 0; m < grp.length; m++) { if (grp[m].checked) { hasChecked = true; break; } }
4701if (hasChecked) continue;
4702var isReq = grp[0].classList.contains('validate-one-required-by-name');
4703if (!isReq) continue;
4704// Pick first non-disabled radio (or one with "no" label)
4705var pick = null;
4706for (var p = 0; p < grp.length; p++) {
4707if (grp[p].disabled) continue;
4708var lbl = findLabelForFormField(grp[p]) || '';
4709if (/^\s*no\b/i.test(lbl)) { pick = grp[p]; break; }
4710if (!pick) pick = grp[p];
4711}
4712if (pick) {
4713log('Safety-net fill (radio): ' + name + ' = ' + pick.value);
4714pick.checked = true;
4715try { pick.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
4716filled++;
4717}
4718}
4719return filled;
4720}
4721// v1.26 -- Carry-forward: capture user's pre-upgrade visible dropdown
4722// selections (Ink Color, Size, Ink Pad, Extra Ink, etc.) by category.
4723// These are re-applied after cascade so the user's original choice
4724// survives the tier swap, even when the theme reveals a different
4725// dropdown for the new tier (e.g., product 4378: Self-Inking exposes
4726// options[7505] "Ink Color" but Professional exposes options[7504]
4727// "Ink Colors" -- same colors, different option IDs).
4728function categorizeLabel(label) {
4729var s = String(label || '').toLowerCase();
4730if (/ink\s*colou?rs?/.test(s))                return 'ink_color';
4731if (/replacement\s*pad|ink\s*pad|extra.*pad/.test(s)) return 'ink_pad';
4732if (/extra\s*ink|extra.*bottle|bottle.*ink/.test(s))  return 'extra_ink';
4733if (/size/.test(s))                           return 'size';
4734// Fallback -- use full lowercased label as its own category
4735return 'other::' + s.trim();
4736}
4737function captureUserSelections(form, stampSelect) {
4738var captured = {};
4739var container = form.querySelector('.product-options, .product-options-wrapper, dl.product-options') || form;
4740var selects = container.querySelectorAll('select[name^="options"]');
4741for (var i = 0; i < selects.length; i++) {
4742var sel = selects[i];
4743if (sel === stampSelect) continue;
4744if (isElementHidden(sel)) continue;
4745if (!sel.value || String(sel.value).trim() === '') continue;
4746var selectedOpt = sel.options[sel.selectedIndex];
4747if (!selectedOpt) continue;
4748var selectedText = String(selectedOpt.text || '').trim();
4749if (!selectedText) continue;
4750var label = findLabelForFormField(sel) || '';
4751var category = categorizeLabel(label);
4752// First-wins -- if two dropdowns share a category (rare), keep the first
4753if (!captured[category]) {
4754captured[category] = { text: selectedText, sourceLabel: label, sourceName: sel.name };
4755}
4756}
4757return captured;
4758}
4759function applyCarriedForward(form, stampSelect, captured) {
4760var applied = 0;
4761if (!captured) return 0;
4762var container = form.querySelector('.product-options, .product-options-wrapper, dl.product-options') || form;
4763var selects = container.querySelectorAll('select[name^="options"]');
4764for (var i = 0; i < selects.length; i++) {
4765var sel = selects[i];
4766if (sel === stampSelect) continue;
4767if (isElementHidden(sel)) continue;
4768var label = findLabelForFormField(sel) || '';
4769var category = categorizeLabel(label);
4770var wanted = captured[category];
4771if (!wanted) continue;
4772var currentText = sel.options[sel.selectedIndex] ? sel.options[sel.selectedIndex].text : '';
4773// If already matches (e.g., cascade happened to pick right), skip
4774if (String(currentText).trim().toLowerCase() === wanted.text.toLowerCase()) continue;
4775// Find option whose text matches (case-insensitive, trimmed)
4776var matchOpt = null;
4777for (var j = 0; j < sel.options.length; j++) {
4778var opt = sel.options[j];
4779if (!opt.value || opt.disabled) continue;
4780// Strip price suffix like " +$5.00" for match tolerance
4781var optText = String(opt.text || '').replace(/\s*[+\-]?\$[\d.,]+.*$/, '').trim();
4782if (optText.toLowerCase() === wanted.text.replace(/\s*[+\-]?\$[\d.,]+.*$/, '').trim().toLowerCase()) {
4783matchOpt = opt;
4784break;
4785}
4786}
4787if (matchOpt) {
4788log('Carry-forward: ' + sel.name + ' (' + category + ') <- "' + wanted.text + '" from ' + wanted.sourceName);
4789sel.value = matchOpt.value;
4790try { sel.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
4791applied++;
4792} else {
4793log('Carry-forward: no match for "' + wanted.text + '" in ' + sel.name + ' (' + category + ') -- cascade default stays');
4794}
4795}
4796return applied;
4797}
4798function syncValuesToForm(form, values, fields) {
4799fields.forEach(function (field) {
4800var v = values[field.name];
4801if (v == null) return;
4802if (field.type === 'select') {
4803var sel = form.querySelector('select[name="' + field.name + '"]');
4804if (sel) {
4805sel.value = v;
4806try { sel.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
4807}
4808} else if (field.type === 'radio') {
4809var rs = form.querySelectorAll('input[type="radio"][name="' + field.name + '"]');
4810for (var i = 0; i < rs.length; i++) {
4811rs[i].checked = (rs[i].value === v);
4812if (rs[i].checked) {
4813try { rs[i].dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
4814}
4815}
4816} else if (field.type === 'text' || field.type === 'textarea') {
4817var ti = form.querySelector('[name="' + field.name + '"]');
4818if (ti) {
4819ti.value = v;
4820try {
4821ti.dispatchEvent(new Event('input',  { bubbles: true }));
4822ti.dispatchEvent(new Event('change', { bubbles: true }));
4823} catch (e) {}
4824}
4825}
4826});
4827}
4828function init() {
4829var form = document.getElementById('product_addtocart_form');
4830if (!form) { log('Not a PDP (no ATC form) -- skipping init'); return; }
4831var stampSelect = findStampTypeSelect();
4832if (!stampSelect) { log('No Stamp Type dropdown -- skipping init'); return; }
4833var upsellPath = detectUpsellPath();
4834var productInput = form.querySelector('input[name="product"]');
4835var productId    = productInput ? productInput.value : '';
4836var skuMeta      = document.querySelector('meta[itemprop="sku"], [itemprop="sku"]');
4837var productSku   = skuMeta ? (skuMeta.getAttribute('content') || skuMeta.textContent || '').trim() : '';
4838var category     = detectCategory();
4839var variant      = getAssignedVariant();
4840var variantCfg   = getVariantConfig(variant);
4841log('Init', { upsellPath: upsellPath, productId: productId, category: category, variant: variant });
4842function buildContext(cartItemId) {
4843// No-badger: if user already accepted an upgrade this session, skip.
4844if (hasUpgradedThisSession()) { log('buildContext: user already upgraded this session -- skipping'); return null; }
4845// Decline cap: if user has declined too many times this session, respect that and skip.
4846if (hasDeclinedTooMany()) { log('buildContext: decline cap reached (' + getDeclineCount() + '/' + MAX_DECLINES_PER_SESSION + ') -- skipping'); return null; }
4847// Category targeting: skip if this product's category isn't allowed.
4848if (!isCategoryAllowed(category)) { log('buildContext: category not in target/excluded by rule'); return null; }
4849var current = getCurrentTier(stampSelect);
4850if (!current) { log('buildContext: no current tier detected'); return null; }
4851var availableTiers = getAvailableTiers(stampSelect);
4852log('buildContext: available tiers on this product:', Object.keys(availableTiers));
4853var next = getNextTier(current.tier, upsellPath, availableTiers);
4854if (!next) { log('buildContext: no upgrade tier (top tier or no upgrade options on product)'); return null; }
4855var upgradeOpt = findOptionInSelect(stampSelect, next);
4856if (!upgradeOpt) { log('buildContext: upgrade option vanished from dropdown'); return null; }
4857log('buildContext: ' + current.tier.name + ' -> ' + next.name);
4858return {
4859current: current, next: next, upgradeOpt: upgradeOpt,
4860priceDelta: calcPriceDelta(current.optionEl, upgradeOpt),
4861cartItemId: cartItemId,
4862productId: productId, productSku: productSku, category: category, upsellPath: upsellPath
4863};
4864}
4865function mergeParams(a, b) {
4866var out = {}; for (var k in a) out[k] = a[k]; for (var k2 in b) out[k2] = b[k2]; return out;
4867}
4868// ----------------------------------------------------------
4869// ATC interception -- v1.2 PRE-ADD flow:
4870//   Show modal IMMEDIATELY on submit (no AJAX wait).
4871//   On accept: change the Stamp Type dropdown to the upgrade value,
4872//              then call the original submit. One cart write, right SKU.
4873//   On decline: just call the original submit unchanged.
4874//
4875//   v1.32 -- Zephyr's primary path is window.handleAjaxAddToCart (see
4876//   patchAjaxHandler below). The Varien patch + form 'submit' listener
4877//   are kept as fallbacks for any legacy/native path.
4878//   1. Patch window.handleAjaxAddToCart (Zephyr AJAX ATC) -- PRIMARY
4879//   2. Patch window.productAddToCartForm.submit (Varien) -- fallback
4880//   3. Fallback: form 'submit' event listener (non-Varien themes)
4881// ----------------------------------------------------------
4882function buildBaseParams(ctx) {
4883return {
4884from_tier:   ctx.current.tier.name,
4885to_tier:     ctx.next.name,
4886upsell_path: ctx.upsellPath,
4887product_id:  ctx.productId,
4888product_sku: ctx.productSku,
4889category:    ctx.category,
4890price_delta: ctx.priceDelta,
4891variant:     variant
4892};
4893}
4894function showUpsellAndSubmit(ctx, doSubmit) {
4895// v1.32b -- never stack two modals if the ATC button is double-clicked.
4896if (document.querySelector('.ss-upsell-overlay')) { log('Modal already open -- ignoring duplicate trigger'); return; }
4897// v1.32b -- the underlying submit (native AJAX add on Zephyr) must run at
4898// most ONCE per flow. The old code relied on a full-page navigation to
4899// make a stray second call harmless; the AJAX add has no navigation, so
4900// we guard it explicitly. onDecline / onCancel / onSuccessComplete all
4901// route through this wrapper.
4902var _rawSubmit = doSubmit, _submitted = false;
4903doSubmit = function () { if (_submitted) return; _submitted = true; _rawSubmit(); };
4904var baseParams = buildBaseParams(ctx);
4905var originalStampValue = ctx.current.optionEl.value;
4906track('upsell_modal_shown', baseParams);
4907// v1.28 -- Fire discount-side telemetry when variant B modal renders
4908// WITH a coupon code available for the target transition. Signals
4909// "user was exposed to the discount incentive" and lets B-only
4910// reports build without filtering upsell_modal_shown by variant.
4911if (variant === 'B') {
4912var shownCode = resolveCouponCode(variantCfg, ctx.next);
4913if (shownCode) {
4914track('upsell_discount_shown', mergeParams(baseParams, { coupon_code: shownCode }));
4915}
4916}
4917function chainToSuccessAndSubmit(overlay, userPicks, userFields, carriedForward) {
4918showSuccessState(overlay, {
4919current: ctx.current,
4920next: ctx.next,
4921onSuccessComplete: function () {
4922// Final safety net: catch any required field the cascade missed
4923// (e.g., chained progressive reveals where the theme exposes
4924// fields after our cascade exited). Without this, Magento
4925// backend validation can reject the submit and leave the user
4926// stuck on the PDP after the success state closes.
4927var filled = safetyNetFill(form, stampSelect);
4928if (filled > 0) log('Safety net filled ' + filled + ' field(s) before submit');
4929// v1.27 -- Re-apply carry-forward AFTER safety net. When the
4930// user changes Size in the modal, the theme cascade can reveal
4931// a NEW Ink Color dropdown that our initial carry-forward
4932// (which ran pre-modal) never touched. Safety net just filled
4933// it with the default (Black). Now we override that default
4934// with the user's original pre-upgrade pick (Red) if we find
4935// a match by category + text.
4936if (carriedForward) {
4937var reCarried = applyCarriedForward(form, stampSelect, carriedForward);
4938if (reCarried > 0) log('Late carry-forward: ' + reCarried + ' field(s) restored to original pick');
4939}
4940// CRITICAL: Re-apply user's explicit picks LAST. The theme's
4941// progressive reveal cascade can reset dependent options (e.g.,
4942// Ink Color resets when Stamp Type/Size change). Without this
4943// final re-sync, the safety net's defaults overwrite the user's
4944// intended color/option picks, leading to "I picked Red but cart
4945// shows Black" bugs. Direct value writes only -- no change event
4946// dispatch -- to avoid re-triggering the theme's cascade.
4947if (userPicks && userFields) {
4948var reapplied = 0;
4949userFields.forEach(function (field) {
4950var v = userPicks[field.name];
4951if (v == null || v === '') return;
4952if (field.type === 'select') {
4953var sel = form.querySelector('select[name="' + field.name + '"]');
4954if (sel && String(sel.value) !== String(v)) {
4955log('Re-applying user pick: ' + field.name + ' ' + sel.value + ' -> ' + v);
4956sel.value = v;
4957reapplied++;
4958}
4959} else if (field.type === 'radio') {
4960var rs = form.querySelectorAll('input[type="radio"][name="' + field.name + '"]');
4961for (var i = 0; i < rs.length; i++) {
4962var shouldCheck = (rs[i].value === v);
4963if (rs[i].checked !== shouldCheck) {
4964rs[i].checked = shouldCheck;
4965if (shouldCheck) reapplied++;
4966}
4967}
4968} else if (field.type === 'text' || field.type === 'textarea') {
4969var ti = form.querySelector('[name="' + field.name + '"]');
4970if (ti && ti.value !== v) {
4971log('Re-applying user pick: ' + field.name + ' = ' + v);
4972ti.value = v;
4973reapplied++;
4974}
4975}
4976});
4977if (reapplied > 0) log('Re-applied ' + reapplied + ' user pick(s) before submit');
4978}
4979log('Success state complete -- submitting');
4980doSubmit();
4981}
4982});
4983}
4984showModal({
4985current: ctx.current, next: ctx.next, priceDelta: ctx.priceDelta,
4986variantCfg: variantCfg,
4987onAccept: function (overlay) {
4988track('upsell_accepted', baseParams);
4989// v1.26 -- Capture user's pre-upgrade visible selections BEFORE
4990// Stamp Type swap wipes visibility (Ink Color, Size, etc.).
4991// These get carried forward to whichever dropdowns the new tier
4992// reveals, matched by category + option text (e.g., "Red").
4993var carriedForward = captureUserSelections(form, stampSelect);
4994log('Captured for carry-forward:', carriedForward);
4995// Multiselect-safe Stamp Type swap (handles options[1508][] style)
4996setSelectValue(stampSelect, ctx.upgradeOpt.value);
4997try { stampSelect.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
4998log('Stamp Type swapped to:', ctx.upgradeOpt.text);
4999// Cascade auto-fill (themes with progressive reveal only show the
5000// next dropdown after the previous has a value). Then extract.
5001setTimeout(function () {
5002cascadeAutoFill(form, stampSelect, function () {
5003// v1.26 -- Apply carry-forward BEFORE extraction so the modal
5004// shows user's original picks as pre-selected values. User
5005// can still override in the modal if they want to change.
5006var carriedCount = applyCarriedForward(form, stampSelect, carriedForward);
5007if (carriedCount > 0) log('Carried forward ' + carriedCount + ' user selection(s) to new tier');
5008var allFields = extractCustomOptions(form, stampSelect);
5009var filtered  = filterFieldsForTier(allFields, ctx.next);
5010// Dedupe by label -- products sometimes have multiple custom
5011// options with identical labels (one per tier variant). Without
5012// tier hints in the label we can't distinguish them, so keep
5013// the first and drop the rest.
5014var seenLabels = {};
5015var fields = filtered.filter(function (f) {
5016var key = String(f.label || '').toLowerCase().trim();
5017if (!key) return true;
5018if (seenLabels[key]) return false;
5019seenLabels[key] = true;
5020return true;
5021});
5022log('Extracted ' + allFields.length + ' option(s), ' +
5023filtered.length + ' apply to ' + ctx.next.name + ', ' +
5024fields.length + ' after dedupe');
5025// Read qty from the form (used in revenue_delta calc later)
5026var qtyInput = form.querySelector('input[name="qty"]');
5027var qty = qtyInput ? qtyInput.value : 1;
5028if (fields.length === 0) {
5029// No other options to configure -- straight to success
5030markUpgradedThisSession();
5031track('upsell_confirmed', mergeParams(baseParams, { config_skipped: true }));
5032recordUpgradeForPurchaseTracking(baseParams, qty);
5033var skipCode = resolveCouponCode(variantCfg, ctx.next);
5034if (skipCode) {
5035log('Applying coupon for transition -> ' + ctx.next.name + ': ' + skipCode);
5036applyVariantCDiscount(skipCode, baseParams);
5037}
5038chainToSuccessAndSubmit(overlay, null, null, carriedForward);
5039return;
5040}
5041showConfigStep(overlay, ctx, fields,
5042function onConfirm(values) {
5043track('upsell_confirmed', mergeParams(baseParams, { config_field_count: fields.length }));
5044markUpgradedThisSession();
5045syncValuesToForm(form, values, fields);
5046recordUpgradeForPurchaseTracking(baseParams, qty);
5047var confirmCode = resolveCouponCode(variantCfg, ctx.next);
5048if (confirmCode) {
5049log('Applying coupon for transition -> ' + ctx.next.name + ': ' + confirmCode);
5050applyVariantCDiscount(confirmCode, baseParams);
5051}
5052// Pass user picks + fields so they get re-applied AFTER
5053// safetyNetFill, defeating theme cascade resets. Also pass
5054// carriedForward for v1.27's late-cascade rescue: if user
5055// changed Size in modal and theme revealed a NEW Ink Color
5056// dropdown, restore original pre-upgrade pick.
5057chainToSuccessAndSubmit(overlay, values, fields, carriedForward);
5058},
5059function onCancel(method) {
5060incrementDeclineCount();
5061track('upsell_canceled_at_config', mergeParams(baseParams, { cancel_method: method, decline_count: getDeclineCount() }));
5062// Revert Stamp Type to original -- user bailed at config
5063setSelectValue(stampSelect, originalStampValue);
5064try { stampSelect.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
5065if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
5066log('Canceled at config -- count:', getDeclineCount(), '-- submitting original selection');
5067doSubmit();
5068}
5069);
5070});  // end cascadeAutoFill callback
5071}, 100);
5072},
5073onDecline: function (method) {
5074incrementDeclineCount();
5075track('upsell_declined', mergeParams(baseParams, { dismiss_method: method, decline_count: getDeclineCount() }));
5076log('Declined (' + method + ') -- count:', getDeclineCount(), '-- submitting original selection');
5077doSubmit();
5078}
5079});
5080}
5081// v1.32 -- PRIMARY Zephyr interception. The ATC button on Zephyr is
5082// <button type="button" onclick="handleAjaxAddToCart(this); return false;"> // so it never reaches Varien's submit or a native form submit event.
5083// We wrap the global handler: validate (Varien validator still exists),
5084// build the upsell context, and if an upgrade is available show the modal
5085// first. The original AJAX add runs only after accept/decline, by which
5086// point the Stamp Type has been swapped (on accept). handleAjaxAddToCart
5087// re-serializes the form when it runs, so the upgraded selection is added.
5088function patchAjaxHandler() {
5089var fn = window.handleAjaxAddToCart;
5090if (typeof fn !== 'function') return false;
5091if (fn._ssTierPatched) return true;
5092var orig = fn;
5093window.handleAjaxAddToCart = function (button) {
5094var args = arguments;
5095function proceed() { return orig.apply(window, args); }
5096// Respect Magento/Varien validation first. handleAjaxAddToCart runs
5097// the same validator internally; if the form isn't valid, hand off to
5098// the original so it surfaces the field errors (and we don't pop the
5099// modal on an incomplete form).
5100try {
5101var paf = window.productAddToCartForm;
5102if (paf && paf.validator && typeof paf.validator.validate === 'function' && !paf.validator.validate()) {
5103log('Validator failed -- passing through to native AJAX add');
5104return proceed();
5105}
5106} catch (e) {}
5107var ctx = buildContext(null);
5108if (!ctx) { log('No upsell context -- native AJAX add'); return proceed(); }
5109log('Upsell available -- showing modal before AJAX add (Zephyr path)');
5110showUpsellAndSubmit(ctx, function () {
5111// Run the native AJAX add, then clean up. Unlike the old Varien
5112// path this does NOT navigate, so WE must remove the success
5113// overlay (otherwise its full-screen fixed layer covers the page
5114// forever) and apply any stashed variant-B coupon now that the
5115// item is in the cart (the drawer never loads /checkout/cart).
5116var r;
5117try { r = proceed(); } catch (e) { r = null; }
5118var _done = false;
5119var done = function () {
5120if (_done) return; _done = true;   // idempotent -- promise OR timeout, whichever first
5121var o = document.querySelector('.ss-upsell-overlay');
5122if (o && o.parentNode) o.parentNode.removeChild(o);
5123try { applyPendingCouponNow(); } catch (e) {}
5124};
5125var thenable = r && typeof r.then === 'function';
5126if (thenable) { r.then(done, done); }
5127// Always arm a max-timeout too: if the add promise is non-thenable
5128// (short delay) OR never settles (6s hang guard), we still remove the
5129// overlay so it can't cover the page or suppress future modals.
5130setTimeout(done, thenable ? 6000 : 1200);
5131});
5132return false; // block the immediate add; proceed() runs after the modal resolves
5133};
5134window.handleAjaxAddToCart._ssTierPatched = true;
5135log('Patched window.handleAjaxAddToCart (Zephyr AJAX ATC)');
5136return true;
5137}
5138(function pollForAjaxHandler(attempts) {
5139attempts = attempts || 0;
5140if (patchAjaxHandler()) return;
5141if (attempts >= 30) { log('Gave up polling for handleAjaxAddToCart after 3s'); return; }
5142setTimeout(function () { pollForAjaxHandler(attempts + 1); }, 100);
5143})();
5144function patchVarienForm() {
5145var paf = window.productAddToCartForm;
5146if (!paf || typeof paf.submit !== 'function' || paf._ssTierPatched) return false;
5147var origSubmit = paf.submit.bind(paf);
5148paf.submit = function (button, url) {
5149if (this.validator && typeof this.validator.validate === 'function' && !this.validator.validate()) {
5150log('Magento validator failed -- letting native submit handle the error UI');
5151return origSubmit(button, url);
5152}
5153var ctx = buildContext(null);
5154if (!ctx) {
5155log('No upsell context -- proceeding with native ATC');
5156return origSubmit(button, url);
5157}
5158log('Upsell available -- showing modal pre-add');
5159showUpsellAndSubmit(ctx, function () { origSubmit(button, url); });
5160};
5161paf._ssTierPatched = true;
5162log('Patched window.productAddToCartForm.submit');
5163return true;
5164}
5165(function pollForVarien(attempts) {
5166attempts = attempts || 0;
5167if (patchVarienForm()) return;
5168if (attempts >= 30) { log('Gave up polling for productAddToCartForm after 3s'); return; }
5169setTimeout(function () { pollForVarien(attempts + 1); }, 100);
5170})();
5171// Fallback for non-Varien / Enter-key submission
5172form.addEventListener('submit', function (e) {
5173if (form._ssBypass) { form._ssBypass = false; return; } // we already showed modal
5174if (window.productAddToCartForm && window.productAddToCartForm._ssTierPatched) return;
5175// If the Zephyr AJAX handler is patched, it owns the flow -- don't
5176// double-fire the modal from a native submit that may also occur.
5177if (window.handleAjaxAddToCart && window.handleAjaxAddToCart._ssTierPatched) return;
5178var ctx = buildContext(null);
5179if (!ctx) return;
5180e.preventDefault();
5181log('Submit event intercepted (fallback path) -- showing modal pre-add');
5182showUpsellAndSubmit(ctx, function () {
5183form._ssBypass = true;
5184form.submit();
5185});
5186});
5187window.SSUpsell = {
5188showTestModal: function () {
5189var ctx = buildContext('TEST-CART-ITEM');
5190if (!ctx) { alert('No upsell available. Current: ' + (stampSelect.options[stampSelect.selectedIndex] || {}).text); return; }
5191var baseParams = { from_tier: ctx.current.tier.name, to_tier: ctx.next.name, upsell_path: ctx.upsellPath, product_id: ctx.productId, price_delta: ctx.priceDelta };
5192track('upsell_modal_shown', baseParams);
5193showModal({
5194current: ctx.current, next: ctx.next, priceDelta: ctx.priceDelta,
5195variantCfg: variantCfg,
5196onAccept:  function (overlay) {
5197track('upsell_accepted', baseParams);
5198if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
5199alert('Accept (test mode -- no cart write)');
5200},
5201onDecline: function (m) { track('upsell_declined', mergeParams(baseParams, { dismiss_method: m })); }
5202});
5203},
5204inspect: function () {
5205var ctx = buildContext(null);
5206var available = getAvailableTiers(stampSelect);
5207var currentTierLive = getCurrentTier(stampSelect);
5208// Dry-run the config-step extraction + filter so you can see what
5209// the user WOULD see if they accepted right now.
5210var optsPreview = null;
5211if (ctx) {
5212var all = extractCustomOptions(form, stampSelect);
5213var kept = filterFieldsForTier(all, ctx.next);
5214var seenL = {};
5215var deduped = kept.filter(function (f) {
5216var k = String(f.label || '').toLowerCase().trim();
5217if (!k) return true;
5218if (seenL[k]) return false;
5219seenL[k] = true;
5220return true;
5221});
5222optsPreview = {
5223extracted: all.map(function (f) { return f.label; }),
5224keptAfterFilter: kept.map(function (f) { return f.label; }),
5225shownToUser: deduped.map(function (f) { return f.label; }),
5226filterRule: TIER_OPTION_FILTERS[tierFilterKey(ctx.next)] || '(none -- show all)'
5227};
5228}
5229return {
5230upsellPath: upsellPath,
5231productId: productId,
5232productSku: productSku,
5233category: category,
5234currentTier: currentTierLive ? currentTierLive.tier.name : null,
5235nextTier:    ctx ? ctx.next.name : null,
5236priceDelta:  ctx ? ctx.priceDelta : null,
5237availableOnProduct: Object.keys(available),
5238ajaxHandlerPatched: !!(window.handleAjaxAddToCart && window.handleAjaxAddToCart._ssTierPatched),
5239ajaxHandlerPresent: typeof window.handleAjaxAddToCart === 'function',
5240varienFormPatched: !!(window.productAddToCartForm && window.productAddToCartForm._ssTierPatched),
5241varienFormPresent: !!window.productAddToCartForm,
5242alreadyUpgradedThisSession: hasUpgradedThisSession(),
5243declineCount: getDeclineCount(),
5244maxDeclinesPerSession: MAX_DECLINES_PER_SESSION,
5245declineCapReached: hasDeclinedTooMany(),
5246variant: variant,
5247variantConfig: variantCfg,
5248devModeAlwaysShow: DEV_MODE_ALWAYS_SHOW,
5249categoryAllowed: isCategoryAllowed(category),
5250targetCategories: TARGET_CATEGORIES,
5251excludedCategories: EXCLUDED_CATEGORIES,
5252configStepPreview: optsPreview
5253};
5254},
5255// QA helper -- clear the session flags (upgrade lock + decline count) so the modal fires again
5256resetSession: function () {
5257try {
5258sessionStorage.removeItem(SESSION_ACCEPTED_KEY);
5259sessionStorage.removeItem(SESSION_DECLINE_KEY);
5260} catch (e) {}
5261console.log('[TierUpsell] Session reset -- modal will fire on next ATC');
5262},
5263// QA helpers for variant testing
5264setVariant: function (v) {
5265v = String(v || '').toUpperCase();
5266if (VARIANTS.indexOf(v) === -1) {
5267console.warn('Invalid variant. Use one of:', VARIANTS);
5268return;
5269}
5270try { localStorage.setItem(VARIANT_STORAGE_KEY, v); } catch (e) {}
5271console.log('[TierUpsell] Variant set to:', v, '-- refresh the page to see changes');
5272},
5273clearVariant: function () {
5274try { localStorage.removeItem(VARIANT_STORAGE_KEY); } catch (e) {}
5275console.log('[TierUpsell] Variant cleared -- next page load will assign a new one');
5276},
5277simulateATC: function () {
5278var ctx = buildContext('TEST-CART-ITEM');
5279if (!ctx) { alert('No upsell available right now.'); return; }
5280var baseParams = {
5281from_tier: ctx.current.tier.name, to_tier: ctx.next.name,
5282upsell_path: ctx.upsellPath, product_id: ctx.productId,
5283product_sku: ctx.productSku, category: ctx.category, price_delta: ctx.priceDelta
5284};
5285track('upsell_modal_shown', baseParams);
5286showModal({
5287current: ctx.current, next: ctx.next, priceDelta: ctx.priceDelta,
5288variantCfg: variantCfg,
5289onAccept:  function (overlay) {
5290track('upsell_accepted', baseParams);
5291if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
5292console.log('[TierUpsell] Would update cart now (simulate mode)');
5293},
5294onDecline: function (m) { track('upsell_declined', mergeParams(baseParams, { dismiss_method: m })); console.log('[TierUpsell] Declined (simulate mode):', m); }
5295});
5296}
5297};
5298}
5299if (document.readyState === 'loading') {
5300document.addEventListener('DOMContentLoaded', init);
5301} else {
5302init();
5303}
5304})(); </script>
5304</div><div class="widget widget-static-block">
5304<script type="application/json" id="cart-kit-personas"> {"version":"2026-09-15","personas":{"brand_champions":{"persona_name":"business owners","match_keywords":["branding","brand identity","logo","custom logo","packaging","small business","business owner","ecommerce","online store","handmade business","promotional","qr code","thank you","bulk orders"],"header_title":"Brand Essentials: finish your packaging strong.","stamp_label":"Business add-ons (popular picks)","ink_label":"Choose an ink color for consistent branding","tooltip_text":"These add-ons help your brand look polished across packaging, mailers, and customer communications.","tile_subtext":"Popular with small businesses","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"2577","product_url":"https://www.simplystamps.com/qr-code-stamp-1-5-8-x-1-5-8","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["qr code stamp","qr stamp","scan me stamp","website link stamp","review qr stamp","google review qr","instagram qr","menu qr code","payment qr","contactless qr","marketing qr code"]},{"product_id":"4419","product_url":"https://www.simplystamps.com/medium-custom-logo-stamp","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["custom logo stamp","business logo stamp","company logo stamp","self inking logo stamp","branding stamp","packaging logo stamp","small business stamp","shop logo stamp","return address + logo","rubber logo stamp"]},{"product_id":"15261","product_url":"https://www.simplystamps.com/loyalty-stamp-logo-round","sort_order":0,"role":"anchor","friction":"medium","experimental":false,"match_keywords":["loyalty stamp","rewards stamp","punch card stamp","loyalty card stamp","round logo stamp","cafe loyalty stamp","retail loyalty stamp","frequent buyer stamp","stamp for loyalty program"]},{"product_id":"2578","product_url":"https://www.simplystamps.com/qr-code-stamp-3-x-1-5","sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["qr code stamp","large qr stamp","scan code stamp","website qr stamp","review qr stamp","menu qr stamp","marketing qr stamp","contactless qr stamp"]},{"product_id":"5227","product_url":"https://www.simplystamps.com/custom-logo-stamp-style-12-w-catalog-ink","sort_order":0,"role":"backup","friction":"medium","experimental":false,"match_keywords":["custom logo stamp","self inking logo stamp","logo stamp with ink","replacement ink included","business branding stamp","packaging logo stamp","company logo stamp","rubber logo stamp"]},{"product_id":"8939","product_url":"https://www.simplystamps.com/medium-round-logo-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["round logo stamp","circular logo stamp","business round stamp","self inking round stamp","company seal-style stamp","packaging round stamp","branding round stamp"]},{"product_id":"10796","product_url":"https://www.simplystamps.com/mini-square-logo-stamp","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["small logo stamp","mini logo stamp","square logo stamp","tiny logo stamp","branding tag stamp","packaging logo stamp","craft fair stamp","etsy logo stamp","label logo stamp"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller","identity theft roller","hide personal info","cover up info roller","id protection roller"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener","desk envelope opener","office mail tool","open envelopes quickly"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope sealer","envelope glue","envelope adhesive","seal envelopes","mailing supplies","envelope sealing bottle","stationery adhesive"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle","replacement stamp ink","2 oz stamp ink","re-ink stamp pad"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","gold foil seals","silver foil seals","raised seal foil","foil sticker seals","certificate seal stickers"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]},{"product_id":"7894","product_url":"https://www.simplystamps.com/black-medium-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","medium ink pad","stamp pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad"]},{"product_id":"7899","product_url":"https://www.simplystamps.com/black-large-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","large ink pad","stamp pad","hand stamp","wood handle stamp","extra large stamp","traditional stamp"]}]},"home_sweet_stampers":{"persona_name":"mail senders","match_keywords":["home organization","return address","home address","address stamp","household","family mail","personal address","home office","family paperwork","mailing at home","personal documents","holiday mail","wedding mail"],"header_title":"Mail Essentials: everything you need for clean, easy stamping.","stamp_label":"Helpful add-ons for everyday mail","ink_label":"Choose an ink color","tooltip_text":"Perfect for cards, envelopes, and seasonal mail - these extras make stamping easier and mess-free.","tile_subtext":"Great for everyday mail","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"23105","product_url":"https://www.simplystamps.com/delicate-flower-envelope-accent-craft-stamp","
5304sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["holiday stamp","merry christmas stamp","christmas stamp","holiday card stamp","envelope holiday stamp","seasonal greeting stamp","xmas stamp","christmas mail stamp"]},{"product_id":"6126","product_url":"https://www.simplystamps.com/happy-birthday-smiley-craft-stamp","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["happy birthday stamp","birthday card stamp","birthday mail stamp","party invitation stamp","envelope birthday stamp","celebration stamp","kids birthday stamp"]},{"product_id":"8557","product_url":"https://www.simplystamps.com/duncan-script-address-stamp","sort_order":0,"role":"anchor","friction":"medium","experimental":false,"match_keywords":["return address stamp","address stamp","script address stamp","cursive address stamp","custom address stamp","home address stamp","mail return address stamp"]},{"product_id":"24186","product_url":"https://www.simplystamps.com/gold-wax-seal-sticker-rosemary-design","sort_order":0,"role":"accessory","friction":"none","experimental":false,"match_keywords":["wax seal stickers","gold wax seal stickers","rosemary wax seal","envelope seal stickers","stationery seal stickers","invitation seal stickers","wax seal sticker set"]},{"product_id":"24188","product_url":"https://www.simplystamps.com/gold-wax-seal-sticker-eucalyptus-design","sort_order":0,"role":"accessory","friction":"none","experimental":false,"match_keywords":["wax seal stickers","gold wax seal stickers","eucalyptus wax seal","greenery wax seal","envelope seal stickers","invitation seal stickers","wax seal sticker set"]},{"product_id":"24189","product_url":"https://www.simplystamps.com/gold-wax-seal-sticker-heart-design","sort_order":0,"role":"accessory","friction":"none","experimental":false,"match_keywords":["wax seal stickers","gold wax seal stickers","heart wax seal","envelope seal stickers","love wax seal sticker","invitation seal stickers","wax seal sticker set"]},{"product_id":"5529","product_url":"https://www.simplystamps.com/live-well-rubber-stamp","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["inspirational stamp","motivational stamp","live well stamp","positivity stamp","journaling stamp","planner stamp","sentiment stamp","encouragement stamp"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller","hide personal info","identity theft roller"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener","desk envelope opener","open envelopes quickly"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope sealer","envelope glue","envelope adhesive","seal envelopes","mailing supplies","stationery adhesive"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":17,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle","replacement stamp ink","2 oz stamp ink"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":18,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","gold foil seals","silver foil seals","raised seal foil","certificate seal stickers"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]}]},"office_optimizers":{"persona_name":"office admin","match_keywords":["office admin","professional","past due","void","scanned","custom date","signature","office workflow","paperwork","document control","internal use","filing","paid","processing","received","approved","office documents"],"header_title":"Office Essentials: stay stocked and avoid reorders.","stamp_label":"Office add-ons (frequently reordered)","ink_label":"Ink selection for daily office use","tooltip_text":"These supplies help keep workflows moving without interruptions or last-minute supply runs.","tile_subtext":"Frequently reordered","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"none","experimental":false,"match_keywords":["envelope sealer","envelope adhesive","seal envelopes","office mailing supplies","envelope glue bottle","mailroom supplies"]},{"product_id":"15209","product_url":"https://www.simplystamps.com/4810-date-only-dater-stamp","
5304sort_order":0,"role":"anchor","friction":"low","experimental":false,"match_keywords":["date stamp","dater stamp","date only stamp","office date stamp","received date stamp","processed date stamp","document date stamp"]},{"product_id":"8926","product_url":"https://www.simplystamps.com/small-signature-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["signature stamp","small signature stamp","name signature stamp","authorized signature stamp","stamp signature","office signature stamp","sign forms stamp"]},{"product_id":"6413","product_url":"https://www.simplystamps.com/rejected-stamp","sort_order":0,"role":"backup","friction":"none","experimental":false,"match_keywords":["rejected stamp","rejected rubber stamp","rejected stock stamp","office rejected stamp","document rejected stamp","return/reject stamp","rejection stamp"]},{"product_id":"4810","product_url":"https://www.simplystamps.com/3-line-deposit-only-stamp","sort_order":0,"role":"core","friction":"high","experimental":false,"match_keywords":["deposit only stamp","for deposit only stamp","bank deposit stamp","endorsement stamp","check deposit stamp","business deposit stamp","deposit endorsement stamp"]},{"product_id":"2350","product_url":"https://www.simplystamps.com/received-outline-stock-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["received stamp","received stock stamp","office received stamp","document received stamp","mail received stamp","received rubber stamp"]},{"product_id":"2334","product_url":"https://www.simplystamps.com/paid-outlined-stock-stamp","sort_order":0,"role":"core","friction":"low","experimental":false,"match_keywords":["paid stamp","paid stock stamp","invoice paid stamp","office paid stamp","accounts payable stamp","payment received stamp","paid rubber stamp"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller","identity theft roller","hide personal info"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"none","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener","desk envelope opener","mailroom tool"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle","replacement stamp ink"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil","certificate seal stickers"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]},{"product_id":"7894","product_url":"https://www.simplystamps.com/black-medium-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","medium ink pad","stamp pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad"]},{"product_id":"7899","product_url":"https://www.simplystamps.com/black-large-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","large ink pad","stamp pad","hand stamp","wood handle stamp","extra large stamp","traditional stamp"]}]},"notary_ninjas":{"persona_name":"notary","match_keywords":["notary","state of","notarization","signing agent","loan signing","jurat","acknowledgment","witness","official document","seal","embosser","certified"],"header_title":"Notary Essentials: add backup ink & supplies before checkout.","stamp_label":"Notary add-ons (quick picks)","ink_label":"Ink choice (recommended for notary work)","tooltip_text":"These items help ensure clean, compliant notarizations and prevent downtime if ink runs out.","tile_subtext":"Recommended for notary work","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"11712","product_url":"https://www.simplystamps.com/the-complete-notary-records-book","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["notary records book","
5304notary journal","notary logbook","notary ledger","notarization record book","notary signing log","notary supplies book"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","gold foil seals","raised seal foil","notary seal foil","certificate seal stickers"]},{"product_id":"7986","product_url":"https://www.simplystamps.com/seal-impression-inker","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embosser inker","seal impression inker","raised seal inker","embosser ink pad","make embossing visible","notary embosser accessory","seal impression enhancer"]},{"product_id":"13384","product_url":"https://www.simplystamps.com/legal-terms-dictionary","sort_order":0,"role":"anchor","friction":"none","experimental":false,"match_keywords":["legal terms dictionary","law dictionary","notary reference book","legal reference","terms dictionary"]},{"product_id":"8927","product_url":"https://www.simplystamps.com/medium-signature-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["signature stamp","medium signature stamp","authorized signature stamp","stamp signature","sign documents stamp","office signature stamp"]},{"product_id":"13387","product_url":"https://www.simplystamps.com/embosser-vinyl-black-pouch","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embosser pouch","embosser case","notary embosser case","embosser storage","carry embosser","protect embosser","vinyl embosser pouch"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller","identity theft roller"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener","desk envelope opener"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope sealer","envelope adhesive","seal envelopes","mailing supplies","envelope glue bottle"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle","replacement stamp ink"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil","certificate seal stickers"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]},{"product_id":"7894","product_url":"https://www.simplystamps.com/black-medium-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","medium ink pad","stamp pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad"]},{"product_id":"7899","product_url":"https://www.simplystamps.com/black-large-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","large ink pad","stamp pad","hand stamp","wood handle stamp","extra large stamp","traditional stamp"]}]},"classroom_heroes":{"persona_name":"teachers","match_keywords":["teacher","classroom","education","grading","student work","homework","lesson planning","classroom management","school"],"header_title":"Classroom Essentials: be ready for every lesson.","stamp_label":"Teacher add-ons (classroom favorites)","ink_label":"Choose an ink color students will love","tooltip_text":"Extra ink and supplies help keep grading, rewards, and feedback flowing all year.","tile_subtext":"Classroom favorites","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"2632","product_url":"https://www.simplystamps.com/a-good-work-teacher-stamp","
5304sort_order":0,"role":"anchor","friction":"none","experimental":false,"match_keywords":["good work stamp","teacher good work stamp","student praise stamp","grading feedback stamp","classroom reward stamp","teacher feedback stamp"]},{"product_id":"18180","product_url":"https://www.simplystamps.com/instagram-like-grading-teacher-stamp","sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["grading stamp","teacher grading stamp","student feedback stamp","like stamp teacher","classroom reward stamp","fun grading stamp"]},{"product_id":"24369","product_url":"https://www.simplystamps.com/the-cubie-teacher-stamp","sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["teacher stamp","personalized teacher stamp","classroom stamp","teacher name stamp","grading stamp","teacher reward stamp"]},{"product_id":"10964","product_url":"https://www.simplystamps.com/missing-check-box-teacher-stamp","sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["missing stamp","missing work stamp","check box stamp","teacher grading stamp","homework missing stamp","classroom feedback stamp"]},{"product_id":"23518","product_url":"https://www.simplystamps.com/modern-sun-custom-book-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["custom book stamp","teacher book stamp","classroom library stamp","book ownership stamp","from the library stamp","personalized book stamp"]},{"product_id":"9810","product_url":"https://www.simplystamps.com/teacher-library-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["teacher library stamp","classroom library stamp","from the library teacher","book stamp teacher","book ownership stamp","student book return stamp"]},{"product_id":"19215","product_url":"https://www.simplystamps.com/stacked-teacher-stamps-set-of-6","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["teacher stamp set","grading stamps set","reward stamp set","classroom feedback stamps","teacher supplies stamps","student praise stamps"]},{"product_id":"10965","product_url":"https://www.simplystamps.com/no-name-no-fame-owl-teacher-stamp","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["personalized teacher stamp","owl teacher stamp","teacher name stamp","classroom stamp","cute grading stamp","teacher gift stamp"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","stamp pad set","teacher stamp ink pads","ink pads for rubber stamps"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]}]},"craft_crowd":{"persona_name":"crafters","match_keywords":["crafting","craft stamp","Create","hand stamp","european","handmade","arts and crafts","creative projects","diy","small batch","maker","personalization","packaging"],"header_title":"Creative Essentials: finish your projects with ease.","stamp_label":"Craft add-ons (popular with makers)","ink_label":"Choose an ink color to match your style","tooltip_text":"These supplies are perfect for handmade goods, packaging, and creative projects.","tile_subtext":"Popular with makers","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"10464","product_url":"https://www.simplystamps.com/gem-cluster-stamp","
5304sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","colored ink pads","stamping ink","stamp pad set","acid free ink pad","cardmaking ink","scrapbook ink pads"]},{"product_id":"9768","product_url":"https://www.simplystamps.com/seamless-eucalyptus-stamp","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["gem stamp","diamond stamp","sparkle stamp","crystal stamp","decorative craft stamp","embellishment stamp","scrapbook stamp"]},{"product_id":"6152","product_url":"https://www.simplystamps.com/mr-mrs-save-the-date-rubber-stamp","sort_order":0,"role":"anchor","friction":"none","experimental":false,"match_keywords":["eucalyptus stamp","botanical stamp","greenery stamp","floral craft stamp","envelope greenery stamp","wedding greenery stamp","leaf stamp"]},{"product_id":"23532","product_url":"https://www.simplystamps.com/book-with-skull-custom-book-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["save the date stamp","mr & mrs stamp","wedding save the date","engagement announcement stamp","wedding mail stamp","envelope save date stamp"]},{"product_id":"23534","product_url":"https://www.simplystamps.com/mushrooms-custom-book-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["custom book stamp","skull book stamp","gothic book stamp","book ownership stamp","personal library stamp","bookplate stamp"]},{"product_id":"23113","product_url":"https://www.simplystamps.com/outlined-heart-envelope-accent-craft-stamp","sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["custom book stamp","mushroom book stamp","whimsical book stamp","book ownership stamp","personal library stamp","bookplate stamp"]},{"product_id":"23110","product_url":"https://www.simplystamps.com/hearts-through-arrow-envelope-accent-craft-stamp","sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["heart envelope stamp","envelope accent stamp","love stamp","cardmaking heart stamp","wedding envelope stamp","valentine stamp"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["heart arrow stamp","love envelope stamp","envelope accent stamp","wedding envelope stamp","valentine stamp","cardmaking stamp"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]}]},"banking_bookkeepers":{"persona_name":"banking & bookkeeping","match_keywords":["banking","bookkeeping","accounting","deposit","endorsement","accounts payable","accounts receivable","invoicing","payment processing","check processing"],"header_title":"Banking Essentials: keep deposits fast, clean, and compliant.","stamp_label":"Banking add-ons (high-utility picks)","ink_label":"Choose an ink color for daily office use","tooltip_text":"These add-ons help reduce errors, speed up deposits, and protect sensitive info at the desk.","tile_subtext":"Popular with finance teams","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"4810","product_url":"https://www.simplystamps.com/3-line-deposit-only-stamp","
5304sort_order":0,"role":"anchor","friction":"medium","experimental":false,"match_keywords":["deposit only stamp","for deposit only stamp","endorsement stamp","check deposit stamp","bank deposit stamp","business deposit stamp","deposit endorsement stamp"]},{"product_id":"2487","product_url":"https://www.simplystamps.com/6-line-self-inking-for-deposit-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["deposit stamp","self inking deposit stamp","endorsement stamp","deposit only endorsement","bank deposit stamp","deposit stamp with account","business deposit stamp"]},{"product_id":"5924","product_url":"https://www.simplystamps.com/mobile-deposit-mobile-check-deposit-rubber-stamp","sort_order":0,"role":"core","friction":"low","experimental":false,"match_keywords":["mobile deposit stamp","remote deposit stamp","mobile check deposit stamp","bank app deposit stamp","mobile endorsement stamp","deposit only mobile stamp"]},{"product_id":"9303","product_url":"https://www.simplystamps.com/remote-deposit-stamp","sort_order":0,"role":"core","friction":"low","experimental":false,"match_keywords":["remote deposit stamp","remote check deposit stamp","deposit endorsement stamp","for deposit only remote","business remote deposit stamp","endorsement stamp remote deposit"]},{"product_id":"6026","product_url":"https://www.simplystamps.com/pay-to-the-order-of-stamp","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["pay to the order of stamp","payable to stamp","check endorsement stamp","accounts payable stamp","payment processing stamp","check handling stamp"]},{"product_id":"4349","product_url":"https://www.simplystamps.com/for-deposit-only-with-underline","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["for deposit only stamp","deposit stamp underline","endorsement stamp","bank deposit stamp","check deposit stamp","office deposit stamp"]},{"product_id":"5915","product_url":"https://www.simplystamps.com/deposited-dater-mobile-deposit-rubber-stamp","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["deposited dater stamp","deposit date stamp","deposit received date stamp","bank deposit date stamp","deposited stamp with date","deposit tracking stamp"]},{"product_id":"2429","product_url":"https://www.simplystamps.com/paid-round-stock-stamp","sort_order":0,"role":"backup","friction":"none","experimental":false,"match_keywords":["paid stamp","paid round stamp","invoice paid stamp","accounts payable stamp","office paid stamp","paid rubber stamp","paid self inking stamp"]},{"product_id":"2420","product_url":"https://www.simplystamps.com/35305-br-black-eksc-4-br-secure-marker-br-4mm-chisel-tip","sort_order":0,"role":"protection","friction":"none","experimental":false,"match_keywords":["secure marker","security marker","check fraud marker","check washing protection","document security marker","tamper resistant marker","fraud prevention marker"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller","identity theft roller"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope sealer","envelope adhesive","seal envelopes","mailing supplies"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]},{"product_id":"7894","product_url":"https://www.simplystamps.com/black-medium-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","medium ink pad","stamp pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad"]},{"product_id":"7899","product_url":"https://www.simplystamps.com/black-large-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","large ink pad","stamp pad","hand stamp","wood handle stamp","extra large stamp","traditional stamp"]}]},"shipping_station":{"persona_name":"shipping & fulfillment","match_keywords":["shipping","shipped","packing","packed","fulfillment","mailing","fragile","handle with care","return to 
5304sender","warehouse","usps","ups","fedex"],"header_title":"Shipping Essentials: keep packages moving with clear handling marks.","stamp_label":"Shipping add-ons (warehouse favorites)","ink_label":"Choose an ink color for shipping workflows","tooltip_text":"Mark packages clearly to reduce damage, speed sorting, and keep fulfillment consistent.","tile_subtext":"Warehouse favorites","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"20191","product_url":"https://www.simplystamps.com/fragile-handle-with-care-business-stamp","sort_order":0,"role":"anchor","friction":"none","experimental":false,"match_keywords":["fragile stamp","handle with care stamp","shipping fragile stamp","package handling stamp","packing station stamp","warehouse handling stamp","fragile rubber stamp"]},{"product_id":"20190","product_url":"https://www.simplystamps.com/fragile-do-not-crush-business-stamp","sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["do not crush stamp","fragile stamp","shipping handling stamp","package protection stamp","warehouse handling stamp","packing station stamp","do not crush rubber stamp"]},{"product_id":"20260","product_url":"https://www.simplystamps.com/fragile-this-side-up-business-stamp","
5304sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["this side up stamp","fragile stamp","keep upright stamp","shipping orientation stamp","package direction stamp","warehouse handling stamp","this side up rubber stamp"]},{"product_id":"2702","product_url":"https://www.simplystamps.com/stock-fragile-1010-stamp","sort_order":0,"role":"backup","friction":"none","experimental":false,"match_keywords":["fragile stock stamp","fragile stamp","shipping fragile stamp","package handling stamp","warehouse stamp","packing stamp","fragile rubber stamp"]},{"product_id":"9294","product_url":"https://www.simplystamps.com/return-to-sender-stamp-simply","sort_order":0,"role":"anchor","friction":"none","experimental":false,"match_keywords":["return to sender stamp","undeliverable stamp","address correction stamp","mail return stamp","package return stamp","shipping return stamp","return mail stamp"]},{"product_id":"9295","product_url":"https://www.simplystamps.com/return-to-sender-block-stamp","sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["return to sender stamp","return to sender block","undeliverable stamp","mail return stamp","return label stamp","shipping return stamp","return package stamp"]},{"product_id":"6442","product_url":"https://www.simplystamps.com/return-to-sender-stamp-blue","sort_order":0,"role":"backup","friction":"none","experimental":false,"match_keywords":["return to sender stamp","blue ink return stamp","undeliverable stamp","mail return stamp","return package stamp","shipping return stamp"]},{"product_id":"2365","product_url":"https://www.simplystamps.com/shipped-via-usps-stock-stamp","sort_order":0,"role":"accessory","friction":"none","experimental":false,"match_keywords":["usps shipped stamp","shipped via usps","usps stamp","shipped stamp","carrier stamp","fulfillment stamp","shipping department stamp"]},{"product_id":"5301","product_url":"https://www.simplystamps.com/shipped-ship-dater-stamp","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["shipped dater stamp","ship date stamp","shipping date stamp","processed date stamp","fulfillment date stamp","warehouse date stamp","shipping workflow stamp"]},{"product_id":"6006","product_url":"https://www.simplystamps.com/packaged-by-cursive-packaging-stamp","sort_order":0,"role":"accessory","friction":"medium","experimental":false,"match_keywords":["packaged by stamp","packed by stamp","packaging stamp","fulfillment stamp","quality control stamp","packing station stamp","packaged by name stamp"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener","mailroom tool"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope sealer","envelope adhesive","seal envelopes","mailing supplies","envelope glue bottle"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]}]},"wedding_bells":{"persona_name":"weddings & events","match_keywords":["wedding","mr & mrs","engagement","save the date","invitations","rsvp","bridal","groom","wedding planning","event stationery","celebration"],"header_title":"Wedding Essentials: elevate invites, save time, and keep mail looking perfect.","stamp_label":"Wedding add-ons (stationery must-haves)","ink_label":"Choose an ink color for invites & envelopes","tooltip_text":"These extras help your invites feel polished — and make mailing a whole lot easier.","tile_subtext":"Stationery must-haves","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"6321","product_url":"https://www.simplystamps.com/simple-written-save-the-date-stamp","
5304sort_order":1,"role":"anchor","friction":"medium","experimental":false,"match_keywords":["save the date stamp","wedding save the date","engagement announcement stamp","wedding mail stamp","envelope save date stamp","custom save the date stamp"]},{"product_id":"11408","product_url":"https://www.simplystamps.com/formal-ornate-wedding-save-the-date-stamp","sort_order":2,"role":"core","friction":"medium","experimental":false,"match_keywords":["save the date stamp","formal save the date","ornate save the date","elegant save date stamp","wedding stationery stamp","envelope save date stamp"]},{"product_id":"11405","product_url":"https://www.simplystamps.com/formal-ornate-wedding-invitation-stamp","sort_order":3,"role":"core","friction":"medium","experimental":false,"match_keywords":["wedding invitation stamp","invitation stamp","formal wedding stamp","ornate wedding stamp","envelope invitation stamp","custom invitation stamp"]},{"product_id":"11406","product_url":"https://www.simplystamps.com/whimsical-wedding-invitation-stamp","
5304sort_order":4,"role":"backup","friction":"medium","experimental":false,"match_keywords":["wedding invitation stamp","whimsical wedding stamp","modern invitation stamp","fun wedding stamp","envelope invitation stamp","custom wedding invitation stamp"]},{"product_id":"22261","product_url":"https://www.simplystamps.com/couples-initial-wedding-address-stamp","sort_order":5,"role":"anchor","friction":"medium","experimental":false,"match_keywords":["wedding address stamp","wedding return address stamp","couple address stamp","newlywed address stamp","initials address stamp","wedding envelope stamp"]},{"product_id":"22259","product_url":"https://www.simplystamps.com/traditional-serif-wedding-address-stamp","sort_order":6,"role":"core","friction":"medium","experimental":false,"match_keywords":["wedding address stamp","wedding return address stamp","serif address stamp","classic wedding stamp","formal address stamp","wedding envelope address stamp"]},{"product_id":"5776","product_url":"https://www.simplystamps.com/nash-return-address-stamp","sort_order":7,"role":"backup","friction":"medium","experimental":false,"match_keywords":["return address stamp","address stamp","custom address stamp","family address stamp","wedding return address stamp","envelope return address stamp"]},{"product_id":"24186","product_url":"https://www.simplystamps.com/gold-wax-seal-sticker-rosemary-design","sort_order":8,"role":"accessory","friction":"none","experimental":false,"match_keywords":["wax seal stickers","gold wax seal stickers","rosemary wax seal","wedding wax seal sticker","invitation seal stickers","envelope seal stickers"]},{"product_id":"24188","product_url":"https://www.simplystamps.com/gold-wax-seal-sticker-eucalyptus-design","sort_order":9,"role":"accessory","friction":"none","experimental":false,"match_keywords":["wax seal stickers","gold wax seal stickers","eucalyptus wax seal","greenery wax seal","invitation seal stickers","envelope seal stickers"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":10,"role":"protection","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller","identity protection roller","security roller"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":11,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener","package opener","paper opener"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":12,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope sealer","envelope glue","envelope adhesive","seal envelopes","wedding invitation sealer","stationery supplies"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":13,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","colored ink pads","stamping ink","stamp pad set","cardmaking ink","scrapbook ink pads"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":14,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle","refill ink bottle"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":15,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil","embossing foil stickers"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]}]},"medical_messengers":{"persona_name":"medical offices","match_keywords":["medical office","healthcare","patient paperwork","hipaa","confidential","privacy","medical records","office workflow","provider","clinic"],"header_title":"Medical Essentials: protect privacy and speed up paperwork.","stamp_label":"Medical add-ons (front-desk essentials)","ink_label":"Choose an ink color for daily use","tooltip_text":"These tools help reduce mistakes, protect patient info, and keep paperwork moving.","tile_subtext":"Front-desk essentials","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"5888","product_url":"https://www.simplystamps.com/read-all-instructions-rubber-stamp","
5304sort_order":0,"role":"anchor","friction":"low","experimental":false,"match_keywords":["read all instructions stamp","read instructions stamp","patient instructions stamp","medical instructions stamp","rx instructions stamp","patient paperwork stamp"]},{"product_id":"2736","product_url":"https://www.simplystamps.com/stock-confidential-1130-stamp","sort_order":0,"role":"core","friction":"none","experimental":false,"match_keywords":["confidential stamp","hipaa confidential stamp","medical confidential stamp","privacy stamp","patient record confidential","confidential paperwork stamp"]},{"product_id":"2420","product_url":"https://www.simplystamps.com/35305-br-black-eksc-4-br-secure-marker-br-4mm-chisel-tip","sort_order":0,"role":"protection","friction":"none","experimental":false,"match_keywords":["id protector roller","hipaa privacy roller","patient info blocker","data masking roller","address blocker roller","confidential info roller"]},{"product_id":"8927","product_url":"https://www.simplystamps.com/medium-signature-stamp","sort_order":0,"role":"backup","friction":"medium","experimental":false,"match_keywords":["secure marker","document security marker","confidential marker","tamper resistant marker","fraud prevention marker","security marker pen"]},{"product_id":"4378","product_url":"https://www.simplystamps.com/7-line-custom-rubber-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener","front desk mail tool","open envelopes quickly"]},{"product_id":"2429","product_url":"https://www.simplystamps.com/paid-round-stock-stamp","sort_order":0,"role":"backup","friction":"none","experimental":false,"match_keywords":["envelope sealer","envelope adhesive","seal envelopes","office envelope sealer","mailing supplies","envelope glue bottle"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["signature stamp","doctor signature stamp","provider signature stamp","office signature stamp","authorized signature stamp","sign forms stamp"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"sLC","friction":"low","experimental":false,"match_keywords":["custom office stamp","medical address stamp","clinic info stamp","practice contact stamp","return address stamp business","patient paperwork stamp"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["paid stamp","billing paid stamp","invoice paid stamp","payment received stamp","accounts paid stamp","office paid stamp"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]}]},"legal_eagles":{"persona_name":"legal offices","match_keywords":["legal office","attorney","law firm","confidential","legal documents","signature authority","privacy","document security","client records"],"header_title":"Legal Essentials: protect sensitive docs and speed up processing.","stamp_label":"Legal add-ons (desk essentials)","ink_label":"Choose an ink color for daily legal use","tooltip_text":"These add-ons help reduce admin friction and protect client information across documents and mail.","tile_subtext":"Desk essentials","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"2736","product_url":"https://www.simplystamps.com/stock-confidential-1130-stamp","
5304sort_order":0,"role":"anchor","friction":"none","experimental":false,"match_keywords":["confidential stamp","legal confidential stamp","attorney confidential stamp","client info confidential","confidential document stamp","confidential file stamp"]},{"product_id":"13384","product_url":"https://www.simplystamps.com/legal-terms-dictionary","sort_order":0,"role":"accessory","friction":"none","experimental":false,"match_keywords":["legal terms dictionary","law dictionary","legal reference book","paralegal reference","attorney desk reference"]},{"product_id":"8927","product_url":"https://www.simplystamps.com/medium-signature-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["signature stamp","attorney signature stamp","authorized signature stamp","office signature stamp","stamp signature","sign documents stamp"]},{"product_id":"11712","product_url":"https://www.simplystamps.com/the-complete-notary-records-book","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["notary records book","notary journal","notarization record book","legal notary logbook","notary ledger","recordkeeping book"]},{"product_id":"2420","product_url":"https://www.simplystamps.com/35305-br-black-eksc-4-br-secure-marker-br-4mm-chisel-tip","sort_order":0,"role":"protection","friction":"none","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller","identity theft roller"]},{"product_id":"4378","product_url":"https://www.simplystamps.com/7-line-custom-rubber-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["secure marker","document security marker","confidential marker","tamper resistant marker","fraud prevention marker","security marker pen"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener","mailroom tool","open envelopes quickly"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope sealer","envelope adhesive","seal envelopes","legal mailing supplies","envelope glue bottle","secure mail sealing"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["custom office stamp","law office stamp","firm info stamp","attorney address stamp","return address stamp business","legal paperwork stamp"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]},{"product_id":"7894","product_url":"https://www.simplystamps.com/black-medium-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","medium ink pad","stamp pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad"]},{"product_id":"7899","product_url":"https://www.simplystamps.com/black-large-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","large ink pad","stamp pad","hand stamp","wood handle stamp","extra large stamp","traditional stamp"]}]},"licensed_professionals":{"persona_name":"Licensed Professionals","header_title":"Professional & Licensed Stamps","tooltip_text":"Designed for licensed professionals who need official, compliant stamps for their trade or certification.","allowed_ink_colors":[],"default_primary_index":0,"tile_subtext":"Built for regulated professions and official use.","uses_product_tiles":true,"match_keywords":["license","architect","state of","tax","engineer","mechanical","auditor","land surveyor","geologist","inspection","licensed","professional stamp","official stamp","certification","corporate","registered","registration number","compliance","legal requirement","authorized","state issued","seal","embosser","jurisdiction","regulated","professional use"],"products":[{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":2,"role":"protection","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller","identity theft roller","hide personal info","cover up info roller","id protection roller"]},{"product_id":"2420","product_url":"https://www.simplystamps.com/35305-br-black-eksc-4-br-secure-marker-br-4mm-chisel-tip","sort_order":3,"role":"protection","friction":"none","experimental":false,"match_keywords":["secure marker","security marker","check fraud marker","check washing protection","document security marker","tamper resistant marker","fraud prevention marker"]},{"product_id":"2736","product_url":"https://www.simplystamps.com/stock-confidential-1130-stamp","
5304sort_order":4,"role":"anchor","friction":"none","experimental":false,"match_keywords":["confidential stamp","legal confidential stamp","attorney confidential stamp","client info confidential","confidential document stamp","confidential file stamp"]},{"product_id":"8927","product_url":"https://www.simplystamps.com/medium-signature-stamp","sort_order":5,"role":"core","friction":"medium","experimental":false,"match_keywords":["signature stamp","medium signature stamp","authorized signature stamp","stamp signature","sign documents stamp","office signature stamp"]},{"product_id":"15209","product_url":"https://www.simplystamps.com/4810-date-only-dater-stamp","sort_order":6,"role":"anchor","friction":"none","experimental":false,"match_keywords":["date stamp","dater stamp","date only stamp","office date stamp","received date stamp","processed date stamp","document date stamp"]},{"product_id":"4378","product_url":"https://www.simplystamps.com/7-line-custom-rubber-stamp","sort_order":7,"role":"core","friction":"medium","experimental":false,"match_keywords":["custom office stamp","law office stamp","firm info stamp","attorney address stamp","return address stamp business","legal paperwork stamp"]},{"product_id":"2350","product_url":"https://www.simplystamps.com/received-outline-stock-stamp","sort_order":8,"role":"core","friction":"none","experimental":false,"match_keywords":["received stamp","received stock stamp","office received stamp","document received stamp","mail received stamp","received rubber stamp"]},{"product_id":"2334","product_url":"https://www.simplystamps.com/paid-outlined-stock-stamp","sort_order":9,"role":"core","friction":"none","experimental":false,"match_keywords":["paid stamp","paid stock stamp","invoice paid stamp","office paid stamp","accounts payable stamp","payment received stamp","paid rubber stamp"]},{"product_id":"4810","product_url":"https://www.simplystamps.com/3-line-deposit-only-stamp","sort_order":10,"role":"core","friction":"none","experimental":false,"match_keywords":["deposit only stamp","for deposit only stamp","bank deposit stamp","endorsement stamp","check deposit stamp","business deposit stamp","deposit endorsement stamp"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":11,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener","desk envelope opener","office mail tool","open envelopes quickly"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":12,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope sealer","envelope glue","envelope adhesive","seal envelopes","mailing supplies","envelope sealing bottle","stationery adhesive"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":13,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","colored ink pads","stamping ink","stamp pad set","acid free ink pad","cardmaking ink","scrapbook ink pads","ink pads for rubber stamps"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":14,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle","replacement stamp ink","2 oz stamp ink","re-ink stamp pad"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":15,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","gold foil seals","silver foil seals","raised seal foil","foil sticker seals","certificate seal stickers"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]},{"product_id":"7894","product_url":"https://www.simplystamps.com/black-medium-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","medium ink pad","stamp pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad"]},{"product_id":"7899","product_url":"https://www.simplystamps.com/black-large-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","large ink pad","stamp pad","hand stamp","wood handle stamp","extra large stamp","traditional stamp"]}]},"bookish_collectors":{"persona_name":"book lovers & collectors","match_keywords":["book collection","book embosser","from the library","custom book","book stamp","personal library","book ownership","collector","collection management","bookplates","archival","name marking"],"header_title":"Library Essentials: mark your books with style (and keep them coming home).","stamp_label":"Book add-ons (collector favorites)","ink_label":"Choose an ink color for book pages","tooltip_text":"Perfect for personal libraries, classrooms, and gifting — these add-ons help your books stay yours.","tile_subtext":"Collector favorites","uses_product_tiles":true,"allowed_ink_colors":[],"default_primary_index":0,"products":[{"product_id":"23518","product_url":"https://www.simplystamps.com/modern-sun-custom-book-stamp","
5304sort_order":0,"role":"anchor","friction":"medium","experimental":false,"match_keywords":["custom book stamp","book ownership stamp","from the library of stamp","personal library stamp","teacher book stamp","bookplate stamp","stamp for books"]},{"product_id":"22204","product_url":"https://www.simplystamps.com/from-the-library-of-teacher-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["from the library of stamp","teacher library stamp","classroom library stamp","book stamp teacher","stamp for classroom books","book ownership stamp"]},{"product_id":"9810","product_url":"https://www.simplystamps.com/teacher-library-stamp","sort_order":0,"role":"backup","friction":"medium","experimental":false,"match_keywords":["teacher library stamp","classroom library stamp","from the library teacher","book stamp teacher","student book return stamp","book ownership stamp"]},{"product_id":"5932","product_url":"https://www.simplystamps.com/from-the-library-book-embosser","sort_order":0,"role":"anchor","friction":"medium","experimental":false,"match_keywords":["book embosser","library embosser","from the library embosser","raised seal book embosser","book ownership embosser","embosser for books"]},{"product_id":"22716","product_url":"https://www.simplystamps.com/round-floral-script-property-of-book-embosser","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["property of book embosser","floral book embosser","round book embosser","custom book embosser","raised seal embosser books","name embosser for books"]},{"product_id":"23528","product_url":"https://www.simplystamps.com/sword-crest-custom-book-stamp","sort_order":0,"role":"backup","friction":"medium","experimental":false,"match_keywords":["custom book stamp","crest book stamp","fantasy book stamp","gothic book stamp","book ownership stamp","personal library stamp","bookplate stamp"]},{"product_id":"23537","product_url":"https://www.simplystamps.com/enchanting-custom-book-stamp","sort_order":0,"role":"backup","friction":"medium","experimental":false,"match_keywords":["custom book stamp","whimsical book stamp","enchanting book stamp","book ownership stamp","personal library stamp","bookplate stamp","gift book stamp"]},{"product_id":"23536","product_url":"https://www.simplystamps.com/snake-seal-custom-book-stamp","sort_order":0,"role":"backup","friction":"medium","experimental":false,"match_keywords":["custom book stamp","snake book stamp","gothic book stamp","book ownership stamp","personal library stamp","bookplate stamp"]},{"product_id":"23509","product_url":"https://www.simplystamps.com/growth-custom-book-stamp","sort_order":0,"role":"core","friction":"medium","experimental":false,"match_keywords":["custom book stamp","botanical book stamp","modern book stamp","book ownership stamp","personal library stamp","from the library stamp","bookplate stamp"]},{"product_id":"24221","product_url":"https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller","sort_order":0,"role":"protection","friction":"low","experimental":false,"match_keywords":["id protector roller","privacy roller","address blocker roller","data masking roller"]},{"product_id":"23810","product_url":"https://www.simplystamps.com/envelope-opener","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope opener","letter opener","mail opener"]},{"product_id":"22023","product_url":"https://www.simplystamps.com/envelope-sealer-2oz-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["envelope sealer","envelope adhesive","seal envelopes","mailing supplies"]},{"product_id":"18507","product_url":"https://www.simplystamps.com/craft-ink-pads","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["craft ink pads","stamping ink","colored ink pads","craft stamp","envelope craft","wood handle","accent stamp","hand stamps","stamp pad set"]},{"product_id":"2517","product_url":"https://www.simplystamps.com/trodat-ideal-ink-2-oz-bottle","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["trodat ideal ink","stamp refill ink","self inking refill ink","stamp ink bottle"]},{"product_id":"6524","product_url":"https://www.simplystamps.com/embossing-adhesive-foil","sort_order":0,"role":"backup","friction":"low","experimental":false,"match_keywords":["embossing foil seals","embosser foil stickers","raised seal foil"]},{"product_id":"2518","product_url":"https://www.simplystamps.com/trodat-ideal-ink-1-oz-6cc-bottle","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink refill","refill ink","stamp ink","re-ink","reink","dried out stamp","replacement ink","self inking","self-inking","ink bottle","more ink","out of ink"]},{"product_id":"7889","product_url":"https://www.simplystamps.com/black-small-ink-pad","sort_order":0,"role":"accessory","friction":"low","experimental":false,"match_keywords":["ink pad","stamp pad","small ink pad","hand stamp","wood handle stamp","traditional stamp","rubber stamp pad","stamping pad"]}]}}} </script>
5304</div><div class="widget widget-static-block"> <style> /* =========================================================
5305Scoped to #cart-kit-upsell-mount to avoid site collisions
5306========================================================= */
5307#cart-kit-upsell-mount {
5308--ck-accent: var(--brand-primary, #1976d2);
5309--ck-accent-hover: var(--brand-primary-hover, #60B5E5);
5310--ck-success: var(--brand-success, #2e7d32);
5311--ck-success-hover: #256628;
5312}
5313#cart-kit-upsell-mount .cart-kit-upsell {
5314margin: 24px 0;
5315border-radius: 8px;
5316box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
5317background: #ffffff;
5318border: 1px solid #e3e3e3;
5319overflow: visible;
5320font-family: inherit;
5321position: relative;
5322}
5323#cart-kit-upsell-mount .cart-kit-upsell__header {
5324width: 100%;
5325text-align: left;
5326border: 0;
5327padding: 12px 16px;
5328background: #f5fafc;
5329display: flex;
5330align-items: center;
5331justify-content: flex-start;
5332gap: 12px;
5333cursor: pointer;
5334border-radius: 8px;
5335}
5336#cart-kit-upsell-mount .cart-kit-upsell__header:focus {
5337outline: 2px solid var(--ck-accent);
5338outline-offset: -2px;
5339}
5340#cart-kit-upsell-mount .cart-kit-upsell__header-text {
5341display: flex;
5342flex-direction: column;
5343gap: 4px;
5344flex: 1 1 auto;
5345}
5346#cart-kit-upsell-mount .cart-kit-upsell__title {
5347font-size: 20px;
5348font-weight: 600;
5349color: #222;
5350}
5351#cart-kit-upsell-mount .cart-kit-upsell__chevron {
5352width: 10px;
5353height: 10px;
5354border-right: 2px solid #555;
5355border-bottom: 2px solid #555;
5356transform: rotate(45deg);
5357transition: transform 0.2s ease;
5358}
5359#cart-kit-upsell-mount .cart-kit-upsell--open .cart-kit-upsell__chevron {
5360transform: rotate(-135deg);
5361}
5362#cart-kit-upsell-mount .cart-kit-upsell__body {
5363padding: 12px 16px 14px;
5364border-top: 1px solid #e3e3e3;
5365animation: cartKitUpsellFadeIn 0.18s ease-out;
5366}
5367@keyframes cartKitUpsellFadeIn {
5368from {
5369opacity: 0;
5370transform: translateY(-2px);
5371}
5372to {
5373opacity: 1;
5374transform: translateY(0);
5375}
5376}
5377/* Icon zone */
5378#cart-kit-upsell-mount .cart-kit-upsell__icon-wrap {
5379flex: 0 0 auto;
5380width: auto;
5381height: 36px;
5382display: flex;
5383align-items: center;
5384justify-content: center;
5385overflow: visible;
5386}
5387#cart-kit-upsell-mount .cart-kit-upsell__icon-img {
5388width: 70px;
5389height: auto;
5390display: block;
5391transform: none;
5392opacity: 1;
5393transition: none;
5394}
5395/* Options */
5396#cart-kit-upsell-mount .cart-kit-upsell__options-group {
5397display: flex;
5398flex-direction: column;
5399gap: 10px;
5400}
5401#cart-kit-upsell-mount .cart-kit-upsell__options-label {
5402font-size: 16px;
5403font-weight: 600;
5404color: #333;
5405}
5406#cart-kit-upsell-mount .cart-kit-upsell__options-header {
5407display: flex;
5408align-items: center;
5409gap: 6px;
5410margin-bottom: 4px;
5411overflow: visible !important;
5412}
5413/* Tooltip */
5414#cart-kit-upsell-mount .cart-kit-upsell__info {
5415position: relative;
5416border: 0;
5417padding: 0;
5418background: none;
5419cursor: pointer;
5420display: inline-flex;
5421z-index: 5;
5422}
5423#cart-kit-upsell-mount .cart-kit-upsell__info-icon {
5424width: 18px;
5425height: 18px;
5426border-radius: 999px;
5427border: 1px solid #888;
5428font-size: 11px;
5429font-weight: 600;
5430display: flex;
5431align-items: center;
5432justify-content: center;
5433color: #555;
5434background: #fff;
5435line-height: 1;
5436}
5437#cart-kit-upsell-mount .cart-kit-upsell__info-tooltip {
5438position: absolute;
5439left: 50%;
5440bottom: calc(100% + 8px);
5441transform: translateX(-50%);
5442background: #222;
5443color: #fff;
5444font-size: 13px;
5445padding: 6px 8px;
5446border-radius: 4px;
5447box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
5448opacity: 0;
5449pointer-events: none;
5450transition: opacity .15s ease, transform .15s ease;
5451white-space: normal;
5452max-width: min(260px, 80vw);
5453width: 180px;
5454line-height: 1.2;
5455z-index: 9999;
5456}
5457#cart-kit-upsell-mount .cart-kit-upsell__info-tooltip::after {
5458content: '';
5459position: absolute;
5460top: 100%;
5461left: 50%;
5462transform: translateX(-50%);
5463border-width: 6px;
5464border-style: solid;
5465border-color: #222 transparent transparent transparent;
5466}
5467#cart-kit-upsell-mount .cart-kit-upsell__info:hover .cart-kit-upsell__info-tooltip,
5468#cart-kit-upsell-mount .cart-kit-upsell__info:focus-visible .cart-kit-upsell__info-tooltip {
5469opacity: 1;
5470transform: translateX(-50%) translateY(-2px);
5471pointer-events: auto;
5472}
5473/* =========================================
5474Grid (Accent stamps OR Notary product tiles)
5475========================================= */
5476/* Default (Accent chips) */
5477#cart-kit-upsell-mount .kit-stamp-grid {
5478display: flex;
5479flex-wrap: wrap;
5480gap: 16px;
5481align-items: stretch;
5482}
5483#cart-kit-upsell-mount .kit-stamp-card {
5484border: none;
5485background: transparent;
5486padding: 0 4px 4px;
5487margin: 0;
5488cursor: pointer;
5489outline: none;
5490flex: 0 0 auto;
5491}
5492/* Accent chip circle */
5493#cart-kit-upsell-mount .kit-stamp-chip {
5494display: flex;
5495flex-direction: column;
5496align-items: center;
5497text-align: center;
5498max-width: 200px;
5499}
5500#cart-kit-upsell-mount .kit-stamp-chip__circle {
5501width: 200px;
5502height: auto;
5503border-radius: 16px;
5504background: #f3fbff;
5505box-shadow: 0 0 0 1px #e1f0fb;
5506display: flex;
5507align-items: center;
5508justify-content: center;
5509margin-bottom: 6px;
5510transition: box-shadow .15s ease, transform .1s ease, background .15s ease;
5511overflow: clip;
5512}
5513#cart-kit-upsell-mount .kit-stamp-chip__image {
5514width: 100%;
5515height: auto;
5516display: block;
5517}
5518#cart-kit-upsell-mount .kit-stamp-card:hover .kit-stamp-chip__circle {
5519box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.25);
5520transform: translateY(-1px);
5521}
5522#cart-kit-upsell-mount .kit-stamp-card.is-selected .kit-stamp-chip__circle {
5523background: #e6f2ff;
5524box-shadow: 0 0 0 2px var(--ck-accent);
5525}
5526/* =========================================
5527Notary tile layout (only when .is-notary-grid is applied)
5528========================================= */
5529#cart-kit-upsell-mount .kit-stamp-grid.is-notary-grid {
5530display: inline-grid;
5531grid-template-columns: repeat(6, minmax(0, 1fr));
5532gap: 16px;
5533align-items: stretch;
5534}
5535@media (max-width: 900px) {
5536#cart-kit-upsell-mount .kit-stamp-grid.is-notary-grid {
5537grid-template-columns: repeat(4, minmax(0, 1fr));
5538}
5539}
5540@media (max-width: 768px) {
5541#cart-kit-upsell-mount .kit-stamp-grid.is-notary-grid {
5542grid-template-columns: repeat(3, minmax(0, 1fr));
5543}
5544}
5545@media (max-width: 520px) {
5546#cart-kit-upsell-mount .cart-kit-upsell__header-text {
5547width: 50%;
5548}
5549/* Swipeable tiles on mobile */
5550#cart-kit-upsell-mount .kit-stamp-grid.is-notary-grid {
5551display: flex;
5552overflow-x: auto;
5553gap: 12px;
5554scroll-snap-type: x mandatory;
5555-webkit-overflow-scrolling: touch;
5556padding-bottom: 8px;
5557padding-top: 4px;
5558padding-left: 4px;
5559padding-right: 4px;
5560}
5561/* Each tile becomes a "slide" */
5562#cart-kit-upsell-mount .kit-stamp-grid.is-notary-grid .kit-stamp-card.kit-product-card {
5563flex: 0 0 78%;
5564scroll-snap-align: start;
5565}
5566/* Hide scrollbar (nice-to-have) */
5567#cart-kit-upsell-mount .kit-stamp-grid.is-notary-grid::-webkit-scrollbar {
5568display: none;
5569}
5570#cart-kit-upsell-mount .kit-stamp-grid.is-notary-grid {
5571scrollbar-width: none;
5572}
5573/* Target the actual tile grid container */
5574#cart-kit-upsell-mount .kit-stamp-grid {
5575display: flex !important;
5576flex-wrap: nowrap !important;
5577overflow-x: auto !important;
5578overflow-y: hidden !important;
5579gap: 12px !important;
5580scroll-snap-type: x mandatory;
5581-webkit-overflow-scrolling: touch;
5582padding-bottom: 10px;
5583}
5584/* Make each tile act like a slide */
5585#cart-kit-upsell-mount .kit-stamp-grid>* {
5586flex: 0 0 78% !important;
5587/* change to 60% to show ~2 at a time */
5588scroll-snap-align: start;
5589}
5590/* Optional: hide scrollbar */
5591#cart-kit-upsell-mount .kit-stamp-grid::-webkit-scrollbar {
5592display: none;
5593}
5594#cart-kit-upsell-mount .kit-stamp-grid {
5595scrollbar-width: none;
5596}
5597#cart-kit-upsell-mount,
5598#cart-kit-upsell-mount .kit-upsell-inner,
5599#cart-kit-upsell-mount .kit-upsell-body {
5600overflow: visible !important;
5601}
5602}
5603/* Notary tile card */
5604#cart-kit-upsell-mount .kit-stamp-card.kit-product-card {
5605background: #fff;
5606border: 1px solid #e1f0fb;
5607border-radius: 12px;
5608padding: 12px;
5609text-align: left;
5610flex: 1 1 auto;
5611transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease, background .12s ease;
5612}
5613#cart-kit-upsell-mount .kit-stamp-card.kit-product-card:hover {
5614transform: translateY(-1px);
5615border-color: rgba(25, 118, 210, 0.35);
5616box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
5617}
5618#cart-kit-upsell-mount .kit-stamp-card.kit-product-card.ck-swapping {
5619opacity: 0;
5620transition: opacity .18s ease;
5621}
5622#cart-kit-upsell-mount .kit-empty-tiles {
5623padding: 18px;
5624border: 1px dashed #d9d9d9;
5625border-radius: 10px;
5626margin-top: 10px;
5627font-size: 14px;
5628color: #444;
5629background: #fafafa;
5630}
5631/* Pool exhaustion: completion tile */
5632#cart-kit-upsell-mount .kit-stamp-card.kit-product-card.is-complete {
5633background: #f6faf7;
5634border: 1px dashed #b6dfc7;
5635cursor: default;
5636}
5637#cart-kit-upsell-mount .kit-stamp-card.kit-product-card.is-complete:hover {
5638transform: none;
5639border-color: #b6dfc7;
5640box-shadow: none;
5641}
5642#cart-kit-upsell-mount .ck-tile-complete {
5643text-align: center;
5644padding: 16px 10px;
5645}
5646#cart-kit-upsell-mount .ck-tile-complete__icon {
5647font-size: 22px;
5648margin-bottom: 6px;
5649line-height: 1;
5650}
5651#cart-kit-upsell-mount .ck-tile-complete__title {
5652font-weight: 700;
5653margin-bottom: 4px;
5654}
5655#cart-kit-upsell-mount .ck-tile-complete__subtext {
5656font-size: 13px;
5657opacity: 0.85;
5658}
5659#cart-kit-upsell-mount .kit-stamp-card.kit-product-card:focus-visible {
5660outline: 3px solid rgba(25, 118, 210, 0.35);
5661outline-offset: 2px;
5662}
5663#cart-kit-upsell-mount .kit-stamp-card.kit-product-card.is-selected {
5664background: #f5f9ff;
5665border-color: var(--ck-accent);
5666box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.14);
5667}
5668/* Notary tile thumb */
5669#cart-kit-upsell-mount .kit-product-thumb-tile {
5670width: 100%;
5671aspect-ratio: 1 / 1;
5672border-radius: 10px;
5673background: #f3f6fb;
5674border: 1px solid rgba(0, 0, 0, 0.06);
5675overflow: hidden;
5676position: relative;
5677display: flex;
5678align-items: center;
5679justify-content: center;
5680}
5681#cart-kit-upsell-mount .kit-product-thumb-tile img {
5682width: 100%;
5683height: 100%;
5684object-fit: contain;
5685display: block;
5686background-color: #fff;
5687}
5688#cart-kit-upsell-mount .kit-product-meta {
5689margin-top: 10px;
5690}
5691#cart-kit-upsell-mount .kit-product-name {
5692font-weight: 700;
5693font-size: 13px;
5694color: #222;
5695line-height: 1.2;
5696margin: 0;
5697}
5698#cart-kit-upsell-mount .kit-product-price {
5699margin-top: 6px;
5700font-size: 12px;
5701color: #4a4a4a;
5702}
5703#cart-kit-upsell-mount .kit-product-sub {
5704margin-top: 4px;
5705font-size: 11px;
5706color: #6b6b6b;
5707}
5708/* Notary tile selected checkmark */
5709#cart-kit-upsell-mount .kit-stamp-card.kit-product-card.is-selected .kit-product-thumb-tile::after {
5710content: "✓";
5711position: absolute;
5712right: 8px;
5713bottom: 8px;
5714width: 22px;
5715height: 22px;
5716border-radius: 999px;
5717background: var(--ck-success);
5718color: #fff;
5719font-size: 13px;
5720font-weight: 900;
5721display: flex;
5722align-items: center;
5723justify-content: center;
5724box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
5725}
5726/* =========================================
5727Micro PDP "Added" state (tile grey-out + overlay)
5728========================================= */
5729#cart-kit-upsell-mount .kit-stamp-card.kit-product-card.is-added {
5730opacity: 0.55;
5731filter: grayscale(0.25);
5732pointer-events: none;
5733/* prevent clicking again */
5734position: relative;
5735}
5736/* Stronger "Added" overlay */
5737#cart-kit-upsell-mount .kit-stamp-card.kit-product-card.is-added::before {
5738content: "✓ Added to cart";
5739position: absolute;
5740left: 10px;
5741right: 10px;
5742bottom: 10px;
5743padding: 10px 12px;
5744border-radius: 999px;
5745background: rgba(20, 20, 20, .90);
5746color: #fff;
5747font-weight: 800;
5748font-size: 13px;
5749text-align: center;
5750z-index: 6;
5751}
5752#cart-kit-upsell-mount .kit-stamp-card.kit-product-card.is-added img {
5753opacity: 0.75;
5754}
5755/* Module success toast */
5756#cart-kit-upsell-mount .kit-success-toast {
5757position: absolute;
5758right: 16px;
5759bottom: 16px;
5760background: var(--ck-success-hover);
5761color: #fff;
5762padding: 10px 12px;
5763border-radius: 10px;
5764font-weight: 800;
5765font-size: 13px;
5766max-width: 340px;
5767box-shadow: 0 10px 24px rgba(0, 0, 0, 0.18);
5768opacity: 0;
5769transform: translateY(6px);
5770transition: opacity .18s ease, transform .18s ease;
5771pointer-events: none;
5772z-index: 50;
5773}
5774#cart-kit-upsell-mount .kit-success-toast.is-visible {
5775opacity: 1;
5776transform: translateY(0);
5777}
5778/* =========================================
5779Cart row highlight after Micro PDP add
5780========================================= */
5781#cart-kit-upsell-mount .ck-cart-highlight {
5782outline: 3px solid rgba(46, 125, 50, 0.35);
5783outline-offset: 4px;
5784border-radius: 8px;
5785animation: ckPulse 1.6s ease-in-out 0s 2;
5786}
5787@keyframes ckPulse {
57880% {
5789box-shadow: 0 0 0 rgba(46, 125, 50, 0.00);
5790}
579150% {
5792box-shadow: 0 0 0 10px rgba(46, 125, 50, 0.10);
5793}
5794100% {
5795box-shadow: 0 0 0 rgba(46, 125, 50, 0.00);
5796}
5797}
5798/* Ink swatches */
5799#cart-kit-upsell-mount .kit-swatch-list {
5800display: flex;
5801flex-wrap: wrap;
5802gap: 8px;
5803}
5804#cart-kit-upsell-mount .kit-swatch {
5805display: inline-flex;
5806align-items: center;
5807gap: 6px;
5808padding: 4px 8px;
5809border-radius: 999px;
5810border: 1px solid #d8e0ea;
5811background: #fff;
5812cursor: pointer;
5813font-size: 14px;
5814color: #333;
5815transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
5816}
5817#cart-kit-upsell-mount .kit-swatch:hover {
5818border-color: var(--ck-accent);
5819box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
5820}
5821#cart-kit-upsell-mount .kit-swatch.is-selected {
5822border-color: var(--ck-accent);
5823background: #f5f9ff;
5824box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.18);
5825}
5826#cart-kit-upsell-mount .kit-swatch__dot {
5827width: 14px;
5828height: 14px;
5829border-radius: 999px;
5830box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
5831}
5832#cart-kit-upsell-mount .kit-swatch__dot--black {
5833background: #111;
5834}
5835#cart-kit-upsell-mount .kit-swatch__dot--blue {
5836background: #1565c0;
5837}
5838#cart-kit-upsell-mount .kit-swatch__dot--red {
5839background: #c62828;
5840}
5841#cart-kit-upsell-mount .kit-swatch__dot--green {
5842background: var(--ck-success);
5843}
5844#cart-kit-upsell-mount .kit-swatch__dot--purple {
5845background: #6a1b9a;
5846}
5847#cart-kit-upsell-mount .kit-swatch__dot--none {
5848background: repeating-linear-gradient(45deg, #ccc, #ccc 3px, #eee 3px, #eee 6px);
5849}
5850/* Footer / CTA */
5851#cart-kit-upsell-mount .cart-kit-upsell__footer {
5852display: flex;
5853flex-wrap: wrap;
5854align-items: center;
5855justify-content: space-between;
5856gap: 8px;
5857margin-top: 18px;
5858padding: 8px;
5859}
5860#cart-kit-upsell-mount .cart-kit-upsell__price {
5861font-size: 16px;
5862color: #222;
5863display: flex;
5864gap: 14px;
5865align-items: center;
5866flex-wrap: wrap;
5867}
5868#cart-kit-upsell-mount .cart-kit-upsell__cta {
5869padding: 8px 16px;
5870border-radius: 4px;
5871border: 0;
5872background: var(--ck-success);
5873color: #fff;
5874font-size: 13px;
5875font-weight: 600;
5876cursor: pointer;
5877white-space: nowrap;
5878}
5879#cart-kit-upsell-mount .cart-kit-upsell__cta:hover {
5880background: var(--ck-success-hover);
5881}
5882#cart-kit-upsell-mount .cart-kit-upsell__cta:disabled {
5883opacity: .6;
5884cursor: default;
5885}
5886#cart-kit-upsell-mount .cart-kit-upsell__cta-secondary {
5887padding: 8px 14px;
5888border-radius: 4px;
5889border: 1px solid #ccc;
5890background: #f7f7f7;
5891color: #333;
5892font-size: 13px;
5893font-weight: 600;
5894cursor: pointer;
5895white-space: nowrap;
5896}
5897#cart-kit-upsell-mount .cart-kit-upsell__cta-secondary:hover {
5898background: #eee;
5899}
5900/* Spinner on Add button */
5901#cart-kit-upsell-mount .cart-kit-upsell__cta.is-loading {
5902position: relative;
5903padding-right: 30px;
5904}
5905#cart-kit-upsell-mount .cart-kit-upsell__cta.is-loading::after {
5906content: '';
5907position: absolute;
5908right: 10px;
5909top: 50%;
5910width: 14px;
5911height: 14px;
5912margin-top: -7px;
5913border-radius: 50%;
5914border: 2px solid rgba(255, 255, 255, 0.7);
5915border-top-color: transparent;
5916animation: cartKitSpinner .6s linear infinite;
5917}
5918@keyframes cartKitSpinner {
5919to {
5920transform: rotate(360deg);
5921}
5922}
5923/*  Add-to-cart progress bar (the ONLY progress bar in this script) */
5924#cart-kit-upsell-mount .cart-kit-upsell__progresswrap {
5925margin-top: 10px;
5926}
5927#cart-kit-upsell-mount .cart-kit-upsell__progresstop {
5928display: flex;
5929justify-content: space-between;
5930gap: 12px;
5931margin-bottom: 6px;
5932}
5933#cart-kit-upsell-mount .cart-kit-upsell__progresstext,
5934#cart-kit-upsell-mount .cart-kit-upsell__progresspct {
5935font-size: 12px;
5936color: #555;
5937}
5938#cart-kit-upsell-mount .cart-kit-upsell__progressbar {
5939height: 10px;
5940border-radius: 999px;
5941background: #eef3f6;
5942overflow: hidden;
5943}
5944#cart-kit-upsell-mount .cart-kit-upsell__progressfill {
5945height: 100%;
5946background: var(--ck-success);
5947transition: width .18s ease;
5948width: 0%;
5949}
5950/* Success header state */
5951#cart-kit-upsell-mount .cart-kit-upsell__header--success {
5952background: #f1faf3;
5953}
5954#cart-kit-upsell-mount .cart-kit-upsell__success-inline {
5955font-size: 14px;
5956color: var(--ck-success);
5957display: flex;
5958align-items: center;
5959gap: 8px;
5960font-weight: 600;
5961}
5962/* =========================================================
5963Micro PDP Modal (iframe-based)
5964========================================================= */
5965#cart-kit-upsell-mount .ck-micropdp-overlay {
5966position: fixed;
5967inset: 0;
5968background: rgba(0, 0, 0, .55);
5969display: none;
5970align-items: center;
5971justify-content: center;
5972z-index: 999999;
5973/* above cart + header */
5974}
5975#cart-kit-upsell-mount .ck-micropdp-overlay.is-open {
5976display: flex;
5977}
5978#cart-kit-upsell-mount .ck-micropdp-modal {
5979width: min(960px, 92vw);
5980height: 96vh;
5981background: #fff;
5982border-radius: 12px;
5983overflow: hidden;
5984box-shadow: 0 12px 40px rgba(0, 0, 0, .35);
5985display: flex;
5986flex-direction: column;
5987}
5988@media (max-width: 600px) {
5989#cart-kit-upsell-mount .ck-micropdp-modal {
5990width: 96vw;
5991height: 98vh;
5992border-radius: 10px;
5993}
5994}
5995#cart-kit-upsell-mount .ck-micropdp-head {
5996display: flex;
5997flex-direction: column;
5998align-items: stretch;
5999justify-content: flex-start;
6000gap: 10px;
6001padding: 12px 14px;
6002border-bottom: 1px solid #e6e6e6;
6003background: #f7fbff;
6004}
6005#cart-kit-upsell-mount .ck-micropdp-head-top {
6006display: flex;
6007align-items: center;
6008justify-content: space-between;
6009gap: 12px;
6010width: 100%;
6011}
6012#cart-kit-upsell-mount .ck-micropdp-progress {
6013width: 100%;
6014display: flex;
6015flex-direction: column;
6016gap: 6px;
6017}
6018#cart-kit-upsell-mount .ck-micropdp-progressbar {
6019width: 100%;
6020height: 8px;
6021background: rgba(25, 118, 210, .14);
6022border-radius: 999px;
6023overflow: hidden;
6024}
6025#cart-kit-upsell-mount .ck-micropdp-progressfill {
6026height: 100%;
6027width: 0%;
6028background: var(--ck-accent);
6029border-radius: 999px;
6030transition: width .25s ease;
6031}
6032#cart-kit-upsell-mount .ck-micropdp-progresstext {
6033font-size: 12px;
6034color: #1b1b1b;
6035opacity: .85;
6036line-height: 1.2;
6037}
6038/* Micro PDP header progress — completion delight */
6039#cart-kit-upsell-mount .ck-micropdp-progress.is-complete .ck-micropdp-progressfill {
6040background: var(--ck-success);
6041animation: ckMicroFillPulse .8s ease-in-out 0s 2;
6042}
6043#cart-kit-upsell-mount .ck-micropdp-progress.is-complete .ck-micropdp-progresstext {
6044font-weight: 700;
6045opacity: 1;
6046}
6047@keyframes ckMicroFillPulse {
60480% {
6049filter: brightness(1);
6050}
605150% {
6052filter: brightness(1.25);
6053}
6054100% {
6055filter: brightness(1);
6056}
6057}
6058#cart-kit-upsell-mount .ck-micropdp-title {
6059font-size: 16px;
6060font-weight: 700;
6061color: #1b1b1b;
6062margin: 0;
6063}
6064#cart-kit-upsell-mount .ck-micropdp-sub {
6065font-size: 12px;
6066color: #666;
6067margin-top: 2px;
6068}
6069#cart-kit-upsell-mount .ck-micropdp-actions {
6070display: flex;
6071gap: 8px;
6072align-items: center;
6073}
6074#cart-kit-upsell-mount .ck-micropdp-btn {
6075border: 1px solid #cfd8e3;
6076background: #fff;
6077color: #1b1b1b;
6078border-radius: 8px;
6079padding: 8px 10px;
6080cursor: pointer;
6081font-weight: 600;
6082font-size: 13px;
6083}
6084/* Loading cursor while micro PDP is submitting */
6085body.ck-loading,
6086body.ck-loading * {
6087cursor: progress !important;
6088}
6089#cart-kit-upsell-mount .ck-micropdp-btn:hover {
6090background: #f2f5f9;
6091}
6092#cart-kit-upsell-mount .ck-micropdp-body {
6093flex: 1 1 auto;
6094position: relative;
6095}
6096#cart-kit-upsell-mount .ck-micropdp-iframe {
6097width: 100%;
6098height: 100%;
6099border: 0;
6100display: block;
6101}
6102#cart-kit-upsell-mount .ck-micropdp-loading {
6103position: absolute;
6104inset: 0;
6105display: flex;
6106align-items: center;
6107justify-content: center;
6108background: rgba(255, 255, 255, .85);
6109font-weight: 700;
6110color: #444;
6111}
6112.ck-debug-keywords {
6113margin-top: 4px;
6114font-size: 11px;
6115line-height: 1.4;
6116}
6117.ck-debug-keyword {
6118display: inline-block;
6119background: rgba(255, 255, 255, 0.08);
6120border: 1px solid rgba(255, 255, 255, 0.12);
6121border-radius: 999px;
6122padding: 2px 8px;
6123margin: 2px 6px 2px 0;
6124opacity: .95;
6125}
6126.ck-debug-keyword.muted,
6127.ck-debug-keywords.muted {
6128opacity: 0.55;
6129font-style: italic;
6130}
6131@media (max-width: 640px) {
6132#cart-kit-upsell-mount .cart-kit-upsell__title {
6133font-size: 16px;
6134}
6135}
6136@media (max-width: 520px) {
6137/* Accent chip mobile layout */
6138#cart-kit-upsell-mount .kit-stamp-grid:not(.is-notary-grid) {
6139display: grid !important;
6140grid-template-columns: repeat(2, 1fr);
6141gap: 20px 12px;
6142justify-items: center;
6143}
6144#cart-kit-upsell-mount .kit-stamp-card {
6145width: 100%;
6146max-width: 160px;
6147}
6148#cart-kit-upsell-mount .kit-stamp-chip__circle {
6149width: 80px;
6150height: 80px;
6151}
6152#cart-kit-upsell-mount .kit-stamp-chip__image {
6153max-width: 62px;
6154max-height: 62px;
6155}
6156} </style> 
6156<script> // ---- GA safety shim (prevents "gtag is not defined") ----
6157window.dataLayer = window.dataLayer || [];
6158window.gtag = window.gtag || function () { window.dataLayer.push(arguments); };
6159/**
6160* Cart Kit Upsell Module — Contract (Do Not Break)
6161* - Runs only on Cart page
6162* - Never blocks checkout interactions
6163* - Never modifies existing cart items (only adds new items)
6164* - Fails silently (no uncaught JS errors if PDP fetch/hydration fails)
6165* - Always has a fallback persona (generic)
6166* - Micro PDP is only used for SKUs that require custom options/uploads
6167*/
6168document.addEventListener('DOMContentLoaded', function () {
6169// ----------------------------------------------------
6170// 0) Render on Cart?
6171// ----------------------------------------------------
6172// Only run SmartCart on the actual cart page.
6173// IMPORTANT: product pages and microPDP iframe pages often contain ".cart" elements (header/minicart),
6174// so the old detection was firing in the iframe and breaking.
6175var isCart =
6176document.body.classList.contains('checkout-cart-index') ||
6177/\/checkout\/cart(\/|$)/.test(window.location.pathname);
6178if (!isCart) return;
6179// ----------------------------------------------------
6180// 1) Mount BELOW .shoppingCartHeader-container
6181// ----------------------------------------------------
6182// Zephyr cart is Tailwind markup (no #shopping-cart-table / .shoppingCartHeader-container).
6183// Mount after the "Shopping Cart" H1 header block; keep legacy anchors as fallback.
6184function ckFindZephyrHeader() {
6185var hs = document.querySelectorAll('h1');
6186for (var i = 0; i < hs.length; i++) {
6187if (/cart/i.test(hs[i].textContent || '')) return hs[i].closest('.mb-6') || hs[i].parentElement;
6188}
6189return null;
6190}
6191var zHeader = ckFindZephyrHeader();
6192var anchor =
6193document.querySelector('.shoppingCartHeader-container') ||
6194zHeader;
6195var fallback =
6196(document.querySelector('#shopping-cart-table') && document.querySelector('#shopping-cart-table').parentElement) ||
6197(zHeader && zHeader.parentElement) ||
6198(document.querySelector('[data-cart-item-id]') && document.querySelector('[data-cart-item-id]').closest('.pb-32, main, #maincontent')) ||
6199document.querySelector('.cart');
6200if (!anchor && !fallback) return;
6201if (document.getElementById('cart-kit-upsell-mount')) return;
6202var mount = document.createElement('div');
6203mount.id = 'cart-kit-upsell-mount';
6204if (anchor && anchor.parentNode) anchor.insertAdjacentElement('afterend', mount);
6205else fallback.appendChild(mount);
6206// ----------------------------------------------------
6207// 2) Query params
6208// ----------------------------------------------------
6209var urlParams = new URLSearchParams(window.location.search);
6210var accentAddedQS = urlParams.get('accentAdded') === '1';
6211var accentInCart = false;
6212var IS_ACCENT_EXPERIENCE = !!(accentAddedQS || accentInCart);
6213var accentAddMoreQS = urlParams.get('accentAddMore') === '1';
6214var microAddedQS = urlParams.get('microAdded'); // productId
6215var microAddedNameQS = urlParams.get('microAddedName'); // product name (best effort)
6216// ===== SmartCart Persona Memory (Device-based) =====
6217const SMARTCART_PERSONA_STORAGE_KEY = "smartcart_persona_v1";
6218// --- Helpers ---
6219function nowMs() { return Date.now(); }
6220function safeJsonParse(str) {
6221try { return JSON.parse(str); } catch { return null; }
6222}
6223// --- Read / Write memory ---
6224function savePersonaToDevice(personaKey) {
6225if (!personaKey) return;
6226const payload = {
6227key: personaKey,
6228setAt: Date.now(),
6229version: 2 // bump version since behavior changed
6230};
6231try {
6232localStorage.setItem(
6233SMARTCART_PERSONA_STORAGE_KEY,
6234JSON.stringify(payload)
6235);
6236} catch (e) {
6237// storage may be blocked; fail silently
6238}
6239}
6240function getPersonaFromDevice() {
6241let raw;
6242try {
6243raw = localStorage.getItem(SMARTCART_PERSONA_STORAGE_KEY);
6244} catch {
6245return null;
6246}
6247if (!raw) return null;
6248const payload = safeJsonParse(raw);
6249if (!payload?.key) return null;
6250return payload;
6251}
6252function clearPersonaFromDevice() {
6253try { localStorage.removeItem(SMARTCART_PERSONA_STORAGE_KEY); } catch { }
6254}
6255// --- GA user_property (set once per session/page lifecycle) ---
6256function setSmartCartUserPropertyOnce(personaKey) {
6257if (!personaKey) return;
6258if (window.__SMARTCART_USER_PROP_SET__) return;
6259window.__SMARTCART_USER_PROP_SET__ = true;
6260// If consent gating exists, you can wrap this (see section 4)
6261if (typeof gtag === "function") {
6262gtag("set", "user_properties", { smartcart_persona: personaKey });
6263}
6264}
6265function onSmartCartMeaningfulEngagement(personaKey) {
6266if (!personaKey) return;
6267// Prevent repeated writes in one session/page lifecycle
6268if (window.__SMARTCART_ENGAGED__) return;
6269window.__SMARTCART_ENGAGED__ = true;
6270// Remember persona on device (UX)
6271savePersonaToDevice(personaKey);
6272// Attribute session in GA (analytics)
6273setSmartCartUserPropertyOnce(personaKey);
6274}
6275// ----------------------------------------------------
6276// Debug exports (safe): expose helpers in console
6277// ----------------------------------------------------
6278if (new URLSearchParams(location.search).get("sc_debug") === "1") {
6279window.__SMARTCART_DEBUG__ = window.__SMARTCART_DEBUG__ || {};
6280window.__SMARTCART_DEBUG__.getPersonaFromDevice = getPersonaFromDevice;
6281window.__SMARTCART_DEBUG__.savePersonaToDevice = savePersonaToDevice;
6282window.__SMARTCART_DEBUG__.setSmartCartUserPropertyOnce = setSmartCartUserPropertyOnce;
6283window.__SMARTCART_DEBUG__.onSmartCartMeaningfulEngagement = onSmartCartMeaningfulEngagement;
6284}
6285// ----------------------------------------------------
6286// 2.5) Hard SKU overrides (micro PDP required)
6287// ----------------------------------------------------
6288var HARD_SKU_OVERRIDES = {
6289"2577": { addMode: "microPdp" },
6290"4419": { addMode: "microPdp" },
6291"10796": { addMode: "microPdp" }
6292};
6293// ----------------------------------------------------
6294// 3) PERSONA DETECTION (v2: Config-driven cart text scan)
6295// ----------------------------------------------------
6296function loadPersonaConfigFromBlock() {
6297var el = document.getElementById('cart-kit-personas');
6298if (!el) return null;
6299try {
6300var raw = (el.textContent || el.innerText || '').trim();
6301if (!raw) return null;
6302return JSON.parse(raw);
6303} catch (e) {
6304console.warn('Cart Kit: Failed to parse persona JSON', e);
6305return null;
6306}
6307}
6308function buildCartSignals() {
6309// Adjust selectors to your cart markup if needed
6310var nameNodes = document.querySelectorAll('.product-name a, .product-name, .cart.item .product-item-name a, .cart.item .product-item-name');
6311var optNodes = document.querySelectorAll('.item-options, dl.item-options, .product.options, .cart.item .item-options');
6312var names = [];
6313for (var i = 0; i < nameNodes.length; i++) {
6314var t = (nameNodes[i].textContent || '').trim();
6315if (t) names.push(t);
6316}
6317var opts = [];
6318for (var j = 0; j < optNodes.length; j++) {
6319var o = (optNodes[j].textContent || '').trim();
6320if (o) opts.push(o);
6321}
6322var titleText = names.join(' | ');
6323var fullText = (names.concat(opts)).join(' | ');
6324return { titleText: titleText, fullText: fullText };
6325}
6326// Product titles only (recommended for persona scoring)
6327function getCartTitleTextLower() {
6328try {
6329// Magento cart on SimplyStamps: h2.cartProductName a
6330var nameNodes = document.querySelectorAll(
6331'[data-cart-item-id] h3 a,' +
6332'[data-cart-item-id] h3,' +
6333'#shopping-cart-table h2.cartProductName a,' +
6334'#shopping-cart-table h2.cartProductName,' +
6335'#shopping-cart-table .product-name a,' +
6336'#shopping-cart-table .product-name,' +
6337'.product-item-name a,' +
6338'.product-item-name'
6339);
6340var names = [];
6341for (var i = 0; i < nameNodes.length; i++) {
6342var t = (nameNodes[i].textContent || '').trim();
6343if (t) names.push(t);
6344}
6345return names.join(' | ').toLowerCase();
6346} catch (e) {
6347return '';
6348}
6349}
6350// Full cart text (titles + options). Keep for product scoring/debug if desired.
6351function getCartFullTextLower() {
6352try {
6353var titleLower = getCartTitleTextLower();
6354var optNodes = document.querySelectorAll(
6355'[data-cart-item-id] .flex-wrap,' +
6356'#shopping-cart-table .item-options,' +
6357'#shopping-cart-table dl.item-options,' +
6358'.item-options,' +
6359'dl.item-options'
6360);
6361var opts = [];
6362for (var j = 0; j < optNodes.length; j++) {
6363var o = (optNodes[j].textContent || '').trim();
6364if (o) opts.push(o);
6365}
6366// titleLower is already lowercase
6367var full = (titleLower ? [titleLower] : []).concat(opts.join(' | ').toLowerCase()).join(' | ');
6368return full.toLowerCase();
6369} catch (e) {
6370return '';
6371}
6372}
6373// ----------------------------------------------------
6374// Cart product path signals (reliable for "already in cart" suppression)
6375// ----------------------------------------------------
6376function normalizeUrlPathForCompare(url) {
6377try {
6378var u = new URL(url, window.location.origin);
6379var p = (u.pathname || '');
6380// normalize: drop .html and trailing slashes
6381p = p.replace(/\.html$/i, '').replace(/\/+$/, '');
6382return String(p || '').toLowerCase();
6383} catch (e) {
6384try {
6385var s = String(url || '').split('#')[0].split('?')[0];
6386s = s.replace(/\.html$/i, '').replace(/\/+$/, '');
6387return String(s || '').toLowerCase();
6388} catch (e2) { return ''; }
6389}
6390}
6391function getCartProductPathSet() {
6392var set = Object.create(null);
6393try {
6394var nodes = document.querySelectorAll(
6395'[data-cart-item-id] h3 a,' +
6396'#shopping-cart-table .product-item-name a,' +
6397'#shopping-cart-table a.product-item-link,' +
6398'.cart.table-wrapper .product-item-name a,' +
6399'.cart.table-wrapper a.product-item-link,' +
6400'.cart-container .product-item-name a,' +
6401'.cart-container a.product-item-link'
6402);
6403for (var i = 0; i < nodes.length; i++) {
6404var href = nodes[i] && (nodes[i].getAttribute('href') || nodes[i].href);
6405if (!href) continue;
6406var pth = normalizeUrlPathForCompare(href);
6407if (!pth) continue;
6408// Exclude cart actions/downloads that appear in the cart table
6409if (/^\/checkout\//i.test(pth)) continue;
6410if (/^\/sales\//i.test(pth)) continue;
6411if (/^\/download\//i.test(pth)) continue;
6412set[pth] = true;
6413}
6414} catch (e) { }
6415return set;
6416}
6417// Zephyr: exact product-id match from cart cards (data-product-id = Magento entity id)
6418function getCartProductIdSet() {
6419var set = Object.create(null);
6420try {
6421var nodes = document.querySelectorAll('[data-product-id]');
6422for (var i = 0; i < nodes.length; i++) {
6423var pid = String(nodes[i].getAttribute('data-product-id') || '').trim();
6424if (pid) set[pid] = true;
6425}
6426} catch (e) { }
6427return set;
6428}
6429// Compatibility alias used by existing scoring code
6430function getCartTextLower() {
6431// Persona scoring should use titles (cleanest signal)
6432return getCartTitleTextLower();
6433// If you ever want “titles + options” instead, swap to:
6434// return getCartFullTextLower();
6435}
6436// Spreadsheet-driven personas (local environment)
6437var PERSONAS_FALLBACK = [
6438{
6439key: 'brand_champions',
6440priority: 80,
6441match: [
6442'business stamp', 'logo stamp', 'logo address stamp', 'custom logo stamp', 'company stamp', 'business address stamp',
6443'return address stamp business', 'self inking logo stamp', 'office branding', 'small business supplies',
6444'custom business stamps', 'bulk stamps'
6445],
6446headerTitle: 'Brand Essentials: finish your packaging strong.',
6447stampLabel: 'Business add-ons (popular picks)',
6448inkLabel: 'Choose an ink color for consistent branding',
6449tooltip: 'These add-ons help your brand look polished across packaging, mailers, and customer communications.',
6450defaultPrimaryIndex: 0,
6451tileSubtext: 'Popular with small businesses',
6452items: [
6453{ productId: '2577', productUrl: 'https://www.simplystamps.com/qr-code-stamp-1-5-8-x-1-5-8', sortOrder: 2, addMode: 'microPdp' },
6454{ productId: '4419', productUrl: 'https://www.simplystamps.com/medium-custom-logo-stamp', sortOrder: 1, addMode: 'microPdp' },
6455{ productId: '10796', productUrl: 'https://www.simplystamps.com/mini-square-logo-stamp', sortOrder: 3, addMode: 'microPdp' }
6456]
6457},
6458{
6459key: 'home_sweet_stampers',
6460priority: 70,
6461match: [
6462'return address stamp', 'address stamp', 'custom address stamp', 'family address stamp', 'wedding address stamp',
6463'holiday address stamp', 'mail stamp', 'envelope stamp', 'home address stamp', 'personalized address stamp'
6464],
6465headerTitle: 'Mail Essentials: everything you need for clean, easy stamping.',
6466stampLabel: 'Helpful add-ons for everyday mail',
6467inkLabel: 'Choose an ink color',
6468tooltip: 'Perfect for cards, envelopes, and seasonal mail — these extras make stamping easier and mess-free.',
6469defaultPrimaryIndex: 0,
6470tileSubtext: 'Great for everyday mail',
6471items: [
6472{ productId: '5654', productUrl: 'https://www.simplystamps.com/merry-christmas-craft-rubber-stamp', sortOrder: 1 },
6473{ productId: '6126', productUrl: 'https://www.simplystamps.com/happy-birthday-smiley-craft-stamp', sortOrder: 2 },
6474{ productId: '5529', productUrl: 'https://www.simplystamps.com/live-well-rubber-stamp', sortOrder: 3 }
6475]
6476},
6477{
6478key: 'office_optimizers',
6479priority: 85,
6480match: [
6481'signature', 'received stamp', 'approved stamp', 'paid stamp', 'office stamp', 'date stamp', 'custom office stamp',
6482'self inking stamp', 'pre inked stamp', 'inspection stamp', 'file stamp', 'reorder stamp', 'replacement ink'
6483],
6484headerTitle: 'Office Essentials: stay stocked and avoid reorders.',
6485stampLabel: 'Office add-ons (frequently reordered)',
6486inkLabel: 'Ink selection for daily office use',
6487tooltip: 'These supplies help keep workflows moving without interruptions or last-minute supply runs.',
6488defaultPrimaryIndex: 0,
6489tileSubtext: 'Frequently reordered',
6490items: [
6491{ productId: '6560', productUrl: 'https://www.simplystamps.com/trodat-id-protection-stamp', sortOrder: 1 },
6492{ productId: '2420', productUrl: 'https://www.simplystamps.com/35305-br-black-eksc-4-br-secure-marker-br-4mm-chisel-tip', sortOrder: 2 }
6493]
6494},
6495{
6496key: 'notary_ninjas',
6497priority: 100,
6498match: [
6499'notary public', 'notary', 'notary stamp', 'notary seal', 'notary embosser', 'state notary stamp', 'notary supplies',
6500'notary kit', 'notary replacement stamp', 'official notary seal', 'notary expiration stamp',
6501'jurat stamp', 'acknowledgment stamp'
6502],
6503headerTitle: 'Notary Essentials: add backup ink & supplies before checkout.',
6504stampLabel: 'Notary add-ons (quick picks)',
6505inkLabel: 'Ink choice (recommended for notary work)',
6506tooltip: 'These items help ensure clean, compliant notarizations and prevent downtime if ink runs out.',
6507defaultPrimaryIndex: 0,
6508tileSubtext: 'Recommended for notary work',
6509items: [
6510{ productId: '11712', productUrl: 'https://www.simplystamps.com/the-complete-notary-records-book', sortOrder: 1 },
6511{ productId: '6524', productUrl: 'https://www.simplystamps.com/embossing-adhesive-foil', sortOrder: 2 },
6512{ productId: '7986', productUrl: 'https://www.simplystamps.com/seal-impression-inker', sortOrder: 3 },
6513{ productId: '13387', productUrl: 'https://www.simplystamps.com/embosser-vinyl-black-pouch', sortOrder: 4 }
6514]
6515},
6516{
6517key: 'classroom_heroes',
6518priority: 60,
6519match: [
6520'teacher stamp', 'teacher ', 'grading stamp', 'school stamp', 'custom teacher stamp', 'classroom stamp',
6521'reward stamp', 'student feedback stamp', 'motivational stamp', 'education stamp', 'teacher name stamp'
6522],
6523headerTitle: 'Classroom Essentials: be ready for every lesson.',
6524stampLabel: 'Teacher add-ons (classroom favorites)',
6525inkLabel: 'Choose an ink color students will love',
6526tooltip: 'Extra ink and supplies help keep grading, rewards, and feedback flowing all year.',
6527defaultPrimaryIndex: 0,
6528tileSubtext: 'Classroom favorites',
6529items: [
6530{ productId: '19215', productUrl: 'https://www.simplystamps.com/stacked-teacher-stamps-set-of-6', sortOrder: 1 },
6531{ productId: '10965', productUrl: 'https://www.simplystamps.com/no-name-no-fame-owl-teacher-stamp', sortOrder: 2 },
6532{ productId: '18507', productUrl: 'https://www.simplystamps.com/craft-ink-pads', sortOrder: 3 }
6533]
6534},
6535{
6536key: 'craft_crowd',
6537priority: 50,
6538match: [
6539'craft stamp', 'handmade stamp', 'custom rubber stamp', 'craft rubber stamp', 'logo stamp small business', 'etsy stamp',
6540'thank you stamp', 'packaging stamp', 'product label stamp', 'arts and crafts stamp', 'creative stamp'
6541],
6542headerTitle: 'Creative Essentials: finish your projects with ease.',
6543stampLabel: 'Craft add-ons (popular with makers)',
6544inkLabel: 'Choose an ink color to match your style',
6545tooltip: 'These supplies are perfect for handmade goods, packaging, and creative projects.',
6546allowedInk: ['black', 'blue', 'red', 'green', 'purple'],
6547defaultPrimaryIndex: 0,
6548tileSubtext: 'Popular with makers',
6549items: [
6550{ productId: '18507', productUrl: 'https://www.simplystamps.com/craft-ink-pads', sortOrder: 1 },
6551{ productId: '10464', productUrl: 'https://www.simplystamps.com/gem-cluster-stamp', sortOrder: 2 },
6552{ productId: '9768', productUrl: 'https://www.simplystamps.com/seamless-eucalyptus-stamp', sortOrder: 3 }
6553]
6554},
6555// Fallback (Accent Stamp grid)
6556{
6557key: 'generic',
6558priority: 0,
6559match: [],
6560headerTitle: 'Choose your favorite accent stamp below and add to cart.',
6561stampLabel: 'Choose one or many Accent Stamp Designs:',
6562inkLabel: 'Would you like an Ink Pad? (Needed to make impression)',
6563tooltip: 'Stamp impression size: approx 1.75" × 0.67".',
6564allowedInk: ['black', 'blue', 'red', 'green', 'purple', 'none'],
6565defaultPrimaryIndex: 0,
6566tileSubtext: '',
6567items: []
6568}
6569];
6570function normalizePersonasConfigToArray(cfg) {
6571// Expect cfg like: { version: "...", personas: { key: { ... }, ... } }
6572if (!cfg || !cfg.personas || typeof cfg.personas !== 'object') return null;
6573var out = [];
6574Object.keys(cfg.personas).forEach(function (key) {
6575var p = cfg.personas[key] || {};
6576// Map JSON schema → existing runtime schema
6577// Your runtime expects: key, priority, match, headerTitle, stampLabel, inkLabel, tooltip, allowedInk, defaultPrimaryIndex, tileSubtext, items[]
6578var persona = {
6579key: key,
6580priority: (typeof p.priority === 'number' ? p.priority : 0),
6581match: Array.isArray(p.match_keywords) ? p.match_keywords : [],
6582headerTitle: p.header_title || '',
6583stampLabel: p.stamp_label || '',
6584inkLabel: p.ink_label || '',
6585tooltip: p.tooltip_text || '',
6586allowedInk: Array.isArray(p.allowed_ink_colors) ? p.allowed_ink_colors : [],
6587defaultPrimaryIndex: (typeof p.default_primary_index === 'number' ? p.default_primary_index : 0),
6588tileSubtext: p.tile_subtext || '',
6589items: []
6590};
6591// Products → items (what buildGrid uses)
6592if (Array.isArray(p.products)) {
6593persona.items = p.products.map(function (prod) {
6594return {
6595productId: String(prod.product_id || '').trim(),
6596productUrl: prod.product_url || '',
6597sortOrder: (typeof prod.sort_order === 'number' ? prod.sort_order : 9999),
6598tileSubtext: prod.tile_subtext || '',
6599addMode: prod.addMode || prod.add_mode || undefined,
6600//  NEW (traits)
6601role: prod.role || 'helper',
6602friction: prod.friction || 'low',
6603experimental: (typeof prod.experimental === 'boolean' ? prod.experimental : false),
6604//  NEW (product-level keywords for scoring)
6605matchKeywords: Array.isArray(prod.match_keywords) ? prod.match_keywords : []
6606};
6607}).filter(function (it) { return !!it.productId || !!it.productUrl; });
6608}
6609out.push(persona);
6610});
6611return out;
6612}
6613var PERSONA_CONFIG = loadPersonaConfigFromBlock();
6614// ----------------------------------------------------
6615// 4) Now set PERSONAS like this (config → normalized array → fallback)
6616// ----------------------------------------------------
6617var PERSONAS = (function () {
6618// Try config block first
6619var fromConfig = normalizePersonasConfigToArray(PERSONA_CONFIG);
6620// Use config if it produced at least 1 persona, otherwise fallback
6621var arr = (Array.isArray(fromConfig) && fromConfig.length) ? fromConfig : PERSONAS_FALLBACK.slice();
6622// Ensure we ALWAYS have a generic fallback persona
6623var hasGeneric = arr.some(function (p) { return p && p.key === 'generic'; });
6624if (!hasGeneric) {
6625var genericFallback = PERSONAS_FALLBACK.find(function (p) { return p.key === 'generic'; });
6626if (genericFallback) arr.push(genericFallback);
6627}
6628// Normalize a few expected fields defensively
6629arr.forEach(function (p) {
6630if (!p) return;
6631if (!Array.isArray(p.match)) p.match = [];
6632if (!Array.isArray(p.items)) p.items = [];
6633if (!Array.isArray(p.allowedInk) || !p.allowedInk.length) {
6634// keep your existing default behavior (includes 'none')
6635p.allowedInk = ['black', 'blue', 'red', 'green', 'purple', 'none'];
6636}
6637});
6638return arr;
6639})();
6640PERSONAS.forEach(function (p) {
6641if (Array.isArray(p.items)) p.items.sort(function (a, b) { return (a.sortOrder || 0) - (b.sortOrder || 0); });
6642});
6643PERSONAS.sort(function (a, b) {
6644if (a.key === 'generic') return 1;
6645if (b.key === 'generic') return -1;
6646return (b.priority || 0) - (a.priority || 0);
6647});
6648function setLoadingCursor(on) {
6649document.body.classList.toggle('ck-loading', !!on);
6650}
6651// ----------------------------------------------------
6652// 3.9) SCORING (persona + product)
6653// ----------------------------------------------------
6654function keywordMatchScore(keywords, haystackLower) {
6655if (!Array.isArray(keywords) || !keywords.length) return { score: 0, hits: 0, matched: [] };
6656var score = 0, hits = 0, matched = [];
6657for (var i = 0; i < keywords.length; i++) {
6658var k = String(keywords[i] || '').toLowerCase().trim();
6659if (!k) continue;
6660// Exact substring match
6661if (haystackLower.indexOf(k) > -1) {
6662score += (k.indexOf(' ') > -1 ? 10 : 4);
6663hits += 1;
6664matched.push(k);
6665continue;
6666}
6667// Phrase fallback: all tokens appear anywhere
6668if (k.indexOf(' ') > -1) {
6669var parts = k.split(/\s+/).filter(Boolean);
6670if (parts.length >= 2) {
6671var all = true;
6672for (var p = 0; p < parts.length; p++) {
6673if (haystackLower.indexOf(parts[p]) === -1) { all = false; break; }
6674}
6675if (all) {
6676score += 6;
6677hits += 1;
6678matched.push(k + ' (tokens)');
6679}
6680}
6681}
6682}
6683return { score: score, hits: hits, matched: matched };
6684}
6685function scoreKeywordHits(keywords, haystackLower) {
6686return keywordMatchScore(keywords, haystackLower).score;
6687}
6688function scorePersonaFromCart(personaObj, cartTextLower) {
6689// Persona match keywords are in personaObj.match[]
6690var base = scoreKeywordHits(personaObj.match, cartTextLower);
6691// Priority is a tie-breaker, not a primary driver.
6692// Still, give a tiny nudge so higher-priority personas win ties.
6693var priorityNudge = (typeof personaObj.priority === 'number') ? (personaObj.priority / 1000) : 0;
6694return base + priorityNudge;
6695}
6696function detectPersonaKey() {
6697var cartTextLower = getCartTitleTextLower();
6698var bestKey = 'generic';
6699var bestScore = -1;
6700for (var i = 0; i < PERSONAS.length; i++) {
6701var p = PERSONAS[i];
6702if (!p || p.key === 'generic') continue;
6703var s = scorePersonaFromCart(p, cartTextLower);
6704if (s > bestScore) {
6705bestScore = s;
6706bestKey = p.key;
6707}
6708}
6709// If nobody matched anything, fall back
6710if (bestScore <= 0) return 'generic';
6711return bestKey;
6712}
6713function getPersonaByKey(key) {
6714for (var i = 0; i < PERSONAS.length; i++) {
6715if (PERSONAS[i].key === key) return PERSONAS[i];
6716}
6717return PERSONAS[PERSONAS.length - 1];
6718}
6719var activePersonaKey = detectPersonaKey();
6720var persona = getPersonaByKey(activePersonaKey);
6721window.__CK = {
6722personas: PERSONAS,
6723activePersonaKey: activePersonaKey,
6724persona: persona
6725};
6726var useTiles = Array.isArray(persona.items) && persona.items.length > 0;
6727// Decide which persona to use for this render
6728function choosePersona({ detectedKey, detectedConfidence = 0, confidenceThreshold = 0.75 }) {
6729const stored = getPersonaFromDevice(); // { key, setAt... } or null
6730// Strong real-time signal wins
6731if (detectedKey && detectedConfidence >= confidenceThreshold) {
6732return { key: detectedKey, source: "detected_strong" };
6733}
6734// Otherwise fall back to remembered persona if available
6735if (stored?.key) {
6736return { key: stored.key, source: "stored" };
6737}
6738// Otherwise use detected (weak) or default
6739if (detectedKey) return { key: detectedKey, source: "detected_weak" };
6740return { key: "default", source: "default" };
6741}
6742// ----------------------------------------------------
6743// 3b) Analytics helper (optional)
6744// ----------------------------------------------------
6745function pushUpsellEvent(eventName, payload) {
6746payload = payload || {};
6747// ----------------------------------------------------
6748// Enrich payload persona context so GA4 can segment
6749// ----------------------------------------------------
6750try {
6751var __ckPersonaKey = (typeof activePersonaKey !== 'undefined' && activePersonaKey) ? String(activePersonaKey) : 'generic';
6752// Prefer persona.headerTitle as a human label, fall back to key
6753var __ckPersonaLabel = (typeof persona !== 'undefined' && persona && (persona.headerTitle || persona.key))
6754? String(persona.headerTitle || persona.key)
6755: __ckPersonaKey;
6756// Only set defaults if caller didn't provide them
6757if (typeof payload.persona_key === 'undefined') payload.persona_key = __ckPersonaKey;
6758if (typeof payload.persona_label === 'undefined') payload.persona_label = __ckPersonaLabel;
6759if (typeof payload.upsell_variant === 'undefined') payload.upsell_variant = (typeof useTiles !== 'undefined' && useTiles) ? 'tiles' : 'stamps';
6760if (typeof payload.page_path === 'undefined') payload.page_path = (window.location && window.location.pathname) ? window.location.pathname : '';
6761} catch (eCtx) { }
6762// ----------------------------------------------------
6763// Option 4: Meaningful engagement → remember persona + set GA user_property
6764// Trigger only on specific SmartCart events (no impressions)
6765// ----------------------------------------------------
6766try {
6767var isMeaningful =
6768eventName === 'SmartCart_upsell_tile_selected' ||
6769eventName === 'SmartCart_upsell_tile_click' ||
6770eventName === 'SmartCart_upsell_tile_add' ||
6771eventName === 'SmartCart_upsell_tile_added' ||
6772eventName === 'SmartCart_upsell_added';
6773if (isMeaningful) {
6774var pk =
6775(payload && (payload.persona_key || payload.personaKey)) ||
6776(typeof activePersonaKey !== 'undefined' && activePersonaKey) ||
6777(window.__CK && window.__CK.activePersonaKey);
6778if (pk) onSmartCartMeaningfulEngagement(String(pk));
6779}
6780} catch (e) { }
6781// Always push to dataLayer (never depend on gtag existing)
6782try {
6783window.dataLayer = window.dataLayer || [];
6784window.dataLayer.push(Object.assign({ event: eventName }, payload));
6785} catch (e) { }
6786// Optional: also send to gtag if it exists (but NEVER crash if missing)
6787try {
6788if (typeof window.gtag === 'function') {
6789window.gtag('event', eventName, payload);
6790}
6791} catch (e) { }
6792// Mirror to GA4 standard ecommerce (see CK_GA4 block below). Never
6793// let an analytics mirror break the module.
6794try { ckGaMirror(eventName, payload); } catch (eGa) { }
6795}
6796// ----------------------------------------------------
6797// 3b-ii) GA4 standard ecommerce mirror   [added 2026-09-15]
6798// ----------------------------------------------------
6799// WHY: the SmartCart_* custom events carry product ids only inside a
6800// nested array (tiles_shown), and GA4 cannot report on nested arrays.
6801// The property has no custom dimension for productId, so per-product
6802// performance has been invisible since launch. GA4's `item_id` is a
6803// BUILT-IN dimension, so emitting standard ecommerce events gives us a
6804// per-product view -> select -> add funnel with zero custom-dimension
6805// setup. These are emitted ALONGSIDE the custom events, never instead.
6806//
6807// dataLayer only -- GTM is confirmed live on this page and already
6808// processing SmartCart pushes. We do not call gtag() here because line
6809// ~1015 stubs window.gtag when it is absent, so a gtag() call would be
6810// an unconditional duplicate push.
6811var CK_GA4 = {
6812EMIT: true,             // master kill switch
6813EMIT_MICRO_ADD: true,   // set false if the micro-PDP iframe fires its own add_to_cart
6814LIST_PREFIX: 'SmartCart',
6815HYDRATE_POLLS: 8,       // 8 x 300ms = up to ~2.4s waiting for tile names
6816HYDRATE_POLL_MS: 300,
6817// SKUs for products that never go through parsePdpDoc, so nothing can
6818// hydrate a SKU for them: the ink parent (added by id + options) and
6819// the six accent stamps used by the non-tile 'generic' persona, where
6820// buildGrid returns before any PDP fetch. Without these, every add in
6821// the generic variant -- the bucket every unmatched cart falls into --
6822// would ship an entity id and be unjoinable to purchase.
6823// All verified against prod 2026-09-16. Re-check if SKUs change.
6824STATIC_SKUS: {
6825'3631': '1009260',   // Ideal Ink Pad for Traditional Wood Handle
6826'23105': '1027259',  // Delicate Flower envelope accent
6827'23106': '1027260',  // Simple botanical branches
6828'23107': '1027261',  // (accent)
6829'23108': '1027262',  // Elegant botanical branch
6830'23110': '1027264',  // Hearts through arrow
6831'23113': '1027267'   // Outlined heart
6832}
6833};
6834// A SKU we are willing to put in item_id. Magento SKUs are short and
6835// token-like; a scrape that picked up a wrapper element's textContent
6836// ('SKU:\n  1012837') or a sentinel ('N/A') must not ship -- and must not
6837// be labelled id_source:'sku', which would hide it from the very
6838// diagnostic added to catch it.
6839function ckGaSkuOk(v) {
6840var s = ckGaValidId(v);
6841if (!s) return '';
6842if (s.length > 40) return '';
6843return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(s) ? s : '';
6844}
6845// ckGaItem records, per built item, whether item_id is a real SKU or the
6846// entity-id fallback. ckGaPush reads and clears it -- the whole mirror runs
6847// synchronously inside one ckGaMirror call, so there is no interleaving.
6848var CK_GA4_IDSRC = [];
6849// Analytics-only mirror of tile metadata for products that entered a slot
6850// after buildGrid (swaps). Read by ckGaItem, by nothing else -- writing
6851// these into TILE_ITEM_BY_ID would change add-flow routing.
6852var CK_GA_TILE_META = Object.create(null);
6853// Reject ids that are not real product ids. TILE_ITEMS tolerates entries
6854// with no productId, and String(undefined) === 'undefined' would ship a
6855// junk item_id that also never hydrates (stalling the impression poll).
6856function ckGaValidId(v) {
6857if (v === null || typeof v === 'undefined') return '';
6858var s = String(v).trim();
6859if (!s || s === 'undefined' || s === 'null' || s === 'NaN') return '';
6860return s;
6861}
6862function ckGaListId() {
6863try {
6864return String((typeof activePersonaKey !== 'undefined' && activePersonaKey) || 'generic');
6865} catch (e) { return 'generic'; }
6866}
6867function ckGaListName() {
6868var label = ckGaListId();
6869try {
6870if (typeof persona !== 'undefined' && persona && persona.headerTitle) {
6871label = String(persona.headerTitle);
6872}
6873} catch (e) { }
6874// GA4 truncates item_list_name at 100 chars; header_title is
6875// CMS-editable, so cap it here rather than letting GA4 split rows.
6876return (CK_GA4.LIST_PREFIX + ' - ' + label).slice(0, 100);
6877}
6878function ckGaPrice(v) {
6879if (v === null || typeof v === 'undefined') return undefined;
6880var n = parseFloat(String(v).replace(/[^0-9.\-]/g, ''));
6881return isFinite(n) ? Math.round(n * 100) / 100 : undefined;
6882}
6883// Build one GA4 item object for a SmartCart product id.
6884function ckGaItem(pid, index, listNameOverride) {
6885var id = String(pid);
6886var raw = null, hyd = null;
6887try {
6888if (typeof TILE_ITEM_BY_ID !== 'undefined' && TILE_ITEM_BY_ID) raw = TILE_ITEM_BY_ID[id] || null;
6889if (!raw && CK_GA_TILE_META) raw = CK_GA_TILE_META[id] || null;
6890} catch (e1) { }
6891try {
6892if (typeof NOTARY_HYDRATED !== 'undefined' && NOTARY_HYDRATED) hyd = NOTARY_HYDRATED.get(id) || null;
6893} catch (e2) { }
6894// SKU is the id GA4 must see (see parsePdpDoc). Fall back to the
6895// entity id only when hydration has not produced a SKU yet -- that is
6896// recoverable and countable via the id_source param on the event,
6897// whereas dropping the item outright would lose the conversion.
6898var gaSku = ckGaSkuOk(hyd && hyd.sku) ||
6899ckGaSkuOk(CK_GA4.STATIC_SKUS && CK_GA4.STATIC_SKUS[id]);
6900var gaId = gaSku || id;
6901try { CK_GA4_IDSRC.push(gaSku ? 'sku' : 'entity'); } catch (eSrc0) { }
6902var item = {
6903item_id: gaId,
6904item_brand: 'SimplyStamps',
6905item_category: 'SmartCart Upsell',
6906item_list_id: ckGaListId(),
6907// Item-scoped item_list_name is what GA4 item reports actually
6908// use, so an event-level override has to reach the items too.
6909item_list_name: listNameOverride || ckGaListName(),
6910quantity: 1
6911};
6912// NEVER send a placeholder name. Tiles hydrate asynchronously, so a
6913// 'Product 12345' filler would permanently split the item_name
6914// dimension against the real name sent by later events. An absent
6915// dimension is recoverable; a wrong one is not.
6916var name = (hyd && hyd.name) || (raw && (raw.title || raw.name)) || '';
6917if (name) item.item_name = String(name);
6918if (typeof index === 'number' && index > 0) item.index = index;
6919// Price: hydrated PDP price for tiles; the flat accent-stamp price
6920// in the non-tile variant, where nothing hydrates at all.
6921var price = ckGaPrice(hyd ? hyd.price : null);
6922if (typeof price !== 'number') {
6923try {
6924if (typeof useTiles !== 'undefined' && !useTiles && typeof STAMP_UNIT_PRICE === 'number') {
6925price = STAMP_UNIT_PRICE;
6926}
6927} catch (eP) { }
6928}
6929if (typeof price === 'number') item.price = price;
6930// Slice-able strategy attributes, so GA4 can answer 'do experimental
6931// tiles convert?' and 'does friction cost us adds?' without new dims.
6932// Only set what the config actually declares -- defaulting friction to
6933// 'low' would read back as 'low friction converts fine' off data that
6934// really means 'we did not know'.
6935if (raw) {
6936if (raw.role) item.item_variant = String(raw.role);
6937if (raw.friction) item.item_category2 = String(raw.friction);
6938if (typeof raw.experimental !== 'undefined') {
6939item.item_category3 = raw.experimental ? 'experimental' : 'core';
6940}
6941}
6942return item;
6943}
6944// startIndex === null means 'do not emit index'. Only view_item_list
6945// knows a real display position; on select_item / add_to_cart an index
6946// would be fabricated (always 1, or add-order), and a fabricated
6947// position silently answers 'which slot converts?' wrong.
6948function ckGaItemsFor(ids, startIndex, listNameOverride) {
6949var out = [];
6950var useIdx = (startIndex !== null && typeof startIndex !== 'undefined');
6951try {
6952(ids || []).forEach(function (pid) {
6953var id = ckGaValidId(pid);
6954if (!id) return;
6955out.push(ckGaItem(id, useIdx ? (startIndex + out.length + 1) : null, listNameOverride));
6956});
6957} catch (e) { }
6958return out;
6959}
6960// True once we can give this product the id GA4 needs. Gating the deferred
6961// impression on the NAME alone was not enough: ckRepaintCardAs writes a
6962// CK_TILE_FALLBACKS title into TILE_ITEM_BY_ID synchronously, before it even
6963// starts the PDP fetch, so a swapped-in fallback tile looked 'resolved' with
6964// no SKU yet -- view_item_list would ship its entity id while the shopper's
6965// later select_item shipped the real SKU. Same product, two item_ids, one
6966// session: exactly the split this is all meant to remove.
6967function ckGaHasSku(pid) {
6968var id = String(pid);
6969if (ckGaSkuOk(CK_GA4.STATIC_SKUS && CK_GA4.STATIC_SKUS[id])) return true;
6970try {
6971if (typeof NOTARY_HYDRATED !== 'undefined' && NOTARY_HYDRATED) {
6972var h = NOTARY_HYDRATED.get(id);
6973if (h && ckGaSkuOk(h.sku)) return true;
6974}
6975} catch (e) { }
6976return false;
6977}
6978// True once a product has a real, non-placeholder name available.
6979function ckGaHasName(pid) {
6980var id = String(pid), hyd = null, raw = null;
6981try { if (typeof NOTARY_HYDRATED !== 'undefined' && NOTARY_HYDRATED) hyd = 
6981NOTARY_HYDRATED.get(id) || null; } catch (e1) { }
6982try {
6983if (typeof TILE_ITEM_BY_ID !== 'undefined' && TILE_ITEM_BY_ID) raw = TILE_ITEM_BY_ID[id] || null;
6984if (!raw && CK_GA_TILE_META) raw = CK_GA_TILE_META[id] || null;
6985} catch (e2) { }
6986return !!((hyd && hyd.name) || (raw && (raw.title || raw.name)));
6987}
6988// Ink pads are options on a single parent SKU, not tiles -- model them as
6989// variants of that parent so ink attach is measurable too.
6990function ckGaInkItems(colorKeys, startIndex) {
6991var out = [];
6992try {
6993// The ink parent never hydrates through parsePdpDoc (it is added
6994// by id + options, not from a tile), so its SKU is pinned here.
6995var inkEnt = (typeof INK_PARENT_ID !== 'undefined' && INK_PARENT_ID) ? String(INK_PARENT_ID) : '';
6996var inkSku = ckGaSkuOk(CK_GA4.STATIC_SKUS && CK_GA4.STATIC_SKUS[inkEnt]);
6997var pid = inkSku || inkEnt || 'ink-pad';
6998(colorKeys || []).forEach(function (ck, i) {
6999var row = {
7000item_id: pid,
7001item_name: 'Ink Pad',
7002item_brand: 'SimplyStamps',
7003item_category: 'SmartCart Upsell',
7004item_category3: 'core',
7005item_variant: String(ck),
7006item_list_id: ckGaListId(),
7007item_list_name: ckGaListName(),
7008quantity: 1
7009};
7010// Ink is a flat-price option the module already prices itself.
7011// Without this, an ink-only add -- the module's core ask --
7012// reports add_to_cart with no value at all.
7013if (typeof INK_PRICE === 'number') row.price = INK_PRICE;
7014if (startIndex !== null && typeof startIndex !== 'undefined') row.index = startIndex + i + 1;
7015// Report ink's id source too, or a mixed add reads as 'entity'
7016// and an analyst quarantining that label throws away good rows.
7017try { CK_GA4_IDSRC.push(inkSku ? 'sku' : 'entity'); } catch (eSrc1) { }
7018out.push(row);
7019});
7020} catch (e) { }
7021return out;
7022}
7023function ckGaValue(items) {
7024var v = 0, any = false;
7025try {
7026(items || []).forEach(function (it) {
7027if (typeof it.price === 'number') { v += it.price * (it.quantity || 1); any = true; }
7028});
7029} catch (e) { }
7030return any ? Math.round(v * 100) / 100 : undefined;
7031}
7032function ckGaPush(eventName, ecommerce) {
7033// Drain the id-source buffer FIRST, above every early return -- a
7034// stale entry would mislabel the next event.
7035var idsrc = CK_GA4_IDSRC;
7036CK_GA4_IDSRC = [];
7037if (!CK_GA4.EMIT) return;
7038if (!ecommerce || !Array.isArray(ecommerce.items) || !ecommerce.items.length) return;
7039try {
7040window.dataLayer = window.dataLayer || [];
7041// Clear the previous ecommerce object first so params never bleed
7042// between events (standard GTM ecommerce practice).
7043window.dataLayer.push({ ecommerce: null });
7044// id_source tells the analyst whether these item_ids are real SKUs
7045// (joinable to purchase) or entity-id fallbacks from a tile that
7046// had not hydrated yet. Ink items are always pinned to a real SKU.
7047var srcTag = 'sku';
7048try {
7049if (idsrc.length) {
7050var nEnt = idsrc.filter(function (x) { return x === 'entity'; }).length;
7051srcTag = (nEnt === 0) ? 'sku' : (nEnt === idsrc.length ? 'entity' : 'mixed');
7052}
7053} catch (eSrc) { }
7054window.dataLayer.push({
7055event: eventName,
7056smartcart: true,
7057persona_key: ckGaListId(),
7058id_source: srcTag,
7059ecommerce: ecommerce
7060});
7061} catch (e) { }
7062}
7063// Single mirror point: called for every pushUpsellEvent. Adding a new
7064// GA4 event means adding a case here, not touching call sites.
7065function ckGaMirror(eventName, payload) {
7066if (!CK_GA4.EMIT) return;
7067payload = payload || {};
7068var items, val, ec;
7069if (eventName === 'SmartCart_upsell_impression') {
7070// null = read the live grid at fire time. See
7071// ckGaFireListWhenHydrated for why that beats the build-time list.
7072ckGaFireListWhenHydrated(null);
7073return;
7074}
7075// Three distinct code paths end in a real selection. _select_fallback
7076// fires when the PDP prefetch failed but the tile WAS still selected,
7077// so omitting it would under-report select_item in proportion to our
7078// own fetch error rate. accent_selected is the non-tile variant.
7079if (eventName === 'SmartCart_upsell_tile_select_fallback' && payload.selected === false) {
7080return; // that path fires on deselect too
7081}
7082if (eventName === 'SmartCart_upsell_tile_selected' ||
7083eventName === 'SmartCart_upsell_tile_select_fallback' ||
7084eventName === 'SmartCart_upsell_accent_selected') {
7085var selId = payload.productId || payload.product_id || payload.stamp_id;
7086items = ckGaItemsFor([selId], null);
7087ckGaPush('select_item', {
7088item_list_id: ckGaListId(),
7089item_list_name: ckGaListName(),
7090items: items
7091});
7092return;
7093}
7094if (eventName === 'SmartCart_upsell_added') {
7095items = ckGaItemsFor(payload.product_ids, null)
7096.concat(ckGaInkItems(payload.ink_colors, null));
7097val = ckGaValue(items);
7098ec = { currency: 'USD', items: items };
7099if (typeof val === 'number') ec.value = val;
7100ckGaPush('add_to_cart', ec);
7101return;
7102}
7103if (eventName === 'SmartCart_upsell_micro_added') {
7104if (!CK_GA4.EMIT_MICRO_ADD) return;
7105items = ckGaItemsFor(payload.product_ids, null);
7106val = ckGaValue(items);
7107ec = { currency: 'USD', items: items };
7108if (typeof val === 'number') ec.value = val;
7109ckGaPush('add_to_cart', ec);
7110return;
7111}
7112}
7113// Read the product ids actually rendered in the grid right now.
7114function ckGaVisibleTileIds() {
7115var out = [];
7116try {
7117if (!stampGrid) return out;
7118var cards = stampGrid.querySelectorAll('[data-kit-primary]');
7119for (var i = 0; i < cards.length; i++) {
7120var id = ckGaValidId(cards[i].getAttribute('data-kit-primary'));
7121if (id && out.indexOf(id) === -1) out.push(id);
7122}
7123} catch (e) { }
7124return out;
7125}
7126// view_item_list is deliberately NOT fired inline with the impression.
7127//
7128// Two reasons. (1) fireTilesImpressionOnce() runs synchronously right
7129// after the hydration fetches are *started*, so at that instant no tile
7130// has a real name or price -- firing then sends an id-only list whose
7131// item_name never matches the select_item / add_to_cart that follow,
7132// splitting every per-product report in two. (2) the tile quality gate
7133// drops or repaints tiles during those same milliseconds, so the
7134// build-time id list is not what the shopper ends up seeing.
7135//
7136// So: pass ids = null to re-read the LIVE grid on every poll, wait for
7137// names, then fire once. On timeout we still fire -- an id-only
7138// impression beats no impression at all, and a stalled poll would let
7139// select_item land before view_item_list and break funnel step order.
7140function ckGaFireListWhenHydrated(ids, attempts, listNameOverride) {
7141if (!CK_GA4.EMIT) return;
7142try {
7143var live = (ids === null || typeof ids === 'undefined');
7144var list = live ? ckGaVisibleTileIds()
7145: (ids || []).map(ckGaValidId).filter(function (x) { return !!x; });
7146if (!list.length) return;
7147attempts = (typeof attempts === 'number') ? attempts : CK_GA4.HYDRATE_POLLS;
7148var resolved = 0;
7149list.forEach(function (id) { if (ckGaHasName(id) && ckGaHasSku(id)) resolved++; });
7150if (resolved < list.length && attempts > 0) {
7151setTimeout(function () {
7152ckGaFireListWhenHydrated(ids, attempts - 1, listNameOverride);
7153}, CK_GA4.HYDRATE_POLL_MS);
7154return;
7155}
7156ckGaPush('view_item_list', {
7157item_list_id: ckGaListId(),
7158item_list_name: listNameOverride || ckGaListName(),
7159items: ckGaItemsFor(list, 0, listNameOverride)
7160});
7161} catch (e) { }
7162}
7163function fireTilesImpressionOnce() {
7164try {
7165if (!useTiles) return;
7166if (!Array.isArray(TILE_ITEMS) || !TILE_ITEMS.length) return;
7167// fire once per page load per persona
7168window.__ckImpressionFired = window.__ckImpressionFired || {};
7169var key = String(activePersonaKey || 'generic');
7170if (window.__ckImpressionFired[key]) return;
7171window.__ckImpressionFired[key] = true;
7172pushUpsellEvent('SmartCart_upsell_impression', {
7173// Flat id list -- GA4 cannot report on the nested tiles_shown
7174// array; ckGaMirror() turns this into view_item_list items[].
7175product_ids: TILE_ITEMS.map(function (t) { return String(t.productId); }),
7176tiles_shown: TILE_ITEMS.map(function (t, idx) {
7177return {
7178productId: t.productId,
7179position: idx + 1,
7180experimental: !!t.experimental,
7181role: t.role || 'helper',
7182friction: t.friction || 'low'
7183};
7184})
7185});
7186} catch (e) { }
7187}
7188function pushTileEvent(action, payload) {
7189payload = payload || {};
7190pushUpsellEvent('SmartCart_upsell_tile_' + action, payload);
7191const personaKey =
7192payload.persona_key ||
7193payload.personaKey ||
7194(window.__CK && window.__CK.activePersonaKey) ||
7195window.activePersonaKey;
7196if (
7197personaKey &&
7198(action === 'view' || action === 'in_view' || action === 'click' || action === 'add' || action === 'added' || action === 'selected')
7199) {
7200onSmartCartMeaningfulEngagement(personaKey);
7201}
7202}
7203// ----------------------------------------------------
7204// 4) CONFIG
7205// ----------------------------------------------------
7206// Ink pad parent product + options
7207var INK_PARENT_ID = '3631';
7208var INK_SIZE_OPTION_ID = '1296'; // Size
7209var INK_COLOR_OPTION_ID = '1295'; // Ink Pad Color
7210var INK_SIZE_VALUE_ID = '6265'; // Small
7211var INK_COLOR_VALUES = {
7212black: '6257',
7213blue: '6258',
7214red: '6259',
7215green: '6260',
7216purple: '6262'
7217};
7218// Accent stamp options
7219var ACCENT_SIZE_OPTION_ID = '1453';
7220var ACCENT_SIZE_VALUE_ID = '6921';
7221var ACCENT_INK_OPTION_ID = '2403';
7222var ACCENT_INK_NO_THANKS_VALUE_ID = '9532';
7223// Pricing model for Accent stamps (existing behavior)
7224var STAMP_UNIT_PRICE = 5.49;
7225var INK_PRICE = 4.99;
7226var STAMP_OPTIONS = [
7227{
7228id: '23105',
7229imgSrc: 'https://cdn.simplystamps.com/media/catalog/product/s/s/ss_envelope-accent_lead-image_floral-1_800.jpg',
7230imgAlt: 'Delicate flower envelope accent craft stamp design'
7231},
7232{
7233id: '23106',
7234imgSrc: 'https://cdn.simplystamps.com/media/catalog/product/s/s/ss_envelope-accent_lead-image_floral-2_800.jpg',
7235imgAlt: 'Simple botanical branches envelope accent craft stamp design'
7236},
7237{
7238id: '23108',
7239imgSrc: 'https://cdn.simplystamps.com/media/catalog/product/s/s/ss_envelope-accent_lead-image_floral-4_800.jpg',
7240imgAlt: 'Elegant botanical branch envelope accent craft stamp design'
7241},
7242{
7243id: '23107',
7244imgSrc: 'https://cdn.simplystamps.com/media/catalog/product/s/s/ss_envelope-accent_lead-image_floral-3_800.jpg',
7245imgAlt: 'Daisy flower envelope accent craft stamp design'
7246},
7247{
7248id: '23110',
7249imgSrc: 'https://cdn.simplystamps.com/media/catalog/product/s/s/ss_envelope-accent_lead-image_heart-arrow_800.jpg',
7250imgAlt: 'Hearts through arrow envelope accent craft stamp design'
7251},
7252{
7253id: '23113',
7254imgSrc: 'https://cdn.simplystamps.com/media/catalog/product/s/s/ss_envelope-accent_lead-image_heart-2_800.jpg',
7255imgAlt: 'Outlined heart envelope accent craft stamp design'
7256}
7257];
7258var MAX_TILES_TO_SHOW = 6;                 // guardrail
7259var ALLOW_EXPERIMENTAL_TILES = true;       // easy toggle
7260// Persona tile items (simple products). If none, we show Accent Stamp grid.
7261// Uses product-level matchKeywords + role/friction modifiers to pick the best N (MAX_TILES_TO_SHOW).
7262function scoreProductForCart(item, cartTextLower, allowExperimental) {
7263var score = 0;
7264// Keyword hits (product-level)
7265score += scoreKeywordHits(item.matchKeywords, cartTextLower);
7266// Role modifiers
7267switch (item.role) {
7268case 'anchor': score += 6; break;
7269case 'core': score += 4; break;
7270case 'compliance': score += 8; break;
7271case 'backup': score += 2; break;
7272case 'accessory': score += 1; break;
7273default: break;
7274}
7275// Friction modifiers
7276switch (item.friction) {
7277case 'none': score += 3; break;
7278case 'low': score += 1; break;
7279case 'medium': score -= 2; break;
7280case 'high': score -= 5; break;
7281default: break;
7282}
7283// Experimental gating
7284if (item.experimental && !allowExperimental) score -= 100;
7285return score;
7286}
7287function pickTopTilesForDisplay(items, cartTextLower, maxTiles, allowExperimental) {
7288var pool = Array.isArray(items) ? items.slice() : [];
7289// Ensure fields exist
7290pool.forEach(function (it) {
7291if (!it) return;
7292if (!Array.isArray(it.matchKeywords)) it.matchKeywords = [];
7293if (!it.role) it.role = 'helper';
7294if (!it.friction) it.friction = 'low';
7295});
7296// Score + stable tiebreakers
7297var scored = pool.map(function (it, idx) {
7298return {
7299it: it,
7300idx: idx,
7301score: scoreProductForCart(it, cartTextLower, allowExperimental)
7302};
7303});
7304scored.sort(function (a, b) {
7305if (b.score !== a.score) return b.score - a.score;
7306// If tie: prefer anchors
7307var ar = (a.it.role === 'anchor') ? 1 : 0;
7308var br = (b.it.role === 'anchor') ? 1 : 0;
7309if (br !== ar) return br - ar;
7310// Then lower friction
7311var fr = { none: 0, low: 1, medium: 2, high: 3 };
7312var af = fr[a.it.friction] != null ? fr[a.it.friction] : 9;
7313var bf = fr[b.it.friction] != null ? fr[b.it.friction] : 9;
7314if (af !== bf) return af - bf;
7315// Then sortOrder
7316return (a.it.sortOrder || 0) - (b.it.sortOrder || 0);
7317});
7318// Dedupe by product id: the pool can carry the same product under two
7319// entries, and rendering it twice reads as a bug.
7320var seenPid = Object.create(null);
7321var selected = [];
7322for (var si = 0; si < scored.length && selected.length < maxTiles; si++) {
7323var cand = scored[si].it;
7324var cpid = String((cand && (cand.productId || cand.product_id)) || '');
7325if (cpid) {
7326if (seenPid[cpid]) continue;
7327seenPid[cpid] = true;
7328}
7329selected.push(cand);
7330}
7331// Default index: highest score, but if an anchor is within 2 points of top, prefer it.
7332var defaultIndex = 0;
7333if (selected.length) {
7334var topScore = scored.length ? scored[0].score : 0;
7335for (var i = 0; i < selected.length; i++) {
7336var it = selected[i];
7337var itScore = scoreProductForCart(it, cartTextLower, allowExperimental);
7338if (it.role === 'anchor' && itScore >= (topScore - 2)) {
7339defaultIndex = i;
7340break;
7341}
7342}
7343}
7344return { tiles: selected, defaultIndex: defaultIndex };
7345}
7346// Respect ?upsellDebug=1 to include experimental tiles
7347var allowExperimentalNow = (ALLOW_EXPERIMENTAL_TILES === true) || (urlParams.get('upsellDebug') === '1');
7348var cartTextForScoring = getCartTitleTextLower();
7349var tilePick = (useTiles ? pickTopTilesForDisplay(persona.items, cartTextForScoring, 999, allowExperimentalNow) : { tiles: [], defaultIndex: 0 });
7350// Best-effort: suppress tiles that are already in the cart using cart link paths (most reliable signal we have on cart)
7351var __cartPaths0 = Object.create(null);
7352try { __cartPaths0 = getCartProductPathSet(); } catch (eCP0) { __cartPaths0 = Object.create(null); }
7353var __cartIds0 = Object.create(null);
7354try { __cartIds0 = getCartProductIdSet(); } catch (eCI0) { __cartIds0 = Object.create(null); }
7355function __isInCartByPath(it) {
7356try {
7357// Zephyr: exact product-id match first (data-product-id on cart cards)
7358var pid0 = it && (it.productId || it.product_id);
7359if (pid0 && __cartIds0[String(pid0)]) return true;
7360// Be tolerant: different shapes may use productUrl vs product_url
7361var url = '';
7362if (it) {
7363url = it.productUrl || it.product_url || it.url || '';
7364}
7365// If url missing but we have an id, try the lookup map (populated during tile build)
7366if (!url && it && it.productId && TILE_ITEM_BY_ID && TILE_ITEM_BY_ID[it.productId]) {
7367url = TILE_ITEM_BY_ID[it.productId].productUrl || TILE_ITEM_BY_ID[it.productId].product_url || '';
7368}
7369var pth = normalizeUrlPathForCompare(url);
7370return !!(pth && __cartPaths0[pth]);
7371} catch (e) { return false; }
7372}
7373// Build initial visible tiles from the ordered pool, skipping in-cart items.
7374// Keep tile count stable up to MAX_TILES_TO_SHOW.
7375var TILE_ITEMS = [];
7376var TILE_ITEM_BY_ID = {};
7377if (useTiles && Array.isArray(tilePick.tiles)) {
7378for (var __i = 0; __i < tilePick.tiles.length; __i++) {
7379if (TILE_ITEMS.length >= MAX_TILES_TO_SHOW) break;
7380var it = tilePick.tiles[__i];
7381if (!it) continue;
7382// Allow tiles even if productId is missing (null IDs can still be previewed). We'll block add-to-cart for them later.
7383// Skip anything already in cart (path match)
7384if (__isInCartByPath(it)) continue;
7385TILE_ITEMS.push(it);
7386try { if (it.productId) TILE_ITEM_BY_ID[it.productId] = it; } catch (eMap) { }
7387}
7388}
7389// Default index: keep "best" (0) after exclusions
7390if (useTiles) persona.defaultPrimaryIndex = 0;
7391// ----------------------------------------------------
7392// 5) Build UI
7393// ----------------------------------------------------
7394var inkPadHtml =
7395'<div class="cart-kit-upsell__options-group" style="margin-top:10px;">'
7396+ '<div class="cart-kit-upsell__options-label" id="kit-ink-label">Would you like an Ink Pad? (Needed to make impression)</div>'
7397+ '<div class="kit-swatch-list" id="kit-swatch-list">'
7398+ '<button type="button" class="kit-swatch" data-kit-color="black"><span class="kit-swatch__dot kit-swatch__dot--black"></span><span class="kit-swatch__label">Black</span></button>'
7399+ '<button type="button" class="kit-swatch" data-kit-color="blue"><span class="kit-swatch__dot kit-swatch__dot--blue"></span><span class="kit-swatch__label">Blue</span></button>'
7400+ '<button type="button" class="kit-swatch" data-kit-color="red"><span class="kit-swatch__dot kit-swatch__dot--red"></span><span class="kit-swatch__label">Red</span></button>'
7401+ '<button type="button" class="kit-swatch" data-kit-color="green"><span class="kit-swatch__dot kit-swatch__dot--green"></span><span class="kit-swatch__label">Green</span></button>'
7402+ '<button type="button" class="kit-swatch" data-kit-color="purple"><span class="kit-swatch__dot kit-swatch__dot--purple"></span><span class="kit-swatch__label">Purple</span></button>'
7403+ '<button type="button" class="kit-swatch" data-kit-color="none"><span class="kit-swatch__dot kit-swatch__dot--none"></span><span class="kit-swatch__label">I have an ink pad</span></button>'
7404+ '</div>'
7405+ '</div>';
7406var inkPadBlock = (!useTiles ? inkPadHtml : '');
7407var upsellMarkup = ''
7408+ '<div class="cart-kit-upsell cart-kit-upsell--open" id="cart-kit-upsell">'
7409+ '<button class="cart-kit-upsell__header" type="button">'
7410+ '<div class="cart-kit-upsell__icon-wrap" aria-hidden="true">'
7411+ '<img class="cart-kit-upsell__icon-img cart-kit-upsell__icon-img--static"'
7412+ ' src="https://cdn.simplystamps.com/media/wysiwyg/SimplyStamps/SS_Homepage-Icon_Stamps_120x120.png"'
7413+ ' alt="" />'
7414+ '</div>'
7415+ '<div class="cart-kit-upsell__header-text">'
7416+ '<span class="cart-kit-upsell__title" id="cart-kit-header-text">$1 Accent Stamp Unlocked! Choose your favorite below and add to cart.</span>'
7417+ '</div>'
7418+ '<span class="cart-kit-upsell__chevron" aria-hidden="true"></span>'
7419+ '</button>'
7420+ '<div class="cart-kit-upsell__body">'
7421+ '<div class="cart-kit-upsell__options-group">'
7422+ '<div class="cart-kit-upsell__options-header">'
7423+ '<div class="cart-kit-upsell__options-label" id="kit-stamp-label">Choose one or many Accent Stamp Designs:</div>'
7424+ '<button type="button" class="cart-kit-upsell__info" aria-label="Stamp impression size information">'
7425+ '<span class="cart-kit-upsell__info-icon">i</span>'
7426+ '<span class="cart-kit-upsell__info-tooltip" id="kit-tooltip">Stamp impression size: approx 1.75&quot; × 0.67&quot;.</span>'
7427+ '</button>'
7428+ '</div>'
7429+ '<div class="kit-stamp-grid" id="kit-stamp-grid"></div>'
7430+ '</div>'
7431+ inkPadBlock
7432+ '<div class="cart-kit-upsell__footer">'
7433+ '<div class="cart-kit-upsell__price">'
7434+ '<span class="cart-kit-upsell__price-label" id="kit-price-label">Total:</span>'
7435+ '<span class="kit-value-line__new" id="kit-price-new">$1.00</span>'
7436+ '<button type="button" class="cart-kit-upsell__cta" id="cart-kit-upsell-add">Add to Cart</button>'
7437+ '<button type="button" class="cart-kit-upsell__cta-secondary" id="cart-kit-upsell-hide">Hide Offer</button>'
7438+ '</div>'
7439+ '<div class="kit-success-toast" id="kit-success-toast"></div>'
7440+ '<div class="cart-kit-upsell__status" id="cart-kit-status" aria-l
7440ive="polite"></div>'
7441+ '<div class="cart-kit-upsell__progresswrap" id="cart-kit-progresswrap" style="display:none;">'
7442+ '<div class="cart-kit-upsell__progresstop">'
7443+ '<div class="cart-kit-upsell__progresstext" id="cart-kit-progresstext">Adding items…</div>'
7444+ '<div class="cart-kit-upsell__progresspct" id="cart-kit-progresspct">0%</div>'
7445+ '</div>'
7446+ '<div class="cart-kit-upsell__progressbar">'
7447+ '<div class="cart-kit-upsell__progressfill" id="cart-kit-progressfill"></div>'
7448+ '</div>'
7449+ '</div>'
7450+ '</div>'
7451+ '</div>'
7452+ '</div>'
7453+ '</div>';
7454mount.insertAdjacentHTML('beforeend', upsellMarkup);
7455var upsell = mount.querySelector('#cart-kit-upsell');
7456var header = upsell.querySelector('.cart-kit-upsell__header');
7457var body = upsell.querySelector('.cart-kit-upsell__body');
7458var addBtn = upsell.querySelector('#cart-kit-upsell-add');
7459if (!upsell || !addBtn) {
7460console.warn('[SmartCart] Required module elements missing. Aborting init.');
7461return;
7462}
7463var hideBtn = upsell.querySelector('#cart-kit-upsell-hide');
7464var stampGrid = upsell.querySelector('#kit-stamp-grid');
7465var swatchList = upsell.querySelector('#kit-swatch-list');
7466var statusEl = upsell.querySelector('#cart-kit-status');
7467if (statusEl) {
7468statusEl.setAttribute('role', 'status');
7469statusEl.setAttribute('aria-live', 'polite');
7470statusEl.setAttribute('aria-atomic', 'true');
7471}
7472var progressWrap = upsell.querySelector('#cart-kit-progresswrap');
7473var progressFill = upsell.querySelector('#cart-kit-progressfill');
7474var progressText = upsell.querySelector('#cart-kit-progresstext');
7475var progressPct = upsell.querySelector('#cart-kit-progresspct');
7476var stampLabelEl = upsell.querySelector('#kit-stamp-label');
7477var inkLabelEl = upsell.querySelector('#kit-ink-label');
7478var tooltipEl = upsell.querySelector('#kit-tooltip');
7479var headerTextEl = upsell.querySelector('#cart-kit-header-text');
7480// Animate icon
7481setTimeout(function () {
7482var wrap = upsell.querySelector('.cart-kit-upsell__icon-wrap');
7483if (wrap) wrap.classList.add('is-entered');
7484}, 40);
7485// ----------------------------------------------------
7486// 6) Apply persona UI (Notary)
7487// ----------------------------------------------------
7488function applyPersonaUI() {
7489// Header + labels + tooltip
7490if (headerTextEl) headerTextEl.textContent = persona.headerTitle || '';
7491if (stampLabelEl) stampLabelEl.textContent = persona.stampLabel || '';
7492if (inkLabelEl) inkLabelEl.textContent = persona.inkLabel || '';
7493if (tooltipEl) tooltipEl.textContent = persona.tooltip || '';
7494// Toggle tile layout when persona has items
7495if (stampGrid) stampGrid.classList.toggle('is-notary-grid', !!useTiles);
7496// Filter ink swatches based on allowedInk
7497var allowed = (persona.allowedInk && persona.allowedInk.length) ? persona.allowedInk : ['black', 'blue', 'red', 'green', 'purple', 'none'];
7498if (swatchList) {
7499var swatches = swatchList.querySelectorAll('.kit-swatch');
7500for (var i = 0; i < swatches.length; i++) {
7501var key = swatches[i].getAttribute('data-kit-color');
7502swatches[i].style.display = (allowed.indexOf(key) > -1) ? '' : 'none';
7503}
7504}
7505}
7506applyPersonaUI();
7507// ----------------------------------------------------
7508// 7) PDP Fetch Helpers (image + real price + real name)
7509// ----------------------------------------------------
7510var PDP_CACHE = new Map(); // url -> { name, price, img }
7511function toNumberPrice(val) {
7512if (val == null) return null;
7513var n = Number(String(val).replace(/[^0-9.]/g, ''));
7514return isFinite(n) ? n : null;
7515}
7516function absUrlMaybe(src) {
7517if (!src) return null;
7518if (/^https?:\/\//i.test(src)) return src;
7519if (src.indexOf('/') === 0) return window.location.origin + src;
7520return src;
7521}
7522function detectRequiresMicro(doc) {
7523try {
7524var form =
7525doc.querySelector('#product_addtocart_form') ||
7526doc.querySelector('form[action*="checkout/cart/add"]') ||
7527doc.querySelector('form');
7528if (!form) return false;
7529// Strong signal: any file upload = micro PDP
7530if (form.querySelector('input[type="file"]')) return true;
7531// Strong signal: Magento custom options payload (options[...])
7532if (form.querySelector('[name^="options["]')) return true;
7533// Strong signal: configurables often use super_attribute
7534if (form.querySelector('[name^="super_attribute"]')) return true;
7535// Strong signal: required fields inside options wrapper
7536var optionsWrap =
7537form.querySelector('.product-options-wrapper') ||
7538form.querySelector('#product-options-wrapper') ||
7539form.querySelector('.fieldset') ||
7540form;
7541// Look for explicit required markers/validators within options area
7542if (
7543optionsWrap.querySelector('[required]') ||
7544optionsWrap.querySelector('[aria-required="true"]') ||
7545optionsWrap.querySelector('[data-validate*="required"]') ||
7546optionsWrap.querySelector('.required-entry') ||
7547optionsWrap.querySelector('.required')
7548) {
7549return true;
7550}
7551// If there are selectable option controls (select/radio/checkbox/textarea)
7552// inside a known options wrapper, treat as "likely micro"
7553// (kept conservative: only if wrapper exists)
7554var hasOptionControls =
7555!!optionsWrap.querySelector('select, input[type="radio"], input[type="checkbox"], textarea');
7556if (hasOptionControls && (optionsWrap !== form)) return true;
7557return false;
7558} catch (e) {
7559return false;
7560}
7561}
7562function requiresMicroPdp(productId, rawTile) {
7563var pid = String(productId || '').trim();
7564if (!pid) return false;
7565// 1) Explicit tile intent
7566if (rawTile && rawTile.addMode === 'microPdp') return true;
7567// 2) Hard override (authoritative)
7568if (
7569typeof HARD_SKU_OVERRIDES !== 'undefined' &&
7570HARD_SKU_OVERRIDES &&
7571HARD_SKU_OVERRIDES[pid] &&
7572HARD_SKU_OVERRIDES[pid].addMode === 'microPdp'
7573) {
7574return true;
7575}
7576// 3) Autodetect fallback from cached PDP parse
7577if (
7578rawTile &&
7579rawTile.productUrl &&
7580typeof PDP_CACHE !== 'undefined' &&
7581PDP_CACHE &&
7582PDP_CACHE.has(rawTile.productUrl)
7583) {
7584var cached = PDP_CACHE.get(rawTile.productUrl);
7585if (cached && cached.requiresMicro === true) return true;
7586}
7587return false;
7588}
7589function isAccentExperience() {
7590try {
7591var urlParams = new URLSearchParams(window.location.search);
7592var accentAddedQS = (urlParams.get('accentAdded') === '1' || urlParams.get('accentAdded') === 'true');
7593return !!accentAddedQS; // keep it strict for now
7594} catch (e) {
7595return false;
7596}
7597}
7598function parsePdpDoc(doc) {
7599var name =
7600(doc.querySelector('meta[property="og:title"]') &&
7601doc.querySelector('meta[property="og:title"]').getAttribute('content')) ||
7602(doc.querySelector('h1') && (doc.querySelector('h1').textContent || '').trim()) ||
7603null;
7604var img =
7605(doc.querySelector('meta[property="og:image"]') &&
7606doc.querySelector('meta[property="og:image"]').getAttribute('content')) ||
7607(doc.querySelector('meta[name="twitter:image"]') &&
7608doc.querySelector('meta[name="twitter:image"]').getAttribute('content')) ||
7609null;
7610img = absUrlMaybe(img);
7611var price =
7612(doc.querySelector('meta[property="product:price:amount"]') &&
7613doc.querySelector('meta[property="product:price:amount"]').getAttribute('content')) ||
7614(doc.querySelector('meta[itemprop="price"]') &&
7615doc.querySelector('meta[itemprop="price"]').getAttribute('content')) ||
7616(doc.querySelector('[itemprop="price"]') &&
7617doc.querySelector('[itemprop="price"]').textContent) ||
7618(doc.querySelector('.price') &&
7619doc.querySelector('.price').textContent) ||
7620null;
7621price = toNumberPrice(price);
7622// Magento SKU. GA4 item_id MUST be the SKU, not the entity id: every
7623// other ecommerce event on this site (view_cart, purchase) keys items
7624// by SKU, so sending entity ids here would make the same product two
7625// different rows in GA4 and break any SmartCart-add -> purchase join.
7626var sku = null;
7627try {
7628var skuEl = doc.querySelector('meta[itemprop="sku"]') || doc.querySelector('[itemprop="sku"]');
7629if (skuEl) {
7630sku = (skuEl.getAttribute('content') || skuEl.textContent || '').trim() || null;
7631}
7632} catch (eSku) { }
7633var requiresMicro = detectRequiresMicro(doc);
7634// The single reliable "is this a product?" signal. An account, login,
7635// 404 or category page still yields an og:title and an og:image, so
7636// title/image alone can't tell you the scrape succeeded.
7637var isProduct = !!(
7638doc.querySelector('#product_addtocart_form') ||
7639doc.querySelector('form[action*="checkout/cart/add"]')
7640);
7641return {
7642name: name,
7643img: img,
7644price: price,
7645sku: sku,
7646isProduct: isProduct,
7647requiresMicro: requiresMicro
7648};
7649}
7650// Same-origin PDP fetch: config product_urls are absolute www.simplystamps.com;
7651// rewrite to the current origin so hydration works on local/staging AND prod (no-op on www).
7652function ckSameOrigin(u) {
7653try {
7654var p = new URL(u, window.location.origin);
7655if (/(^|\.)simplystamps\.com$/i.test(p.hostname) && p.origin !== window.location.origin) {
7656return window.location.origin + p.pathname + p.search + p.hash;
7657}
7658return p.href;
7659} catch (e) { return u; }
7660}
7661// ----------------------------------------------------
7662// ZEPHYR SIDE-CART BRIDGE (added 2026-09-11)
7663// Zephyr ships a slide-out cart drawer driven by an Alpine store, and the
7664// cart PAGE is a SECOND, independent Alpine component (cartPage()) holding
7665// its own copy of the same /zephyr/cart/getItems/ JSON. They do not talk:
7666//   cart-drawer.js  store.refresh()  -> fires  'cart-refreshed'
7667//   cart-page.js    init()           -> listens 'cart-updated'
7668// Nothing bridges those two names, so ANY out-of-band cart mutation must do
7669// BOTH: refresh the store (drawer + header badge) AND dispatch 'cart-updated'
7670// (main cart list + Order Summary). Doing only one leaves half the page
7671// stale -- which is the reason this module used to hard-reload.
7672// ----------------------------------------------------
7673function ckCartStore(win) {
7674try {
7675var w = win || window;
7676if (!w.Alpine || typeof w.Alpine.store !== 'function') return null;
7677return w.Alpine.store('cart') || null;
7678} catch (e) {
7679return null;
7680}
7681}
7682// Push a cart mutation into BOTH Zephyr cart trees, optionally revealing the
7683// side cart. Returns false when the Zephyr store is absent (legacy theme) so
7684// callers can fall back to the old page-reload path.
7685function ckAfterCartMutation(openDrawer) {
7686var st = ckCartStore(window);
7687if (!st) return false;
7688if (openDrawer && typeof st.openDrawer === 'function') {
7689try { st._scrollToNew = true; } catch (e) { }
7690try { st.openDrawer(); } catch (e) { }
7691}
7692// Mirror Zephyr's own post-add timing (handleAjaxAddToCart waits 100ms).
7693setTimeout(function () {
7694try { if (typeof st.refresh === 'function') st.refresh(); } catch (e) { }
7695try { window.dispatchEvent(new CustomEvent('cart-updated')); } catch (e) { }
7696}, 100);
7697return true;
7698}
7699function fetchPdpData(productUrl) {
7700if (!productUrl) return Promise.resolve({ name: null, img: null, price: null });
7701if (PDP_CACHE.has(productUrl)) return Promise.resolve(PDP_CACHE.get(productUrl));
7702var __fetchUrl = ckSameOrigin(productUrl);
7703return fetch(__fetchUrl, { credentials: 'same-origin' })
7704.then(function (res) { return res.text(); })
7705.then(function (html) {
7706var doc = new DOMParser().parseFromString(html, 'text/html');
7707var data = parsePdpDoc(doc);
7708PDP_CACHE.set(productUrl, data);
7709return data;
7710})
7711.catch(function () {
7712var data = { name: null, img: null, price: null };
7713PDP_CACHE.set(productUrl, data);
7714return data;
7715});
7716}
7717// ----------------------------------------------------
7718// Tile State Model (no UX changes yet)
7719// ----------------------------------------------------
7720var TILE_STATE = {
7721AVAILABLE: 'available',
7722SELECTED: 'selected',
7723ADDED: 'added'
7724};
7725// Central store keyed by SKU
7726// ----------------------------------------------------
7727// TILE QUALITY GATE + APPROVED FALLBACKS (added 2026-09-15)
7728// Some persona entries point at URLs that aren't product pages on every store
7729// view (a prod-only SKU, a retired URL). The scrape then returns whatever that
7730// page happens to be -- "Create New Customer Account", no price, the site logo
7731// -- and we rendered it as a product. Never show that. Substitute an approved
7732// alternative the shopper doesn't already have, or drop the slot entirely.
7733// ----------------------------------------------------
7734var CK_TILE_FALLBACKS = [
7735{ productId: '22023', productUrl: 'https://www.simplystamps.com/envelope-sealer-2oz-bottle', title: 'Envelope Sealer - 2oz Bottle' },
7736{ productId: '25340', productUrl: 'https://www.simplystamps.com/stamp-cleaner-kit', title: 'Stamp Cleaner Kit' },
7737{ productId: '24186', productUrl: 'https://www.simplystamps.com/gold-wax-seal-sticker-rosemary-design', title: 'Gold Wax Seal Sticker, Rosemary Design' },
7738{ productId: '18507', productUrl: 'https://www.simplystamps.com/craft-ink-pads', title: 'Craft Ink Pads' },
7739{ productId: '24221', productUrl: 'https://www.simplystamps.com/guard-your-id-with-the-trodat-id-protector-roller', title: 'Guard Your ID with the Trodat ID Protector Roller' },
7740{ productId: '23105', productUrl: 'https://www.simplystamps.com/delicate-flower-envelope-accent-craft-stamp', title: 'Delicate Flower Envelope Accent Craft Stamp' }
7741];
7742var CK_FALLBACKS_USED = new Set();
7743function ckIsUsableTileData(data) {
7744if (!data) return false;
7745if (data.isProduct === false) return false;          // definitely not a PDP
7746if (data.price == null && !data.img) return false;   // nothing worth showing
7747var n = String(data.name || '').trim().toLowerCase();
7748if (!n) return false;
7749if (/create new customer|customer login|^log ?in|^sign ?in|page not found|404|access denied|shopping cart|my account|dashboard/.test(n)) return false;
7750return true;
7751}
7752function ckCartIdsNow() {
7753try { return getCartProductIdSet() || Object.create(null); }
7754catch (e) { return Object.create(null); }
7755}
7756// First approved alternative that is NOT already in the cart and NOT already on
7757// screen. Null when there is nothing safe left to show.
7758function ckPickFallbackTile() {
7759var inCart = ckCartIdsNow();
7760var onScreen = Object.create(null);
7761try {
7762var rendered = document.querySelectorAll('[data-kit-primary]');
7763for (var r = 0; r < rendered.length; r++) {
7764onScreen[String(rendered[r].getAttribute('data-kit-primary'))] = true;
7765}
7766} catch (e) { }
7767for (var i = 0; i < CK_TILE_FALLBACKS.length; i++) {
7768var f = CK_TILE_FALLBACKS[i];
7769var id = String(f.productId);
7770if (CK_FALLBACKS_USED.has(id)) continue;
7771if (inCart[id]) continue;
7772if (onScreen[id]) continue;
7773CK_FALLBACKS_USED.add(id);
7774return f;
7775}
7776return null;
7777}
7778// Nothing safe for this slot -- remove it rather than render junk.
7779function ckDropTile(card) {
7780try {
7781var pid = String(card.getAttribute('data-kit-primary') || '');
7782if (pid) { try { selectedPrimaryIds.delete(pid); } catch (eS) { } }
7783if (card.parentNode) card.parentNode.removeChild(card);
7784try { updatePriceUI(); } catch (eU) { }
7785// Tiles are dropped asynchronously, so this is the moment the module
7786// can become empty -- re-check every time.
7787try { ckSyncModuleEmptyState(); } catch (eE) { }
7788} catch (e) { }
7789}
7790// Repaint an already-rendered card onto a different product, then hydrate it.
7791function ckRepaintCardAs(card, item) {
7792if (!card || !item) return;
7793var id = String(item.productId);
7794try { TILE_ITEM_BY_ID[id] = item; } catch (e) { }
7795try { if (ckSeenTileIds) ckSeenTileIds.add(id); } catch (e) { }
7796card.setAttribute('data-kit-primary', id);
7797card.setAttribute('data-sku', id);
7798card.setAttribute('data-kit-url', item.productUrl || '');
7799try { card.classList.remove('is-added', 'is-selected', 'is-complete'); } catch (e) { }
7800try { card.removeAttribute('aria-disabled'); } catch (e) { }
7801var nameEl = card.querySelector('.kit-product-name');
7802var priceEl = card.querySelector('.kit-product-price');
7803var thumbEl = card.querySelector('.kit-product-thumb-tile');
7804if (nameEl) nameEl.textContent = item.title || 'Loading\u2026';
7805if (priceEl) priceEl.textContent = '\u2014';
7806if (thumbEl) thumbEl.innerHTML = '<span style="font-size:12px;color:#888;">Loading\u2026</span>';
7807fetchPdpData(item.productUrl).then(function (d2) {
7808if (String(card.getAttribute('data-kit-primary') || '') !== id) return; // swapped again
7809if (!ckIsUsableTileData(d2)) {
7810// This alternative isn't available in this store view either. Try the
7811// next approved one; CK_FALLBACKS_USED makes the recursion terminate.
7812var nextAlt = ckPickFallbackTile();
7813if (nextAlt) ckRepaintCardAs(card, nextAlt);
7814else ckDropTile(card);
7815return;
7816}
7817try {
7818NOTARY_HYDRATED.set(id, {
7819sku: d2.sku,
7820productId: id, productUrl: item.productUrl,
7821name: d2.name || item.title, img: d2.img || null,
7822price: (d2.price != null ? Number(d2.price) : null)
7823});
7824} catch (e) { }
7825if (nameEl) nameEl.textContent = d2.name || item.title;
7826if (priceEl) priceEl.textContent = (d2.price != null ? ('$' + Number(d2.price).toFixed(2)) : 'See price in cart');
7827if (thumbEl) {
7828if (d2.img) thumbEl.innerHTML = '<img src="' + d2.img + '" alt="" />';
7829else thumbEl.innerHTML = '<span style="font-size:12px;color:#888;">No image</span>';
7830}
7831});
7832}
7833// ----------------------------------------------------
7834// MODULE EMPTY STATE (added 2026-09-15)
7835// Tiles can vanish AFTER render: the quality gate drops non-products and
7836// in-cart suppression removes what the shopper already owns. The original
7837// empty-state branch only ran when the pool was empty at BUILD time, and it
7838// targeted #kit-add-btn -- which does not exist, the CTA is
7839// #cart-kit-upsell-add -- so an emptied module was left on the page as a
7840// header + "Total: $0.00" + a live Add to Cart button with nothing to add.
7841// ----------------------------------------------------
7842function ckSyncModuleEmptyState() {
7843try {
7844var tileCount = stampGrid ? stampGrid.querySelectorAll('[data-kit-primary]').length : 0;
7845var swatchCount = swatchList ? swatchList.querySelectorAll('.kit-swatch').length : 0;
7846if (tileCount > 0 || swatchCount > 0) {
7847if (upsell) upsell.style.display = '';
7848return false;
7849}
7850// Nothing left to offer. An upsell with no offer is noise on a cart
7851// page, so take the whole module off the page rather than leave a
7852// header and a dead CTA behind.
7853try { console.warn('[SmartCart] nothing offerable remains (all in cart or filtered); hiding the module.'); } catch (e1) { }
7854try {
7855if (addBtn) {
7856addBtn.disabled = true;
7857addBtn.setAttribute('aria-disabled', 'true');
7858}
7859} catch (e2) { }
7860if (upsell) upsell.style.display = 'none';
7861return true;
7862} catch (e) { return false; }
7863}
7864var TileStore = (function () {
7865var map = Object.create(null);
7866function getSkuFromTile(tileEl) {
7867return (
7868tileEl.getAttribute('data-sku') ||
7869tileEl.getAttribute('data-product-sku') ||
7870tileEl.getAttribute('data-kit-sku') ||
7871''
7872).trim();
7873}
7874function inferIsHard(tileEl) {
7875// Preferred: explicit attribute you set when building tiles
7876// data-hard="1" or data-is-hard="true"
7877var hardAttr =
7878tileEl.getAttribute('data-hard') ||
7879tileEl.getAttribute('data-is-hard') ||
7880'';
7881if (hardAttr) return hardAttr === '1' || hardAttr === 'true';
7882// Fallback: if you already tag these somewhere else
7883// (you can tighten this later when we implement Step 2)
7884return tileEl.classList.contains('is-hard') || false;
7885}
7886function initFromDom(rootEl) {
7887if (!rootEl) return;
7888var tiles = Array.from(rootEl.querySelectorAll('.kit-stamp-card[data-kit-primary]'));
7889tiles.forEach(function (tileEl) {
7890var sku = getSkuFromTile(tileEl);
7891if (!sku) return;
7892try { ckSeenTileIds.add(String(sku)); } catch (e) { }
7893if (!map[sku]) {
7894map[sku] = {
7895sku: sku,
7896el: tileEl,
7897isHard: inferIsHard(tileEl),
7898state: TILE_STATE.AVAILABLE
7899};
7900} else {
7901// re-rendered tile, keep state but update DOM ref
7902map[sku].el = tileEl;
7903// do not overwrite isHard unless you want to re-infer
7904}
7905// Bootstrap state from current UI (so no visible changes)
7906// You can adjust these classnames to match your actual markup.
7907if (tileEl.classList.contains('is-added') || tileEl.querySelector('.ck-added, .cart-kit-added, .cart-kit-check')) {
7908map[sku].state = TILE_STATE.ADDED;
7909} else if (tileEl.classList.contains('is-selected') || tileEl.getAttribute('aria-pressed') === 'true') {
7910map[sku].state = TILE_STATE.SELECTED;
7911} else {
7912map[sku].state = TILE_STATE.AVAILABLE;
7913}
7914syncDom(sku);
7915});
7916}
7917function get(sku) {
7918return map[String(sku || '')] || null;
7919}
7920function setState(sku, nextState) {
7921sku = String(sku || '');
7922if (!map[sku]) return;
7923var prev = map[sku].state;
7924map[sku].state = nextState;
7925// Keep DOM aligned (but we preserve current look)
7926syncDom(sku);
7927return { sku: sku, prev: prev, next: nextState };
7928}
7929function syncDom(sku) {
7930var t = map[String(sku || '')];
7931if (!t || !t.el) return;
7932var el = t.el;
7933// Normalize classes (keep current behavior intact)
7934el.classList.toggle('is-selected', t.state === TILE_STATE.SELECTED);
7935el.classList.toggle('is-added', t.state === TILE_STATE.ADDED);
7936// Optional aria alignment if you already use it
7937// (doesn't change visuals but improves correctness)
7938if (t.state === TILE_STATE.SELECTED) el.setAttribute('aria-pressed', 'true');
7939else el.setAttribute('aria-pressed', 'false');
7940}
7941function all() {
7942return map;
7943}
7944return {
7945initFromDom: initFromDom,
7946get: get,
7947setState: setState,
7948syncDom: syncDom,
7949all: all
7950};
7951})();
7952// ----------------------------------------------------
7953// Micro PDP (iframe) — for hard SKUs w/ uploads + dependent options
7954// ----------------------------------------------------
7955var MICRO_PDP = {
7956overlay: null,
7957iframe: null,
7958loading: null,
7959lastUrl: null,
7960lastProductId: null,
7961returnUrl: null,
7962progressDone: 0,
7963progressTotal: 0
7964};
7965// ----------------------------------------------------
7966// Micro PDP progress (header bar)
7967// ----------------------------------------------------
7968function getMicroProgressFromUrl() {
7969try {
7970var u = new URL(window.location.href);
7971var done = parseInt(u.searchParams.get('microDone') || '0', 10);
7972var total = parseInt(u.searchParams.get('microTotal') || '0', 10);
7973if (!isFinite(done) || done < 0) done = 0;
7974if (!isFinite(total) || total < 0) total = 0;
7975return { done: done, total: total };
7976} catch (e) {
7977return { done: 0, total: 0 };
7978}
7979}
7980function bumpMicroDoneInUrl() {
7981try {
7982var u = new URL(window.location.href);
7983var done = parseInt(u.searchParams.get('microDone') || '0', 10);
7984var total = parseInt(u.searchParams.get('microTotal') || '0', 10);
7985if (!isFinite(done) || done < 0) done = 0;
7986if (!isFinite(total) || total < 0) total = 0;
7987if (total > 0) {
7988done = Math.min(total, done + 1);
7989u.searchParams.set('microDone', String(done));
7990window.history.replaceState({}, document.title, u.toString());
7991}
7992return { done: done, total: total };
7993} catch (e) {
7994return { done: 0, total: 0 };
7995}
7996}
7997function renderMicroProgress(done, total) {
7998var wrap = mount.querySelector('#ck-micropdp-progress');
7999var fill = mount.querySelector('#ck-micropdp-progressfill');
8000var txt = mount.querySelector('#ck-micropdp-progresstext');
8001// Only show when the user is doing a multi-item add (2+ total)
8002if (!wrap || !fill || !txt || !total || total <= 1) {
8003if (wrap) wrap.hidden = true;
8004return;
8005}
8006if (!isFinite(done) || done < 0) done = 0;
8007if (!isFinite(total) || total < 1) total = 1;
8008if (done > total) done = total;
8009var pct = Math.round((done / total) * 100);
8010wrap.hidden = false;
8011fill.style.width = pct + '%';
8012// Completion state (for “All set” moment)
8013if (done >= total) {
8014wrap.classList.add('is-complete');
8015txt.textContent = 'All set — ' + total + ' of ' + total + ' items added';
8016} else {
8017wrap.classList.remove('is-complete');
8018txt.textContent = done + ' of ' + total + ' items added';
8019}
8020}
8021ensureMicroPdpModal();
8022function ensureMicroPdpModal() {
8023if (MICRO_PDP.overlay) return;
8024var modalMarkup = `<div class="ck-micropdp-overlay" id="ck-micropdp-overlay" role="dialog" aria-modal="true" aria-label="Customize and add item"><div class="ck-micropdp-modal"><div class="ck-micropdp-head"><div class="ck-micropdp-head-top"><div><div class="ck-micropdp-title" id="ck-micropdp-title">Customize item</div><div class="ck-micropdp-sub" id="ck-micropdp-sub">Complete the required fields, then click “Add To Cart”.</div></div><div class="ck-micropdp-actions"> <button type="button" class="ck-micropdp-btn" id="ck-micropdp-full">View full details</button> <button type="button" class="ck-micropdp-btn" id="ck-micropdp-close">Close</button></div></div><div class="ck-micropdp-progress" id="ck-micropdp-progress" hidden><div class="ck-micropdp-progressbar"><div class="ck-micropdp-progressfill" id="ck-micropdp-progressfill"></div></div><div class="ck-micropdp-progresstext" id="ck-micropdp-progresstext">0 of 0 items added</div></div></div><div class="ck-micropdp-body"><div class="ck-micropdp-loading" id="ck-micropdp-loading">Loading…</div> <iframe class="ck-micropdp-iframe" id="ck-micropdp-iframe" title="Micro product details"></iframe></div></div></div> `;
8025// Append inside mount so scoped CSS applies
8026mount.insertAdjacentHTML('beforeend', modalMarkup);
8027MICRO_PDP.overlay = mount.querySelector('#ck-micropdp-overlay');
8028MICRO_PDP.iframe = mount.querySelector('#ck-micropdp-iframe');
8029MICRO_PDP.loading = mount.querySelector('#ck-micropdp-loading');
8030var closeBtn = mount.querySelector('#ck-micropdp-close');
8031var fullBtn = mount.querySelector('#ck-micropdp-full');
8032// Safety guards
8033if (MICRO_PDP.overlay) MICRO_PDP.overlay.classList.remove('is-open');
8034if (closeBtn) closeBtn.addEventListener('click', function () { closeMicroPdp('button'); });
8035if (fullBtn) fullBtn.addEventListener('click', function () {
8036if (MICRO_PDP.lastUrl) window.open(MICRO_PDP.lastUrl, '_blank', 'noopener');
8037});
8038// Optional overlay click closes
8039if (MICRO_PDP.overlay) {
8040MICRO_PDP.overlay.addEventListener('click', function (e) {
8041if (e.target === MICRO_PDP.overlay) closeMicroPdp('overlay');
8042});
8043}
8044}
8045// ESC closes
8046document.addEventListener('keydown', function (e) {
8047if (e.key === 'Escape' && MICRO_PDP.overlay && MICRO_PDP.overlay.classList.contains('is-open')) {
8048closeMicroPdp();
8049}
8050});
8051// On iframe load, inject “micro” CSS + force form submit to top window
8052MICRO_PDP.iframe.addEventListener('load', function () {
8053try {
8054var doc = MICRO_PDP.iframe.contentDocument;
8055if (!doc) return;
8056// Ensure scope class exists inside iframe (so our selectors always match)
8057doc.documentElement.classList.add('cart-kit-micro-pdp');
8058doc.body.classList.add('cart-kit-micro-pdp');
8059// Hide big site chrome + tighten layout (best-effort; selectors may vary)
8060var style = doc.createElement('style');
8061style.id = 'ck-micro-css';
8062style.textContent = `
8063/* =========================================================
8064Micro PDP trims — chrome off, form focus, thumbnail preview
8065========================================================= */
8066/* 1) Remove site chrome */
8067header, .header,
8068.nav-container, .navigation,
8069.breadcrumbs, .breadcrumb,
8070.page-header, .header.content,
8071.sections.nav-sections,
8072.message.global, .message.notice,
8073.cookie-status-message,
8074footer, .footer, .page-footer {
8075display: none !important;
8076}
8077/* 1b) Zephyr side cart must never render inside the configurator.
8078Neither the drawer nor its trigger sits inside <header>, so the
8079chrome rules above miss both. */
8080button[aria-label="Open cart"],
8081[x-show*="drawerOpen"] {
8082display: none !important;
8083}
8084/* 2) Normalize layout */
8085html, body { height: auto !important; }
8086body {
8087margin: 0 !important;
8088padding: 12px !important;
8089background: #fff !important;
8090overflow-x: hidden !important;
8091}
8092.page-wrapper, .page-main, main, .columns, .column.main {
8093width: 100% !important;
8094max-width: none !important;
8095margin: 0 !important;
8096padding: 0 !important;
8097}
8098/* Hide the INLINE form add-to-cart row (but keep the sticky bottom bar) */
8099/* Safety: ensure the STICKY/mobile bar still shows */
8100.cart-kit-micro-pdp .add-to-box-mobile .qty-and-add-to-cart-container,
8101.cart-kit-micro-pdp .add-to-box-mobile .add-to-cart-button-container-non-grouped,
8102.cart-kit-micro-pdp .add-to-box-mobile .input-group-mobile-container {
8103display: flex !important;
8104}
8105/* 2) Re-show the sticky/mobile bar at the bottom (it’s outside the form) */
8106.cart-kit-micro-pdp .add-to-box-mobile .qty-and-add-to-cart-container {
8107display: flex !important;
8108}
8109/* Hide qty+button ONLY when it's NOT inside the sticky bottom bar */
8110.cart-kit-micro-pdp .qty-and-add-to-cart-container:not(.add-to-box-mobile .qty-and-add-to-cart-container) {
8111display: none !important;
8112}
8113/* Kill common "extra" sections we don't need */
8114#ss-main-content { margin-top: unset !important; display:none !important; }
8115#sticky-mobile-header{ display: none !important; }
8116.gqcs-swiper-container{ display: none !important; }
8117.product-wapper-tab { display: none !important; }
8118/* 3) Preview: make it a small thumbnail WITHOUT reflowing layouts
8119(works across templates: big hero, centered preview, swiper, etc.) */
8120.cart-kit-micro-pdp .product.media,
8121.cart-kit-micro-pdp .product-image-carousel-container,
8122.cart-kit-micro-pdp .product-image-carousel-select,
8123.cart-kit-micro-pdp #ss-image-sheet,
8124.cart-kit-micro-pdp .gallery-placeholder,
8125.cart-kit-micro-pdp .product-img-content {
8126width: 100% !important;
8127max-width: 100% !important;
8128}
8129/* Center the preview area */
8130.cart-kit-micro-pdp .product.media,
8131.cart-kit-micro-pdp .product-image-carousel-container,
8132.cart-kit-micro-pdp .product-image-carousel-select,
8133.cart-kit-micro-pdp .gallery-placeholder {
8134display: flex !important;
8135justify-content: center !important;
8136}
8137/* Image itself: cap size (no cropping) */
8138.cart-kit-micro-pdp img.img-responsive,
8139.cart-kit-micro-pdp .product.media img,
8140.cart-kit-micro-pdp .product-image-photo,
8141.cart-kit-micro-pdp .fotorama__img,
8142.cart-kit-micro-pdp .swiper-slide img {
8143max-height: 300px !important;
8144max-width: 300px !important;
8145width: auto !important;
8146height: auto !important;
8147object-fit: contain !important;
8148margin: 0 !important;
8149}
8150/* Prevent swiper/nav chrome from adding vertical junk */
8151/* Swiper UI: show dots when multiple images exist (arrows stay hidden) */
8152.cart-kit-micro-pdp .swiper-button-prev,
8153.cart-kit-micro-pdp .swiper-button-next,
8154.cart-kit-micro-pdp .swiper-scrollbar {
8155display: none !important;
8156}
8157/* Dots ON (we'll hide them via JS when only 1 image) */
8158.cart-kit-micro-pdp .swiper-pagination {
8159display: flex !important;
8160justify-content: center !important;
8161gap: 6px !important;
8162margin-top: 6px !important;
8163position: relative !important;
8164}
8165/* Make dots smaller + subtle so they don't add height */
8166.cart-kit-micro-pdp .swiper-pagination-bullet {
8167width: 6px !important;
8168height: 6px !important;
8169opacity: 0.35 !important;
8170}
8171.cart-kit-micro-pdp .swiper-pagination-bullet-active {
8172opacity: 0.9 !important;
8173}
8174/* Tighten spacing under preview */
8175.cart-kit-micro-pdp .product.media,
8176.cart-kit-micro-pdp .product-image-carousel-container,
8177.cart-kit-micro-pdp .product-image-carousel-select,
8178.cart-kit-micro-pdp .gallery-placeholder {
8179margin-bottom: 64px !important;
8180}
8181/* 4) Form focus */
8182#product_addtocart_form, .product-add-form {
8183margin-top: 10px !important;
8184}
8185/* 5) Hide “Extra bottle of ink?” field (best effort selectors)
8186Note: :has() may not be supported everywhere; we also keep the JS fallback below */
8187.field.extra_ink, .field.extra-ink, .field.extra_bottle_ink,
8188.product-options-wrapper .field:has(select[name*="extra"]) {
8189display: none !important;
8190}
8191.full-size-container{
8192display:none;
8193}
8194`;
8195// Prevent double-inject if iframe reloads quickly
8196var existing = doc.getElementById('ck-micro-css');
8197if (existing && existing.parentNode) existing.parentNode.removeChild(existing);
8198doc.head.appendChild(style);
8199// Force add-to-cart form to submit in TOP window so we return to cart normally
8200var form =
8201doc.querySelector('#product_addtocart_form') ||
8202doc.querySelector('form[action*="checkout/cart/add"]');
8203if (form) {
8204form.setAttribute('target', '_top');
8205// Ensure return_url points back to cart (best effort)
8206var existingReturn = form.querySelector('input[name="return_url"]');
8207if (!existingReturn) {
8208existingReturn = doc.createElement('input');
8209existingReturn.type = 'hidden';
8210existingReturn.name = 'return_url';
8211form.appendChild(existingReturn);
8212}
8213existingReturn.value = (MICRO_PDP.returnUrl || window.location.href);
8214// Some Magento setups use return_url_encoded; set it if present
8215var encodedReturn = form.querySelector('input[name="return_url_encoded"]');
8216if (encodedReturn) {
8217try {
8218encodedReturn.value = btoa(unescape(encodeURIComponent(MICRO_PDP.returnUrl || window.location.href)));
8219} catch (e) { }
8220}
8221// If there's a quantity input, default to 1
8222var qty = form.querySelector('input[name="qty"]');
8223if (qty && !qty.value) qty.value = '1';
8224// ---- LOADING CURSOR HOOK (reliable) ----
8225try {
8226// Zephyr's ATC is id="product-addtocart-button" and type="button",
8227// so look for it FIRST -- a bare button[type="submit"] lookup can
8228// match an unrelated submit (search / newsletter) earlier in the DOM.
8229var btn =
8230doc.querySelector('button#product-addtocart-button') ||
8231form.querySelector('button[type="submit"], input[type="submit"]') ||
8232doc.querySelector('button.addtocart') ||
8233doc.querySelector('button.action.tocart') ||
8234doc.querySelector('button[type="submit"]') ||
8235doc.querySelector('input[type="submit"]');
8236if (btn && !btn.__ckBound) {
8237btn.__ckBound = true;
8238btn.addEventListener('click', function () {
8239// NOTE: arming happens in the handleAjaxAddToCart
8240// wrapper, not here -- the button the shopper actually
8241// clicks is the sticky bar's, which lives outside
8242// #product_addtocart_form and may not be this element.
8243setLoadingCursor(true);
8244if (MICRO_PDP.overlay) MICRO_PDP.overlay.classList.add('is-loading');
8245}, { capture: true });
8246}
8247if (!form.__ckBound) {
8248form.__ckBound = true;
8249form.addEventListener('submit', function () {
8250setLoadingCursor(true);
8251if (MICRO_PDP.overlay) MICRO_PDP.overlay.classList.add('is-loading');
8252}, { capture: true });
8253}
8254} catch (e) {
8255// If iframe isn't accessible due to cross-domain, this will throw.
8256}
8257}
8258// Hook the iframe's own cart store so the side cart cannot open in
8259// here, and so the AJAX add gives us a reliable success signal.
8260try {
8261var ckSrc = String(MICRO_PDP.iframe.getAttribute('src') || '');
8262if (ckSrc && ckSrc.indexOf('about:blank') !== 0) bindMicroPdpAddBridge();
8263} catch (e) { }
8264// ----------------------------------------------------
8265// Cut everything below the shipping block (keep shipping visible)
8266// Anchor: .ships-and-made-in-usa-container
8267// ----------------------------------------------------
8268(function () {
8269var d = MICRO_PDP.iframe.contentDocument;
8270if (!d) return;
8271var shipContainer = d.querySelector('.ships-and-made-in-usa-container');
8272if (!shipContainer) return;
8273// Find a reasonable scope that contains the PDP “content” sections
8274var scope =
8275shipContainer.closest('.vpt-product, .product-shop-content, .product-info-main, .column.main, main')
8276|| d.body;
8277// We want to hide everything AFTER the shipping container's row
8278var row = shipContainer.closest('.row') || shipContainer.parentElement;
8279if (!row) return;
8280var sib = row.nextElementSibling;
8281while (sib) {
8282sib.style.display = 'none';
8283sib = sib.nextElementSibling;
8284}
8285})();
8286// ----------------------------------------------------
8287// Hide “Extra bottle of ink?” by label text (fallback)
8288// ----------------------------------------------------
8289(function () {
8290var d = MICRO_PDP.iframe.contentDocument;
8291if (!d) return;
8292var labels = Array.from(d.querySelectorAll('label'));
8293var inkLabel = labels.find(function (l) {
8294return (l.textContent || '').toLowerCase().includes('extra bottle of ink');
8295});
8296if (!inkLabel) return;
8297var field = inkLabel.closest('.field, .control, .admin__field, .product-options-wrapper') || inkLabel.parentElement;
8298if (field) field.style.display = 'none';
8299})();
8300// Done loading
8301if (MICRO_PDP.loading) MICRO_PDP.loading.style.display = 'none';
8302} catch (err) {
8303// If cross-domain or blocked, just hide loader after a beat
8304if (MICRO_PDP.loading) MICRO_PDP.loading.style.display = 'none';
8305}
8306});
8307// ----------------------------------------------------
8308// MICRO-PDP ADD BRIDGE (added 2026-09-11)
8309// Zephyr's PDP add-to-cart control is
8310// <button type="button" onclick="handleAjaxAddToCart(this); return false;"> // It never submits the form, so the form.target='_top' hook further down --
8311// which used to be the ONLY way the parent learned an add had happened -- is
8312// inert on a Zephyr PDP. The add is AJAX, and on success handleAjaxAddToCart
8313// calls Alpine.store('cart').openDrawer() INSIDE this iframe: the side cart
8314// slides out within the 960px modal and the parent cart page never updates.
8315//
8316// Fix: the iframe is same-origin, so we replace openDrawer() on the iframe's
8317// own cart store. That call is our exact "add succeeded" signal (it runs only
8318// in handleAjaxAddToCart's success branch) and the drawer never renders in the
8319// frame. We then reproduce the receipt state the old reload used to deliver in
8320// the URL -- via history.replaceState instead of navigating -- so the whole
8321// existing downstream pipeline (tile -> "added", tile swap, multi-SKU chaining)
8322// keeps working untouched. form.target='_top' stays as a fallback for any PDP
8323// that still submits natively.
8324// ----------------------------------------------------
8325var MICRO_BRIDGE = {
8326timer: null, loadTimer: null, tries: 0,
8327armed: false, handled: false,
8328addPatched: false, storePatched: false,
8329countBefore: null
8330};
8331// Zephyr exposes a running cart count at /zephyr/cart/count/ (verified live:
8332// returns {"count": N} where N is total UNITS, not lines -- so a qty bump on an
8333// existing line still moves it). Authoritative fallback for when the in-frame
8334// success signal never arrives, so a miss degrades to a late receipt, not silence.
8335function ckCartCount() {
8336try {
8337return fetch('/zephyr/cart/count/', {
8338credentials: 'same-origin',
8339headers: { 'X-Requested-With': 'XMLHttpRequest' }
8340})
8341.then(function (r) {
8342if (!r.ok) throw new Error('cart count HTTP ' + r.status);
8343return r.json();
8344})
8345.then(function (j) { return (j && typeof j.count === 'number') ? j.count : null; })
8346.catch(function (err) {
8347try { console.warn('[SmartCart] /zephyr/cart/count/ unavailable:', err && err.message); } catch (e) { }
8348return null;
8349});
8350} catch (e) {
8351return Promise.resolve(null);
8352}
8353}
8354// A 200 from /checkout/cart/add is NOT proof the cart changed: OpenMage
8355// answers a bare add for a product it can't sell (missing from this store
8356// view, disabled, required options) with a 200 and an empty result, and
8357// postToCart() only throws on !res.ok. Without this the module shows a full
8358// success state -- drawer, toast, tile swap -- over an unchanged cart.
8359function ckVerifySimpleAdd(baseline) {
8360if (typeof baseline !== 'number') return;   // no baseline: stay optimistic
8361setTimeout(function () {
8362ckCartCount().then(function (after) {
8363if (after === null || after > baseline) return;   // added, or can't tell
8364try { console.warn('[SmartCart] add returned 200 but the cart did not change (baseline ' + baseline + ').'); } catch (e) { }
8365var msg = 'That item could not be added \u2014 it may be unavailable. Please try another.';
8366try { if (statusEl) statusEl.textContent = msg; } catch (e) { }
8367try {
8368var fl = (window.Alpine && typeof window.Alpine.store === 'function') ? window.Alpine.store('flash') : null;
8369if (fl && typeof fl.show === 'function') fl.show('error', msg);
8370} catch (e) { }
8371// Don't leave a side cart open over an unchanged cart.
8372try {
8373var st = ckCartStore(window);
8374if (st && typeof st.closeDrawer === 'function') st.closeDrawer();
8375} catch (e) { }
8376});
8377}, 600);
8378}
8379// Clears the visual loading state ONLY. It must never disarm: the add can
8380// still be in flight, and disarming would make the openDrawer success signal
8381// get swallowed when it finally arrives.
8382function clearMicroLoadingState() {
8383try { setLoadingCursor(false); } catch (e) { }
8384try { if (MICRO_PDP.overlay) MICRO_PDP.overlay.classList.remove('is-loading'); } catch (e) { }
8385}
8386// Ask the server whether the add actually landed. Callers decide when it's
8387// worth asking; this only refuses to act if a receipt already fired.
8388// Polls rather than asking once. handleAjaxAddToCart returns a BOOLEAN, not a
8389// promise (measured on local 2026-09-14), so there is no completion callback --
8390// a real add on this catalog takes ~3.5s. A single check right after the click
8391// always misses, and treating that miss as failure is what left the modal open.
8392function confirmMicroAddByCount(reason, attemptsLeft) {
8393if (MICRO_BRIDGE.handled) return;
8394if (typeof attemptsLeft !== 'number') attemptsLeft = 0;
8395if (MICRO_BRIDGE.countBefore === null) {
8396try { console.warn('[SmartCart] cannot confirm micro-PDP add, no cart-count baseline (' + reason + ').'); } catch (e) { }
8397clearMicroLoadingState();
8398return;
8399}
8400var baseline = MICRO_BRIDGE.countBefore;
8401ckCartCount().then(function (after) {
8402if (MICRO_BRIDGE.handled) return;
8403if (after !== null && after > baseline) {
8404try { console.warn('[SmartCart] micro-PDP add confirmed by cart count (' + reason + ').'); } catch (e) { }
8405try { onMicroPdpAddSucceeded(); } catch (e) { }
8406return;
8407}
8408if (attemptsLeft > 0) {
8409setTimeout(function () {
8410confirmMicroAddByCount(reason, attemptsLeft - 1);
8411}, 1200);
8412return;
8413}
8414// Out of attempts. Drop the spinner but STAY ARMED -- if the add is
8415// simply slow, openDrawer will still arrive and fire the receipt.
8416clearMicroLoadingState();
8417});
8418}
8419function resetMicroBridge() {
8420try { if (MICRO_BRIDGE.timer) clearInterval(MICRO_BRIDGE.timer); } catch (e) { }
8421try { if (MICRO_BRIDGE.loadTimer) clearTimeout(MICRO_BRIDGE.loadTimer); } catch (e) { }
8422MICRO_BRIDGE.timer = null;
8423MICRO_BRIDGE.loadTimer = null;
8424MICRO_BRIDGE.tries = 0;
8425MICRO_BRIDGE.armed = false;
8426MICRO_BRIDGE.addPatched = false;
8427MICRO_BRIDGE.storePatched = false;
8428// `handled` is deliberately NOT cleared -- closeMicroPdp() runs as part of a
8429// successful receipt. openMicroPdp() clears it, so the guard really is one
8430// receipt per configurator open.
8431}
8432// Arm on the ACTUAL add call, not a button click. Validation runs inside
8433// handleAjaxAddToCart, so armed only means "an add was attempted".
8434function armMicroBridge() {
8435MICRO_BRIDGE.armed = true;
8436try { setLoadingCursor(true); } catch (e) { }
8437try { if (MICRO_PDP.overlay) MICRO_PDP.overlay.classList.add('is-loading'); } catch (e) { }
8438// Long backstop only. The real resolution is onMicroAddSettled(), which
8439// fires the moment handleAjaxAddToCart's own promise settles -- so a slow
8440// upload resolves when it lands rather than being cut off by a fixed timer.
8441try { if (MICRO_BRIDGE.loadTimer) clearTimeout(MICRO_BRIDGE.loadTimer); } catch (e) { }
8442MICRO_BRIDGE.loadTimer = setTimeout(function () {
8443if (MICRO_BRIDGE.handled || !MICRO_BRIDGE.armed) return;
8444confirmMicroAddByCount('60s backstop', 0);
8445}, 60000);
8446}
8447// handleAjaxAddToCart returns its fetch promise. When it settles the add is
8448// decided: either our openDrawer signal already fired (handled), or it did not
8449// and we resolve it against the server instead of waiting out the backstop.
8450function onMicroAddSettled() {
8451// ~10s of polling (500ms + 8 x 1200ms), which comfortably covers a real
8452// add. The openDrawer signal normally wins long before this; this only
8453// catches an add that completes without ever reaching the Alpine store.
8454setTimeout(function () {
8455if (MICRO_BRIDGE.handled || !MICRO_BRIDGE.armed) return;
8456confirmMicroAddByCount('no in-frame signal yet', 8);
8457}, 500);
8458}
8459function bindMicroPdpAddBridge() {
8460try { if (MICRO_BRIDGE.timer) clearInterval(MICRO_BRIDGE.timer); } catch (e) { }
8461MICRO_BRIDGE.timer = null;
8462MICRO_BRIDGE.tries = 0;
8463MICRO_BRIDGE.addPatched = false;
8464MICRO_BRIDGE.storePatched = false;
8465// Two hooks, both inside the iframe, both same-origin:
8466//   1. window.handleAjaxAddToCart -- wrapped to ARM on a real add call.
8467//      This is the pattern ss-tier-upsell-zephyr.html v1.32 proved out;
8468//      binding a button click is what it proved unreliable.
8469//   2. Alpine store cart.openDrawer -- the SUCCESS signal. Zephyr calls
8470//      it only in handleAjaxAddToCart's success branch, and overriding it
8471//      also keeps the side cart from rendering inside the 960px modal.
8472// handleAjaxAddToCart is an inline PDP script so it lands well before
8473// Alpine boots; poll for each independently.
8474MICRO_BRIDGE.timer = setInterval(function () {
8475MICRO_BRIDGE.tries++;
8476var w = null;
8477try { w = MICRO_PDP.iframe && MICRO_PDP.iframe.contentWindow; } catch (e) { w = null; }
8478if (!w) return;
8479if (!MICRO_BRIDGE.addPatched) {
8480try {
8481if (typeof w.handleAjaxAddToCart === 'function') {
8482if (w.handleAjaxAddToCart.__ckWrapped) {
8483MICRO_BRIDGE.addPatched = true;
8484} else {
8485var origAdd = w.handleAjaxAddToCart;
8486var wrapped = function () {
8487try { armMicroBridge(); } catch (eA) { }
8488var r;
8489try {
8490r = origAdd.apply(this, arguments);
8491} catch (eR) {
8492// Do NOT disarm here: origAdd may have dispatched
8493// the fetch before throwing, so the add can still
8494// land. Resolve it against the server like any other.
8495try { console.warn('[SmartCart] handleAjaxAddToCart threw:', eR && eR.message); } catch (eW) { }
8496try { onMicroAddSettled(); } catch (eS) { }
8497throw eR;
8498}
8499// The precise completion signal -- see onMicroAddSettled.
8500try {
8501// NOTE: on this catalog handleAjaxAddToCart returns a
8502// BOOLEAN, so the thenable branch is the exception, not
8503// the rule -- onMicroAddSettled() must therefore poll.
8504if (r && typeof r.then === 'function') r.then(onMicroAddSettled, onMicroAddSettled);
8505else onMicroAddSettled();
8506} catch (eT) { }
8507return r;
8508};
8509wrapped.__ckWrapped = true;
8510// The ATC uses an inline onclick, which resolves the
8511// global at call time -- so replacing it is enough.
8512w.handleAjaxAddToCart = wrapped;
8513MICRO_BRIDGE.addPatched = true;
8514}
8515}
8516} catch (eP1) { }
8517}
8518if (!MICRO_BRIDGE.storePatched) {
8519var st = ckCartStore(w);
8520if (st) {
8521try {
8522if (!st.__ckMicroPatched) {
8523st.__ckMicroPatched = true;
8524var suppress = function () {
8525// The side cart must never render in the configurator.
8526try { st.drawerOpen = false; } catch (e1) { }
8527try {
8528if (w && w.document && w.document.body) w.document.body.style.overflow = '';
8529} catch (e2) { }
8530if (!MICRO_BRIDGE.armed) return;
8531try { onMicroPdpAddSucceeded(); } catch (e3) { }
8532};
8533st.openDrawer = suppress;
8534st.toggleDrawer = suppress;
8535}
8536MICRO_BRIDGE.storePatched = true;
8537} catch (eP2) { }
8538}
8539}
8540if (MICRO_BRIDGE.addPatched && MICRO_BRIDGE.storePatched) {
8541try { clearInterval(MICRO_BRIDGE.timer); } catch (e) { }
8542MICRO_BRIDGE.timer = null;
8543return;
8544}
8545if (MICRO_BRIDGE.tries > 120) { // ~6s
8546try { clearInterval(MICRO_BRIDGE.timer); } catch (e) { }
8547MICRO_BRIDGE.timer = null;
8548// Never give up silently -- that is how this class of bug hides.
8549try {
8550console.warn('[SmartCart] micro-PDP bridge gave up after 6s.',
8551'handleAjaxAddToCart patched:', MICRO_BRIDGE.addPatched,
8552'| cart store patched:', MICRO_BRIDGE.storePatched,
8553'| the count fallback still covers the add.');
8554} catch (eW) { }
8555}
8556}, 50);
8557}
8558function onMicroPdpAddSucceeded() {
8559if (MICRO_BRIDGE.handled) return; // one receipt per configurator open
8560MICRO_BRIDGE.handled = true;
8561MICRO_BRIDGE.armed = false;
8562try { if (MICRO_BRIDGE.loadTimer) clearTimeout(MICRO_BRIDGE.loadTimer); } catch (e) { }
8563// Recreate, in the parent's own URL, the receipt state the old
8564// target="_top" reload used to deliver -- without navigating.
8565try {
8566if (MICRO_PDP.returnUrl) {
8567var ru = new URL(MICRO_PDP.returnUrl, window.location.origin);
8568window.history.replaceState({}, document.title, ru.pathname + ru.search + ru.hash);
8569}
8570} catch (e) { }
8571// If more hard SKUs are queued the next configurator opens in ~350ms --
8572// don't pop the side cart underneath it. Reveal it only on the last one.
8573var moreQueued = false;
8574try { moreQueued = parseRemainingHardFromUrl().length > 0; } catch (e) { }
8575try { setLoadingCursor(false); } catch (e) { }
8576closeMicroPdp('added');
8577ckAfterCartMutation(!moreQueued);
8578// Existing receipt pipeline: added-state on the tile, tile swap, strip the
8579// params, and chain the next hard SKU if one is queued.
8580try { handleMicroAddedFromQuery(); } catch (e) { }
8581}
8582function openMicroPdp(productUrl, titleText, productId, remainingHardIds, progressDone, progressTotal) {
8583ensureMicroPdpModal();
8584MICRO_PDP.lastUrl = productUrl;
8585MICRO_PDP.lastProductId = String(productId || '');
8586// Progress defaults (if not passed)
8587var progFromUrl = getMicroProgressFromUrl();
8588if (typeof progressDone !== 'number') progressDone = progFromUrl.done;
8589if (typeof progressTotal !== 'number') progressTotal = progFromUrl.total;
8590MICRO_PDP.progressDone = progressDone || 0;
8591MICRO_PDP.progressTotal = progressTotal || 0;
8592// Paint header progress bar (only shows when total > 1)
8593renderMicroProgress(MICRO_PDP.progressDone, MICRO_PDP.progressTotal);
8594// Build a "receipt" return URL so the parent can show success + grey out tile + scroll
8595try {
8596var u = new URL(window.location.href);
8597// Set receipt params (overwrite if they already exist)
8598u.searchParams.set('microAdded', MICRO_PDP.lastProductId);
8599u.searchParams.set('microAddedName', titleText || '');
8600// Carry micro progress counts
8601if (progressTotal && progressTotal > 0) {
8602u.searchParams.set('microTotal', String(progressTotal));
8603u.searchParams.set('microDone', String(progressDone || 0));
8604} else {
8605u.searchParams.delete('microTotal');
8606u.searchParams.delete('microDone');
8607}
8608// If we are chaining multiple HARD SKUs, persist the remaining list
8609if (Array.isArray(remainingHardIds) && remainingHardIds.length) {
8610u.searchParams.set('remainingHard', remainingHardIds.join(','));
8611} else {
8612u.searchParams.delete('remainingHard');
8613}
8614// Keep anchor consistent so user lands near module area
8615u.hash = 'cart-kit-upsell';
8616MICRO_PDP.returnUrl = u.toString();
8617} catch (e) {
8618// Fallback if URL API fails
8619MICRO_PDP.returnUrl = window.location.href + (window.location.search ? '&' : '?')
8620+ 'microAdded=' + encodeURIComponent(MICRO_PDP.lastProductId)
8621+ '&microAddedName=' + encodeURIComponent(titleText || '')
8622+ '#cart-kit-upsell';
8623}
8624var titleEl = MICRO_PDP.overlay.querySelector('#ck-micropdp-title');
8625if (titleEl) titleEl.textContent = titleText || 'Customize item';
8626if (MICRO_PDP.loading) MICRO_PDP.loading.style.display = 'flex';
8627// Add a cache-buster so iframe refreshes if same SKU reopened
8628var url = ckSameOrigin(productUrl);
8629url += (url.indexOf('?') > -1 ? '&' : '?') + 'cartUpsellMicro=1&_=' + Date.now();
8630// Fresh receipt state + a count snapshot for the fallback in armMicroBridge.
8631MICRO_BRIDGE.handled = false;
8632MICRO_BRIDGE.countBefore = null;
8633MICRO_BRIDGE.countGen = (MICRO_BRIDGE.countGen || 0) + 1;
8634try {
8635// Tagged so a slow response from the PREVIOUS chain item (opens ~350ms
8636// apart) can't land here and produce a false-positive receipt.
8637(function (gen) {
8638ckCartCount().then(function (n) {
8639if (gen === MICRO_BRIDGE.countGen) MICRO_BRIDGE.countBefore = n;
8640});
8641})(MICRO_BRIDGE.countGen);
8642} catch (e) { }
8643MICRO_PDP.iframe.src = url;
8644MICRO_PDP.overlay.classList.add('is-open');
8645pushUpsellEvent('SmartCart_upsell_micro_pdp_open', { url: productUrl });
8646}
8647function closeMicroPdp(reason) {
8648if (!MICRO_PDP.overlay) return;
8649var added = (reason === 'added');
8650var ckWasArmed = MICRO_BRIDGE.armed;
8651try { resetMicroBridge(); } catch (e) { }
8652// Shopper closed while an add was in flight. Don't drop it on the floor --
8653// check the server before discarding the tile state and the chain.
8654if (!added && ckWasArmed) {
8655try { confirmMicroAddByCount('closed mid-add', 3); } catch (e) { }
8656}
8657try { setLoadingCursor(false); } catch (e) { }
8658try { MICRO_PDP.overlay.classList.remove('is-loading'); } catch (e) { }
8659MICRO_PDP.overlay.classList.remove('is-open');
8660// Stop network / reset iframe
8661if (MICRO_PDP.iframe) MICRO_PDP.iframe.src = 'about:blank';
8662if (MICRO_PDP.loading) MICRO_PDP.loading.style.display = 'none';
8663// Tell anything listening that the micro PDP closed
8664try {
8665document.dispatchEvent(new CustomEvent('smartcart:microPdpClosed', { detail: { reason: reason || 'close' } }));
8666} catch (e) { }
8667// CRITICAL: refresh cart UI and un-stick the SmartCart state
8668Promise.resolve()
8669.then(function () {
8670// Force the same refresh path we use after adds
8671// 1) Reload Magento customer-data cart section (minicart + cart counters)
8672// 2) Refresh cart DOM fragments (table/totals) if we’re on the cart page
8673// Legacy (pre-Zephyr) refresh path -- a no-op on Zephyr, kept for safety.
8674return reloadCustomerDataCart()
8675.then(refreshCartDomIfOnCartPage)
8676.then(function () {
8677// After an add the caller syncs; on a plain close re-sync
8678// anyway in case the shopper added inside the configurator
8679// by some other route.
8680if (!added) ckAfterCartMutation(false);
8681});
8682})
8683.then(function () {
8684// reset CTA back to normal (user cancelled hard sku)
8685try {
8686addBtn.classList.remove('is-loading');
8687// This tail resolves after handleMicroAddedFromQuery() has run.
8688// On a SUCCESSFUL add the receipt pipeline owns the status line,
8689// the progress bar and the tile grid -- buildGrid() in particular
8690// would re-render over replaceAddedTileWithNext()'s swap. Touch
8691// none of it unless the shopper cancelled.
8692if (!added) {
8693addBtn.disabled = false;
8694addBtn.textContent = 'Add to Cart';
8695if (statusEl) statusEl.textContent = 'No worries — you can keep shopping or add something else.';
8696resetAddProgressUI();
8697}
8698// Safe on BOTH paths and required on the added one: derives the
8699// CTA's disabled state and label from the live selection, so a
8700// completed add leaves it correctly disabled rather than enabled
8701// with nothing selected.
8702updatePriceUI();
8703} catch (e) { }
8704// Rebuild tiles so "already in cart" suppression is correct
8705if (!added) { try { buildGrid(); } catch (e) { } }
8706})
8707.catch(function () {
8708// Even if refresh fails, never leave the UI stuck
8709try {
8710addBtn.disabled = false;
8711addBtn.classList.remove('is-loading');
8712addBtn.textContent = 'Add to Cart';
8713} catch (e) { }
8714});
8715}
8716// ----------------------------------------------------
8717// 8) Selection state
8718// ----------------------------------------------------
8719var selectedPrimaryIds = new Set(); // stamp IDs OR notary product IDs (strings)
8720var selectedInkColors = new Set();
8721var inkChoiceMade = false;
8722// Track tiles that have been added via Micro PDP (so we can grey them out)
8723var microAddedIds = new Set();
8724var ckSeenTileIds = new Set();
8725// Defaults
8726var defaultIdx = (typeof persona.defaultPrimaryIndex === 'number' ? persona.defaultPrimaryIndex : 0);
8727if (useTiles && TILE_ITEMS[defaultIdx] && TILE_ITEMS[defaultIdx].productId) selectedPrimaryIds.add(String(TILE_ITEMS[defaultIdx].productId));
8728else if (STAMP_OPTIONS.length) selectedPrimaryIds.add(STAMP_OPTIONS[0].id);
8729// ----------------------------------------------------
8730// 9) Build grid (Accent or Notary)
8731// ----------------------------------------------------
8732var NOTARY_HYDRATED = new Map(); // productId -> {productId, productUrl, name, img, price}
8733function buildGrid() {
8734// Build cart signals for duplicate suppression (4C)
8735var __cartPaths = Object.create(null);
8736try { __cartPaths = getCartProductPathSet(); } catch (eCP0) { }
8737stampGrid.innerHTML = '';
8738// Accent stamps (unchanged)
8739if (!useTiles) {
8740for (var i = 0; i < STAMP_OPTIONS.length; i++) {
8741var opt = STAMP_OPTIONS[i];
8742var card = document.createElement('button');
8743card.type = 'button';
8744card.className = 'kit-stamp-card';
8745card.setAttribute('data-kit-primary', opt.id);
8746card.innerHTML =
8747'<div class="kit-stamp-chip">'
8748+ '<div class="kit-stamp-chip__circle">'
8749+ '<img class="kit-stamp-chip__image"'
8750+ ' src="' + opt.imgSrc + '"'
8751+ ' alt="' + (opt.imgAlt || '') + '"'
8752+ (opt.imgStyle ? ' style="' + opt.imgStyle + '"' : '')
8753+ '/>'
8754+ '</div>'
8755+ '</div>';
8756if (selectedPrimaryIds.has(opt.id)) card.classList.add('is-selected');
8757stampGrid.appendChild(card);
8758}
8759// Step 1: sync tile state store after grid render
8760try { TileStore.initFromDom(mount); } catch (e) { }
8761return;
8762}
8763// Notary product tiles (PDP fetched)
8764// Empty-state: if everything is already in cart (or filtered), show "All set" instead of a blank module.
8765if (!TILE_ITEMS || TILE_ITEMS.length === 0) {
8766// Nothing to build. (The old code here appended an "all set" note next
8767// to an undefined `tilesGrid` and tried to disable a #kit-add-btn that
8768// has never existed in this markup -- both silent no-ops.)
8769ckSyncModuleEmptyState();
8770return;
8771}
8772for (var j = 0; j < TILE_ITEMS.length; j++) {
8773(function (raw) {
8774var pid = String(raw.productId || '');
8775// Keep a reference so we can apply persona rules later (micro PDP gating, etc.)
8776TILE_ITEM_BY_ID[pid] = raw;
8777var card = document.createElement('button');
8778card.type = 'button';
8779card.className = 'kit-stamp-card kit-product-card';
8780card.setAttribute('data-kit-primary', pid);
8781card.setAttribute('data-sku', pid);
8782card.setAttribute('data-kit-url', raw.productUrl || '');
8783if (microAddedIds.has(pid)) {
8784card.classList.add('is-added');
8785}
8786card.innerHTML =
8787'<div class="kit-product-thumb-tile"><span style="font-size:12px;color:#888;">Loading…</span></div>'
8788+ '<div class="kit-product-meta">'
8789+ '<p class="kit-product-name">Loading…</p>'
8790+ '<div class="kit-product-price">—</div>'
8791+ '<div class="kit-product-sub">' + (persona.tileSubtext || '') + '</div>'
8792+ '</div>';
8793if (selectedPrimaryIds.has(pid)) card.classList.add('is-selected');
8794stampGrid.appendChild(card);
8795fetchPdpData(raw.productUrl).then(function (data) {
8796// Guard: this tile may have been swapped; ignore stale hydration
8797if (String(card.getAttribute('data-kit-primary') || '') !== String(pid)) return;
8798// Quality gate: this URL did not resolve to a real product page,
8799// so do NOT render whatever came back. Substitute an approved
8800// alternative the shopper doesn't already have, else drop the slot.
8801if (!ckIsUsableTileData(data)) {
8802try { console.warn('[SmartCart] tile ' + pid + ' did not resolve to a product page; substituting.'); } catch (eW) { }
8803var alt = ckPickFallbackTile();
8804if (alt) ckRepaintCardAs(card, alt);
8805else ckDropTile(card);
8806return;
8807}
8808var hydrated = {
8809productId: pid,
8810productUrl: raw.productUrl,
8811name: data.name || 'Notary add-on',
8812img: data.img || null,
8813price: (data.price != null ? Number(data.price) : null)
8814};
8815try { hydrated.sku = data.sku; } catch (eSk1) { }
8816NOTARY_HYDRATED.set(pid, hydrated);
8817var nameEl = card.querySelector('.kit-product-name');
8818var priceEl = card.querySelector('.kit-product-price');
8819var thumbEl = card.querySelector('.kit-product-thumb-tile');
8820if (nameEl) nameEl.textContent = hydrated.name;
8821if (priceEl) priceEl.textContent = (hydrated.price != null ? ('$' + hydrated.price.toFixed(2)) : 'See price in cart');
8822if (thumbEl) {
8823if (hydrated.img) thumbEl.innerHTML = '<img src="' + hydrated.img + '" alt="" />';
8824else thumbEl.innerHTML = '<span style="font-size:12px;color:#888;">No image</span>';
8825}
8826if (selectedPrimaryIds.has(pid)) updatePriceUI();
8827});
8828})(TILE_ITEMS[j]);
8829}
8830fireTilesImpressionOnce();
8831// Step 1: sync tile state store after grid render
8832try { TileStore.initFromDom(mount); } catch (e) { }
8833ckSyncModuleEmptyState();
8834}
8835// ----------------------------------------------------
8836// 10) Pricing
8837// ----------------------------------------------------
8838var priceLabelEl = upsell.querySelector('#kit-price-label');
8839var priceNewEl = upsell.querySelector('#kit-price-new');
8840function computeTotals() {
8841var inkCount = selectedInkColors.size;
8842if (!useTiles) {
8843var stampCount = selectedPrimaryIds.size;
8844return {
8845primaryCount: stampCount,
8846inkCount: inkCount,
8847total: (stampCount * STAMP_UNIT_PRICE) + (inkCount * INK_PRICE)
8848};
8849}
8850// Notary: sum selected notary items (real PDP price if fetched; otherwise 0 until loaded)
8851var total = 0;
8852var count = 0;
8853selectedPrimaryIds.forEach(function (pid) {
8854var it = NOTARY_HYDRATED.get(String(pid));
8855count += 1;
8856total += (it && it.price != null) ? Number(it.price) : 0;
8857});
8858total += inkCount * INK_PRICE;
8859return { primaryCount: count, inkCount: inkCount, total: total };
8860}
8861function updatePriceUI() {
8862var totals = computeTotals();
8863if (!totals.primaryCount) {
8864priceLabelEl.textContent = 'Total:';
8865priceNewEl.textContent = '$0.00';
8866addBtn.disabled = true;
8867addBtn.textContent = 'Add to Cart';
8868return;
8869}
8870priceLabelEl.textContent = 'Total:';
8871priceNewEl.textContent = '$' + totals.total.toFixed(2);
8872// CTA label: if any selected item requires configuration, guide user into Micro PDP.
8873var hasHard = false;
8874try {
8875Array.from(selectedPrimaryIds).some(function (pid) {
8876var raw = (TILE_ITEM_BY_ID && TILE_ITEM_BY_ID[String(pid)]) ? TILE_ITEM_BY_ID[String(pid)] : null;
8877if (requiresMicroPdp(pid, raw)) { hasHard = true; return true; }
8878return false;
8879});
8880} catch (e) { }
8881addBtn.textContent = hasHard ? 'Customize & Add' : 'Add to Cart';
8882addBtn.disabled = false;
8883}
8884// ----------------------------------------------------
8885// 11) Build + init
8886// ----------------------------------------------------
8887buildGrid();
8888updatePriceUI();
8889pushUpsellEvent('SmartCart_upsell_shown', { matched: activePersonaKey !== 'generic' });
8890// ----------------------------------------------------
8891// 12) Interactions
8892// ----------------------------------------------------
8893header.addEventListener('click', function () {
8894var isOpen = upsell.classList.toggle('cart-kit-upsell--open');
8895body.hidden = !isOpen;
8896});
8897// Primary selection (stamps or notary items)
8898stampGrid.addEventListener('click', function (evt) {
8899var card = evt.target.closest('.kit-stamp-card');
8900if (!card) return; if (card.classList && card.classList.contains('is-complete')) return;
8901var id = String(card.getAttribute('data-kit-primary') || '');
8902if (!id) return;
8903// ---- TRACK: tile click ----
8904try {
8905if (useTiles) {
8906// find tile position in TILE_ITEMS
8907var pos = -1;
8908var rawTile = null;
8909for (var i = 0; i < TILE_ITEMS.length; i++) {
8910if (String(TILE_ITEMS[i].productId) === String(id)) {
8911pos = i + 1;
8912rawTile = TILE_ITEMS[i];
8913break;
8914}
8915}
8916var hydrated = NOTARY_HYDRATED.get(String(id));
8917pushUpsellEvent('SmartCart_upsell_tile_click', {
8918persona: activePersonaKey || 'generic',
8919upsell_mode: 'tiles',
8920product_id: id,
8921position: pos,
8922product_name: hydrated && hydrated.name ? hydrated.name : undefined,
8923product_price: hydrated && hydrated.price != null ? hydrated.price : undefined,
8924product_url: rawTile && rawTile.productUrl ? rawTile.productUrl : undefined,
8925requires_micro: rawTile ? requiresMicroPdp(id, rawTile) : undefined,
8926already_selected: selectedPrimaryIds.has(id)
8927});
8928} else {
8929// Accent stamp click
8930pushUpsellEvent('SmartCart_upsell_accent_click', {
8931stamp_id: id
8932});
8933}
8934} catch (e) { }
8935// ----------------------------------------------------
8936// Tile tracking + micro PDP routing (tiles only)
8937// ----------------------------------------------------
8938var raw = null;
8939if (useTiles) {
8940for (var k = 0; k < TILE_ITEMS.length; k++) {
8941if (String(TILE_ITEMS[k].productId) === String(id)) { raw = TILE_ITEMS[k]; break; }
8942}
8943if (raw && raw.productUrl) {
8944// If we already know it's micro (tile config, hard override, or cached autodetect)
8945if (requiresMicroPdp(id, raw)) {
8946// Hard SKU: tile click should ONLY toggle selection.
8947// Do not open Micro PDP here; Micro PDP opens only from the main CTA.
8948}
8949// If we don't have PDP cached yet, fetch once and decide (airbag)
8950if (!PDP_CACHE.has(raw.productUrl)) {
8951evt.preventDefault();
8952evt.stopPropagation();
8953setLoadingCursor(true);
8954fetchPdpData(raw.productUrl).then(function (data) {
8955setLoadingCursor(false);
8956// If autodetect says "micro", open modal
8957if (data && data.requiresMicro) {
8958// NOTE: Do NOT auto-open Micro PDP on tile click.
8959// We only open Micro PDP when the user explicitly clicks the CTA to add.
8960pushUpsellEvent('SmartCart_upsell_tile_micro_detected', { productId: id, productUrl: raw.productUrl });
8961}
8962// Otherwise proceed with normal select toggle now
8963var willSelect = !selectedPrimaryIds.has(id);
8964if (willSelect) {
8965selectedPrimaryIds.add(id);
8966card.classList.add('is-selected');
8967} else {
8968selectedPrimaryIds.delete(id);
8969card.classList.remove('is-selected');
8970}
8971pushUpsellEvent('SmartCart_upsell_tile_' + (willSelect ? 'selected' : 'deselected'), {
8972productId: id,
8973selected_count: selectedPrimaryIds.size
8974});
8975updatePriceUI();
8976}).catch(function () {
8977setLoadingCursor(false);
8978// On failure, fall back to normal select so UX doesn't dead-end
8979var willSelect2 = !selectedPrimaryIds.has(id);
8980if (willSelect2) {
8981selectedPrimaryIds.add(id);
8982card.classList.add('is-selected');
8983} else {
8984selectedPrimaryIds.delete(id);
8985card.classList.remove('is-selected');
8986}
8987pushUpsellEvent('SmartCart_upsell_tile_select_fallback', {
8988productId: id,
8989// This path fires on DESELECT as well as select.
8990// Without this flag the GA4 mirror would count a
8991// deselect as a select_item.
8992selected: willSelect2,
8993selected_count: selectedPrimaryIds.size
8994});
8995updatePriceUI();
8996});
8997return;
8998}
8999}
9000}
9001var willSelect = !selectedPrimaryIds.has(id);
9002if (willSelect) {
9003selectedPrimaryIds.add(id);
9004card.classList.add('is-selected');
9005} else {
9006selectedPrimaryIds.delete(id);
9007card.classList.remove('is-selected');
9008}
9009if (useTiles) {
9010pushUpsellEvent('SmartCart_upsell_tile_' + (willSelect ? 'selected' : 'deselected'), {
9011productId: id,
9012selected_count: selectedPrimaryIds.size
9013});
9014} else {
9015pushUpsellEvent('SmartCart_upsell_accent_' + (willSelect ? 'selected' : 'deselected'), {
9016stamp_id: id,
9017selected_count: selectedPrimaryIds.size
9018});
9019}
9020updatePriceUI();
9021});
9022// Ink multi-select with "none"
9023if (swatchList) {
9024swatchList.addEventListener('click', function (evt) {
9025var swatch = evt.target.closest('.kit-swatch');
9026if (!swatch) return;
9027if (swatch.style.display === 'none') return;
9028var colorKey = swatch.getAttribute('data-kit-color');
9029inkChoiceMade = true;
9030if (colorKey === 'none') {
9031selectedInkColors.clear();
9032var all = swatchList.querySelectorAll('.kit-swatch');
9033for (var i = 0; i < all.length; i++) {
9034var s = all[i];
9035if (s.style.display === 'none') continue;
9036s.classList.toggle('is-selected', s.getAttribute('data-kit-color') === 'none');
9037}
9038} else {
9039if (selectedInkColors.has(colorKey)) selectedInkColors.delete(colorKey);
9040else selectedInkColors.add(colorKey);
9041var hasAnyInk = selectedInkColors.size > 0;
9042var all2 = swatchList.querySelectorAll('.kit-swatch');
9043for (var j = 0; j < all2.length; j++) {
9044var b = all2[j];
9045if (b.style.display === 'none') continue;
9046var key = b.getAttribute('data-kit-color');
9047if (key === 'none') b.classList.toggle('is-selected', !hasAnyInk);
9048else b.classList.toggle('is-selected', selectedInkColors.has(key));
9049}
9050}
9051updatePriceUI();
9052});
9053}
9054if (hideBtn) {
9055hideBtn.addEventListener('click', function (evt) {
9056evt.preventDefault();
9057upsell.classList.remove('cart-kit-upsell--open');
9058body.hidden = true;
9059upsell.scrollIntoView({ behavior: 'smooth', block: 'start' });
9060});
9061}
9062// ----------------------------------------------------
9063// 13)  Add-to-cart progress bar helpers (ONLY progress bar)
9064// ----------------------------------------------------
9065function resetAddProgressUI() {
9066if (progressWrap) progressWrap.style.display = 'none';
9067if (progressFill) progressFill.style.width = '0%';
9068if (progressPct) progressPct.textContent = '0%';
9069if (progressText) progressText.textContent = '';
9070}
9071function setAddProgress(done, total) {
9072if (!progressWrap || !progressFill) return;
9073var pct = total ? Math.round((done / total) * 100) : 0;
9074progressWrap.style.display = '';
9075progressFill.style.width = pct + '%';
9076if (progressPct) progressPct.textContent = pct + '%';
9077if (progressText) progressText.textContent = 'Adding ' + done + ' of ' + total + ' items…';
9078if (statusEl) statusEl.textContent = 'Added ' + done + ' of ' + total + ' items';
9079}
9080// ----------------------------------------------------
9081// 14) Add to Cart
9082// ----------------------------------------------------
9083function postToCart(params) {
9084return fetch('/checkout/cart/add', {
9085method: 'POST',
9086headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
9087body: params.toString(),
9088credentials: 'same-origin'
9089}).then(function (res) {
9090if (!res.ok) throw new Error('Add to cart failed: ' + res.status);
9091return res;
9092});
9093}
9094// ----------------------------------------------------
9095// Cart sync helpers (run after async adds so UI + tile logic are up-to-date)
9096// ----------------------------------------------------
9097function reloadCustomerDataCart() {
9098// Zephyr: /customer/section/load/ 404s on this build (no M2 customer-data)
9099// and the Alpine store is the real cart state. Skip the hop entirely.
9100if (ckCartStore(window)) return Promise.resolve();
9101return new Promise(function (resolve) {
9102try {
9103if (window.require) {
9104window.require(['Magento_Customer/js/customer-data'], function (customerData) {
9105try {
9106var r = customerData.reload(['cart'], true);
9107if (r && r.always) return r.always(function () { resolve(); });
9108resolve();
9109} catch (e) { resolve(); }
9110}, function () { resolve(); });
9111return;
9112}
9113fetch('/customer/section/load/?sections=cart&force_new_section_timestamp=true', {
9114credentials: 'same-origin'
9115}).then(function () { resolve(); }).catch(function () { resolve(); });
9116} catch (e) { resolve(); }
9117});
9118}
9119function refreshCartDomIfOnCartPage() {
9120// Zephyr: the cart page is Alpine-rendered. This swaps innerHTML on
9121// .cart-summary / [data-block="cart-summary"] if either matches, which
9122// would destroy live Alpine bindings in the Order Summary -- and it costs
9123// a full-page HTML fetch every time. ckAfterCartMutation() is the Zephyr
9124// path; this one is for the legacy theme only.
9125if (ckCartStore(window)) return Promise.resolve();
9126return new Promise(function (resolve) {
9127try {
9128// Cart page fragments we attempt to refresh. Selectors vary by theme.
9129var table = document.querySelector('#shopping-cart-table');
9130var totals = document.querySelector('#cart-totals')
9131|| document.querySelector('.cart-summary')
9132|| document.querySelector('.cart-totals')
9133|| document.querySelector('[data-block="cart-summary"]')
9134|| document.querySelector('[data-role="cart-totals"]');
9135if (!table && !totals) return resolve();
9136// Bust caches (some themes/CDNs will otherwise hand back stale totals)
9137var url = String(window.location.href || '').split('#')[0];
9138url += (url.indexOf('?') > -1 ? '&' : '?') + 'ckts=' + Date.now();
9139fetch(url, { credentials: 'same-origin', cache: 'no-store' })
9140.then(function (r) { return r.text(); })
9141.then(function (html) {
9142try {
9143var doc = new DOMParser().parseFromString(html, 'text/html');
9144var newTable = doc.querySelector('#shopping-cart-table');
9145if (table && newTable) table.innerHTML = newTable.innerHTML;
9146// Totals / summary fragment (try multiple fallbacks)
9147var newTotals = doc.querySelector('#cart-totals')
9148|| doc.querySelector('.cart-summary')
9149|| doc.querySelector('.cart-totals')
9150|| doc.querySelector('[data-block="cart-summary"]')
9151|| doc.querySelector('[data-role="cart-totals"]');
9152if (totals && newTotals) totals.innerHTML = newTotals.innerHTML;
9153try { document.body.dispatchEvent(new Event('contentUpdated')); } catch (e) { }
9154} catch (e2) { }
9155resolve();
9156})
9157.catch(function () { resolve(); });
9158} catch (e) { resolve(); }
9159});
9160}
9161function syncCartAfterAdds() {
9162return reloadCustomerDataCart()
9163.then(refreshCartDomIfOnCartPage)
9164.catch(function () { /* silent */ });
9165}
9166addBtn.addEventListener('click', function () {
9167if (!selectedPrimaryIds.size) {
9168alert(useTiles ? 'Please choose at least one add-on.' : 'Please choose at least one stamp design.');
9169return;
9170}
9171if (swatchList && !inkChoiceMade) {
9172alert('Please select an ink pad color or select you already have an ink pad');
9173return;
9174}
9175var formKeyInput = document.querySelector('input[name="form_key"]');
9176var formKey = formKeyInput ? formKeyInput.value : null;
9177if (!formKey) {
9178console.error('Cart kit upsell: form_key not found.');
9179alert('Sorry, we could not add the kit to your cart. Please refresh and try again.');
9180return;
9181}
9182// Baseline for the post-add reality check (see ckVerifySimpleAdd).
9183var ckSimpleCountBefore = null;
9184try { ckCartCount().then(function (n) { ckSimpleCountBefore = n; }); } catch (eCB) { }
9185var primaryArr = Array.from(selectedPrimaryIds);
9186var inkArr = Array.from(selectedInkColors);
9187// ----------------------------------------------------
9188// Hard SKU gating:
9189// - NEVER add "hard" products directly (they need options/uploads)
9190// - Only open Micro PDP when user clicks the CTA
9191// ----------------------------------------------------
9192var hardPrimaryArr = [];
9193var simplePrimaryArr = [];
9194primaryArr.forEach(function (pid) {
9195var rawTile = (typeof TILE_ITEM_BY_ID !== 'undefined' && TILE_ITEM_BY_ID) ? TILE_ITEM_BY_ID[String(pid)] : null;
9196if (requiresMicroPdp(pid, rawTile)) {
9197hardPrimaryArr.push(pid);
9198} else {
9199simplePrimaryArr.push(pid);
9200}
9201});
9202// Only count items we can actually add via POST chain (hard items are handled via Micro PDP)
9203var totalItemsCount = simplePrimaryArr.length + inkArr.length;
9204// If the user only selected hard items, we skip the POST chain and jump straight to Micro PDP.
9205var willOpenMicroAfterChain = hardPrimaryArr.length > 0;
9206pushUpsellEvent('SmartCart_upsell_add_clicked', {
9207primary_count: simplePrimaryArr.length,
9208hard_primary_count: hardPrimaryArr.length,
9209ink_count: inkArr.length,
9210total_count: totalItemsCount
9211});
9212var completedCount = 0;
9213// If there's nothing to POST (only hard items), open Micro PDP immediately.
9214if (totalItemsCount === 0 && willOpenMicroAfterChain) {
9215var firstHard = hardPrimaryArr[0];
9216var rawFirst = TILE_ITEM_BY_ID[String(firstHard)] || {};
9217var hydratedFirst = NOTARY_HYDRATED.get(String(firstHard));
9218var titleFirst = (hydratedFirst && hydratedFirst.name) ? hydratedFirst.name : (rawFirst.title || rawFirst.name || 'Customize item');
9219// Seed Micro PDP progress for HARD-only flows
9220// done = 0 (nothing added yet), total = number of hard items selected
9221var progDone0 = 0;
9222var progTotal0 = hardPrimaryArr.length;
9223openMicroPdp(rawFirst.productUrl, titleFirst, firstHard, hardPrimaryArr.slice(1), progDone0, progTotal0);
9224return;
9225}
9226addBtn.disabled = true;
9227addBtn.classList.add('is-loading');
9228addBtn.textContent = totalItemsCount > 1 ? ('Adding ' + totalItemsCount + ' items…') : 'Adding item…';
9229if (statusEl) statusEl.textContent = totalItemsCount > 1 ? ('Adding ' + totalItemsCount + ' items to your cart…') : 'Adding item to your cart…';
9230//  show + init progress bar
9231setAddProgress(0, totalItemsCount);
9232var chain = Promise.resolve();
9233// Add primary selections
9234simplePrimaryArr.forEach(function (id) {
9235chain = chain.then(function () {
9236var params = new URLSearchParams();
9237params.append('form_key', formKey);
9238if (!useTiles) {
9239params.append('product', id);
9240params.append('qty', '1');
9241params.append('options[' + ACCENT_SIZE_OPTION_ID + '][]', ACCENT_SIZE_VALUE_ID);
9242params.append('options[' + ACCENT_INK_OPTION_ID + ']', ACCENT_INK_NO_THANKS_VALUE_ID);
9243} else {
9244params.append('product', id);
9245params.append('qty', '1');
9246}
9247return postToCart(params).then(function () {
9248completedCount += 1;
9249setAddProgress(completedCount, totalItemsCount);
9250});
9251});
9252});
9253// Add ink pads
9254inkArr.forEach(function (colorKey) {
9255chain = chain.then(function () {
9256var colorValueId = INK_COLOR_VALUES[colorKey];
9257if (!colorValueId) throw new Error('Unknown ink color key: ' + colorKey);
9258var inkParams = new URLSearchParams();
9259inkParams.append('form_key', formKey);
9260inkParams.append('product', INK_PARENT_ID);
9261inkParams.append('qty', '1');
9262inkParams.append('options[' + INK_SIZE_OPTION_ID + ']', INK_SIZE_VALUE_ID);
9263inkParams.append('options[' + INK_COLOR_OPTION_ID + ']', colorValueId);
9264return postToCart(inkParams).then(function () {
9265completedCount += 1;
9266setAddProgress(completedCount, totalItemsCount);
9267});
9268});
9269});
9270chain
9271.then(function () {
9272pushUpsellEvent('SmartCart_upsell_added', {
9273// Per-product ids drive the GA4 add_to_cart mirror.
9274product_ids: simplePrimaryArr.map(function (p) { return String(p); }),
9275ink_colors: inkArr.slice(),
9276primary_count: simplePrimaryArr.length,
9277hard_primary_count: hardPrimaryArr.length,
9278ink_count: inkArr.length,
9279total_count: totalItemsCount
9280});
9281// CRITICAL: sync cart state + DOM BEFORE swap/dedupe logic runs
9282return syncCartAfterAdds().then(function () {
9283if (willOpenMicroAfterChain) {
9284// The simple items are already in the cart but the configurator
9285// is about to take over the screen -- push them into both cart
9286// trees now, or the drawer, badge and cart column sit stale for
9287// as long as the shopper spends configuring.
9288try { ckAfterCartMutation(false); } catch (eZ) { }
9289// Mark the items we successfully added (simple + ink) without reloading.
9290try {
9291simplePrimaryArr.forEach(function (pid) {
9292var el = stampGrid && stampGrid.querySelector('[data-kit-primary="' + pid + '"]');
9293if (el) {
9294el.classList.remove('is-selected');
9295el.classList.add('is-added');
9296}
9297try { microAddedIds.add(String(pid)); } catch (eM0) { }
9298try { selectedPrimaryIds.delete(pid); } catch (eD0) { }
9299//  Swap AFTER cart sync so in-cart suppression works
9300setTimeout(function () { try { replaceAddedTileWithNext(pid); } catch (eR0) { } }, 800);
9301});
9302} catch (e) { }
9303try {
9304// Clear ink selections after adding ink pads
9305selectedInkColors.clear();
9306inkChoiceMade = false;
9307if (swatchList) {
9308var allSw = swatchList.querySelectorAll('.kit-swatch');
9309for (var s = 0; s < allSw.length; s++) allSw[s].classList.remove('is-selected');
9310}
9311} catch (e2) { }
9312addBtn.disabled = false;
9313addBtn.classList.remove('is-loading');
9314addBtn.textContent = 'Customize & Add';
9315if (statusEl) statusEl.textContent = 'Added to cart. Next, customize your selected item.';
9316// Open Micro PDP for the first hard item selected
9317var firstHard2 = hardPrimaryArr[0];
9318var rawFirst2 = TILE_ITEM_BY_ID[String(firstHard2)] || {};
9319var hydratedFirst2 = NOTARY_HYDRATED.get(String(firstHard2));
9320var titleFirst2 = (hydratedFirst2 && hydratedFirst2.name) ? hydratedFirst2.name : (rawFirst2.title || rawFirst2.name || 'Customize item');
9321// Seed Micro PDP progress for mixed flows:
9322// done = count of items already added via POST chain (simple + ink)
9323// total = done + remaining hard items to configure
9324var progDone1 = totalItemsCount;
9325var progTotal1 = totalItemsCount + hardPrimaryArr.length;
9326openMicroPdp(rawFirst2.productUrl, titleFirst2, firstHard2, hardPrimaryArr.slice(1), progDone1, progTotal1);
9327pushUpsellEvent('SmartCart_upsell_micro_pdp_open_from_cta', {
9328productId: firstHard2,
9329remaining_hard_count: hardPrimaryArr.length
9330});
9331// Keep progress visible but "complete" the chain we ran
9332setAddProgress(totalItemsCount, totalItemsCount);
9333return; // prevent redirect
9334}
9335// If we are on the full cart page, do a hard refresh so Magento totals/summary
9336// update correctly (these often won't update reliably via partial DOM swaps).
9337// We preserve the success-state UX via ?accentAdded=1.
9338// ----------------------------------------------------
9339// Zephyr: the rest of this site mutates the cart in place, so do
9340// the same here instead of reloading. ckAfterCartMutation refreshes
9341// BOTH cart trees (drawer store + cart-page component) and pops the
9342// side cart. It returns false only when the Zephyr store is absent
9343// (legacy theme) -- then, and only then, fall back to the old
9344// ?accentAdded=1 reload. Returning true lets the in-place success UX
9345// below finally run; that block was unreachable for as long as this
9346// branch always reloaded.
9347// ----------------------------------------------------
9348try {
9349var onCartPage = document.body.classList.contains('checkout-cart-index')
9350|| /\/checkout\/cart(\/|$)/.test(window.location.pathname)
9351|| !!document.querySelector('[data-cart-item-id]')
9352|| !!document.querySelector('#shopping-cart-table')
9353|| !!document.querySelector('form#form-validate')
9354|| !!document.querySelector('.cart-container')
9355|| !!document.querySelector('[data-block="cart-summary"]')
9356|| !!document.querySelector('.cart-summary');
9357if (onCartPage && !ckAfterCartMutation(true)) {
9358try {
9359var u2 = new URL(window.location.href);
9360u2.searchParams.set('accentAdded', '1');
9361window.location.href = u2.toString();
9362} catch (eU2) {
9363window.location.reload();
9364}
9365return;
9366}
9367} catch (eCPR) { }
9368// Reality check: confirm the cart actually changed before the
9369// success UX below is allowed to stand.
9370try { ckVerifySimpleAdd(ckSimpleCountBefore); } catch (eVS) { }
9371// ----------------------------------------------------
9372// No hard items left → keep the module live and refresh tiles in-place (no page reload)
9373// ----------------------------------------------------
9374try {
9375// Mark added + swap tiles (stable slots)
9376simplePrimaryArr.forEach(function (pid) {
9377try { microAddedIds.add(String(pid)); } catch (eM) { }
9378try {
9379var el2 = stampGrid && stampGrid.querySelector('[data-kit-primary="' + pid + '"]');
9380if (el2) {
9381el2.classList.remove('is-selected');
9382el2.classList.add('is-added');
9383}
9384} catch (eC) { }
9385try { selectedPrimaryIds.delete(pid); } catch (eD) { }
9386//  Swap AFTER cart sync so in-cart suppression works
9387setTimeout(function () { try { replaceAddedTileWithNext(pid); } catch (eR) { } }, 800);
9388});
9389} catch (eS3) { }
9390try {
9391// Clear ink selections after adding ink pads
9392selectedInkColors.clear();
9393inkChoiceMade = false;
9394if (swatchList) {
9395var allSw3 = swatchList.querySelectorAll('.kit-swatch');
9396for (var s3 = 0; s3 < allSw3.length; s3++) allSw3[s3].classList.remove('is-selected');
9397// restore "none" default if visible
9398var noneBtn3 = swatchList.querySelector('.kit-swatch[data-kit-color="none"]');
9399if (noneBtn3 && noneBtn3.style.display !== 'none') {
9400noneBtn3.classList.add('is-selected');
9401inkChoiceMade = true;
9402}
9403}
9404} catch (eS4) { }
9405// Success toast
9406try {
9407var toast2 = document.getElementById('kit-success-toast');
9408if (toast2) {
9409var msg = (totalItemsCount > 1) ? ('✓ Added ' + totalItemsCount + ' items to cart') : '✓ Added to cart';
9410toast2.textContent = msg;
9411toast2.classList.add('is-visible');
9412setTimeout(function () { toast2.classList.remove('is-visible'); }, 3000);
9413}
9414} catch (eT2) { }
9415// Reset CTA + progress
9416addBtn.disabled = false;
9417addBtn.classList.remove('is-loading');
9418addBtn.textContent = 'Add to Cart';
9419if (statusEl) statusEl.textContent = 'Added to cart. Want to add another?';
9420resetAddProgressUI();
9421updatePriceUI();
9422// Keep module open to encourage additional add-ons
9423upsell.classList.add('cart-kit-upsell--open');
9424body.hidden = false;
9425});
9426})
9427.catch(function (err) {
9428console.error('Cart kit upsell error:', err);
9429var ckErrMsg = 'Sorry, we could not add the kit to your cart. Please try again.';
9430try {
9431var ckFlash = (window.Alpine && typeof window.Alpine.store === 'function')
9432? window.Alpine.store('flash') : null;
9433if (ckFlash && typeof ckFlash.show === 'function') ckFlash.show('error', ckErrMsg);
9434else alert(ckErrMsg);
9435} catch (eFl) { alert(ckErrMsg); }
9436addBtn.disabled = false;
9437addBtn.classList.remove('is-loading');
9438addBtn.textContent = 'Add to Cart';
9439if (statusEl) statusEl.textContent = '';
9440// hide progress bar on error
9441resetAddProgressUI();
9442});
9443});
9444// ----------------------------------------------------
9445// Cart sync helpers (run after async adds so UI + tile logic are up-to-date)
9446// ----------------------------------------------------
9447// 15) Success + Add More
9448// ----------------------------------------------------
9449function resetSelections() {
9450selectedPrimaryIds.clear();
9451selectedInkColors.clear();
9452inkChoiceMade = false;
9453var defaultIdx = (typeof persona.defaultPrimaryIndex === 'number' ? persona.defaultPrimaryIndex : 0);
9454if (useTiles && TILE_ITEMS[defaultIdx] && TILE_ITEMS[defaultIdx].productId) selectedPrimaryIds.add(String(TILE_ITEMS[defaultIdx].productId));
9455else if (STAMP_OPTIONS.length) selectedPrimaryIds.add(STAMP_OPTIONS[0].id);
9456buildGrid();
9457if (swatchList) {
9458var sw = swatchList.querySelectorAll('.kit-swatch');
9459for (var i = 0; i < sw.length; i++) sw[i].classList.remove('is-selected');
9460// Default to "none" (I have an ink pad) so user isn't forced into an alert loop
9461var noneBtn = swatchList.querySelector('.kit-swatch[data-kit-color="none"]');
9462if (noneBtn && noneBtn.style.display !== 'none') {
9463noneBtn.classList.add('is-selected');
9464// keep selectedInkColors empty, but mark a choice as made
9465inkChoiceMade = true;
9466}
9467}
9468addBtn.disabled = false;
9469addBtn.classList.remove('is-loading');
9470addBtn.textContent = 'Add to Cart';
9471if (statusEl) statusEl.textContent = '';
9472resetAddProgressUI();
9473updatePriceUI();
9474}
9475function handleAddMoreFromQuery() {
9476if (!accentAddMoreQS) return;
9477upsell.classList.add('cart-kit-upsell--open');
9478body.hidden = false;
9479resetSelections();
9480try {
9481var href = window.location.href;
9482var clean = href
9483.replace(/([?&])accentAdded=1(?=(&|#|$))/, '$1')
9484.replace(/[?&]#/, '#')
9485.replace(/[?&]$/, '');
9486window.history.replaceState({}, document.title, clean);
9487} catch (e) { }
9488}
9489function applySuccessStateFromQuery() {
9490if (!accentAddedQS) return;
9491var headerText = upsell.querySelector('#cart-kit-header-text');
9492if (headerText) {
9493headerText.innerHTML =
9494'<span class="cart-kit-upsell__success-inline">'
9495+ '✓ Items added to cart!'
9496+ ' <span style="color:#000; text-decoration: underline;">Add more?</span>'
9497+ '</span>';
9498}
9499upsell.classList.remove('cart-kit-upsell--open');
9500body.hidden = true;
9501try {
9502var clean2 = window.location.href.replace(/([?&])accentAdded=1(&|$)/, '$1').replace(/[?&]$/, '');
9503window.history.replaceState({}, document.title, clean2);
9504} catch (e) { }
9505}
9506// ----------------------------------------------------
9507// Micro PDP queue helpers (supports 2+ hard SKUs)
9508// ----------------------------------------------------
9509function getTileInfoByProductId(pid) {
9510pid = String(pid || '').trim();
9511if (!pid) return null;
9512var raw = (typeof TILE_ITEM_BY_ID !== 'undefined' && TILE_ITEM_BY_ID) ? (TILE_ITEM_BY_ID[pid] || TILE_ITEM_BY_ID[String(pid)]) : null;
9513if (!raw) return null;
9514var hydrated = (typeof NOTARY_HYDRATED !== 'undefined' && NOTARY_HYDRATED) ? NOTARY_HYDRATED.get(pid) : null;
9515var title = (hydrated && hydrated.name) ? hydrated.name : (raw.title || raw.name || 'Customize item');
9516var url = raw.productUrl || raw.url || raw.href || null;
9517if (!url) return null;
9518return { productId: pid, title: title, url: url };
9519}
9520function parseRemainingHardFromUrl() {
9521try {
9522var u = new URL(window.location.href);
9523var val = (u.searchParams.get('remainingHard') || '').trim();
9524if (!val) return [];
9525return val.split(',').map(function (s) { return String(s || '').trim(); }).filter(Boolean);
9526} catch (e) {
9527return [];
9528}
9529}
9530function stripRemainingHardParam() {
9531try {
9532var u = new URL(window.location.href);
9533u.searchParams.delete('remainingHard');
9534u.searchParams.delete('microTotal');
9535u.searchParams.delete('microDone');
9536window.history.replaceState({}, document.title, u.toString());
9537} catch (e) { }
9538}
9539function openNextHardFromRemaining() {
9540var remaining = parseRemainingHardFromUrl();
9541if (!remaining.length) {
9542stripRemainingHardParam();
9543return false;
9544}
9545var nextId = remaining.shift();
9546var info = getTileInfoByProductId(nextId);
9547if (!info) {
9548// If we can't resolve the next item, stop chaining to avoid a loop
9549stripRemainingHardParam();
9550return false;
9551}
9552// Rebuild the remainingHard param (after removing the one we're about to open)
9553try {
9554var u = new URL(window.location.href);
9555if (remaining.length) u.searchParams.set('remainingHard', remaining.join(','));
9556else u.searchParams.delete('remainingHard');
9557window.history.replaceState({}, document.title, u.toString());
9558} catch (e) { }
9559// Small status hint before opening the next configurator
9560if (statusEl) statusEl.textContent = 'Next: customize ' + info.title + '.';
9561// Open next Micro PDP
9562var prog = getMicroProgressFromUrl();
9563openMicroPdp(info.url, info.title, info.productId, remaining, prog.done, prog.total);
9564return true;
9565}
9566// --- SmartCart: ensure cart + tiles refresh when micro-PDP closes ---
9567(function bindMicroPdpCloseRefresh() {
9568var MODAL_SEL = '#smartcartMicroPdp, .smartcart-micro-pdp, [data-smartcart-micro-pdp]';
9569var CLOSE_SEL = '[data-smartcart-close], .smartcart-close, .close, [aria-label="Close"]';
9570var OVERLAY_SEL = '.smartcart-overlay, .modal-backdrop, [data-smartcart-overlay]';
9571// You likely already have these functions; map to your actual names:
9572var refresh = window.smartcartRefresh || window.SmartCartRefresh || window.renderSmartCart || null;
9573// If you *don’t* have a single refresh entrypoint, use whatever you normally call after add-to-cart:
9574function hardRefreshFallback() {
9575try {
9576if (typeof refresh === 'function') return refresh();
9577// Fallback: call the same method you use after cart update
9578if (window.SmartCart && typeof window.SmartCart.render === 'function') return window.SmartCart.render();
9579if (window.smartcart && typeof window.smartcart.render === 'function') return window.smartcart.render();
9580} catch (e) {
9581console.warn('[SmartCart] refresh on modal close failed:', e);
9582}
9583}
9584// Debounce so multiple close signals (button + overlay) don’t double-trigger
9585var t;
9586function refreshSoon() {
9587clearTimeout(t);
9588t = setTimeout(function () {
9589hardRefreshFallback();
9590}, 150);
9591}
9592function onDocClick(e) {
9593var modal = document.querySelector(MODAL_SEL);
9594if (!modal) return;
9595var isClose = e.target.closest && e.target.closest(CLOSE_SEL);
9596var isOverlay = e.target.closest && e.target.closest(OVERLAY_SEL);
9597if (isClose || isOverlay) {
9598// Let the DOM close animation finish, then refresh
9599refreshSoon();
9600}
9601}
9602function onEsc(e) {
9603if (e.key === 'Escape' || e.keyCode === 27) refreshSoon();
9604}
9605document.addEventListener('click', onDocClick, true);
9606document.addEventListener('keydown', onEsc, true);
9607// Optional: if you dispatch a custom "microPdpClosed" event already, hook that too:
9608document.addEventListener('smartcart:microPdpClosed', refreshSoon);
9609})();
9610// ----------------------------------------------------
9611// 4A) Replace an "Added" product tile with the next best recommendation
9612//     (keeps the grid fresh without requiring a full page reload)
9613// ----------------------------------------------------
9614function convertTileToCompleteState(card, totalCount) {
9615if (!card) return;
9616// Stable-slot swap: fade this card out before repopulating
9617try { card.classList.add('ck-swapping'); } catch (eFade0) { }
9618try {
9619card.classList.remove('is-selected');
9620card.classList.remove('is-added');
9621card.classList.add('is-complete');
9622// prevent future selection toggles
9623card.setAttribute('aria-disabled', 'true');
9624card.innerHTML =
9625'<div class="ck-tile-complete">'
9626+ '<div class="ck-tile-complete__icon">✓</div>'
9627+ '<div class="ck-tile-complete__title">All set</div>'
9628+ '<div class="ck-tile-complete__subtext">You\'ve added all recommended items for this order.</div>'
9629+ '</div>';
9630} catch (e) { }
9631}
9632function replaceAddedTileWithNext(addedPid) {
9633try {
9634if (!useTiles || !stampGrid) return;
9635addedPid = String(addedPid || '').trim();
9636if (!addedPid) return;
9637var card = stampGrid.querySelector('.kit-product-card[data-kit-primary="' + addedPid + '"]');
9638if (!card) return;
9639// Build an ordered pool (highest relevance first)
9640var ordered = pickTopTilesForDisplay(persona.items, cartTextForScoring, 999, allowExperimentalNow).tiles;
9641// Build exclusion map
9642var exclude = Object.create(null);
9643// Exclude currently visible tiles
9644var visible = stampGrid.querySelectorAll('.kit-product-card[data-kit-primary]');
9645for (var i = 0; i < visible.length; i++) {
9646var v = String(visible[i].getAttribute('data-kit-primary') || '').trim();
9647if (v) exclude[v] = true;
9648}
9649// Exclude anything already added (session + micro returns)
9650try {
9651if (microAddedIds && microAddedIds.forEach) {
9652microAddedIds.forEach(function (id) { exclude[String(id)] = true; });
9653}
9654} catch (eAdded) { }
9655// Exclude anything already shown during this session (prevents repeats when pool is small)
9656try {
9657if (ckSeenTileIds && ckSeenTileIds.forEach) {
9658ckSeenTileIds.forEach(function (id) { exclude[String(id)] = true; });
9659}
9660} catch (eSeen) { }
9661// Exclude queued HARD SKUs still pending configuration
9662try {
9663var queued = parseRemainingHardFromUrl();
9664for (var q = 0; q < queued.length; q++) {
9665var qid = String(queued[q] || '').trim();
9666if (qid) exclude[qid] = true;
9667}
9668} catch (eQ) { }
9669// Exclude anything currently selected (so swaps don't "steal" into a tile the user already picked)
9670try {
9671if (selectedPrimaryIds && selectedPrimaryIds.forEach) {
9672selectedPrimaryIds.forEach(function (sid) {
9673var s = String(sid || '').trim();
9674if (s) exclude[s] = true;
9675});
9676}
9677} catch (eSel) { }
9678// Exclude anything already in the cart (prefer URL-path match; fallback to title match)
9679var cartPaths = Object.create(null);
9680var cartFullLower = '';
9681try { cartPaths = getCartProductPathSet(); } catch (eCP) { }
9682try { cartFullLower = getCartFullTextLower(); } catch (eCT) { }
9683// Find next eligible item
9684var next = null;
9685for (var k = 0; k < ordered.length; k++) {
9686var it = ordered[k];
9687var pid = String(it && (it.productId || it.id || it.product || it.primaryId) || '').trim();
9688if (!pid || exclude[pid]) continue;
9689// URL-path in-cart exclusion
9690var pth = '';
9691try { pth = normalizeUrlPathForCompare(it.productUrl || it.url || ''); } catch (eNP) { }
9692if (pth && cartPaths[pth]) continue;
9693// Title fallback exclusion
9694var nm = String(it.name || it.title || '').toLowerCase();
9695if (nm && cartFullLower && cartFullLower.indexOf(nm) !== -1) continue;
9696next = it;
9697break;
9698}
9699if (!next) {
9700// Pool exhausted -- offer an approved alternative before giving up.
9701var altNext = ckPickFallbackTile();
9702if (altNext) {
9703try { card.classList.remove('ck-swapping'); } catch (eFA) { }
9704ckRepaintCardAs(card, altNext);
9705return;
9706}
9707// Nothing left → convert this tile to completion tile
9708try { convertTileToCompleteState(card); } catch (eDone) { }
9709setTimeout(function () { try { card.classList.remove('ck-swapping'); } catch (eFadeX) { } }, 160);
9710return;
9711}
9712// Swap this card to the new item
9713var nextId = String(next.productId || next.id || next.product || next.primaryId || '').trim();
9714var nextUrl = next.productUrl || next.url || '';
9715var nextHard = !!requiresMicroPdp(nextId, next);
9716// Mark seen immediately so async hydration can't cause repeats
9717try { if (ckSeenTileIds) ckSeenTileIds.add(String(nextId)); } catch (eS) { }
9718// Fade out before repopulating (stable slot)
9719try { card.classList.add('ck-swapping'); } catch (eFade0) { }
9720// Reset card state + dataset
9721try {
9722card.classList.remove('is-added', 'is-selected', 'is-complete');
9723card.removeAttribute('aria-disabled');
9724} catch (eState) { }
9725card.setAttribute('data-kit-primary', nextId);
9726card.setAttribute('data-kit-url', nextUrl);
9727card.setAttribute('data-kit-hard', nextHard ? '1' : '0');
9728// Paint skeleton while hydration finishes (DOM structure used by buildGrid)
9729var titleEl = card.querySelector('.kit-product-name');
9730var priceEl = card.querySelector('.kit-product-price');
9731var thumbTile = card.querySelector('.kit-product-thumb-tile');
9732if (titleEl) titleEl.textContent = (next.title || next.name || 'Loading…');
9733if (priceEl) priceEl.textContent = '';
9734// Reset the thumb; we'll hydrate with a real image after fetchPdpData
9735if (thumbTile) {
9736var imgSrc = (next.image || next.img || '');
9737if (imgSrc) {
9738var altText = String(next.title || next.name || '').replace(/"/g, '&quot;');
9739thumbTile.innerHTML = '<img src="' + imgSrc + '" alt="' + altText + '">';
9740} else {
9741thumbTile.innerHTML = '<span class="kit-thumb-loading">Loading...</span>';
9742}
9743}
9744// Fade back in once the new tile has been painted
9745setTimeout(function () {
9746try { card.classList.remove('ck-swapping'); } catch (eFade1) { }
9747}, 160);
9748// Record the swapped-in product's strategy attributes (role,
9749// friction, experimental) in an ANALYTICS-ONLY map.
9750//
9751// Deliberately NOT written to TILE_ITEM_BY_ID: that map feeds
9752// requiresMicroPdp(), so registering swapped tiles there would
9753// flip the CTA to 'Customize & Add' and reroute the add through
9754// the micro-PDP. That may be the correct behaviour -- a bare
9755// product=id&qty=1 POST of a required-options SKU is wrong -- but
9756// it is an add-flow change and does not belong in an analytics
9757// patch. Tracked separately.
9758try { CK_GA_TILE_META[String(nextId)] = next; } catch (eReg) { }
9759// Hydrate: fetch canonical name/price/image (guard against stale async writes)
9760(function (el, expectedId, raw) {
9761try {
9762var url = raw.productUrl || raw.url || '';
9763var p = fetchPdpData(url);
9764if (!p || !p.then) return;
9765p.then(function (data) {
9766try {
9767// Guard a stale async write: the slot may have been
9768// swapped again while this fetch was in flight.
9769if (!el || el.getAttribute('data-kit-primary') !== String(expectedId)) return;
9770var tEl = el.querySelector('.kit-product-name');
9771var prEl = el.querySelector('.kit-product-price');
9772var thEl = el.querySelector('.kit-product-thumb-tile');
9773if (tEl && data.name) tEl.textContent = data.name;
9774// Cache the canonical name/price so analytics and
9775// the price UI see the same product the shopper does.
9776// Cache the canonical name/price. Gated by the same
9777// quality check buildGrid and ckRepaintCardAs use --
9778// a URL that scrapes to 'Create New Customer Account'
9779// must not reach the cache, because it feeds the
9780// micro-PDP modal title and the displayed total.
9781// Also normalises price the way the other two sites do.
9782try {
9783if (typeof NOTARY_HYDRATED !== 'undefined' && NOTARY_HYDRATED
9784&& ckIsUsableTileData(data)) {
9785NOTARY_HYDRATED.set(String(expectedId), {
9786productId: String(expectedId),
9787sku: data.sku,
9788productUrl: (raw && (raw.productUrl || raw.url)) || '',
9789name: data.name || '',
9790img: data.img || '',
9791price: (data.price != null ? Number(data.price) : null)
9792});
9793}
9794} catch (eHyd) { }
9795// A swapped tile is newly visible and was in no
9796// view_item_list, so its funnel would otherwise start
9797// at select_item. Give it its own one-item impression.
9798// attempts = 0 -> fire now; the name is already cached.
9799try {
9800ckGaFireListWhenHydrated(
9801[String(expectedId)], 0,
9802(CK_GA4.LIST_PREFIX + ' - ' + ckGaListId() + ' (swap)').slice(0, 100)
9803);
9804} catch (eSwapGa) { }
9805if (prEl) {
9806prEl.textContent = (data.price != null ? ('$' + Number(data.price).toFixed(2)) : 'See price in cart');
9807}
9808if (thEl) {
9809if (data.img) {
9810var alt2 = String(data.name || '').replace(/"/g, '&quot;');
9811thEl.innerHTML = '<img src="' + data.img + '" alt="' + alt2 + '">';
9812} else {
9813thEl.innerHTML = '<span style="font-size:12px;color:#888;">No image</span>';
9814}
9815}
9816} catch (eH) { }
9817}).catch(function () { });
9818} catch (eF) { }
9819})(card, nextId, next);
9820} catch (e) {
9821// fail silently
9822}
9823}
9824function handleMicroAddedFromQuery() {
9825// The micro-PDP no longer reloads the page, so microAddedQS /
9826// microAddedNameQS -- captured from window.location.search back at
9827// DOMContentLoaded -- are stale by definition. onMicroPdpAddSucceeded()
9828// writes the receipt into the URL via history.replaceState, so re-read
9829// it live here. Still correct for the legacy reload path.
9830try {
9831var ckLive = new URL(window.location.href).searchParams;
9832microAddedQS = ckLive.get('microAdded');
9833microAddedNameQS = ckLive.get('microAddedName');
9834} catch (eLive) { }
9835if (!microAddedQS) return;
9836var pid = String(microAddedQS || '').trim();
9837if (!pid) return;
9838// Mark as added so tile greys out
9839microAddedIds.add(pid);
9840// Remove from current selections (prevents stale totals when tile is swapped)
9841try { selectedPrimaryIds.delete(pid); } catch (eSel0) { }
9842// Micro progress: user just completed one configurator
9843var progNow = bumpMicroDoneInUrl();
9844MICRO_PDP.progressDone = progNow.done;
9845MICRO_PDP.progressTotal = progNow.total;
9846renderMicroProgress(MICRO_PDP.progressDone, MICRO_PDP.progressTotal);
9847// Hard SKUs add inside the micro-PDP iframe, so they never reach the
9848// POST chain above and were invisible to analytics. Fire the
9849// per-product event here. Dedupe on pid + completed-count: the
9850// receipt params can survive a re-entry of this handler.
9851try {
9852// Dedupe on a short time window, NOT on progNow.done:
9853// bumpMicroDoneInUrl() increments on every call, so a re-entry
9854// would produce a different key and defeat the guard entirely.
9855// A configurator takes seconds, so two legitimate adds of the
9856// same SKU inside 5s are impossible.
9857window.__ckMicroAddReported = window.__ckMicroAddReported || {};
9858var ckNowT = Date.now();
9859if (ckNowT - (window.__ckMicroAddReported[pid] || 0) > 5000) {
9860window.__ckMicroAddReported[pid] = ckNowT;
9861// On the legacy reload path this handler runs in the same tick as
9862// buildGrid(), which only STARTS hydration -- so firing now would
9863// send the entity id for the highest-AOV items in the module.
9864// Wait for the SKU, then fire; fire anyway on timeout.
9865(function emitMicroAdd(tries) {
9866try {
9867if (!ckGaHasSku(pid) && tries > 0) {
9868setTimeout(function () { emitMicroAdd(tries - 1); }, CK_GA4.HYDRATE_POLL_MS);
9869return;
9870}
9871} catch (eW) { }
9872pushUpsellEvent('SmartCart_upsell_micro_added', {
9873product_ids: [pid],
9874productId: pid,
9875micro_done: progNow.done,
9876micro_total: progNow.total
9877});
9878})(CK_GA4.HYDRATE_POLLS);
9879}
9880} catch (eGaM) { }
9881// Show a simple success message in the module status area
9882var niceName = (microAddedNameQS || '').trim();
9883if (!niceName) {
9884// If name not provided, try hydrated cache
9885var hydrated = NOTARY_HYDRATED.get(pid);
9886if (hydrated && hydrated.name) niceName = hydrated.name;
9887}
9888if (!niceName) niceName = 'Product';
9889var toast = document.getElementById('kit-success-toast');
9890if (toast) {
9891toast.textContent = '✓ Added to cart: ' + niceName;
9892// If this was the LAST remaining hard item, celebrate completion
9893if (progNow.total > 1 && progNow.done >= progNow.total) {
9894toast.textContent = '???? All set — ' + progNow.done + ' of ' + progNow.total + ' items added';
9895}
9896toast.classList.add('is-visible');
9897setTimeout(function () {
9898toast.classList.remove('is-visible');
9899}, 3500);
9900} else if (statusEl) {
9901// fallback
9902statusEl.textContent = '✓ Added to cart: ' + niceName;
9903if (progNow.total > 1 && progNow.done >= progNow.total) {
9904statusEl.textContent = '???? All set — ' + progNow.done + ' of ' + progNow.total + ' items added';
9905}
9906}
9907// Rebuild grid so the "is-added" state is applied
9908if (useTiles) buildGrid();
9909try { updatePriceUI(); } catch (ePU0) { }
9910// 4B) After a HARD SKU is completed, swap the added tile for the next recommendation
9911// so the customer can keep adding without stale/greyed tiles.
9912// (Run after buildGrid() so we target the freshly-rendered card.)
9913setTimeout(function () {
9914try { replaceAddedTileWithNext(pid); } catch (e) { }
9915}, 800);
9916// If there are more HARD SKUs waiting, open the next Micro PDP automatically
9917// (keeps the user in a guided configuration flow)
9918setTimeout(function () {
9919try {
9920openNextHardFromRemaining();
9921} catch (e) { }
9922}, 350);
9923// Scroll to the newly added item in cart + highlight
9924// Strategy: find a cart row that contains the product name
9925setTimeout(function () {
9926try {
9927var table = document.querySelector('#shopping-cart-table');
9928if (!table) return;
9929var rows = Array.from(table.querySelectorAll('tr'));
9930var targetRow = null;
9931if (niceName && niceName !== 'Product') {
9932var nameLower = niceName.toLowerCase();
9933targetRow = rows.find(function (r) {
9934return (r.innerText || '').toLowerCase().indexOf(nameLower) > -1;
9935}) || null;
9936}
9937// Fallback: if we can't find the row by name, just scroll to cart table
9938var scrollEl = targetRow || table;
9939scrollEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
9940if (targetRow) {
9941targetRow.classList.add('ck-cart-highlight');
9942setTimeout(function () {
9943targetRow.classList.remove('ck-cart-highlight');
9944}, 3500);
9945}
9946} catch (e) { }
9947}, 250);
9948// Remove receipt params so refresh doesn't repeat the success behavior
9949try {
9950var u = new URL(window.location.href);
9951u.searchParams.delete('microAdded');
9952u.searchParams.delete('microAddedName');
9953window.history.replaceState({}, document.title, u.toString());
9954} catch (e) { }
9955}
9956// Query-param handlers MUST run in this order (priority: micro receipt > add more > success)
9957handleMicroAddedFromQuery();     // receipt event → toast/greyout/highlight, then strip params
9958handleAddMoreFromQuery();        // user intent to reopen + reset
9959applySuccessStateFromQuery();    // generic success collapse/header state
9960// ----------------------------------------------------
9961// 9) DEBUG OVERLAY (persona + product scores)
9962//    Enable with ?upsellDebug=1
9963// ----------------------------------------------------
9964(function initDebugOverlay() {
9965var debugOn = (urlParams.get('upsellDebug') === '1');
9966if (!debugOn) return;
9967// ---- styles (scoped) ----
9968var style = document.createElement('style');
9969style.textContent =
9970'#ck-debug-overlay{position:fixed;right:12px;bottom:12px;z-index:999999;' +
9971'width:360px;max-width:calc(100vw - 24px);' +
9972'background:#0b1220;color:#e8eefc;border:1px solid rgba(255,255,255,.15);' +
9973'border-radius:10px;box-shadow:0 10px 30px rgba(0,0,0,.35);font-family:inherit;}' +
9974'#ck-debug-overlay *{box-sizing:border-box;}' +
9975'#ck-debug-head{display:flex;align-items:center;justify-content:space-between;' +
9976'padding:10px 12px;border-bottom:1px solid rgba(255,255,255,.12);}' +
9977'#ck-debug-title{font-weight:700;font-size:13px;letter-spacing:.2px;}' +
9978'#ck-debug-actions{display:flex;gap:8px;align-items:center;}' +
9979'#ck-debug-actions button{background:rgba(255,255,255,.12);color:#e8eefc;border:1px solid rgba(255,255,255,.15);' +
9980'border-radius:8px;padding:6px 8px;font-size:12px;cursor:pointer;}' +
9981'#ck-debug-actions button:hover{background:rgba(255,255,255,.18);}' +
9982'#ck-debug-body{padding:10px 12px;max-height:55vh;overflow:auto;}' +
9983'#ck-debug-body h4{margin:10px 0 6px 0;font-size:12px;opacity:.9;}' +
9984'#ck-debug-kv{display:grid;grid-template-columns:120px 1fr;gap:6px 10px;font-size:12px;}' +
9985'#ck-debug-kv .k{opacity:.75;}' +
9986'#ck-debug-table{width:100%;border-collapse:collapse;font-size:12px;}' +
9987'#ck-debug-table th,#ck-debug-table td{padding:6px 6px;border-bottom:1px solid rgba(255,255,255,.10);vertical-align:top;}' +
9988'#ck-debug-table th{opacity:.75;text-align:left;font-weight:600;}' +
9989'#ck-debug-pill{display:inline-block;padding:2px 6px;border-radius:999px;' +
9990'background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.14);font-size:11px;}' +
9991'#ck-debug-muted{opacity:.7;font-size:11px;line-height:1.35;}' +
9992'#ck-debug-overlay.collapsed #ck-debug-body{display:none;}';
9993document.head.appendChild(style);
9994// ---- UI ----
9995var box = document.createElement('div');
9996box.id = 'ck-debug-overlay';
9997box.innerHTML =
9998'<div id="ck-debug-head">' +
9999'<div id="ck-debug-title">Cart Kit Debug</div>' +
10000'<div id="ck-debug-actions">' +
10001'<button type="button" id="ck-debug-copy">Copy</button>' +
10002'<button type="button" id="ck-debug-toggle">Collapse</button>' +
10003'</div>' +
10004'</div>' +
10005'<div id="ck-debug-body">' +
10006'<div id="ck-debug-kv"></div>' +
10007'<h4>Persona scores</h4>' +
10008'<table id="ck-debug-table"><thead><tr><th>Persona</th><th>Score</th><th>Hits</th></tr></thead><tbody id="ck-debug-personas"></tbody></table>' +
10009'<h4>Top products (selected persona)</h4>' +
10010'<table id="ck-debug-table"><thead><tr><th>Product</th><th>Score</th><th>Role</th></tr></thead><tbody id="ck-debug-products"></tbody></table>' +
10011'<div id="ck-debug-muted" style="margin-top:10px;">Tip: add/remove items and watch scores update live.</div>' +
10012'</div>';
10013document.body.appendChild(box);
10014var btnToggle = document.getElementById('ck-debug-toggle');
10015var btnCopy = document.getElementById('ck-debug-copy');
10016btnToggle.addEventListener('click', function () {
10017box.classList.toggle('collapsed');
10018btnToggle.textContent = box.classList.contains('collapsed') ? 'Expand' : 'Collapse';
10019});
10020function safeJson(obj) {
10021try { return JSON.stringify(obj, null, 2); } catch (e) { return String(obj); }
10022}
10023btnCopy.addEventListener('click', function () {
10024var payload = window.__CK_DEBUG_LAST__ || {};
10025var textToCopy = safeJson(payload);
10026if (navigator.clipboard && navigator.clipboard.writeText) {
10027navigator.clipboard.writeText(textToCopy).catch(function () { });
10028} else {
10029// fallback
10030var ta = document.createElement('textarea');
10031ta.value = textToCopy;
10032ta.style.position = 'fixed';
10033ta.style.left = '-9999px';
10034document.body.appendChild(ta);
10035ta.select();
10036try { document.execCommand('copy'); } catch (e) { }
10037document.body.removeChild(ta);
10038}
10039});
10040// ---- scoring helpers for overlay ----
10041function personaScoreBreakdown(personaObj, cartTextLower) {
10042var kw = keywordMatchScore((personaObj.match || personaObj.matchKeywords || []), cartTextLower);
10043var base = kw.score;
10044var priorityNudge = (typeof personaObj.priority === 'number') ? (personaObj.priority / 1000) : 0;
10045return {
10046base: base,
10047hits: kw.hits,
10048matched: kw.matched,
10049priorityNudge: priorityNudge,
10050total: base + priorityNudge
10051};
10052}
10053function productScoreForOverlay(item, cartTextLower, allowExperimental) {
10054return scoreProductForCart(item, cartTextLower, allowExperimental);
10055}
10056function esc(s) {
10057return String(s || '').replace(/[&<>"']/g, function (c) {
10058return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]);
10059});
10060}
10061// Debug helpers: get cart titles/options as arrays
10062function getCartTitlesList() {
10063try {
10064var nameNodes = document.querySelectorAll(
10065'[data-cart-item-id] h3 a,' +
10066'[data-cart-item-id] h3,' +
10067'#shopping-cart-table h2.cartProductName a,' +
10068'#shopping-cart-table h2.cartProductName,' +
10069'#shopping-cart-table .product-name a,' +
10070'#shopping-cart-table .product-name,' +
10071'.product-item-name a,' +
10072'.product-item-name'
10073);
10074var out = [];
10075var seen = {};
10076for (var i = 0; i < nameNodes.length; i++) {
10077var t = (nameNodes[i].textContent || '').trim();
10078if (!t) continue;
10079var k = t.toLowerCase();
10080if (seen[k]) continue;
10081seen[k] = true;
10082out.push(t);
10083}
10084return out;
10085} catch (e) { return []; }
10086}
10087function getCartOptionsList() {
10088try {
10089var optNodes = document.querySelectorAll(
10090'[data-cart-item-id] .flex-wrap,' +
10091'#shopping-cart-table .item-options,' +
10092'#shopping-cart-table dl.item-options,' +
10093'.item-options,' +
10094'dl.item-options'
10095);
10096var out = [];
10097for (var j = 0; j < optNodes.length; j++) {
10098var o = (optNodes[j].textContent || '').trim();
10099if (o) out.push(o);
10100}
10101return out;
10102} catch (e) { return []; }
10103}
10104function renderOverlay() {
10105var cartTitleLower = getCartTitleTextLower();
10106var cartFullLower = getCartFullTextLower();
10107var allowExperimental = (ALLOW_EXPERIMENTAL_TILES === true) || (urlParams.get('upsellDebug') === '1');
10108// Persona scoring
10109var personaRows = [];
10110var best = { key: 'generic', score: -1 };
10111for (var i = 0; i < PERSONAS.length; i++) {
10112var p = PERSONAS[i];
10113if (!p || p.key === 'generic') continue;
10114var br = personaScoreBreakdown(p, cartTitleLower);
10115personaRows.push({
10116key: p.key,
10117name: p.name,
10118score: br.total,
10119hits: br.hits,
10120matched: br.matched || [],
10121priority: br.priorityNudge
10122});
10123if (br.hits > 0 && br.total > best.score) {
10124best = { key: p.key, score: br.total };
10125}
10126}
10127personaRows.sort(function (a, b) { return b.score - a.score; });
10128// If nobody matched, generic
10129var chosenKey = (best.score <= 0) ? 'generic' : best.key;
10130var chosenPersona = getPersonaByKey(chosenKey);
10131// Product scoring for chosen persona
10132var pool = (chosenPersona && chosenPersona.items) ? chosenPersona.items.slice() : [];
10133var prodRows = pool.map(function (it) {
10134return {
10135id: it.productId,
10136url: it.productUrl,
10137name: it.productName || it.name || it.title || it.label || '',
10138role: it.role || '',
10139friction: it.friction || '',
10140experimental: (it.experimental === true),
10141score: productScoreForOverlay(it, cartFullLower, allowExperimental)
10142};
10143});
10144prodRows.sort(function (a, b) { return b.score - a.score; });
10145// Top N shown for overlay (not just 3)
10146var topN = prodRows.slice(0, 8);
10147// Update KV
10148var kv = document.getElementById('ck-debug-kv');
10149var titleList = getCartTitlesList();
10150var optList = getCartOptionsList();
10151var cartTitlesText = (titleList || []).join(' | ');
10152var cartOptionsText = (optList || []).join(' | ');
10153kv.innerHTML =
10154'<div class="k">Chosen persona</div><div><span id="ck-debug-pill" class="ck-pill"></span></div>' +
10155'<div class="k">Experimental</div><div>' + (allowExperimental ? 'ON (debug)' : 'OFF') + '</div>' +
10156'<div class="k">Cart titles (used for persona)</div><div id="ck-debug-muted">' + esc(cartTitlesText.slice(0, 220)) + (cartTitlesText.length > 220 ? '…' : '') + '</div>' +
10157'<div class="k">Cart options (additional signals)</div><div id="ck-debug-muted">' + (cartOptionsText ? (esc(cartOptionsText.slice(0, 220)) + (cartOptionsText.length > 220 ? '…' : '')) : '<span style="opacity:.7;">none detected</span>') + '</div>';
10158var pill = document.getElementById('ck-debug-pill');
10159if (pill) {
10160pill.textContent = chosenKey;
10161pill.className = '';
10162pill.id = 'ck-debug-pill';
10163pill.style.display = 'inline-block';
10164pill.style.padding = '2px 6px';
10165pill.style.borderRadius = '999px';
10166pill.style.background = 'rgba(255,255,255,.12)';
10167pill.style.border = '1px solid rgba(255,255,255,.14)';
10168pill.style.fontSize = '11px';
10169}
10170// Persona tables
10171var tbody = document.getElementById('ck-debug-personas');
10172tbody.innerHTML = personaRows.map(function (r) {
10173var isChosen = (r.key === chosenKey);
10174// Build keyword pills (or fallback)
10175var kwHtml = (r.matched && r.matched.length)
10176? '<div class="ck-debug-keywords">' +
10177r.matched.slice(0, 10).map(function (k) {
10178return '<span class="ck-debug-keyword">' + esc(k) + '</span>';
10179}).join('') +
10180(r.matched.length > 10 ? '<span class="ck-debug-keyword muted">+' + (r.matched.length - 10) + ' more</span>' : '') +
10181'</div>'
10182: '<div class="ck-debug-keywords muted">no keyword matches</div>';
10183return ''
10184+ '<tr>'
10185+ '<td>' + esc(r.key) + (isChosen ? ' <span class="ck-badge" style="margin-left:6px;opacity:.9;">★</span>' : '') + '</td>'
10186+ '<td>
10186' + (Math.round(r.score * 100) / 100) + '</td>'
10187+ '<td>' + r.hits + '</td>'
10188+ '</tr>'
10189+ '<tr class="ck-debug-keyword-row">'
10190+ '<td colspan="3">' + kwHtml + '</td>'
10191+ '</tr>';
10192}).join('');
10193// Products table
10194var prodTbody = document.getElementById('ck-debug-products');
10195if (prodTbody) {
10196if (!topN || !topN.length) {
10197prodTbody.innerHTML = '<tr><td colspan="3" style="opacity:.7;">No products found for this persona.</td></tr>';
10198} else {
10199prodTbody.innerHTML = topN.map(function (r) {
10200var label = r.name || r.url || r.id || '';
10201var roleBits = [];
10202if (r.role) roleBits.push(r.role);
10203if (r.friction) roleBits.push('friction: ' + r.friction);
10204if (r.experimental) roleBits.push('exp');
10205return ''
10206+ '<tr>'
10207+ '<td>' + esc(label) + '</td>'
10208+ '<td>' + (Math.round(r.score * 100) / 100) + '</td>'
10209+ '<td>' + esc(roleBits.join(' • ')) + '</td>'
10210+ '</tr>';
10211}).join('');
10212}
10213}
10214// Store last payload for easy copy/paste to stakeholders
10215window.__CK_DEBUG_LAST__ = {
10216chosen_persona: chosenKey,
10217allow_experimental: allowExperimental,
10218persona_scores: personaRows,
10219product_scores_top: topN,
10220cart_titles: titleList,
10221cart_options: optList,
10222cart_titles_used_for_persona: cartTitleLower,
10223cart_options_text: (cartOptionsText || '').toLowerCase(),
10224cart_full_text: cartFullLower
10225};
10226}
10227// Update live (cart can change after ajax)
10228renderOverlay();
10229setInterval(renderOverlay, 800);
10230})();
10231}); </script>
10231</div><div class="widget widget-static-block"></div><div class="widget widget-static-block"></div> 
10231<script> /* Ported from the pre-Zephyr footer's jQuery block. Zephyr ships a jQuery
10232no-op shim, so the original jQuery(document).ready() never fired — see
10233note 1 in the header comment. Only the email-CTA rewrite was live; the
10234#blog and #corporate-accounts branches were dead and are not carried over. */
10235(function () {
10236"use strict";
10237var done = false;
10238function apply() {
10239if (done) return true;
10240var s = document.querySelectorAll('.email-cta > strong');
10241if (s.length < 2) return false;
10242s[1].textContent = 'and SAVE 15%';
10243done = true;
10244return true;
10245}
10246if (!apply()) {
10247document.addEventListener('DOMContentLoaded', apply);
10248/* the email CTA is injected by a widget block that can render late */
10249if (window.MutationObserver) {
10250var obs = new MutationObserver(function () { if (apply()) obs.disconnect(); });
10251obs.observe(document.documentElement, { childList: true, subtree: true });
10252setTimeout(function () { obs.disconnect(); }, 10000);
10253}
10254}
10255})();
10256</script>
10256<div class="widget widget-static-block"> <style> /* Hairline light, HARDENED against theme globals. ID-scoped (#fu-foot) for
10257specificity + a child reset so theme font / letter-spacing / text-transform /
10258line-height / margins / list & link styling can't bleed in. Targeted
10259!important only on the props themes tend to override. */
10260#fu-foot.fu-foot{--fu-accent:var(--brand-primary,#012e4a);--fu-border:#e7e9ec;--fu-sep:#e3e6ea;
10261display:flex!important;align-items:center!important;justify-content:center!important;
10262gap:8px!important;width:100%!important;min-height:46px!important;
10263margin:0!important;padding:9px 12px!important;margin-top:2rem;
10264background:#fff!important;border-top:1px solid var(--fu-border)!important;border-bottom:1px solid var(--fu-border)!important;
10265box-sizing:border-box!important;font-family:inherit!important;list-style:none!important}
10266#fu-foot.fu-foot *{margin:0!important;padding:0!important;box-sizing:border-box!important;
10267font-family:inherit!important;letter-spacing:normal!important;text-transform:none!important;
10268line-height:normal!important;list-style:none!important;text-decoration:none!important;float:none!important}
10269#fu-foot .fu-item{display:flex!important;align-items:center!important;gap:9px!important;flex:1 1 0!important;justify-content:center!important;min-width:0!important}
10270#fu-foot .fu-icon{width:22px!important;height:22px!important;flex:0 0 22px!important;color:var(--fu-accent)!important;display:inline-block!important;vertical-align:middle!important}
10271#fu-foot .fu-head{font-size:13px!important;font-weight:700!important;color:var(--fu-accent)!important;white-space:nowrap!important;line-height:1.2!important}
10272#fu-foot .fu-sep{width:1px!important;height:20px!important;background:var(--fu-sep)!important;flex:0 0 1px!important}
10273/* Copy is approved marketing copy and is never trimmed to fit. nowrap keeps the
10274crisp single line on wide desktop; below 1280px the longest lines wrap instead of
10275overflowing their equal-width column. Measured on live NTW: its slot 1 ("Badges
10276Your Team Will Be Proud to Wear", 38 chars) renders 251px wide. Text space per
10277column is ~430px at 1920, ~250px at 1200, ~270px at 1280 - so 1200 was 1px short
10278and 1280 is the correct boundary. Re-measure if any line gets longer. */
10279@media(max-width:1280px){
10280#fu-foot .fu-head{white-space:normal!important}
10281#fu-foot.fu-foot{min-height:56px!important}
10282}
10283/* symbol library — out of the flex container, never rendered */
10284#fu-defs{position:absolute!important;width:0!important;height:0!important;overflow:hidden!important}
10285@media(max-width:768px){
10286#fu-foot.fu-foot{flex-wrap:wrap!important;min-height:82px!important}
10287#fu-foot .fu-item{flex:1 1 46%!important;justify-content:flex-start!important}
10288#fu-foot .fu-head{font-size:12px!important;white-space:normal!important}
10289#fu-foot .fu-sep{display:none!important}
10290} </style> <svg id="fu-defs" width="0" height="0" aria-hidden="true" focusable="false"><defs> <symbol id="fu-quality" viewBox="0 0 24 24"><path d="M12 2 4 5v6c0 5 3.4 8.5 8 11 4.6-2.5 8-6 8-11V5l-8-3z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M9 12l2 2 4-4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></symbol> <symbol id="fu-design" viewBox="0 0 24 24"><path d="M4 20l1.2-4L16 5.2 18.8 8 8 18.8 4 20z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M14.5 6.7 17.3 9.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M15.5 4.5 18 2l4 4-2.5 2.5z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></symbol> <symbol id="fu-customization" viewBox="0 0 24 24"><line x1="4" y1="7" x2="20" y2="7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><line x1="4" y1="12" x2="20" y2="12" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><line x1="4" y1="17" x2="20" y2="17" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="9" cy="7" r="2.4" fill="#fff" stroke="currentColor" stroke-width="1.6"/><circle cx="15" cy="12" r="2.4" fill="#fff" stroke="currentColor" stroke-width="1.6"/><circle cx="8" cy="17" r="2.4" fill="#fff" stroke="currentColor" stroke-width="1.6"/></symbol>
10290 <symbol id="fu-speed" viewBox="0 0 24 24"><path d="M13 2 4 14h6l-1 8 9-12h-6l1-8z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></symbol> <symbol id="fu-compliance" viewBox="0 0 24 24"><rect x="5" y="4.5" width="14" height="16.5" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M9 3.5h6v3H9z" fill="#fff" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M8.5 13l2 2 4.5-4.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></symbol> <symbol id="fu-shipping" viewBox="0 0 24 24"><path d="M2 6h11v9H2z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M13 9h4l3.2 3.2V15H13z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><circle cx="6.5" cy="17" r="1.8" fill="#fff" stroke="currentColor" stroke-width="1.6"/><circle cx="16.5" cy="17" r="1.8" fill="#fff" stroke="currentColor" stroke-width="1.6"/></symbol> <symbol id="fu-guarantee" viewBox="0 0 24 24"><circle cx="12" cy="9" r="5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M10 9l1.5 1.5L15 7" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 13l-2 8 5-3 5 3-2-8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></symbol> <symbol id="fu-usa" viewBox="0 0 24 24"><line x1="5" y1="3" x2="5" y2="21" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M5 4h14l-3 3.5 3 3.5H5z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></symbol> <symbol id="fu-gift" viewBox="0 0 24 24"><rect x="4" y="9.5" width="16" height="10.5" rx="1" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M3 9.5h18V13H3z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><line x1="12" y1="6" x2="12" y2="20" stroke="currentColor" stroke-width="1.6"/><path d="M12 6S10.5 3 8.8 4.2 9.5 6.2 12 6z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M12 6s1.5-3 3.2-1.8S14.5 6.2 12 6z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></symbol> <symbol id="fu-star" viewBox="0 0 24 24"><path d="M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 17.8 6.8 19.1l1-5.8L3.5 9.2l5.9-.9z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></symbol> <symbol id="fu-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 7v5l3.5 2" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></symbol> </defs></svg><div class="fu-foot" id="fu-foot" role="list"> <span class="fu-item" role="listitem"><svg class="fu-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#fu-quality" xlink:href="#fu-quality"></use></svg><span class="fu-head">Passionate About Quality</span></span> <span class="fu-sep" aria-hidden="true"></span> <span class="fu-item" role="listitem"><svg class="fu-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#fu-design" xlink:href="#fu-design"></use></svg><span class="fu-head">Design Experts</span></span> <span class="fu-sep" aria-hidden="true"></span> <span class="fu-item" role="listitem"><svg class="fu-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#fu-customization" xlink:href="#fu-customization"></use></svg><span class="fu-head">Easy Customization</span></span> <span class="fu-sep" aria-hidden="true"></span> <span class="fu-item" role="listitem"><svg class="fu-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#fu-speed" xlink:href="#fu-speed"></use></svg><span class="fu-head">Personalization Fast</span></span></div> 
10290<script type="text/javascript"> // <![CDATA[
10291(function () {
10292"use strict";
10293var SITE_DATA = {
10294"simplystamps":           [["fu-quality","Crafted with Precision"],["fu-guarantee","70+ Years of Stamp Expertise"],["fu-customization","Personalize in a Few Clicks"],["fu-shipping","Ships Next Business Day"]],
10295"customsigns":            [["fu-quality","Built to Last"],["fu-customization","Customized Your Way"],["fu-guarantee","Experts Since 1954"],["fu-star","Reliable Business Signs"]],
10296"allstatenotarysupplies": [["fu-guarantee","Trusted Since 2008"],["fu-compliance","Compliance Experts"],["fu-customization","Easy to Customize"],["fu-clock","Ships in 24 Hours"]],
10297"nametagwizard":          [["fu-quality","Badges Your Team Will Be Proud to Wear"],["fu-design","Logo Color Matching Included"],["fu-customization","Outfit Your Whole Team in Minutes"],["fu-shipping","Ready in 1-2 Business Days"]],
10298"corpconnect":            [["fu-quality","Professional Grade"],["fu-compliance","State Approved"],["fu-customization","Order Your Way"],["fu-shipping","Ships Next Business Day"]],
10299"mykoozie":               [["fu-star","100% Official Koozie\u00ae"],["fu-design","Full-Color Printing"],["fu-gift","No Minimums or Fees"],["fu-shipping","Ships in 2 Business Days"]],
10300"904custom":              [["fu-quality","Heirloom Quality"],["fu-design","Real Craftspeople"],["fu-customization","Personalize in Minutes"],["fu-shipping","Ships Next Day"]],
10301"hcbrands":               [["fu-guarantee","70 Years of Making It Right"],["fu-design","Expert Craftspeople"],["fu-customization","Easy to Personalize"],["fu-shipping","Ships Next Business Day"]]
10302};
10303// Per-brand icon/text color. Omit a site to inherit var(--brand-primary) with
10304// a navy fallback. MyKoozie is pinned to its theme blue so the strip matches
10305// the footer headers directly beneath it on legacy AND Zephyr.
10306var SITE_ACCENT = { "mykoozie": "#456FB2" };
10307// Matching below is a SUBSTRING test against the hostname, so a shared staging
10308// host resolves to whichever key it happens to contain — mk.staging.hcbrands.com
10309// would render HCBrands copy, not MyKoozie's. Force any such host here; this map
10310// is consulted first and wins outright. Key = exact lowercase hostname.
10311var HOST_OVERRIDE = {
10312// "mk.staging.hcbrands.com": "mykoozie",
10313// "cs.staging.hcbrands.com": "customsigns"
10314};
10315try {
10316var root = document.getElementById("fu-foot");
10317if (!root) return;
10318var host = (location.hostname || "").toLowerCase();
10319var key = null;
10320if (HOST_OVERRIDE[host] && SITE_DATA[HOST_OVERRIDE[host]]) {
10321key = HOST_OVERRIDE[host];
10322} else {
10323for (var k in SITE_DATA) {
10324if (Object.prototype.hasOwnProperty.call(SITE_DATA, k) && host.indexOf(k) > -1) { key = k; break; }
10325}
10326}
10327if (!key) return; // unknown host -> keep the generic copy already in the markup
10328var cfg = SITE_DATA[key];
10329if (SITE_ACCENT[key]) root.style.setProperty("--fu-accent", SITE_ACCENT[key]);
10330var items = root.getElementsByClassName("fu-item");
10331var seps  = root.getElementsByClassName("fu-sep");
10332for (var i = 0; i < items.length; i++) {
10333if (!cfg[i]) {
10334items[i].style.display = "none";
10335if (seps[i - 1]) seps[i - 1].style.display = "none"; // no orphan divider
10336continue;
10337}
10338var use = items[i].getElementsByTagName("use")[0];
10339if (use) {
10340use.setAttribute("href", "#" + cfg[i][0]);
10341use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#" + cfg[i][0]);
10342}
10343var head = items[i].getElementsByClassName("fu-head")[0];
10344if (head) head.textContent = cfg[i][1];
10345}
10346} catch (e) { /* strip keeps its default copy rather than breaking the footer */ }
10347})();
10348// ]]> </script>
10348</div><div class="bg-white p-12"><div class="max-w-7xl mx-auto"><div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-10"> <nav aria-labelledby="footer-products" class="mb-6 sm:mb-0"><h3 id="footer-products" class="text-base font-bold text-gray-900 mb-5">Products</h3><ul class="space-y-2.5"><li><a href="/address-stamps/view-all" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Address Stamps</a></li><li><a href="/custom-rubber-stamps" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Custom Stamps</a></li><li><a href="/rubber-logo-stamps" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Logo Stamps</a></li><li><a href="/business-office-stamps/signature-stamps" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Signature Stamps</a></li></ul> </nav> <nav aria-labelledby="footer-company" class="mb-6 sm:mb-0"><h3 id="footer-company" class="text-base font-bold text-gray-900 mb-5">Company</h3><ul class="space-y-2.5"><li><a href="/about-us" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">About Us</a></li><li><a href="/buy-in-america" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">American Made</a></li><li><a href="/rubber-stamp-videos" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Videos</a></li><li><a href="/blog" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Blog</a></li></ul> </nav> <nav aria-labelledby="footer-account" class="mb-6 sm:mb-0"><h3 id="footer-account" class="text-base font-bold text-gray-900 mb-5">My Account</h3><ul class="space-y-2.5"><li><a href="/customer/account/login/" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Account</a></li><li><a href="/sales/order/history/" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Reorders</a></li><li><a href="/customer/account/forgotpassword/" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Forgot Password</a></li><li><a href="/contacts" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Track My Order</a></li></ul> </nav> <nav aria-labelledby="footer-support" class="mb-6 sm:mb-0"><h3 id="footer-support" class="text-base font-bold text-gray-900 mb-5">Support</h3><ul class="space-y-2.5"><li><a href="/contacts" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Contact Us</a></li><li><a href="/shipping" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Shipping</a></li><li><a href="/returns" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Returns</a></li><li><a href="/faq" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">FAQ</a></li><li><a href="/accessibility" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">Accessibility</a></li></ul> </nav><div class="mb-6 sm:mb-0"><h3 class="text-base font-bold text-gray-900 mb-5">Customer Service</h3> <address class="not-italic"><ul class="space-y-2.5"><li><a href="tel:+18886889998" aria-label="Call customer service at (888) 688-9998" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">(888) 688-9998</a></li><li><span class="text-sm text-gray-700">M-Th: 10am - 4pm ET</span></li><li><a href="mailto:[email protected]" aria-label="Email sales at [email protected]" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">[email protected]</a></li><li><a href="https://www.google.com/maps/search/?api=1&amp;query=2021+St+Augustine+Rd+E+Suite+2+Jacksonville+FL+32207" target="_blank" rel="noopener noreferrer" aria-label="Open address in Google Maps (opens in new tab)" class="text-sm text-gray-700 hover:text-brand-primary hover:underline focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 rounded-sm transition-colors">
103482021 St Augustine Rd E, Suite 2<br>Jacksonville, FL 32207</a></li></ul> </address></div> <nav aria-labelledby="footer-social" class="mb-6 sm:mb-0"><h3 id="footer-social" class="text-base font-bold text-gray-900 mb-5">Follow Us</h3><ul class="flex flex-wrap gap-3"><li> <a href="https://www.facebook.com/SimplyStamps/" target="_blank" rel="noopener noreferrer" aria-label="Follow Simply Stamps on Facebook (opens in new tab)" class="flex items-center justify-center w-10 h-10 bg-brand-primary rounded-full text-white hover:bg-brand-primary-hover hover:scale-110 focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 transition-all"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M22.675 0h-21.35C.595 0 0 .593 0 1.325v21.351C0 23.407.595 24 1.325 24H12.82v-9.294H9.692v-3.622h3.128V8.413c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.795.143v3.24l-1.918.001c-1.504 0-1.795.715-1.795 1.763v2.313h3.587l-.467 3.622h-3.12V24h6.116c.73 0 1.323-.593 1.323-1.325V1.325C24 .593 23.405 0 22.675 0z"/></svg> </a></li><li> <a href="https://www.pinterest.com/simplystamps/" target="_blank" rel="noopener noreferrer" aria-label="Follow Simply Stamps on Pinterest (opens in new tab)" class="flex items-center justify-center w-10 h-10 bg-brand-primary rounded-full text-white hover:bg-brand-primary-hover hover:scale-110 focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 transition-all"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z"/></svg> </a></li><li> <a href="https://www.instagram.com/simplystamps/" target="_blank" rel="noopener noreferrer" aria-label="Follow Simply Stamps on Instagram (opens in new tab)" class="flex items-center justify-center w-10 h-10 bg-brand-primary rounded-full text-white hover:bg-brand-primary-hover hover:scale-110 focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 transition-all"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg> </a></li></ul> </nav></div></div></div> 
10348<script type="application/ld+json"> {
10349"@context": "http://schema.org",
10350"@type": "LocalBusiness",
10351"name": "SimplyStamps.com",
10352"address": {
10353"@type": "PostalAddress",
10354"streetAddress": "2021 St Augustine Rd E, Suite 2",
10355"addressLocality": "Jacksonville",
10356"addressRegion": "FL",
10357"postalCode": "32207",
10358"addressCountry": "US"
10359},
10360"geo": {
10361"@type": "GeoCoordinates",
10362"latitude": "30.3220",
10363"longitude": "-81.6690"
10364},
10365"telephone": "+1-888-688-9998",
10366"email": "[email protected]",
10367"url": "https://www.simplystamps.com",
10368"sameAs": [
10369"https://www.facebook.com/SimplyStamps",
10370"https://twitter.com/SimplyStamps",
10371"https://www.instagram.com/SimplyStamps",
10372"https://www.pinterest.com/SimplyStamps",
10373"https://www.linkedin.com/company/SimplyStamps"
10374],
10375"openingHoursSpecification": [
10376{
10377"@type": "OpeningHoursSpecification",
10378"dayOfWeek": [
10379"Monday",
10380"Tuesday",
10381"Wednesday",
10382"Thursday"
10383],
10384"opens": "10:00",
10385"closes": "16:00"
10386}
10387],
10388"hasMerchantReturnPolicy": {
10389"@type": "MerchantReturnPolicy",
10390"name": "Return Policy",
10391"url": "https://www.simplystamps.com/returns",
10392"merchantReturnDays": 60,
10393"returnFees": "FreeReturn",
10394"returnMethod": "ReturnByMail",
10395"returnPolicyCategory": "Refundable",
10396"inStoreReturnsOffered": false,
10397"itemCondition": "New",
10398"description": "We offer a straightforward 60-day return policy. Custom-made products are generally non-refundable, but exceptions can be made in specific cases. If a mistake occurs on our part, we will remake the order within 60 days or offer a refund if necessary. Customers may choose to exchange items instead of requesting a refund."
10399}
10400} </script>
10400</div><div itemscope itemtype="https://schema.org/WebSite" class="hidden"><meta itemprop="name" content="Simply Stamps"><link itemprop="url" href="https://www.simplystamps.com/"><div itemprop="potentialAction" itemscope itemtype="https://schema.org/SearchAction"><meta itemprop="target" content="https://www.simplystamps.com/catalogsearch/result/?q={search_term_string}"><meta itemprop="query-input" content="required name=search_term_string"></div></div><div class="bg-gray-800 text-white" itemscope itemtype="https://schema.org/Organization"><meta itemprop="name" content="Simply Stamps"><link itemprop="url" href="https://www.simplystamps.com/"><meta itemprop="logo" content="https://cdn.simplystamps.com/skin/frontend/zephyr/sstamps-modern/images/SS-Desktop-logo.svg"><div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6"> <address class="text-center text-sm text-gray-400 not-italic"> &copy; 2026 Simply Stamps. All Rights Reserved.<p><a href="/terms-and-conditions">Terms & Conditions</a> | <a href="/privacy-policy">Privacy</a></p> &nbsp;&nbsp; </address></div></div> </footer> <style> @keyframes shake {
104010%, 100% { transform: translateX(0); }
1040210%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
1040320%, 40%, 60%, 80% { transform: translateX(4px); }
10404}
10405.shake {
10406animation: shake 0.5s ease-in-out;
10407} </style><div
10408x-data
10409x-show="$store.cart.drawerOpen"
10410@click="$store.cart.closeDrawer()"
10411x-transition:enter="transition-opacity ease-out duration-300"
10412x-transition:enter-start="opacity-0"
10413x-transition:enter-end="opacity-100"
10414x-transition:leave="transition-opacity ease-in duration-200"
10415x-transition:leave-start="opacity-100"
10416x-transition:leave-end="opacity-0"
10417class="fixed inset-0 z-40 transition-all duration-500"
10418:class="$store.cart.drawerFocused ? 'bg-black/15' : 'bg-transparent'"
10419style="display: none;"
10420></div><div
10421x-data
10422x-show="$store.cart.drawerOpen"
10423@click.away="if (!$store.imagePreview?.isOpen && !$store.promo?.isOpen && !$store.confirm?.isOpen && !document.getElementById('wizardModal')?._x_dataStack?.[0]?.isOpen) $store.cart.closeDrawer()"
10424@mouseenter="clearTimeout($store.cart._focusTimer); $store.cart._focusTimer = setTimeout(() => { $store.cart.drawerFocused = true }, 215)"
10425@mouseleave="clearTimeout($store.cart._focusTimer); $store.cart._focusTimer = setTimeout(() => { $store.cart.drawerFocused = false }, 100)"
10426x-transition:enter="transition ease-out duration-300"
10427x-transition:enter-start="translate-x-full"
10428x-transition:enter-end="translate-x-0"
10429x-transition:leave="transition ease-in duration-200"
10430x-transition:leave-start="translate-x-0"
10431x-transition:leave-end="translate-x-full"
10432:style="{ top: $store.ui.getHeaderOffset() + 'px' }"
10433class="fixed right-0 bottom-0 w-full md:w-[26rem] bg-white shadow-2xl z-50 flex flex-col transition-all duration-300"
10434style="display: none;"
10435><div class="flex flex-col h-full relative"><div class="flex items-center justify-between px-4 py-2 border-b border-gray-200"> <span class="text-lg font-bold text-gray-900">Shopping Cart</span> <button
10436@click="$store.cart.closeDrawer()"
10437class="p-2 hover:bg-gray-100 rounded-lg transition-colors"
10438aria-label="Close cart"
10439> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6"> <path d="M18 6L6 18" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M6 6L18 18" stroke="#4F4F4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button></div><div
10440x-show="$store.cart.isRefreshing"
10441x-cloak
10442x-transition:enter="transition ease-out duration-200"
10443x-transition:enter-start="opacity-0"
10444x-transition:enter-end="opacity-100"
10445x-transition:leave="transition ease-in duration-150"
10446x-transition:leave-start="opacity-100"
10447x-transition:leave-end="opacity-0"
10448class="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center z-10"
10449><div class="flex flex-col items-center gap-3"> <svg class="animate-spin h-10 w-10 text-brand-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> <span class="text-sm font-medium text-gray-700">
10449Updating cart...</span></div></div><div class="flex-1 min-h-0 overflow-hidden relative"
10450x-data="{
10451canScroll: false,
10452atBottom: false,
10453checkScroll() {
10454const el = this.$refs.scrollContainer;
10455if (!el) return;
10456this.canScroll = el.scrollHeight > el.clientHeight + 10;
10457this.atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 20;
10458},
10459scrollToLastItem() {
10460const container = this.$refs.scrollContainer;
10461if (!container) return;
10462const items = container.querySelectorAll('[data-cart-item-id]');
10463if (!items.length) return;
10464const lastItem = items[items.length - 1];
10465const cRect = container.getBoundingClientRect();
10466const iRect = lastItem.getBoundingClientRect();
10467if (iRect.bottom > cRect.bottom || iRect.top < cRect.top) {
10468lastItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
10469}
10470this.checkScroll();
10471}
10472}"
10473x-init="$nextTick(() => checkScroll()); $watch(() => $store.cart.drawerOpen, (open) => { if (open) $nextTick(() => checkScroll()) })"
10474@cart-refreshed.window="$nextTick(() => { checkScroll(); if ($store.cart.drawerOpen && $store.cart._scrollToNew) { scrollToLastItem(); $store.cart._scrollToNew = false; } })"><div class="absolute inset-0 overflow-y-auto"
10475id="cart-items-container"
10476x-ref="scrollContainer"
10477@scroll.throttle.100ms="checkScroll()"
10478@resize.window.throttle.200ms="checkScroll()"><div class="text-center py-12"> <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" /> </svg><p class="mt-4 text-gray-500">Your cart is empty</p></div></div><div x-show="canScroll && !atBottom"
10479x-transition:enter="transition ease-out duration-200"
10480x-transition:enter-start="opacity-0"
10481x-transition:enter-end="opacity-100"
10482x-transition:leave="transition ease-in duration-150"
10483x-transition:leave-start="opacity-100"
10484x-transition:leave-end="opacity-0"
10485class="absolute bottom-0 left-0 right-0"><div class="h-12 bg-gradient-to-t from-white via-white/80 to-transparent pointer-events-none"></div> <button type="button"
10486@click.prevent.stop="
10487const container = document.getElementById('cart-items-container');
10488if (!container) return;
10489const items = container.querySelectorAll('[data-cart-item-id]');
10490if (!items.length) return;
10491const containerTop = container.getBoundingClientRect().top;
10492let target = null;
10493const containerBottom = container.getBoundingClientRect().bottom;
10494for (let i = 0; i < items.length; i++) {
10495if (items[i].getBoundingClientRect().bottom > containerBottom) {
10496target = items[i];
10497break;
10498}
10499}
10500if (target) {
10501const scrollAmount = target.getBoundingClientRect().top - containerTop;
10502container.scrollBy({ top: scrollAmount, behavior: 'smooth' });
10503}
10504"
10505class="absolute bottom-2 left-1/2 -translate-x-1/2 animate-bounce p-1 rounded-full hover:bg-gray-100 transition-colors cursor-pointer"
10506aria-label="Scroll to next item"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> </svg> </button></div></div><div class="p-4" id="cart-footer"
10507x-data
10508x-init="$store.cart.hasError = false; $store.cart.subtotal = &quot;$0.00&quot;; $store.cart.originalSubtotal = null; $store.cart.grandTotal = &quot;$0.00&quot;; $store.cart.totals = []; $store.cart.couponCode = &quot;&quot;; $store.cart.expeditedProduct = {&quot;id&quot;:&quot;12060&quot;,&quot;price&quot;:&quot;$7.99&quot;,&quot;add_url&quot;:&quot;https:\/\/www.simplystamps.com\/checkout\/cart\/add\/uenc\/aHR0cHM6Ly93d3cuc2ltcGx5c3RhbXBzLmNvbS8,\/product\/12060\/form_key\/e3374479ab86b5888526972805eb3d8f\/qty\/1\/&quot;}; $store.cart.expeditedInCart = false; $store.cart.totalSavings = null; $store.cart.savings = {&quot;label&quot;:null,&quot;has_savings&quot;:false,&quot;sale_savings&quot;:null,&quot;price_adjustments&quot;:[],&quot;your_price&quot;:null}"> <template x-if="!$store.cart.isEmpty"><div><div class="mb-3 text-right"> <button
10509onclick="clearCart(); return false;"
10510class="text-xs text-red-600 hover:text-red-700 hover:underline transition-colors">
10510 Clear Cart </button></div><div x-data="{ threshold: 79 }"
10511x-show="$store.cart.subtotalNumeric > 0 && $store.cart.subtotalNumeric >= threshold"
10512x-cloak
10513class="mb-3"><div class="flex items-center justify-center gap-1.5"> <span class="text-sm font-semibold text-green-700">You qualify for FREE economy shipping!</span> <span class="text-lg">🎉</span></div></div><div class="space-y-2 mb-2"><div class="flex items-center justify-between"> <span class="text-sm text-gray-600">Subtotal:</span> <span class="text-sm font-semibold text-gray-900" x-text="$store.cart.originalSubtotal ? $store.cart.originalSubtotal : $store.cart.subtotal"></span></div> <template x-if="$store.cart.originalSubtotal && $store.cart.savings && $store.cart.savings.sale_savings"><div class="flex items-center justify-between"> <span class="text-sm text-green-600">Savings:</span> <span class="text-sm font-semibold text-green-600" x-text="$store.cart.savings.sale_savings"></span></div> </template> <template x-for="adj in ($store.cart.savings && $store.cart.savings.price_adjustments ? $store.cart.savings.price_adjustments : [])" :key="adj.code"><div class="flex items-center justify-between"> <span class="text-sm" :class="adj.is_discount ? 'text-green-600' : 'text-gray-600'" x-text="adj.title + ':'"></span> <span class="text-sm font-semibold" :class="adj.is_discount ? 'text-green-600' : 'text-gray-900'" x-text="adj.value"></span></div> </template> <template x-if="$store.cart.savings && $store.cart.savings.your_price"><div class="flex items-center justify-between"> <span class="text-sm text-gray-600">Your Price:</span> <span class="text-sm font-semibold text-green-900" x-text="$store.cart.savings.your_price"></span></div> </template> <template x-for="total in $store.cart.totals" :key="total.code"><div class="flex items-center justify-between"> <span class="text-sm text-gray-600" x-text="total.title + ':'"></span> <span class="text-sm font-semibold text-gray-900" x-text="total.value"></span></div> </template><div class="flex items-center justify-between border-t pt-2"> <span class="text-lg font-semibold text-gray-900">Total:</span> <span class="text-2xl font-bold text-gray-900" x-text="$store.cart.grandTotal"></span></div><div class="pt-1 flex gap-2"><div class="flex-1"> <template x-if="$store.cart.couponCode"><div class="h-full flex items-center justify-between p-2 bg-green-50 rounded-lg border border-green-200"><div class="flex items-center gap-2"> <svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" /> </svg> <span class="text-sm font-medium text-green-800" x-text="$store.cart.couponCode.toUpperCase()"></span></div> <button
10514type="button"
10515@click="$store.promo.remove()"
10516class="text-xs text-green-700 hover:text-green-900 underline"
10517>Remove</button></div> </template> <template x-if="!$store.cart.couponCode"> <button
10518type="button"
10519@click="$store.promo.open()"
10520class="h-full w-full flex items-center justify-center gap-2 py-2.5 text-sm text-gray-600 hover:text-gray-900 border border-dashed border-gray-300 rounded-lg hover:border-gray-400 transition-colors"
10521> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" /> </svg> <span>Add Promo Code</span> </button> </template></div> <template x-if="$store.cart.expeditedProduct && !$store.cart.expeditedInCart"><div class="flex-1 relative" x-data="{ showTip: false }"><div x-show="showTip" x-cloak
10522x-transition:enter="transition ease-out duration-200"
10523x-transition:enter-start="opacity-0 translate-y-1"
10524x-transition:enter-end="opacity-100 translate-y-0"
10525x-transition:leave="transition ease-in duration-150"
10526x-transition:leave-start="opacity-100 translate-y-0"
10527x-transition:leave-end="opacity-0 translate-y-1"
10528class="absolute bottom-full left-1/2 -translate-x-1/2 -ml-5 mb-3 z-20 pointer-events-none w-56"><div class="bg-brand-primary text-white text-sm rounded-lg px-3 py-2 shadow-lg text-center leading-snug relative"> <span x-text="'⚡ Send your order to the front of the production line for just ' + $store.cart.expeditedProduct.price + '!'"></span><div class="absolute top-full left-1/2 -translate-x-1/2 w-0 h-0 border-l-[6px] border-r-[6px] border-t-[6px] border-l-transparent border-r-transparent" style="border-top-color: var(--brand-primary)"></div></div></div> <button
10529type="button"
10530onclick="addExpeditedToCart()"
10531@mouseenter="showTip = true"
10532@mouseleave="showTip = false"
10533class="w-full flex items-center justify-center gap-2 py-2.5 text-sm font-medium border border-brand-primary text-brand-primary bg-white rounded-lg hover:bg-brand-primary/10 transition-colors"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> </svg> <span x-text="'Add Expedite ' + $store.cart.expeditedProduct.price"></span> </button></div> </template></div></div><p x-show="$store.cart.hasError" x-cloak class="mb-2 text-xs text-amber-800 text-center" :class="$store.cart.nudged ? 'font-bold' : ''">Edit the marked items to make your selection(s) before continuing to checkout.</p> <a href="https://www.simplystamps.com/checkout/onepage/"
10534@click.prevent="$store.cart.hasError ? $store.cart.shake($event.currentTarget) : $store.upsell.maybeProceed('https://www.simplystamps.com/checkout/onepage/')"
10535class="btn-crt flex items-center justify-center gap-2 w-full text-white hover:text-white font-semibold py-3 rounded-lg transition-colors mb-2"> <span>Proceed to Checkout</span> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" /> </svg> </a> <a href="https://www.simplystamps.com/checkout/cart/"
10536class="block w-full text-center text-sm border border-brand-success text-brand-success hover:bg-brand-success hover:text-white font-medium py-1.5 rounded-lg transition-colors"> View Full Cart </a></div> </template> <template x-if="$store.cart.isEmpty"> <button
10537@click="$store.cart.closeDrawer()"
10538class="w-full bg-gray-600 hover:bg-gray-700 text-white font-semibold py-3 rounded-lg transition-colors"> Continue Shopping </button> </template></div></div></div><div
10539x-data
10540x-show="$store.promo.isOpen"
10541x-cloak
10542class="fixed inset-0 z-[100] overflow-y-auto"
10543aria-labelledby="promo-modal-title"
10544role="dialog"
10545aria-modal="true"
10546><div
10547x-show="$store.promo.isOpen"
10548x-transition:enter="transition ease-out duration-200"
10549x-transition:enter-start="opacity-0"
10550x-transition:enter-end="opacity-100"
10551x-transition:leave="transition ease-in duration-150"
10552x-transition:leave-start="opacity-100"
10553x-transition:leave-end="opacity-0"
10554@click="$store.promo.close()"
10555class="fixed inset-0 bg-black/50 backdrop-blur-sm"
10556></div><div @click.self="$store.promo.close()" class="fixed inset-0 flex items-center justify-center p-4"><div
10557x-show="$store.promo.isOpen"
10558x-transition:enter="transition ease-out duration-200"
10559x-transition:enter-start="opacity-0 scale-95"
10560x-transition:enter-end="opacity-100 scale-100"
10561x-transition:leave="transition ease-in duration-150"
10562x-transition:leave-start="opacity-100 scale-100"
10563x-transition:leave-end="opacity-0 scale-95"
10564@click.stop
10565class="relative bg-white rounded-xl shadow-2xl max-w-sm w-full p-6"
10566> <button
10567@click="$store.promo.close()"
10568type="button"
10569class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors z-10"
10570> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button><div
10571x-show="$store.promo.success"
10572x-transition:enter="transition ease-out duration-300"
10573x-transition:enter-start="opacity-0 scale-95"
10574x-transition:enter-end="opacity-100 scale-100"
10575class="flex flex-col items-center justify-center py-4"
10576><div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-4"> <svg class="h-10 w-10 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" /> </svg></div><h3 class="text-xl font-semibold text-gray-900 text-center mb-2" x-text="$store.promo.successMessage"></h3><div class="flex items-center gap-2 px-4 py-2 bg-green-50 rounded-lg border border-green-200 mb-2"> <svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" /> </svg> <span class="text-lg font-bold text-green-800 uppercase" x-text="$store.promo.appliedCode"></span></div><p x-show="$store.promo.discount" class="text-sm text-gray-600"> You save <span class="font-semibold text-green-700" x-text="$store.promo.discount"></span></p></div><div x-show="!$store.promo.success"><div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100 mb-4"> <svg class="h-6 w-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" /> </svg></div><h3 id="promo-modal-title" class="text-lg font-semibold text-gray-900 text-center mb-2"> Enter Promo Code</h3><p class="text-sm text-gray-600 text-center mb-4"> Have a promo code? Enter it below to apply your discount.</p><form @submit.prevent="$store.promo.apply()" class="space-y-4"><div class="relative"> <label for="promo-code-input" class="sr-only">Promo Code</label> <input
10577type="text"
10578id="promo-code-input"
10579x-model="$store.promo.code"
10580placeholder="Enter promo code"
10581class="w-full pl-12 pr-12 py-3 text-center text-lg font-medium uppercase tracking-wider border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary focus:border-transparent placeholder:normal-case placeholder:tracking-normal placeholder:font-normal placeholder:text-base"
10582:disabled="$store.promo.isLoading"
10583@keydown.escape="$store.promo.close()"
10584/> <button
10585type="button"
10586@click="navigator.clipboard.readText().then(text => { $store.promo.code = text.trim(); })"
10587class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-gray-400 hover:text-gray-600 transition-colors rounded-md hover:bg-gray-100"
10588title="Paste from clipboard"
10589:disabled="$store.promo.isLoading"
10590> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" /> </svg> </button></div><div
10591x-show="$store.promo.error"
10592x-transition
10593class="text-sm text-red-600 text-center"
10594x-text="$store.promo.error"
10595></div><div class="flex gap-3"> <button
10596@click="$store.promo.close()"
10597type="button"
10598class="flex-1 px-4 py-2.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-300 transition-colors"
10599:disabled="$store.promo.isLoading"
10600> Cancel </button> <button
10601type="submit"
10602class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-brand-primary rounded-lg hover:bg-brand-primary-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-brand-primary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
10603:disabled="$store.promo.isLoading || !$store.promo.code.trim()"
10604> <span x-show="!$store.promo.isLoading">
10604Apply Code</span> <span x-show="$store.promo.isLoading" class="flex items-center justify-center gap-2"> <svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> Applying... </span> </button></div></form></div></div></div></div><div
10605x-data="{ open: false }"
10606@expedited-added.window="open = true"
10607x-show="open"
10608x-cloak
10609class="fixed inset-0 z-[100] overflow-y-auto"
10610aria-labelledby="expedited-modal-title"
10611role="dialog"
10612aria-modal="true"
10613><div
10614x-show="open"
10615x-transition:enter="transition ease-out duration-200"
10616x-transition:enter-start="opacity-0"
10617x-transition:enter-end="opacity-100"
10618x-transition:leave="transition ease-in duration-150"
10619x-transition:leave-start="opacity-100"
10620x-transition:leave-end="opacity-0"
10621@click="open = false"
10622class="fixed inset-0 bg-black/50 backdrop-blur-sm"
10623></div><div @click.self="open = false" class="fixed inset-0 flex items-center justify-center p-4"><div
10624x-show="open"
10625x-transition:enter="transition ease-out duration-200"
10626x-transition:enter-start="opacity-0 scale-95"
10627x-transition:enter-end="opacity-100 scale-100"
10628x-transition:leave="transition ease-in duration-150"
10629x-transition:leave-start="opacity-100 scale-100"
10630x-transition:leave-end="opacity-0 scale-95"
10631@click.stop
10632class="relative bg-white rounded-xl shadow-2xl max-w-sm w-full p-6"
10633> <button
10634@click="open = false"
10635type="button"
10636class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors z-10"
10637> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button><div class="flex flex-col items-center justify-center py-4"><div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-4"> <svg class="h-10 w-10 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" /> </svg></div><h3 id="expedited-modal-title" class="text-xl font-semibold text-gray-900 text-center mb-2"> Added to your cart!</h3><div class="flex items-center gap-2 px-4 py-2 bg-green-50 rounded-lg border border-green-200 mb-2"> <svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> </svg> <span class="text-lg font-bold text-green-800">Expedited Processing</span></div><p class="text-sm text-gray-600 text-center"> Your order will now be sent to the front of the production queue.</p> <button
10638@click="open = false"
10639type="button"
10640class="mt-4 px-6 py-2 text-sm font-medium text-white bg-brand-primary rounded-lg hover:bg-brand-primary-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-brand-primary transition-colors"
10641> Close </button></div></div></div></div><div
10642x-data
10643x-show="$store.upsell.isOpen"
10644x-cloak
10645@keydown.escape.window="$store.upsell.isOpen && $store.upsell.skip()"
10646class="fixed inset-0 z-[120] overflow-y-auto"
10647role="dialog"
10648aria-modal="true"
10649aria-labelledby="upsell-modal-title"
10650style="display: none;"
10651><div
10652x-show="$store.upsell.isOpen"
10653x-transition:enter="transition ease-out duration-200"
10654x-transition:enter-start="opacity-0"
10655x-transition:enter-end="opacity-100"
10656x-transition:leave="transition ease-in duration-150"
10657x-transition:leave-start="opacity-100"
10658x-transition:leave-end="opacity-0"
10659@click="$store.upsell.skip()"
10660class="fixed inset-0 bg-black/50 backdrop-blur-sm"
10661></div><div class="fixed inset-0 flex items-center justify-center p-4"><div
10662x-show="$store.upsell.isOpen"
10663x-transition:enter="transition ease-out duration-200"
10664x-transition:enter-start="opacity-0 scale-95"
10665x-transition:enter-end="opacity-100 scale-100"
10666x-transition:leave="transition ease-in duration-150"
10667x-transition:leave-start="opacity-100 scale-100"
10668x-transition:leave-end="opacity-0 scale-95"
10669@click.stop
10670class="relative bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] flex flex-col"
10671><div class="flex items-start justify-between gap-4 p-5 border-b border-gray-200"><div><h3 id="upsell-modal-title" class="text-[1.52rem] font-bold text-gray-900" x-text="$store.upsell.header || 'You might also like'"></h3><p x-show="$store.upsell.subheader" class="text-[1.18rem] text-gray-500 mt-1" x-text="$store.upsell.subheader"></p></div> <button @click="$store.upsell.skip()" type="button" class="text-gray-400 hover:text-gray-600 transition-colors p-1 -m-1" aria-label="Close"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button></div><div class="p-5 overflow-y-auto grid grid-cols-2 sm:grid-cols-3 gap-4"> <template x-for="product in $store.upsell.products" :key="product.id"><div class="relative border rounded-lg p-3 flex flex-col bg-white transition-shadow hover:shadow-md"
10672:class="product.addable && $store.upsell.isSelected(product.id) ? 'ring-2 ring-brand-primary border-brand-primary' : 'border-gray-200'"> <a :href="product.url" target="_blank" class="block aspect-square mb-2 rounded overflow-hidden bg-gray-50"> <img :src="product.image" :alt="product.name" class="w-full h-full object-contain" /> </a> <a :href="product.url" target="_blank" class="block text-sm font-medium text-gray-900 hover:text-brand-primary line-clamp-2 mb-1 no-underline" x-text="product.name"></a><div class="mb-2"> <template x-if="product.has_discount"><div class="flex items-baseline gap-1.5"> <span class="text-gray-400 line-through text-xs font-normal" x-html="product.original_price"></span>
10672 <span class="text-sm font-semibold text-gray-900" x-html="product.price_html"></span></div> </template> <template x-if="!product.has_discount"><div class="text-sm font-semibold text-gray-900" x-html="product.price_html"></div> </template></div><div class="mt-auto"> <template x-if="product.addable"> <button type="button"
10673@click="$store.upsell.toggle(product.id)"
10674class="w-full flex items-center justify-center gap-1.5 py-2 text-sm font-medium rounded-lg border transition-colors"
10675:class="$store.upsell.isSelected(product.id) ? 'bg-brand-primary text-white border-brand-primary hover:bg-brand-primary-hover' : 'bg-white text-brand-primary border-brand-primary hover:bg-brand-primary/10'"> <svg x-show="$store.upsell.isSelected(product.id)" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> </svg> <svg x-show="!$store.upsell.isSelected(product.id)" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> </svg> <span x-text="$store.upsell.isSelected(product.id) ? 'Added' : 'Add'"></span> </button> </template> <template x-if="!product.addable"> <a :href="product.customize_url"
10676class="w-full flex items-center justify-center gap-1.5 py-2 text-sm font-medium rounded-lg border border-gray-300 text-gray-700 bg-white hover:bg-gray-50 transition-colors no-underline"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /> </svg> Customize </a> </template></div></div> </template></div><div class="p-5 border-t border-gray-200 flex flex-col sm:flex-row gap-3 sm:justify-between sm:items-center"> <button type="button" @click="$store.upsell.skip()"
10677class="order-2 sm:order-1 text-sm text-gray-500 hover:text-gray-700 hover:underline transition-colors"> No thanks, continue to checkout </button> <button type="button" @click="$store.upsell.addSelectedAndCheckout()"
10678:disabled="$store.upsell.isLoading"
10679class="order-1 sm:order-2 btn-crt flex items-center justify-center gap-2 text-white hover:text-white font-semibold py-3 px-6 rounded-lg transition-colors disabled:opacity-60"> <span x-show="!$store.upsell.selected.length">Continue to Checkout</span> <span x-show="$store.upsell.selected.length" x-text="'Add ' + $store.upsell.selected.length + ' & Continue'"></span> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" /> </svg> </button></div></div></div></div><div x-data="{ open: false }"
10680x-show="$store.auth.isStaff"
10681x-cloak
10682@mouseenter="open = true"
10683@mouseleave="open = false"
10684class="fixed left-0 top-3/4 -translate-y-1/2 z-[9999] hidden lg:block"
10685style="display: none;"><div x-show="!open"
10686x-transition:enter="transition ease-out duration-200"
10687x-transition:enter-start="opacity-0 -translate-x-2"
10688x-transition:enter-end="opacity-100 translate-x-0"
10689x-transition:leave="transition ease-in duration-150"
10690x-transition:leave-start="opacity-100"
10691x-transition:leave-end="opacity-0"
10692class="w-8 bg-blue-600 rounded-r-lg flex flex-col items-center justify-center cursor-pointer shadow-lg py-4 gap-3"> <svg class="w-5 h-5 text-white flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/> </svg> <span class="text-white text-sm font-medium tracking-wide" style="writing-mode: vertical-rl; text-orientation: mixed;">Mage Admin Quick Links</span></div><div x-show="open"
10693x-transition:enter="transition ease-out duration-200"
10694x-transition:enter-start="opacity-0 -translate-x-4"
10695x-transition:enter-end="opacity-100 translate-x-0"
10696x-transition:leave="transition ease-in duration-150"
10697x-transition:leave-start="opacity-100 translate-x-0"
10698x-transition:leave-end="opacity-0 -translate-x-4"
10699class="absolute left-0 top-1/2 -translate-y-1/2 bg-white rounded-r-lg shadow-xl p-4 min-w-[220px]"><div class="text-xs font-semibold uppercase tracking-wide text-gray-500 mb-3 pb-2 border-b border-gray-200"> Mage Admin Quick Links</div><ul class="space-y-1"><li> <a :href="$store.auth.adminEditBase ? $store.auth.adminEditBase + 'type/config/id/hsc_zephyr/store/sstamps_en/' : '#'"
10700target="_blank"
10701class="flex items-center gap-3 px-3 py-2 text-sm text-gray-700 hover:text-brand-primary hover:bg-gray-100 rounded-md transition-colors">
10701 <svg class="w-5 h-5 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/> </svg> Zephyr Theme Settings </a></li><li> <a :href="$store.auth.adminEditBase ? $store.auth.adminEditBase + 'type/cms_page/id/108/' : '#'"
10702target="_blank"
10703class="flex items-center gap-3 px-3 py-2 text-sm text-gray-700 hover:text-brand-primary hover:bg-gray-100 rounded-md transition-colors"> <svg class="w-5 h-5 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/> </svg> <span class="flex flex-col leading-tight"> <span>Edit CMS Page</span> <span class="text-xs text-gray-500">Custom Stamps &amp; Personalized Rubber Stamps | Simply Stamps</span> </span> </a></li><li> <a :href="$store.auth.adminEditBase ? $store.auth.adminEditBase + 'type/zephyr_layout/id/44/' : '#'"
10704target="_blank"
10705class="flex items-center gap-3 px-3 py-2 text-sm text-gray-700 hover:text-brand-primary hover:bg-gray-100 rounded-md transition-colors"> <svg class="w-5 h-5 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"/> </svg> <span class="flex flex-col leading-tight"> <span>Edit Zephyr Block</span> <span class="text-xs text-gray-500">Simply Stamps Home</span> </span> </a></li><li class="border-t border-gray-200 pt-2 mt-2"> <button @click="$store.navEdit.open()"
10706class="flex items-center gap-3 px-3 py-2 text-sm text-gray-700 hover:text-brand-primary hover:bg-gray-100 rounded-md transition-colors w-full text-left"> <svg class="w-5 h-5 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"/> </svg> Edit Site Navigation </button></li></ul></div></div> 
10706<script type="text/javascript"> document.addEventListener('DOMContentLoaded', function() {
10707Mage.Cookies.set(
10708'FASTLY_CDN_ENV',
10709'250e7c153723fdf375c9db11b6b2b305',
10710new Date(new Date().getTime() + 86400000)
10711);
10712}); </script>
10712 
10712<script type="text/javascript"> (function () {
10713var name = 'FASTLY_CDN_FORMKEY';
10714if (document.cookie.indexOf(name + '=') !== -1) {
10715return;
10716}
10717var expires = new Date(new Date().getTime() + 86400000);
10718document.cookie = name + '=' + 'e3374479ab86b5888526972805eb3d8f'
10719+ '; expires=' + expires.toUTCString()
10720+ '; path=/'        + '; domain=.www.simplystamps.com';
10721})(); </script>
10721<div id="wizardModal"
10722x-data="{ isOpen: false, wizardUrl: '', showConfirm: false, showResume: false, isLoading: true, hasSession: false, pendingUrl: '' }"
10723x-show="isOpen || showResume"
10724x-cloak
10725@keydown.escape.window="showConfirm ? showConfirm = false : (showResume ? showResume = false : null)"
10726class="fixed inset-0 z-50 overflow-hidden"
10727style="display: none;"><div class="fixed inset-0 bg-black bg-opacity-75 transition-opacity"
10728@click="if (showResume) { showResume = false; } else { isOpen = false; }"></div><div class="fixed inset-0" x-show="isOpen" style="display: none;"><div class="w-full h-full bg-white overflow-hidden" id="wizardIframeContainer"><div x-show="isLoading"
10729x-transition:leave="transition ease-in duration-300"
10730x-transition:leave-start="opacity-100"
10731x-transition:leave-end="opacity-0"
10732class="absolute inset-0 flex items-center justify-center bg-white z-10"><div class="flex flex-col items-center gap-4"> <svg class="animate-spin h-12 w-12 text-brand-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> <span class="text-gray-600 font-medium">Loading Designer...</span></div></div> <template x-if="wizardUrl"> <iframe id="wizard_wrapper"
10733:src="wizardUrl"
10734@load="isLoading = false; clearTimeout(window._wizardLoadTimeout);"
10735class="w-full h-full"
10736frameborder="0"
10737allow="clipboard-read; clipboard-write"
10738title="Product Customization Wizard"> </iframe> </template></div></div><div x-show="showResume"
10739x-transition:enter="transition ease-out duration-200"
10740x-transition:enter-start="opacity-0"
10741x-transition:enter-end="opacity-100"
10742x-transition:leave="transition ease-in duration-150"
10743x-transition:leave-start="opacity-100"
10744x-transition:leave-end="opacity-0"
10745class="fixed inset-0 z-[60] flex items-center justify-center p-4"
10746style="display: none;"><div class="absolute inset-0 bg-black bg-opacity-50" @click="showResume = false"></div><div class="relative bg-white rounded-lg shadow-xl max-w-md w-full p-6"
10747x-transition:enter="transition ease-out duration-200"
10748x-transition:enter-start="opacity-0 scale-95"
10749x-transition:enter-end="opacity-100 scale-100"
10750x-transition:leave="transition ease-in duration-150"
10751x-transition:leave-start="opacity-100 scale-100"
10752x-transition:leave-end="opacity-0 scale-95"><div class="flex items-center mb-4"><div class="flex-shrink-0 w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center"> <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path> </svg></div><h3 class="ml-3 text-lg font-semibold text-gray-900">Resume Design?</h3></div><p class="text-gray-600 mb-6">You have an in-progress design. Would you like to pick up where you left off, or start fresh?</p><div class="flex justify-end gap-3"> <button @click="showResume = false; hasSession = false; wizardUrl = ''; isLoading = true; isOpen = true; $nextTick(() => { window._wizardStartNew(pendingUrl); })"
10753class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors">
10754Start New Design </button> <button @click="showResume = false; isOpen = true;"
10755class="px-4 py-2 text-sm font-medium text-white bg-brand-primary rounded-lg hover:bg-brand-primary/90 transition-colors"> Continue Design </button></div></div></div><div x-show="showConfirm"
10756x-transition:enter="transition ease-out duration-200"
10757x-transition:enter-start="opacity-0"
10758x-transition:enter-end="opacity-100"
10759x-transition:leave="transition ease-in duration-150"
10760x-transition:leave-start="opacity-100"
10761x-transition:leave-end="opacity-0"
10762class="fixed inset-0 z-[60] flex items-center justify-center p-4"
10763style="display: none;"><div class="absolute inset-0 bg-black bg-opacity-50" @click="showConfirm = false"></div><div class="relative bg-white rounded-lg shadow-xl max-w-md w-full p-6"
10764x-transition:enter="transition ease-out duration-200"
10765x-transition:enter-start="opacity-0 scale-95"
10766x-transition:enter-end="opacity-100 scale-100"
10767x-transition:leave="transition ease-in duration-150"
10768x-transition:leave-start="opacity-100 scale-100"
10769x-transition:leave-end="opacity-0 scale-95"><div class="flex items-center mb-4"><div class="flex-shrink-0 w-10 h-10 rounded-full bg-amber-100 flex items-center justify-center"> <svg class="w-6 h-6 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path> </svg></div><h3 class="ml-3 text-lg font-semibold text-gray-900">Close Designer</h3></div><p class="text-gray-600 mb-6">Are you sure you want to close the designer? Your design will be saved for this session — you can resume it if you reopen the designer.</p><div class="flex justify-end gap-3"> <button @click="showConfirm = false"
10770class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors"> Cancel </button> <button @click="showConfirm = false; $nextTick(() => { isOpen = false; hasSession = true; window._wizardOnClose(false); })"
10771class="px-4 py-2 text-sm font-medium text-white bg-brand-primary rounded-lg hover:bg-brand-primary/90 transition-colors">
10772Close Designer </button></div></div></div></div>
10772<script> window._wizardLoadTimeout = null;// Internal: load a fresh wizard URL into the iframe
10773window._wizardStartNew = function(url) {
10774var modal = document.getElementById('wizardModal');
10775if (!modal || !modal._x_dataStack) return;
10776var data = modal._x_dataStack[0];
10777data.wizardUrl = '';
10778setTimeout(function() {
10779data.wizardUrl = url;
10780}, 0);
10781// Fallback: if iframe hasn't loaded after 30s, show error state
10782clearTimeout(window._wizardLoadTimeout);
10783window._wizardLoadTimeout = setTimeout(function() {
10784if (data.isLoading && data.isOpen) {
10785console.error('[Wizard] Load timeout — iframe did not respond');
10786data.isLoading = false;
10787data.hasSession = false;
10788var iframe = document.getElementById('wizard_wrapper');
10789if (iframe) {
10790iframe.srcdoc = '<div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;flex-direction:column;gap:16px;color:#374151;">'
10791+ '<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>'
10792+ '<p style="font-size:18px;font-weight:600;margin:0;">Something went wrong</p>'
10793+ '<p style="font-size:14px;color:#6b7280;margin:0;">The designer took too long to load.</p>'
10794+ '<button onclick="window.parent.closeWizardModal(false)" style="margin-top:8px;padding:8px 20px;background:#2563eb;color:#fff;border:none;border-radius:8px;font-size:14px;cursor:pointer;">Close &amp; Try Again</button>'
10795+ '</div>';
10796}
10797}
10798}, 30000);
10799};// Set when a customer signs in or registers inside the wizard iframe.
10800//
10801// The ajax-login module calls parent.LogInParent() across the frame boundary after a
10802// successful login or registration. Defining that name here is what makes the call land
10803// on Zephyr — without it the notification is silently dropped, since this theme has its
10804// own wizard modal and never loads the PCE one that defines LogInParent.
10805window._wizardAuthChanged = false;
10806window.LogInParent = function() {
10807console.log('[Wizard] Customer authenticated inside the iframe');
10808window._wizardAuthChanged = true;
10809};// Internal: handle post-close actions (cart refresh etc)
10810window._wizardOnClose = function(refreshCart) {
10811clearTimeout(window._wizardLoadTimeout);// The page behind the wizard was rendered for a logged-out visitor, so everything
10812// keyed off login state is stale — most visibly the My Account entry in the nav.
10813// A reload also picks up the cart, so it supersedes the refresh below.
10814if (window._wizardAuthChanged) {
10815window._wizardAuthChanged = false;
10816console.log('[Wizard] Auth changed inside wizard, reloading host page');
10817window.location.reload();
10818return;
10819}if (refreshCart && window.Alpine && Alpine.store('cart')) {
10820setTimeout(function() {
10821console.log('[Wizard] Refreshing cart data...');
10822Alpine.store('cart').refresh();
10823}, 300);
10824}
10825};// Global function to open wizard modal
10826window.openWizardModal = function(url) {
10827console.log('[Wizard] Opening modal with URL:', url);
10828var modal = document.getElementById('wizardModal');
10829if (modal && modal._x_dataStack) {
10830var data = modal._x_dataStack[0];
10831// If there's an existing session, ask user what to do
10832if (data.hasSession && data.wizardUrl) {
10833data.pendingUrl = url;
10834data.showResume = true;
10835return;
10836}
10837// Fresh open — load the wizard
10838data.isLoading = true;
10839data.isOpen = true;
10840data.hasSession = false;
10841window._wizardStartNew(url);
10842} else {
10843console.error('[Wizard] Modal not found or Alpine.js not initialized');
10844}
10845};// Allow wizard iframe to close the modal and optionally refresh cart
10846window.closeWizardModal = function(refreshCart) {
10847if (typeof refreshCart === 'undefined') refreshCart = false;
10848console.log('[Wizard] Closing modal, refresh cart:', refreshCart);
10849var modal = document.getElementById('wizardModal');
10850if (modal && modal._x_dataStack) {
10851var data = modal._x_dataStack[0];
10852data.isOpen = false;
10853// If the wizard completed (added to cart), destroy the session
10854if (refreshCart) {
10855data.hasSession = false;
10856data.wizardUrl = '';
10857} else {
10858data.hasSession = true;
10859}
10860window._wizardOnClose(refreshCart);
10861}
10862};// Legacy compatibility: alias for old PCE modal functions
10863window.closeModalWindow = function() {
10864window.closeWizardModal(true);
10865};// Minimal jQuery shim for PCE wizard iframe compatibility
10866// newpce.js calls window.parent.jQuery("iframe#wizard_wrapper").height()
10867if (typeof window.jQuery === 'undefined') {
10868window.jQuery = function(selector) {
10869var el = document.querySelector(selector);
10870return {
10871height: function() { return el ? el.clientHeight : window.innerHeight; }
10872};
10873};
10874}// Called by wizard when user clicks close button - shows confirmation dialog
10875window.requestCloseIframe = function() {
10876console.log('[Wizard] Close requested, showing confirmation');
10877var modal = document.getElementById('wizardModal');
10878if (modal && modal._x_dataStack) {
10879modal._x_dataStack[0].showConfirm = true;
10880}
10881};window.redirectToCart = function() {
10882window.location.href = '/checkout/cart';
10883}; </script>
10883<div x-data="loginModal()"
10884x-show="$store.auth.showLoginModal"
10885x-cloak
10886@keydown.escape.window="$store.auth.showLoginModal = false"
10887class="fixed inset-0 z-50 overflow-y-auto"
10888style="display: none;"><div x-show="$store.auth.showLoginModal"
10889x-transition:enter="transition ease-out duration-300"
10890x-transition:enter-start="opacity-0"
10891x-transition:enter-end="opacity-100"
10892x-transition:leave="transition ease-in duration-200"
10893x-transition:leave-start="opacity-100"
10894x-transition:leave-end="opacity-0"
10895@click="if (Date.now() - $store.auth._loginOpenedAt > 300) $store.auth.showLoginModal = false"
10896class="fixed inset-0 bg-black bg-opacity-50"></div><div class="flex min-h-full items-center justify-center p-4"><div x-show="$store.auth.showLoginModal"
10897x-transition:enter="transition ease-out duration-300"
10898x-transition:enter-start="opacity-0 transform scale-95"
10899x-transition:enter-end="opacity-100 transform scale-100"
10900x-transition:leave="transition ease-in duration-200"
10901x-transition:leave-start="opacity-100 transform scale-100"
10902x-transition:leave-end="opacity-0 transform scale-95"
10903@click.stop
10904class="relative bg-white rounded-lg shadow-xl w-full max-w-md"> <button @click="$store.auth.showLoginModal = false"
10905class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> </button><div x-show="currentForm === 'login'" class="p-8"><div class="mb-4 mt-2 min-h-[56px]"><div x-show="message" x-text="message"
10906:class="messageType === 'error' ? 'bg-brand-danger/10 text-brand-danger border-brand-danger/30' : 'bg-brand-success/10 text-brand-success border-brand-success/30'"
10907class="p-4 rounded-md border text-sm"></div></div><div class="flex justify-center mb-6"> <img src="https://cdn.simplystamps.com/skin/frontend/zephyr/sstamps-modern/images/SS-Desktop-logo.svg"
10908alt="Simply Stamps"
10909class="h-16 w-auto"></div><form @submit.prevent="submitLogin" id="login-form"> <input type="hidden" name="form_key" :value="formKey" /><div class="space-y-4"><div> <label for="login-email" class="block text-sm font-medium text-gray-700 mb-1"> Email Address <span class="text-brand-danger">*</span> </label> <input type="email"
10910id="login-email"
10911name="login[username]"
10912x-model="loginForm.username"
10913required
10914class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div><div> <label for="login-password" class="block text-sm font-medium text-gray-700 mb-1"> Password <span class="text-brand-danger">*</span> </label> <input type="password"
10915id="login-password"
10916name="login[password]"
10917x-model="loginForm.password"
10918required
10919class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div><div class="flex items-center"> <input type="checkbox"
10920id="login-remember"
10921name="persistent_remember_me"
10922x-model="loginForm.rememberMe"
10923class="w-4 h-4 text-brand-primary border-gray-300 rounded focus:ring-brand-primary"> <label for="login-remember" class="ml-2 text-sm text-gray-700"> Remember Me </label></div></div><div class="mt-6 space-y-3"> <button type="submit"
10924:disabled="loading"
10925class="w-full h-[42px] flex items-center justify-center bg-brand-primary hover:bg-brand-primary-hover disabled:bg-brand-primary/70 text-white font-medium py-2 px-4 rounded-md transition-colors"> <span x-show="!loading">Log In</span> <span x-show="loading" class="flex items-center justify-center"> <svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> </span> </button><button type="button"
10926@click="switchForm('register')"
10927class="w-full bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-2 px-4 roun
10927ded-md transition-colors"> Create an Account </button></div><div class="mt-4 text-center"> <button type="button"
10928@click="switchForm('forgot')"
10929class="text-sm text-brand-primary hover:text-brand-primary-hover transition-colors"> Forgot Your Password? </button></div></form></div><div x-show="currentForm === 'register'" class="p-8"><div class="mb-4 mt-2 min-h-[56px]"><div x-show="message" x-text="message"
10930:class="messageType === 'error' ? 'bg-brand-danger/10 text-brand-danger border-brand-danger/30' : 'bg-brand-success/10 text-brand-success border-brand-success/30'"
10931class="p-4 rounded-md border text-sm"></div></div><div class="flex justify-center mb-6"> <img src="https://cdn.simplystamps.com/skin/frontend/zephyr/sstamps-modern/images/SS-Desktop-logo.svg"
10932alt="Simply Stamps"
10933class="h-16 w-auto"></div><form @submit.prevent="submitRegister" id="register-form"> <input type="hidden" name="form_key" :value="formKey" /><div class="space-y-4"><div> <label for="register-firstname" class="block text-sm font-medium text-gray-700 mb-1"> First Name <span class="text-brand-danger">*</span> </label> <input type="text"
10934id="register-firstname"
10935name="firstname"
10936x-model="registerForm.firstname"
10937required
10938class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div><div> <label for="register-lastname" class="block text-sm font-medium text-gray-700 mb-1"> Last Name <span class="text-brand-danger">*</span> </label> <input type="text"
10939id="register-lastname"
10940name="lastname"
10941x-model="registerForm.lastname"
10942required
10943class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div><div> <label for="register-email" class="block text-sm font-medium text-gray-700 mb-1"> Email Address <span class="text-brand-danger">*</span> </label> <input type="email"
10944id="register-email"
10945name="email"
10946x-model="registerForm.email"
10947required
10948class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div><div> <label for="register-password" class="block text-sm font-medium text-gray-700 mb-1"> Password <span class="text-brand-danger">*</span> </label> <input type="password"
10949id="register-password"
10950name="password"
10951x-model="registerForm.password"
10952required
10953class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div><div> <label for="register-confirmation" class="block text-sm font-medium text-gray-700 mb-1"> Confirm Password <span class="text-brand-danger">*</span> </label> <input type="password"
10954id="register-confirmation"
10955name="confirmation"
10956x-model="registerForm.confirmation"
10957required
10958class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div><div class="flex items-center"> <input type="checkbox"
10959id="register-newsletter"
10960name="is_subscribed"
10961x-model="registerForm.newsletter"
10962value="1"
10963class="w-4 h-4 text-brand-primary border-gray-300 rounded focus:ring-brand-primary"> <label for="register-newsletter" class="ml-2 text-sm text-gray-700"> Sign Up for Newsletter </label></div></div><div class="mt-6 space-y-3"> <button type="submit"
10964:disabled="loading"
10965class="w-full h-[42px] flex items-center justify-center bg-brand-primary hover:bg-brand-primary-hover disabled:bg-brand-primary/70 text-white font-medium py-2 px-4 rounded-md transition-colors"> <span x-show="!loading">Create Account</span> <span x-show="loading" class="flex items-center justify-center"> <svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> </span> </button><button type="button"
10966@click="switchForm('login')"
10967class="w-full bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-2 px-4 roun
10967ded-md transition-colors"> Back to Log In </button></div></form></div><div x-show="currentForm === 'forgot'" class="p-8"><div class="mb-4 mt-2 min-h-[56px]"><div x-show="message" x-text="message"
10968:class="messageType === 'error' ? 'bg-brand-danger/10 text-brand-danger border-brand-danger/30' : 'bg-brand-success/10 text-brand-success border-brand-success/30'"
10969class="p-4 rounded-md border text-sm"></div></div><div class="flex justify-center mb-6"> <img src="https://cdn.simplystamps.com/skin/frontend/zephyr/sstamps-modern/images/SS-Desktop-logo.svg"
10970alt="Simply Stamps"
10971class="h-16 w-auto"></div><h2 class="text-2xl font-bold text-gray-900 mb-6 text-center">Forgot Your Password?</h2><p class="text-sm text-gray-600 mb-6"> Enter your email address below and we will send you a link to reset your password.</p><form @submit.prevent="submitForgot" id="forgot-form"> <input type="hidden" name="form_key" :value="formKey" /><div class="space-y-4"><div> <label for="forgot-email" class="block text-sm font-medium text-gray-700 mb-1"> Email Address <span class="text-brand-danger">*</span> </label> <input type="email"
10972id="forgot-email"
10973name="email"
10974x-model="forgotForm.email"
10975required
10976class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-brand-primary focus:border-brand-primary transition-colors"></div></div><div class="mt-6 space-y-3"> <button type="submit"
10977:disabled="loading"
10978class="w-full h-[42px] flex items-center justify-center bg-brand-primary hover:bg-brand-primary-hover disabled:bg-brand-primary/70 text-white font-medium py-2 px-4 rounded-md transition-colors"> <span x-show="!loading">Submit</span> <span x-show="loading" class="flex items-center justify-center"> <svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> </span> </button><button type="button"
10979@click="switchForm('login')"
10980class="w-full bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-2 px-4 rounded-md transition-colors"> Back to Log In </button></div></form></div></div></div></div>
10980<script> function loginModal() {
10981return {
10982currentForm: 'login',
10983loading: false,
10984message: '',
10985messageType: 'error',
10986formKey: 'e3374479ab86b5888526972805eb3d8f',loginForm: {
10987username: '',
10988password: '',
10989rememberMe: false
10990},registerForm: {
10991firstname: '',
10992lastname: '',
10993email: '',
10994password: '',
10995confirmation: '',
10996newsletter: false
10997},forgotForm: {
10998email: ''
10999},switchForm(formName) {
11000this.currentForm = formName;
11001this.message = '';
11002this.loading = false;
11003},async submitLogin() {
11004this.loading = true;
11005this.message = '';const formData = new FormData();
11006formData.append('form_key', this.formKey);
11007formData.append('login[username]', this.loginForm.username);
11008formData.append('login[password]', this.loginForm.password);
11009if (this.loginForm.rememberMe) {
11010formData.append('persistent_remember_me', 'on');
11011}try {
11012const response = await fetch('https://www.simplystamps.com/zephyr/auth/login/', {
11013method: 'POST',
11014body: formData,
11015credentials: 'same-origin'
11016});const data = await response.json();if (data.success) {
11017if (typeof zGA4 === 'function') {
11018zGA4('login', { method: 'site' });
11019if (data.hashedUserData) {
11020zGA4('set_user_data', { user_data: data.hashedUserData });
11021}
11022}
11023window.location.reload();
11024} else {
11025await new Promise(resolve => setTimeout(resolve, 800));
11026this.message = data.error || 'Invalid login or password.';
11027this.messageType = 'error';
11028this.loading = false;
11029}
11030} catch (error) {
11031await new Promise(resolve => setTimeout(resolve, 800));
11032this.message = 'An error occurred. Please try again.';
11033this.messageType = 'error';
11034this.loading = false;
11035}
11036},async submitRegister() {
11037this.loading = true;
11038this.message = '';if (this.registerForm.password !== this.registerForm.confirmation) {
11039await new Promise(resolve => setTimeout(resolve, 800));
11040this.message = 'Passwords do not match.';
11041this.messageType = 'error';
11042this.loading = false;
11043return;
11044}const formData = new FormData();
11045formData.append('form_key', this.formKey);
11046formData.append('firstname', this.registerForm.firstname);
11047formData.append('lastname', this.registerForm.lastname);
11048formData.append('email', this.registerForm.email);
11049formData.append('password', this.registerForm.password);
11050formData.append('confirmation', this.registerForm.confirmation);
11051if (this.registerForm.newsletter) {
11052formData.append('is_subscribed', '1');
11053}const recaptchaToken = await getZephyrRecaptchaToken('register');
11054if (recaptchaToken) {
11055formData.append('recaptcha_response', recaptchaToken);
11056}try {
11057const response = await fetch('https://www.simplystamps.com/zephyr/auth/register/', {
11058method: 'POST',
11059body: formData,
11060credentials: 'same-origin'
11061});const data = await response.json();if (data.success) {
11062if (typeof zGA4 === 'function') {
11063zGA4('sign_up', { method: 'site' });
11064if (data.hashedUserData) {
11065zGA4('set_user_data', { user_data: data.hashedUserData });
11066}
11067}
11068window.location.reload();
11069} else {
11070await new Promise(resolve => setTimeout(resolve, 800));
11071this.message = data.error || 'There was a problem with your registration.';
11072this.messageType = 'error';
11073this.loading = false;
11074}
11075} catch (error) {
11076await new Promise(resolve => setTimeout(resolve, 800));
11077this.message = 'An error occurred. Please try again.';
11078this.messageType = 'error';
11079this.loading = false;
11080}
11081},async submitForgot() {
11082this.loading = true;
11083this.message = '';const formData = new FormData();
11084formData.append('form_key', this.formKey);
11085formData.append('email', this.forgotForm.email);// reCAPTCHA v3. The forgotpasswordpost endpoint rejects a token-less post
11086// outright, so no token means no reset email.
11087const recaptchaToken = await getZephyrRecaptchaToken('forgot');
11088if (recaptchaToken) {
11089formData.append('recaptcha_response', recaptchaToken);
11090}try {
11091const response = await fetch('https://www.simplystamps.com/customer/account/forgotpasswordpost/', {
11092method: 'POST',
11093body: formData,
11094credentials: 'same-origin'
11095});const text = await response.text();if (text.includes('success-msg') || text.includes('password-reset')) {
11096this.message = 'If there is an account associated with this email, you will receive an email with a link to reset your password.';
11097this.messageType = 'success';
11098this.forgotForm.email = '';
11099} else if (text.includes('error-msg')) {
11100const parser = new DOMParser();
11101const doc = parser.parseFromString(text, 'text/html');
11102const errorMsg = doc.querySelector('.error-msg');
11103this.message = errorMsg ? errorMsg.textContent.trim() : 'Unable to send password reset email.';
11104this.messageType = 'error';
11105} else {
11106this.message = 'If there is an account associated with this email, you will receive an email with a link to reset your password.';
11107this.messageType = 'success';
11108}
11109} catch (error) {
11110this.message = 'An error occurred. Please try again.';
11111this.messageType = 'error';
11112} finally {
11113this.loading = false;
11114}
11115}
11116}
11117} </script>
11117 <input id="searchFocusProxy" type="text" aria-hidden="true"
11118style="position:fixed;top:0;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none;" /><div id="searchModal"
11119x-data="searchModal()"
11120x-show="$store.search.isOpen"
11121x-cloak
11122@keydown.escape.window="$store.search.close()"
11123class="fixed inset-0 z-[70] overflow-hidden"
11124style="display: none;"><div class="fixed inset-0 bg-black bg-opacity-75 backdrop-blur-sm transition-opacity hidden md:block"
11125x-show="$store.search.isOpen"
11126x-transition:enter="transition ease-out duration-300"
11127x-transition:enter-start="opacity-0"
11128x-transition:enter-end="opacity-100"
11129x-transition:leave="transition ease-in duration-200"
11130x-transition:leave-start="opacity-100"
11131x-transition:leave-end="opacity-0"
11132@click="$store.search.close()"></div><div class="fixed inset-0 flex items-start justify-center md:pt-12 md:px-4"
11133@mousedown.self="$store.search.close()"><div x-show="$store.search.isOpen"
11134class="relative bg-white w-full h-full flex flex-col md:h-auto md:max-w-2xl md:rounded-xl md:shadow-2xl"
11135x-ref="modalContent"><div class="flex items-center justify-between p-4 md:px-6 border-b border-gray-200 shrink-0"> <a href="https://www.simplystamps.com/" class="block"> <img src="https://cdn.simplystamps.com/skin/frontend/zephyr/sstamps-modern/images/SS-Desktop-logo.svg"
11136alt="Simply Stamps"
11137class="h-8 w-auto" /> </a> <button @click="$store.search.close()"
11138class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"> <svg class="w-7 h-7 md:w-6 md:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> </button></div><div class="px-4 pt-4 md:px-6 md:pt-6 shrink-0"><form @submit.prevent="submitSearch" class="relative"><div class="relative flex items-center"><div class="absolute left-4 flex items-center pointer-events-none"> <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
11139d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg></div> <input type="text"
11140x-ref="searchInput"
11141x-model="query"
11142@input="onInput()"
11143@keydown.down.prevent="selectNext"
11144@keydown.tab.prevent="selectNext"
11145@keydown.up.prevent="selectPrev"
11146@keydown.enter.prevent="selectCurrent"
11147placeholder="Search products, articles..."
11148class="w-full pl-12 pr-24 py-4 text-lg border-2 border-gray-200 rounded-xl
11149focus:border-brand-primary focus:outline-none focus:ring-0
11150transition-colors"
11151autocomplete="off" /><div x-show="$store.search.isLoading"
11152class="absolute right-[4.5rem] md:right-[5.5rem] flex items-center"> <svg class="animate-spin h-5 w-5 text-brand-primary" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg></div> <button x-show="query.length > 0 && !$store.search.isLoading"
11153@click="clearSearch()"
11154type="button"
11155class="absolute right-[4.5rem] md:right-[5.5rem] flex items-center text-gray-400 hover:text-gray-600"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> <button @click="submitSearch()"
11156type="button"
11157:disabled="query.length < minChars"
11158class="absolute right-2 flex items-center justify-center h-10 px-3 bg-brand-primary text-white
11159rounded-lg hover:bg-brand-primary-hover transition-colors
11160disabled:opacity-40 disabled:cursor-not-allowed"> <svg class="w-5 h-5 md:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
11161d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg> <span class="hidden md:inline text-sm font-medium">Search</span> </button></div></form></div><div x-show="!$store.search.hasSearched" class="px-4 pt-4 pb-2 md:px-6"><h3 class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3"> Popular Searches</h3><div class="flex flex-wrap gap-2"> <button type="button"
11162@click.prevent="selectPopular('self-inking stamp')"
11163class="inline-flex items-center px-3 py-2 bg-gray-100 hover:bg-gray-200
11164rounded-lg text-sm font-medium text-gray-700 transition-colors cursor-pointer"> <svg class="w-4 h-4 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
11165d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg> self-inking stamp </button> <button type="button"
11166@click.prevent="selectPopular('wood handle stamp')"
11167class="inline-flex items-center px-3 py-2 bg-gray-100 hover:bg-gray-200
11168rounded-lg text-sm font-medium text-gray-700 transition-colors cursor-pointer"> <svg class="w-4 h-4 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
11169d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg> wood handle stamp </button> <button type="button"
11170@click.prevent="selectPopular('library embosser')"
11171class="inline-flex items-center px-3 py-2 bg-gray-100 hover:bg-gray-200
11172rounded-lg text-sm font-medium text-gray-700 transition-colors cursor-pointer"> <svg class="w-4 h-4 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
11173d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg> library embosser </button> <button type="button"
11174@click.prevent="selectPopular('wedding address stamps')"
11175class="inline-flex items-center px-3 py-2 bg-gray-100 hover:bg-gray-200
11176rounded-lg text-sm font-medium text-gray-700 transition-colors cursor-pointer"> <svg class="w-4 h-4 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
11177d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg> wedding address stamps </button></div></div><div x-show="$store.search.hasSearched"
11178x-ref="resultsPane"
11179class="flex-1 flex flex-col overflow-y-auto md:max-h-[60vh]"> <template x-if="results.suggestions && results.suggestions.length > 0"><div class="order-1 px-4 pt-2 pb-0.5"> <template x-for="(suggestion, index) in visibleSuggestions" :key="suggestion.text"> <a :href="'https://www.simplystamps.com/catalogsearch/result/?q=' + encodeURIComponent(suggestion.text)"
11180:class="{ 'bg-blue-50 text-blue-900': selectedIndex === index }"
11181class="flex items-center gap-3 px-3 py-1.5 rounded-lg
11182hover:bg-gray-50 transition-colors cursor-pointer group"
11183@mousemove="selectedIndex = index"
11184@click.prevent="selectSuggestion(suggestion)"> <svg class="w-4 h-4 text-gray-300 shrink-0" :class="{ 'text-blue-400': selectedIndex === index }"
11185fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
11186d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg> <span class="flex-1 text-sm text-gray-700"> <span x-show="suggestion.fuzzy" class="text-xs text-gray-400 italic mr-1">did you mean:</span> <span x-html="highlightMatch(suggestion.text, typedQuery)"></span> </span> </a> </template></div> </template><div x-show="showSkeleton"
11187x-transition:enter="transition ease-out duration-200"
11188x-transition:enter-start="opacity-0"
11189x-transition:enter-end="opacity-100"
11190x-transition:leave="transition ease-in duration-150"
11191x-transition:leave-start="opacity-100"
11192x-transition:leave-end="opacity-0"
11193class="order-2 animate-pulse p-4 border-t border-gray-300"><div class="space-y-1"><div class="flex items-center gap-4 p-3"><div class="w-14 h-14 bg-gray-200 rounded-lg shrink-0"></div><div class="flex-1 space-y-2"><div class="h-4 bg-gray-200 rounded w-3/4"></div><div class="h-3 bg-gray-200 rounded w-1/2"></div></div></div><div class="flex items-center gap-4 p-3"><div class="w-14 h-14 bg-gray-200 rounded-lg shrink-0"></div><div class="flex-1 space-y-2"><div class="h-4 bg-gray-200 rounded w-2/3"></div><div class="h-3 bg-gray-200 rounded w-2/5"></div></div></div><div class="flex items-center gap-4 p-3"><div class="w-14 h-14 bg-gray-200 rounded-lg shrink-0"></div><div class="flex-1 space-y-2"><div class="h-4 bg-gray-200 rounded w-4/5"></div><div class="h-3 bg-gray-200 rounded w-1/3"></div></div></div></div></div> <template x-if="results.products && results.products.length > 0"><div class="order-4 p-4 transition-opacity duration-150"
11194:class="{
11195'border-t border-gray-300': suggestionsCount > 0 || (blogFirst && blogCount > 0),
11196'opacity-40 pointer-events-none': $store.search.isLoading
11197}"><h3 class="text-xs font-semibold text-gray-600 uppercase tracking-wider mb-2 px-1"> Products</h3><div class="space-y-1"> <template x-for="(product, index) in results.products" :key="product.url"> <a :href="product.url"
11198:class="{ 'bg-blue-50 border-blue-200': selectedIndex === (productsOffset + index) }"
11199class="flex items-center gap-4 p-3 rounded-lg border border-transparent
11200hover:bg-gray-50 hover:border-gray-200 transition-colors cursor-pointer"
11201@mousemove="selectedIndex = productsOffset + index"><div class="flex-shrink-0 w-14 h-14 bg-gray-100 rounded-lg overflow-hidden"> <img :src="product.image" :alt="product.name"
11202class="w-full h-full object-cover"
11203onerror="this.src='https://cdn.simplystamps.com/skin/frontend/zephyr/default/images/catalog/product/placeholder/small_image.jpg'" /></div><div class="flex-1 min-w-0"><h4 class="font-medium text-gray-900 text-sm truncate" x-html="highlightMatch(product.name, typedQuery)"></h4><p x-show="product.description"
11204class="text-xs text-gray-500 line-clamp-1 mt-0.5"
11205x-text="product.description"></p></div> </a> </template></div></div> </template> <template x-if="results.blog && results.blog.length > 0"><div class="p-4 transition-opacity duration-150"
11206:class="{
11207'order-3': blogFirst,
11208'order-5': !blogFirst,
11209'border-t border-gray-300': !blogFirst || suggestionsCount > 0,
11210'opacity-40 pointer-events-none': $store.search.isLoading
11211}"><h3 class="text-xs font-semibold text-gray-600 uppercase tracking-wider mb-2 px-1"> From the blog</h3><div class="space-y-1"> <template x-for="(post, index) in results.blog" :key="post.url"> <a :href="post.url"
11212:class="{ 'bg-blue-50 border-blue-200': selectedIndex === (blogOffset + index) }"
11213class="flex items-center gap-4 p-3 rounded-lg border border-transparent
11214hover:bg-gray-50 hover:border-gray-200 transition-colors cursor-pointer"
11215@mousemove="selectedIndex = blogOffset + index"><div class="flex-shrink-0 w-14 h-14 bg-gray-100 rounded-lg overflow-hidden flex items-center justify-center"> <template x-if="post.image"> <img :src="post.image" :alt="post.name" class="w-full h-full object-cover" /> </template> <template x-if="!post.image"> <svg class="w-6 h-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
11216d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"></path> </svg> </template></div><div class="flex-1 min-w-0"><h4 class="font-medium text-gray-900 text-sm truncate" x-html="highlightMatch(post.name, typedQuery)"></h4><p x-show="post.description"
11217class="text-xs text-gray-500 line-clamp-1 mt-0.5"
11218x-text="post.description"></p></div> <span class="hidden sm:inline-flex shrink-0 items-center rounded-md bg-gray-100 px-2 py-0.5 text-[11px] font-medium uppercase tracking-wide text-gray-500"> Article </span> </a> </template></div></div> </template> <template x-if="results.categories && results.categories.length > 0"><div class="order-6 p-4 border-t border-gray-300"><h3 class="text-xs font-semibold text-gray-600 uppercase tracking-wider mb-2 px-1"> Categories</h3><div class="flex flex-wrap gap-2"> <template x-for="category in results.categories" :key="category.url"> <a :href="category.url"
11219class="inline-flex items-center px-3 py-2 bg-gray-100 hover:bg-gray-200
11220rounded-lg text-sm font-medium text-gray-700 transition-colors"> <svg class="w-4 h-4 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
11221d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path> </svg> <span x-text="category.name"></span> </a> </template></div></div> </template></div><div class="p-4 border-t border-gray-300 bg-gray-50 md:rounded-b-xl shrink-0"><div class="flex items-center justify-between"><div class="hidden md:flex items-center gap-6 text-xs text-gray-400"> <span class="flex items-center gap-1"> <kbd class="px-2 py-1 bg-gray-200 rounded text-gray-600">&#8593;</kbd> <kbd class="px-2 py-1 bg-gray-200 rounded text-gray-600">&#8595;</kbd> <kbd class="px-2 py-1 bg-gray-200 rounded text-gray-600">Tab</kbd> to navigate </span> <span class="flex items-center gap-1"> <kbd class="px-2 py-1 bg-gray-200 rounded text-gray-600">Enter</kbd> to select </span> <span class="flex items-center gap-1"> <kbd class="px-2 py-1 bg-gray-200 rounded text-gray-600">Esc</kbd> to close </span></div> <a x-show="query.length >= minChars"
11222:href="'https://www.simplystamps.com/catalogsearch/result/?q=' + encodeURIComponent(query)"
11223class="flex items-center gap-1 text-sm font-medium text-brand-primary hover:text-brand-primary-hover
11224transition-colors ml-auto">
11225View All Results <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> </svg> </a></div></div></div></div></div>
11225<script src="https://cdn.simplystamps.com/skin/frontend/zephyr/default/js/components/search-modal.js?v=1126"></script>
11225 
11225<script> searchModalConfig.minChars = 3;
11226searchModalConfig.searchUrl = 'https://www.simplystamps.com/searchautocomplete/ajax/get/';
11227searchModalConfig.suggestUrl = 'https://www.simplystamps.com/zephyr/search/suggest/';
11228searchModalConfig.resultUrl = 'https://www.simplystamps.com/catalogsearch/result/';
11229searchModalConfig.formKey = 'e3374479ab86b5888526972805eb3d8f';
11230searchModalConfig.suggestedQueries = ["self-inking stamp","wood handle stamp","library embosser","wedding address stamps"];
11231searchModalConfig.blogFirst = false; </script>
11231<style> .line-clamp-1 {
11232overflow: hidden;
11233display: -webkit-box;
11234-webkit-box-orient: vertical;
11235-webkit-line-clamp: 1;
11236} </style><div
11237x-data="{
11238visible: false,
11239getCookie(name) {
11240const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
11241return match ? match[2] : null;
11242},
11243setCookie(name, value) {
11244const d = new Date();
11245d.setTime(d.getTime() + (30 * 24 * 60 * 60 * 1000));
11246document.cookie = name + '=' + value + ';expires=' + d.toUTCString() + ';path=/';
11247},
11248init() {
11249if (!this.getCookie('cookie_banner_seen')) {
11250this.visible = true;
11251setTimeout(() => { this.setCookie('cookie_banner_seen', 'seen'); }, 1000);
11252}
11253},
11254dismiss(choice) {
11255this.setCookie('cookie_banner_seen', choice || 'dismissed');
11256this.visible = false;
11257}
11258}"
11259x-show="visible"
11260x-transition:enter="transition ease-out duration-300"
11261x-transition:enter-start="translate-y-full opacity-0"
11262x-transition:enter-end="translate-y-0 opacity-100"
11263x-transition:leave="transition ease-in duration-200"
11264x-transition:leave-start="translate-y-0 opacity-100"
11265x-transition:leave-end="translate-y-full opacity-0"
11266x-cloak
11267class="fixed bottom-0 inset-x-0 z-50 bg-white border-t border-gray-200"
11268role="dialog"
11269aria-label="Cookie consent"
11270><div class="flex flex-col sm:flex-row items-center justify-center gap-3 px-4 sm:px-6 py-3 text-center"><p class="text-sm text-gray-600 leading-relaxed"> Our website relies on cookies to provide your shopping experience.
11271By using our site, you consent to our use of cookies.
11272For more information, please review our <a href="https://www.simplystamps.com/privacy-policy/" class="underline text-gray-900 hover:text-gray-700">Privacy Policy</a>.</p><div class="flex items-center gap-3 shrink-0"> <button
11273@click="dismiss('declined')"
11274type="button"
11275class="flex items-center gap-1.5 rounded px-4 py-1.5 text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors"
11276> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg> Close </button> <button
11277@click="dismiss('accepted')"
11278type="button"
11279class="flex items-center gap-1.5 rounded px-4 py-1.5 text-sm font-medium text-white transition-opacity hover:opacity-90"
11280style="background-color: var(--brand-primary);"
11281> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg> Accept </button></div></div></div> 
11281<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
11281</div></div><link rel="preload" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"></noscript> 
11281<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js" defer></script>
11281 
11281<script src="https://cdn.simplystamps.com/skin/frontend/zephyr/default/js/components/rileyprint.js?v=1126" defer></script>
11281 
11281<script src="https://cdn.simplystamps.com/skin/frontend/zephyr/default/js/components/product-media.js?v=1126" defer></script>
11281 
11281<script src="https://cdn.simplystamps.com/skin/frontend/zephyr/default/js/components/file-upload.js?v=1126" defer></script>
11281 
11281<script src="https://cdn.simplystamps.com/skin/frontend/zephyr/default/js/components/cart-drawer.js?v=1126" defer></script>
11281 
11281<script> // Configure cart drawer URLs. cart-drawer.js is now deferred (its var cartDrawerConfig
11282// default object still runs before DOMContentLoaded per spec), and every read of
11283// cartDrawerConfig.* inside cart-drawer.js happens lazily inside user-triggered
11284// handlers (open drawer, add/remove item, checkout) — none of it runs at
11285// alpine:init — so it's safe to set these values on DOMContentLoaded too.
11286document.addEventListener('DOMContentLoaded', function() {
11287cartDrawerConfig.getItemsUrl = 'https://www.simplystamps.com/zephyr/cart/getItems/';
11288cartDrawerConfig.deleteUrl = 'https://www.simplystamps.com/checkout/cart/delete/';
11289cartDrawerConfig.removeItemsUrl = 'https://www.simplystamps.com/zephyr/cart/removeItems/';
11290cartDrawerConfig.updatePostUrl = 'https://www.simplystamps.com/checkout/cart/updatePost/';
11291cartDrawerConfig.clearUrl = 'https://www.simplystamps.com/zephyr/cart/clear/';
11292cartDrawerConfig.checkoutUrl = 'https://www.simplystamps.com/checkout/onepage/';
11293cartDrawerConfig.cartUrl = 'https://www.simplystamps.com/checkout/cart/';
11294cartDrawerConfig.formKey = 'e3374479ab86b5888526972805eb3d8f';
11295// Cart drawer feature settings (from admin config)
11296cartDrawerConfig.enableCheckboxDelete = true;
11297cartDrawerConfig.enableIndividualDelete = true;
11298cartDrawerConfig.showClearCart = true;
11299cartDrawerConfig.checkoutButtonText = 'Proceed to Checkout';
11300}); </script>
11300 
11300<script src="https://cdn.simplystamps.com/skin/frontend/zephyr/default/js/components/cart-upsell.js?v=1126"></script>
11300 
11300<script> // Configure cart upsell popup
11301cartUpsellConfig.getUpsellsUrl = 'https://www.simplystamps.com/zephyr/cart/getUpsells/';
11302cartUpsellConfig.addUpsellsUrl = 'https://www.simplystamps.com/zephyr/cart/addUpsells/';
11303cartUpsellConfig.checkoutUrl = 'https://www.simplystamps.com/checkout/onepage/';
11304cartUpsellConfig.formKey = 'e3374479ab86b5888526972805eb3d8f';
11305cartUpsellConfig.enabled = true;
11306cartUpsellConfig.storeId = '5';
11307cartUpsellConfig.devMode = false; </script>
11307<script> // Fetch a reCAPTCHA v3 token, lazy-loading Google's api.js on first use so it
11308// adds no page weight until a protected form is actually submitted. Shared by
11309// every Zephyr form protected by v3 (registration, contact, quote), which is
11310// also why it must be the ONLY thing loading recaptcha on modern pages — a
11311// v3 key can't execute against a grecaptcha client created by a bare v2
11312// api.js load, so don't reintroduce v2 script tags into modern templates.
11313// Resolves null when reCAPTCHA is disabled or the script/execution fails —
11314// the server decides what to do with a missing token. Never rejects or hangs.
11315window.zephyrRecaptchaKey = '6Lem-q0gAAAAAMhOqUz2tke2IN3t0Pt37x5oqRq3';
11316window.getZephyrRecaptchaToken = function(action) {
11317var key = window.zephyrRecaptchaKey;
11318if (!key) return Promise.resolve(null);
11319return new Promise(function(resolve) {
11320var settled = false;
11321var done = function(token) { if (!settled) { settled = true; resolve(token || null); } };
11322// Whatever happens, never leave the submit hanging
11323setTimeout(function() { done(null); }, 15000);function execute() {
11324try {
11325grecaptcha.ready(function() {
11326try {
11327grecaptcha.execute(key, { action: action })
11328.then(done, function() { done(null); });
11329} catch (e) { done(null); }
11330});
11331} catch (e) { done(null); }
11332}if (window.grecaptcha) { execute(); return; }
11333var s = document.createElement('script');
11334s.src = 'https://www.google.com/recaptcha/api.js?render=' + key;
11335s.onload = execute;
11336s.onerror = function() { done(null); };
11337document.head.appendChild(s);
11338});
11339}; </script>
11339 
11339<script src="https://cdn.simplystamps.com/skin/frontend/zephyr/default/js/components/promo-code.js?v=1126"></script>
11339 
11339<script> // Configure promo code URLs
11340promoCodeConfig.applyCouponUrl = 'https://www.simplystamps.com/zephyr/cart/applyCoupon/';
11341promoCodeConfig.removeCouponUrl = 'https://www.simplystamps.com/zephyr/cart/removeCoupon/';
11342promoCodeConfig.formKey = 'e3374479ab86b5888526972805eb3d8f'; </script>
11342 
11342<script>function setLocation(url){window.location.href=url;}</script>
11342 
11342<script src="https://cdn.simplystamps.com/skin/frontend/zephyr/default/js/alpine.min.js?v=1126" defer></script>
11342 
11342<script> document.addEventListener('alpine:init', () => {
11343// Global UI state - controls visibility of shared UI elements
11344Alpine.store('ui', {
11345hideHeader: false,
11346// Header scroll state (updated by stickyHeader component)
11347headerVisible: true,
11348headerHeight: 0,// Call this when opening fullscreen overlays (lightbox, modals, etc.)
11349enterFullscreen() {
11350this.hideHeader = true;
11351},// Call this when closing fullscreen overlays
11352exitFullscreen() {
11353this.hideHeader = false;
11354},// Get the effective top position for fixed elements below the header
11355getHeaderOffset() {
11356// If header is hidden (scroll or fullscreen), no offset needed
11357if (this.hideHeader || !this.headerVisible) {
11358return 0;
11359}
11360return this.headerHeight;
11361}
11362});Alpine.store('auth', {
11363showLoginModal: false,
11364showRefreshOverlay: false,
11365refreshMessage: '',
11366isLoggedIn: false,
11367isStaff: false,
11368adminEditBase: '',
11369customerName: '',
11370customerEmail: '',
11371companyStoreUrl: '',
11372cartCount: 0,
11373cartSubtotal: 0,
11374formKey: '',
11375ready: false,
11376_loginOpenedAt: 0,init() {
11377// State is hydrated via AJAX so the page HTML stays static/compressible.
11378this.fetchStatus();
11379},/**
11380* Fetch auth status from session via AJAX.
11381* Primary hydration path (called from init()), and also used
11382* after login/logout to refresh state reactively.
11383*/
11384fetchStatus() {
11385var self = this;
11386fetch('https://www.simplystamps.com/zephyr/auth/status/', {
11387credentials: 'same-origin',
11388headers: { 'X-Requested-With': 'XMLHttpRequest' }
11389})
11390.then(function(r) { return r.json(); })
11391.then(function(data) {
11392self.isLoggedIn = !!data.isLoggedIn;
11393self.isStaff = !!data.isStaff;
11394self.adminEditBase = data.adminEditBase || '';
11395self.customerName = data.customerName || '';
11396self.customerEmail = data.customerEmail || '';
11397self.companyStoreUrl = data.companyStoreUrl || '';
11398self.cartCount = data.cartCount || 0;
11399self.cartSubtotal = data.cartSubtotal || 0;
11400self.formKey = data.formKey || '';// Hydrate the cart store's badge count / free-shipping subtotal from
11401// this same response — the async equivalent of the inline global
11402// state object the ESI block used to provide synchronously.
11403if (window.Alpine && Alpine.store('cart')) {
11404var cartStore = Alpine.store('cart');
11405cartStore.itemCount = data.cartCount || 0;
11406cartStore.isEmpty = !(data.cartCount > 0);
11407cartStore.subtotalNumeric = data.cartSubtotal || 0;
11408cartStore._esiReady = true;
11409}
11410if (data.messages && data.messages.length && window.Alpine) {
11411var flash = Alpine.store('flash');
11412for (var i = 0; i < data.messages.length; i++) {
11413if (flash && flash.addMessage) flash.addMessage(data.messages[i].type, data.messages[i].text);
11414}
11415}
11416self.ready = true;
11417})
11418.catch(function() {
11419self.ready = true;
11420});
11421},setLoggedIn(customerData) {
11422this.isLoggedIn = true;
11423this.customerName = customerData.name || '';window.dispatchEvent(new CustomEvent('customer-logged-in', {
11424detail: customerData
11425}));
11426},setLoggedOut() {
11427this.isLoggedIn = false;
11428this.isStaff = false;
11429this.adminEditBase = '';
11430this.customerName = '';
11431this.customerEmail = '';
11432this.companyStoreUrl = '';window.dispatchEvent(new CustomEvent('customer-logged-out'));
11433}
11434});Alpine.store('search', {
11435isOpen: false,
11436isLoading: false,
11437hasSearched: false,
11438_openedAt: 0,open() {
11439// Focus proxy input immediately to preserve iOS user-gesture context
11440// so the virtual keyboard opens. Focus transfers to real input in $nextTick.
11441var proxy = document.getElementById('searchFocusProxy');
11442if (proxy) proxy.focus();
11443this.isOpen = true;
11444this._openedAt = Date.now();
11445},close() {
11446if (Date.now() - this._openedAt < 300) return;
11447this.isOpen = false;
11448}
11449});// Confirmation modal store
11450Alpine.store('confirm', {
11451isOpen: false,
11452title: '',
11453message: '',
11454html: null,
11455confirmText: 'Confirm',
11456cancelText: 'Cancel',
11457confirmStyle: 'danger', // 'danger' or 'primary'
11458onConfirm: null,
11459onCancel: null,show(options) {
11460this.title = options.title || 'Confirm';
11461this.message = options.message || 'Are you sure?';
11462this.html = options.html || null;
11463this.confirmText = options.confirmText || 'Confirm';
11464this.cancelText = options.cancelText || 'Cancel';
11465this.confirmStyle = options.confirmStyle || 'danger';
11466this.onConfirm = options.onConfirm || null;
11467this.onCancel = options.onCancel || null;
11468this.isOpen = true;
11469},confirm() {
11470if (this.onConfirm) {
11471this.onConfirm();
11472}
11473this.close();
11474},cancel() {
11475if (this.onCancel) {
11476this.onCancel();
11477}
11478this.close();
11479},close() {
11480this.isOpen = false;
11481this.onConfirm = null;
11482this.onCancel = null;
11483// Defer clearing html until after the 150ms leave transition
11484// so the image doesn't vanish mid-animation
11485var self = this;
11486setTimeout(function() {
11487if (!self.isOpen) self.html = null;
11488}, 200);
11489}
11490});// Flash messages store — messages arrive via auth store's fetchStatus() (AJAX),
11491// or as inline JSON on pages that embed them directly (fallback below).
11492Alpine.store('flash', {
11493messages: [],
11494_nextId: 1,
11495_seen: {},init() {
11496var self = this;// Fallback: read messages embedded as JSON in page HTML (for pages without ESI block)
11497var scriptEl = document.querySelector('script[data-flash-messages]');
11498if (scriptEl) {
11499try {
11500var inline = JSON.parse(scriptEl.textContent);
11501for (var i = 0; i < inline.length; i++) {
11502self.addMessage(inline[i].type, inline[i].text);
11503}
11504} catch (e) {}
11505scriptEl.remove();
11506}
11507},addMessage(type, text) {
11508// Deduplication
11509var key = type + ':' + text;
11510if (this._seen[key]) {
11511return;
11512}
11513this._seen[key] = true;var id = this._nextId++;
11514this.messages.push({ id: id, type: type, text: text, visible: true });// Auto-dismiss non-error messages after 8 seconds
11515if (type !== 'error') {
11516var self = this;
11517setTimeout(function() {
11518self.dismiss(id);
11519}, 8000);
11520}
11521},dismiss(id) {
11522var idx = this.messages.findIndex(function(m) { return m.id === id; });
11523if (idx !== -1) {
11524this.messages[idx].visible = false;
11525var self = this;
11526setTimeout(function() {
11527self.messages = self.messages.filter(function(m) { return m.id !== id; });
11528}, 300);
11529}
11530},show(type, text) {
11531this.addMessage(type, text);
11532}
11533});Alpine.store('flash').init();
11534});// BFCache restoration handler — when iOS Safari restores a page from BFCache,
11535// Alpine stores are frozen with stale data. Refresh critical stores.
11536window.addEventListener('pageshow', function(event) {
11537if (event.persisted) {
11538// Page was restored from BFCache — refresh cart data and auth status
11539if (typeof Alpine !== 'undefined' && Alpine.store) {
11540var cart = Alpine.store('cart');
11541if (cart) {
11542cart.lastFetch = null;
11543cart.refresh();
11544}
11545var auth = Alpine.store('auth');
11546if (auth && typeof auth.fetchStatus === 'function') {
11547auth.fetchStatus();
11548}
11549}
11550}
11551});
11552</script>
11552<div x-data
11553x-show="$store.auth.showRefreshOverlay"
11554x-cloak
11555x-transition:enter="transition ease-out duration-200"
11556x-transition:enter-start="opacity-0"
11557x-transition:enter-end="opacity-100"
11558class="fixed inset-0 z-[200] flex items-center justify-center bg-white/80 backdrop-blur-sm"
11559style="display: none;"><div class="flex flex-col items-center gap-3"> <svg class="animate-spin h-8 w-8 text-brand-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg><p class="text-gray-700 font-medium text-lg" x-text="$store.auth.refreshMessage"></p></div></div><div
11560x-data
11561x-show="$store.confirm.isOpen"
11562x-cloak
11563@keydown.escape.window="$store.confirm.cancel()"
11564class="fixed inset-0 z-[100] overflow-y-auto"
11565aria-labelledby="confirm-modal-title"
11566role="dialog"
11567aria-modal="true"
11568><div
11569x-show="$store.confirm.isOpen"
11570x-transition:enter="transition ease-out duration-200"
11571x-transition:enter-start="opacity-0"
11572x-transition:enter-end="opacity-100"
11573x-transition:leave="transition ease-in duration-150"
11574x-transition:leave-start="opacity-100"
11575x-transition:leave-end="opacity-0"
11576@click="$store.confirm.cancel()"
11577class="fixed inset-0 bg-black/50 backdrop-blur-sm"
11578></div><div class="fixed inset-0 flex items-center justify-center p-4"><div
11579x-show="$store.confirm.isOpen"
11580x-transition:enter="transition ease-out duration-200"
11581x-transition:enter-start="opacity-0 scale-95"
11582x-transition:enter-end="opacity-100 scale-100"
11583x-transition:leave="transition ease-in duration-150"
11584x-transition:leave-start="opacity-100 scale-100"
11585x-transition:leave-end="opacity-0 scale-95"
11586@click.stop
11587class="relative bg-white rounded-xl shadow-2xl max-w-md w-full p-6"
11588> <button
11589@click="$store.confirm.cancel()"
11590type="button"
11591class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors z-10"
11592> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button><div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full mb-4"
11593:class="$store.confirm.confirmStyle === 'danger' ? 'bg-red-100' : 'bg-blue-100'"> <svg x-show="$store.confirm.confirmStyle === 'danger'"
11594class="h-6 w-6 text-red-600"
11595fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> </svg> <svg x-show="$store.confirm.confirmStyle !== 'danger'"
11596class="h-6 w-6 text-blue-600"
11597fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg></div><h3 id="confirm-modal-title"
11598class="text-lg font-semibold text-gray-900 text-center mb-2"
11599x-text="$store.confirm.title"></h3><div x-show="$store.confirm.html" class="text-sm text-gray-600 text-center mb-6"
11600x-html="$store.confirm.html"></div><p x-show="!$store.confirm.html" class="text-sm text-gray-600 text-center mb-6"
11601x-text="$store.confirm.message"></p><div class="flex gap-3"> <button
11602@click="$store.confirm.cancel()"
11603type="button"
11604class="flex-1 px-4 py-2.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-300 transition-colors"
11605x-text="$store.confirm.cancelText"> </button> <button
11606@click="$store.confirm.confirm()"
11607type="button"
11608class="flex-1 px-4 py-2.5 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors"
11609:class="$store.confirm.confirmStyle === 'danger'
11610? 'bg-red-600 hover:bg-red-700 focus:ring-red-500'
11611: 'bg-brand-primary hover:bg-brand-primary-hover focus:ring-brand-primary'"
11612x-text="$store.confirm.confirmText"> </button></div></div></div></div></body></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.