diff --git a/apps/caramel-app/src/components/HeroSection.tsx b/apps/caramel-app/src/components/HeroSection.tsx index 88cfbff..0be4760 100644 --- a/apps/caramel-app/src/components/HeroSection.tsx +++ b/apps/caramel-app/src/components/HeroSection.tsx @@ -493,9 +493,19 @@ export default function HeroSection() { reduce={!!reduceMotion} /> + {/* The canvas raster BLEEDS 48px/40px past the layout + box (must match CANVAS_BLEED_X/Y_PX in + HeroTicketScene, which subtracts the bleed from its + fit math) so a tilted coupon or drifting droplet + never touches the raster edge — the section's + overflow-hidden still crops safely far away at the + SECTION edge. 48px of left bleed = exactly the + column gap (gap-12), so the pointer-capturing + canvas never covers the copy column's links. The + anchor box keeps its reserved h-[34rem] — zero CLS. */} {showCanvas && (
({ origin: new THREE.Vector3( (Math.random() - 0.5) * 7, - (Math.random() - 0.5) * 6, + (Math.random() - 0.5) * 5, (Math.random() - 0.5) * 4 - 1, ), speed: 0.2 + Math.random() * 0.5, @@ -242,34 +262,40 @@ function StatCoupon3D({ color={isDark ? CARAMEL_LIGHT : '#fff2e6'} /> {/* Soft dark outline-blur = printed-ink shadow, so the type - reads as pressed into the caramel glass. */} + reads as pressed into the caramel glass (deliberately a + touch stronger on the number so it pops off the caramel). */} {shown} + {/* Label fit math (longest label = "SUPPORTED STORES", 16 + glyphs): Poppins-Bold uppercase averages ~0.62em advance, + so width ≈ 16 × 0.11 × (0.62 + 0.08 letterSpacing) ≈ 1.23 — + inside the usable face width of STAT.w 1.7 − 2 × notch r + 0.14 = 1.42, with ~0.095 margin per side. That's why the + letterSpacing dropped 0.12 → 0.08 alongside the size bump. */} {stat.label.toUpperCase()} @@ -384,16 +410,26 @@ function Droplets({ isDark }: { isDark: boolean }): React.JSX.Element { function SceneContents({ isDark }: { isDark: boolean }): React.JSX.Element { // The vertical FOV keeps world height constant, so on the narrow hero column // the main ticket + the stat-coupon row can overflow the frustum — scale the - // whole ticket group down to fit both dimensions (with a small margin). - // Reactive to resize via R3F size state; no per-frame camera work. - const aspect = useThree(state => state.size.width / state.size.height) + // whole ticket group down to fit both dimensions. The canvas raster is BLED + // past the layout box (CANVAS_BLEED_*_PX), so the fit is computed against + // the layout-box slice of the frustum, not the whole canvas: the composition + // keeps the size it had when canvas == layout box, and the bleed becomes + // pure headroom that tilted geometry can swing into without clipping. The + // 0.9 margin (was 0.94) leaves ~10% of the layout box as rest-pose slack + // before the bleed even starts. Reactive to resize via R3F size state; no + // per-frame camera work. + const { width, height } = useThree(state => state.size) const dist = 7 const halfH = Math.tan((42 * Math.PI) / 180 / 2) * dist - const halfW = halfH * aspect + const worldPerPx = (halfH * 2) / height + const layoutHalfW = + (Math.max(1, width - CANVAS_BLEED_X_PX * 2) / 2) * worldPerPx + const layoutHalfH = + (Math.max(1, height - CANVAS_BLEED_Y_PX * 2) / 2) * worldPerPx const fit = Math.min( 1, - (halfW * 0.94) / CONTENT_HALF_W, - (halfH * 0.94) / CONTENT_HALF_H, + (layoutHalfW * 0.9) / CONTENT_HALF_W, + (layoutHalfH * 0.9) / CONTENT_HALF_H, ) return ( @@ -416,8 +452,12 @@ function SceneContents({ isDark }: { isDark: boolean }): React.JSX.Element { + {/* Inside the fit group ON PURPOSE: the droplet field is wider + than the layout box in raw world units, so scaling it with + the content keeps every sphere inside the (bled) frustum + instead of slicing them mid-sphere at the raster edge. */} + - = { green: 'bg-green-100 text-green-700 ring-green-200 dark:bg-green-900/30 dark:text-green-300 dark:ring-green-900/50', amber: 'bg-amber-100 text-amber-700 ring-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:ring-amber-900/50', - grey: 'bg-gray-100 text-gray-600 ring-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:ring-gray-700', + grey: 'bg-gray-100 text-gray-600 ring-gray-200 dark:bg-white/10 dark:text-gray-300 dark:ring-white/20', red: 'bg-red-100 text-red-700 ring-red-200 dark:bg-red-900/30 dark:text-red-300 dark:ring-red-900/50', } @@ -55,7 +55,7 @@ export default function CouponCard({ coupon, index }: CouponCardProps) { animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, scale: 0.9 }} transition={{ duration: 0.3, delay: index * 0.05 }} - className="group relative overflow-hidden rounded-3xl border border-orange-100 bg-gradient-to-br from-orange-50/50 via-white to-orange-50/40 p-5 shadow-md transition-all duration-200 hover:-translate-y-0.5 hover:border-orange-200 hover:shadow-lg dark:border-orange-900/50 dark:from-gray-900 dark:via-gray-900 dark:to-gray-900 dark:hover:border-orange-800/70" + className="group relative overflow-hidden rounded-3xl border border-orange-100 bg-gradient-to-br from-orange-50/50 via-white to-orange-50/40 p-5 shadow-md transition-all duration-200 hover:-translate-y-0.5 hover:border-orange-200 hover:shadow-lg dark:border-orange-900/50 dark:from-darkSurface dark:via-darkSurface dark:to-darkSurface dark:hover:border-orange-800/70" >
{/* Left: Discount Badge */} @@ -111,7 +111,7 @@ export default function CouponCard({ coupon, index }: CouponCardProps) { diff --git a/apps/caramel-app/src/components/coupons/coupon-filters.tsx b/apps/caramel-app/src/components/coupons/coupon-filters.tsx index 3eb3b4a..36abc8c 100644 --- a/apps/caramel-app/src/components/coupons/coupon-filters.tsx +++ b/apps/caramel-app/src/components/coupons/coupon-filters.tsx @@ -100,22 +100,25 @@ export default function CouponFilters({ paddingLeft: '4px', paddingRight: '4px', minHeight: '44px', - backgroundColor: isDarkMode ? '#111827' : '#ffffff', + // react-select can't take Tailwind classes, so these hexes must + // track the brand surfaces: darkSurface #1E1916 (warm charcoal, + // tailwind.config.ts) instead of the blue-hued gray-900/800. + backgroundColor: isDarkMode ? '#1E1916' : '#ffffff', width: '100%', }), menu: base => ({ ...base, zIndex: 20, - backgroundColor: isDarkMode ? '#111827' : '#ffffff', + backgroundColor: isDarkMode ? '#1E1916' : '#ffffff', }), option: (base, state) => ({ ...base, backgroundColor: state.isFocused ? isDarkMode - ? '#1f2937' + ? '#2B241F' // warm hover step above darkSurface : '#f3f4f6' : isDarkMode - ? '#111827' + ? '#1E1916' : '#ffffff', color: isDarkMode ? '#ffffff' : '#111827', cursor: 'pointer', @@ -155,7 +158,7 @@ export default function CouponFilters({ value={localSearch} onChange={e => handleSearchChange(e.target.value)} placeholder="Search coupons by site, title, or description..." - className="w-full rounded-md border-2 border-caramel/30 bg-white px-3 py-[11px] pl-10 text-sm outline-none transition-all focus:border-caramel dark:bg-gray-900 dark:text-white dark:placeholder-gray-500 dark:focus:border-orange-400" + className="w-full rounded-md border-2 border-caramel/30 bg-white px-3 py-[11px] pl-10 text-sm outline-none transition-all focus:border-caramel dark:bg-darkSurface dark:text-white dark:placeholder-gray-500 dark:focus:border-orange-400" />
@@ -234,7 +237,7 @@ export default function CouponFilters({ type="button" aria-expanded={showFilters} onClick={() => setShowFilters(!showFilters)} - className="mt-2 hidden w-full items-center justify-center gap-2 rounded-full bg-white px-6 py-3 text-caramel shadow-md transition-all hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-caramel focus-visible:ring-offset-2 dark:bg-gray-900 dark:text-orange-400 dark:focus-visible:ring-offset-darkBg md:flex" + className="mt-2 hidden w-full items-center justify-center gap-2 rounded-full bg-white px-6 py-3 text-caramel shadow-md transition-all hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-caramel focus-visible:ring-offset-2 dark:bg-darkSurface dark:text-orange-400 dark:focus-visible:ring-offset-darkBg md:flex" > Filters diff --git a/apps/caramel-app/src/components/coupons/coupons-section.tsx b/apps/caramel-app/src/components/coupons/coupons-section.tsx index 71e3942..d775f1f 100644 --- a/apps/caramel-app/src/components/coupons/coupons-section.tsx +++ b/apps/caramel-app/src/components/coupons/coupons-section.tsx @@ -291,7 +291,7 @@ export default function CouponsSection({
@@ -354,7 +354,7 @@ export default function CouponsSection({ {/* Sidebar */}