chris ec748c75a9 perf+fix: lazy images, API caching, iOS scroll lock, color name wrapping
Performance:
- Add loading="lazy" decoding="async" to product card images
- Preconnect to Square S3 image CDN and fonts.googleapis.com in layout
- Cache-Control headers on catalog (20s), inventory (10s), occasions/categories (5min)

Scroll lock:
- Update useLockBodyScroll to use position:fixed + scroll-restore for iOS Safari
- Apply same fix to CartDrawer's inline scroll lock

Color names:
- Remove word-break:break-word so single words never split across lines;
  multi-word names still wrap at spaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 10:13:50 -04:00

258 lines
5.6 KiB
CSS

/* ── Import Bulma first so our overrides take effect ────────────────────────── */
@import 'bulma/css/bulma.min.css';
/* ── Main site stylesheet — copied from website/style.css ───────────────────── */
@import './site.css';
/* ── Shop-only additions (not in the main site) ─────────────────────────────── */
/* Force Bulma 1.x into light mode — prevents dark-OS from inverting the palette */
:root {
color-scheme: light;
--bulma-scheme-main-l: 100%;
--bulma-scheme-main-bis-l: 98%;
--bulma-scheme-main-ter-l: 96%;
--bulma-scheme-invert-l: 4%;
--bulma-text-strong-l: 21%;
}
/* More breathing room in the product grid */
.shop-section .columns {
gap: 1.5rem;
}
.shop-section .column {
padding: 0.75rem;
}
.product-card .card-content {
padding: 1.25rem 1.5rem;
}
.product-card {
background: #fff;
border: 1px solid #e6dfc8;
border-radius: 14px;
box-shadow: 0 10px 22px rgba(24, 40, 72, 0.08);
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
transition: transform 180ms ease, box-shadow 180ms ease;
}
.product-card:hover {
transform: translateY(-4px);
box-shadow: 0 18px 36px rgba(24, 40, 72, 0.13);
}
.product-card .card-image {
padding: 0.6rem 0.6rem 0;
background: #fff;
}
.product-card .card-image img {
object-fit: contain;
width: 100%;
height: 220px;
display: block;
background: #fff;
border-radius: 10px;
}
.product-card .no-image {
width: 100%;
height: 220px;
display: flex;
align-items: center;
justify-content: center;
font-size: 4rem;
background: #fef6e4;
}
.product-card .card-content {
flex: 1;
display: flex;
flex-direction: column;
}
.product-card .card-tags {
margin-top: auto;
padding-top: 0.75rem;
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
}
.product-tag {
font-size: 0.7rem;
background: #e7e6dd;
color: #6b6b6b;
padding: 0.2rem 0.6rem;
border-radius: 999px;
text-transform: capitalize;
}
.shop-section h2,
.shop-section .is-size-3 {
color: #7585ff !important;
}
/* ── Balloon swatches — ported from color-picker/color.css ──────────────────── */
:root {
--swatch-w: 72px;
--swatch-h: 98px;
}
.swatch-container {
display: grid;
grid-template-columns: repeat(auto-fill, var(--swatch-w));
gap: 10px 8px;
padding: 4px 0;
justify-content: start;
}
.swatch-wrapper {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 4px;
width: var(--swatch-w);
transition: filter 0.2s ease;
filter: drop-shadow(0 3px 4px rgba(0, 0, 0, 0.18));
cursor: pointer;
}
.swatch-wrapper:hover {
filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.2));
}
.color-swatch {
width: var(--swatch-w);
height: var(--swatch-h);
position: relative;
z-index: 1;
transition: transform 0.2s ease;
-webkit-mask-image: url('/color/images/balloon-mask.svg');
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-image: url('/color/images/balloon-mask.svg');
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
}
.color-swatch:hover {
transform: translateY(-2px) scale(1.05);
}
.color-background {
width: 100%;
height: 100%;
border-radius: 50%;
border: 2px solid rgba(0, 0, 0, 0.08);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.color-background.finish-image {
background-size: 160%;
}
.color-background.chosen {
border-color: rgba(12, 86, 109, 0.55);
box-shadow: inset 0 0 0 3px rgba(14, 167, 160, 0.5);
}
.color-shine {
position: absolute;
top: 43%;
left: 43%;
width: 52px;
opacity: 0.62;
z-index: 100;
pointer-events: none;
transform: translate(-50%, -50%);
height: auto;
}
.color-shine.has-halo {
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
}
.color-name {
font-size: 0.68rem;
line-height: 1.15;
text-align: center;
color: #334854;
width: 100%;
white-space: normal;
overflow-wrap: normal;
word-break: normal;
}
.color-family-heading {
font-size: 0.8rem;
font-weight: bold;
color: #15384c;
margin: 12px 0 4px;
padding-bottom: 4px;
border-bottom: 1px solid #e4dbc1;
}
/* ── Modal z-index — above cart FAB (999) and drawer (1001) ─────────────────── */
.modal { z-index: 1100 !important; }
.modal-background { z-index: 1100 !important; }
.modal-card { z-index: 1101 !important; }
/* ── Mobile overrides ────────────────────────────────────────────────────────── */
@media (max-width: 768px) {
/* Smaller swatches so 3+ fit per row in the modal */
:root {
--swatch-w: 56px;
--swatch-h: 76px;
}
.color-shine {
width: 40px;
}
/* Category tabs: scroll horizontally instead of wrapping */
.tabs ul {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding-bottom: 2px;
}
/* Tighten product grid gap on mobile */
.shop-section .columns {
gap: 0.75rem;
}
/* Modal takes more vertical space on mobile */
.modal-card {
margin: 0.5rem !important;
max-height: calc(100dvh - 1rem) !important;
}
.modal-card-body {
max-height: calc(100dvh - 10rem) !important;
}
}
@keyframes skeleton-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.skeleton-block {
background: #dddcd3;
border-radius: 8px;
animation: skeleton-pulse 1.5s ease-in-out infinite;
}