diff --git a/components/SelfGuidedTours.tsx b/components/SelfGuidedTours.tsx new file mode 100644 index 0000000000..33d154a0a5 --- /dev/null +++ b/components/SelfGuidedTours.tsx @@ -0,0 +1,309 @@ +'use client'; +import React from 'react'; +import Image from 'next/image'; +import Script from 'next/script'; + +/** + * Card data shape passed from MDX. + * - navatticOpen: the Navattic demo id (e.g., "cmfkxwfa5000004lc8408f5wi") + * - navatticTitle: optional; title for the Navattic popup chrome + * - img: optional; if omitted, a dark placeholder fills the media area + */ +type Card = { + badge: string; + title: string; + blurb?: string; + img?: string; + href?: string; + navatticOpen?: string; + navatticTitle?: string; +}; + +interface Props { + cards: Card[]; +} + +/* ---- Constants / design tokens (keep in sync with MDX usage) ---- */ +const MP_PURPLE = 'rgb(139 92 246)'; +const BORDER_RADIUS = 14; +const CARD_W = 296; +const CARD_H = 319; +/** Image height is fixed for consistent badge anchoring */ +const IMAGE_H = 140; +/** Image width is indented left and bleeds to the right edge */ +const IMAGE_W = 276; + +/* ---- Inline “CSS-in-TS” styles (layout is pixel-exact to your spec) ---- */ +const styles = { + grid: { + display: 'grid', + gap: 20, + gridTemplateColumns: 'repeat(auto-fit, minmax(296px, 1fr))', + justifyContent: 'center', + marginTop: 32, + } as React.CSSProperties, + + card: { + position: 'relative', + width: CARD_W, + height: CARD_H, + borderRadius: BORDER_RADIUS, + overflow: 'hidden', + border: `2px solid ${MP_PURPLE}`, + boxShadow: '0 10px 30px rgba(0,0,0,.25)', + transition: 'transform .25s ease, box-shadow .25s ease, background .3s ease, color .3s ease', + } as React.CSSProperties, + + dogEar: { + position: 'absolute', + right: 10, + top: 10, + width: 22, + height: 22, + background: 'var(--sgt-dogear)', + clipPath: 'polygon(0 0, 100% 0, 100% 100%)', + boxShadow: '0 0 0 2px rgba(0,0,0,.15) inset', + zIndex: 5, + pointerEvents: 'none', + } as React.CSSProperties, + + mediaWrap: { + position: 'absolute', + top: 18, // aligns the image to the badge’s left indent + height: IMAGE_H, + width: IMAGE_W, + marginLeft: 16, // left indent (aligns with badge) + marginRight: -16, // bleed to right edge (no right indent) + borderTopLeftRadius: 8, + borderBottomLeftRadius: 8, + overflow: 'hidden', + background: 'var(--sgt-media-bg)', // placeholder color behind images + zIndex: 1, + } as React.CSSProperties, + + mediaImg: { + width: '100%', + height: '100%', + objectFit: 'cover', + objectPosition: 'left top', + display: 'block', + } as React.CSSProperties, + + placeholder: { + width: '100%', + height: '100%', + background: 'var(--sgt-media-bg)', + } as React.CSSProperties, + + /** + * Anchored text block: + * - Badge top is locked to IMAGE_H + offset so all cards align visually + * - Title and blurb naturally flow below the badge + */ + bottom: { + position: 'absolute' as const, + top: IMAGE_H + 22, + left: 0, + right: 0, + bottom: 0, + padding: '16px 18px 22px', + zIndex: 3, + }, + + /* Badge = stronger weight + tighter tracking for a pill look */ + badge: { + display: 'inline-block', + background: 'var(--sgt-badge-bg)', + color: 'var(--sgt-badge-fg)', + fontWeight: 800, + letterSpacing: '.04em', + fontSize: '11.5px', + lineHeight: 1, + borderRadius: 8, + padding: '8px 10px', + marginBottom: 10, + } as React.CSSProperties, + + title: { + fontSize: 23, + fontWeight: 700, + lineHeight: 1.2, + margin: 0, + color: 'var(--sgt-title)', + } as React.CSSProperties, + + blurb: { + marginTop: 8, + fontSize: 15, + color: 'var(--sgt-blurb)', + opacity: 0.75, + } as React.CSSProperties, + + clickable: { + display: 'block', + width: '100%', + height: '100%', + background: 'transparent', + border: 0, + padding: 0, + cursor: 'pointer', + textAlign: 'inherit', + } as React.CSSProperties, +}; + +/* ---- One card view (supports Navattic popup or plain link) ---- */ +function CardView({ c }: { c: Card }) { + const inside = ( + <> +
+
+ {c.img ? ( + + ) : ( +
+ )} +
+ +
+
{c.badge}
+

{c.title}

+ {c.blurb ?
{c.blurb}
: null} +
+ + ); + + // Use Navattic popup if navatticOpen is provided + if (c.navatticOpen) { + const navatticUrl = c.navatticOpen.startsWith('http') + ? c.navatticOpen + : `https://capture.navattic.com/${c.navatticOpen}`; + + return ( +
+ +
+ ); + } + + // Fallback to href links if needed + if (c.href) { + return ( +
+ + {inside} + +
+ ); + } + + // Static (non-clickable) card + return ( +
+ {inside} +
+ ); +} + +/** + * SelfGuidedTours + * - Renders a responsive grid of product-tour cards + * - Loads Navattic's embed script once (popup mode) + * - Exposes a simple props API so MDX controls the content + */ +export default function SelfGuidedTours({ cards }: Props) { + return ( + <> + {/* Navattic embed loader (newer API) */} +