/* postcards.jsx — A Shade Above neighbor postcard, 6x11 EDDM jumbo (landscape)
   Three FRONT variations + one shared BACK with subtle accents per variation.
   Renders into a 11.25" × 6.25" canvas (with 0.125" bleed on each side). */

/* Wedding-invite ampersand — splits a string on " & " and renders the
   ampersand with a stylized italic span (.amp class). Returns React
   children, so use anywhere user copy gets rendered as text. */
// True when this campaign is NMB-only (single myrtle showroom). Drives
// front-of-card subhead copy + the coastal team block on the back.
function isNmbCampaign(s) {
  return Array.isArray(s.showrooms) && s.showrooms.length === 1 && s.showrooms[0] === 'myrtle';
}

function withAmp(text) {
  if (!text) return text;
  const str = String(text);
  if (str.indexOf(' & ') === -1) return str;
  const parts = str.split(' & ');
  const out = [];
  parts.forEach((p, i) => {
    if (i > 0) out.push(React.createElement('span', { className: 'amp', key: 'a' + i }, '&'));
    out.push(p);
  });
  return out;
}

const SC = 72;                // px per inch (display)
const TRIM   = { w: 11 * SC, h: 6 * SC };          // 792 × 432
const BLEED  = 0.125 * SC;                          // 9
const SAFE   = 0.25 * SC;                           // 18
const FULL   = { w: TRIM.w + BLEED * 2, h: TRIM.h + BLEED * 2 }; // 810 × 450

/* Showroom data — Charlotte/Matthews HQ and Southpark are the same operation; only Southpark shows. */
const SHOWROOMS = {
  southpark:  { name: 'SouthPark',       addr: '721 Gov Morrison St, Suite F-110B', sub: 'Charlotte, NC 28211',     phone: '704-541-1200', note: 'By Appointment Only' },
  cornelius:  { name: 'Lake Norman',     addr: '19924 Jetton Rd, Suite 103',   sub: 'Cornelius, NC 28031',     phone: '704-541-1200', note: 'Call for an Appointment' },
  matthews:   { name: 'Matthews',        addr: '1253 Matthews-Mint Hill Rd',   sub: 'Matthews, NC 28105',      phone: '704-541-1200', note: 'By Appointment Only' },
  // NMB-only: storefront photo + walk-in hours. The other showrooms
  // are by-appointment HQ spaces, so we don't display photos for them.
  myrtle:     { name: 'N. Myrtle Beach', addr: '1730 Hwy 17 N, Cherry Grove',  sub: 'N. Myrtle Beach, SC 29582', phone: '704-541-1200', note: 'Walk-ins welcome', hours: '10am–4pm · Mon–Fri', photo: 'assets/photos/a-shade-above-north-myrtle-beach-storefront.jpg' },
};

// Per-campaign phone routing. Each variant has a default tracking number
// and a separate one for North-Myrtle-Beach territory. Used both for the
// main contact display on the front and the showroom list on the back.
function getShowroomPhone(variant, showroomId) {
  const isJustSold = variant === 'justSold';
  const isMyrtle   = showroomId === 'myrtle';
  if (isJustSold) return isMyrtle ? '843-612-8669' : '704-610-5962';
  return isMyrtle ? '843-474-6422' : '704-508-9444';
}
// NMB territory uses a separate brand domain (asacoastal.com); everywhere
// else uses asacharlotte.com. Used both for the front contact line and any
// per-showroom display.
function getShowroomWeb(showroomId) {
  return showroomId === 'myrtle' ? 'asacoastal.com' : 'asacharlotte.com';
}
const ALL_SHOWROOMS = Object.keys(SHOWROOMS);

/* Admin-curated default offer (always In-Home Design Consultation when not running a promo) */
const DEFAULT_OFFER_TEXT = 'Design Consultation';

/* photo defaults — email-blast PNGs as approximations until user drops real shots */
const PHOTO_DEFAULTS = {
  /* shades */
  motorized_shades:        { src: 'assets/photos/halfoff_powerview.png', position: '50% 12%' },
  roller_shades:           { src: 'assets/photos/halfoff_powerview.png', position: '50% 25%' },
  honeycomb_shades:        { src: 'assets/photos/halfoff_powerview.png', position: '50% 35%' },
  shades:                  { src: 'assets/photos/halfoff_powerview.png', position: '50% 30%' },
  roman_shades:            { src: 'assets/photos/halfoff_powerview.png', position: '50% 40%' },
  /* drapery */
  drapery:                 { src: 'assets/photos/freeliterise.png',      position: '50% 47%' },
  motorized_drapery:       { src: 'assets/photos/freeliterise.png',      position: '50% 40%' },
  drapery_roman:           { src: 'assets/photos/freeliterise.png',      position: '50% 45%' },
  drapery_roller:          { src: 'assets/photos/freeliterise.png',      position: '50% 50%' },
  drapery_cornices:        { src: 'assets/photos/freeliterise.png',      position: '50% 35%' },
  /* shutters */
  shutters:                { src: 'assets/photos/magnatrack.png',        position: '50% 60%' },
  /* blinds */
  blinds:                  { src: 'assets/photos/halfoff_powerview.png', position: '50% 50%' },
  /* exterior */
  exterior_patio_shades:   { src: 'assets/photos/magnatrack.png',        position: '50% 50%' },
  motorized_screen_shades: { src: 'assets/photos/magnatrack.png',        position: '50% 40%' },
};

/* HeroImg / ThumbImg — read uploaded src from state, fall back to category default */
/* Parse "X% Y%" objectPosition string into [x, y] floats. */
function parsePos(p) {
  const m = String(p || '').match(/(-?\d+(?:\.\d+)?)%\s+(-?\d+(?:\.\d+)?)%/);
  return m ? [parseFloat(m[1]), parseFloat(m[2])] : [50, 50];
}

/* Hint badge shown over draggable photos. */
function DragHint({ small }) {
  return (
    <div className="photo-drag-hint print-hide" style={{
      position: 'absolute', left: 8, top: 8, zIndex: 4,
      display: 'inline-flex', alignItems: 'center', gap: 6,
      padding: small ? '3px 7px' : '4px 9px',
      background: 'rgba(15,28,44,0.78)', color: 'var(--asa-cream)',
      borderRadius: 999, pointerEvents: 'none',
      fontFamily: 'var(--ff-sans)', fontSize: small ? 7.5 : 8.5, fontWeight: 600,
      letterSpacing: '0.18em', textTransform: 'uppercase',
      backdropFilter: 'blur(4px)',
      transition: 'opacity 180ms ease',
      whiteSpace: 'nowrap',
    }}>
      <svg width={small ? 9 : 11} height={small ? 9 : 11} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
        <path d="M8 2v12M2 8h12M5 5l-3 3 3 3M11 5l3 3-3 3M5 11l3 3 3-3M5 5l3-3 3 3" />
      </svg>
      Drag to reposition
    </div>
  );
}

/* DraggablePhoto — generic wrapper. Reads position from s[posKey], writes back via setState. */
function DraggablePhoto({ src, position, onChange, alt = '', children, hintSmall = false, draggable = true }) {
  const wrapRef = React.useRef(null);
  const [drag, setDrag] = React.useState(null);
  const [hover, setHover] = React.useState(false);
  const [px, py] = parsePos(position);

  const onPointerDown = (e) => {
    if (!draggable || !onChange) return;
    e.preventDefault();
    e.currentTarget.setPointerCapture?.(e.pointerId);
    const rect = wrapRef.current?.getBoundingClientRect();
    if (!rect) return;
    setDrag({ startX: e.clientX, startY: e.clientY, px, py, w: rect.width, h: rect.height });
  };
  const onPointerMove = (e) => {
    if (!drag) return;
    const dx = (e.clientX - drag.startX) / drag.w * 100;
    const dy = (e.clientY - drag.startY) / drag.h * 100;
    const nx = Math.max(0, Math.min(100, drag.px - dx));
    const ny = Math.max(0, Math.min(100, drag.py - dy));
    onChange(`${nx.toFixed(1)}% ${ny.toFixed(1)}%`);
  };
  const onPointerUp = () => setDrag(null);

  return (
    <div
      ref={wrapRef}
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      onPointerUp={onPointerUp}
      onPointerCancel={onPointerUp}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        position: 'absolute', inset: 0, overflow: 'hidden',
        cursor: draggable && onChange ? (drag ? 'grabbing' : 'grab') : 'default',
        touchAction: 'none',
      }}
    >
      {src && (
        // <img> with object-fit:cover + object-position is the only
        // mode that gives true CSS-cover behavior under user drag —
        // no whitespace at any drag extreme because the image is
        // always scaled to fully cover the container. The SVG
        // approach was scaling the image to viewbox dimensions and
        // then translating it, which revealed empty SVG canvas at
        // the edges. Print quality through html2canvas 1.4.1 is
        // fine now that Flux Kontext Pro outputs 2048×2048 native —
        // the source has enough pixels to absorb any minor downsample.
        <img
          src={src}
          alt={alt}
          draggable={false}
          style={{
            position: 'absolute', inset: 0,
            width: '100%', height: '100%',
            objectFit: 'cover',
            objectPosition: position,
            userSelect: 'none', pointerEvents: 'none', display: 'block',
          }}
        />
      )}
      {children}
      {draggable && onChange && (
        <div style={{ opacity: hover || drag ? 1 : 0.45 }}>
          <DragHint small={hintSmall} />
        </div>
      )}
      {/* subtle ring on hover */}
      {draggable && onChange && (hover || drag) && (
        <div style={{
          position: 'absolute', inset: 0, pointerEvents: 'none',
          boxShadow: 'inset 0 0 0 2px rgba(243,237,228,0.55), inset 0 0 0 3px rgba(15,28,44,0.35)',
        }} />
      )}
    </div>
  );
}

/* HeroImg / ThumbImg — read uploaded src from state. If no src, render a quiet
   placeholder motif instead of a stock photo. */
function PhotoPlaceholder({ label = 'Upload an install photo' }) {
  return (
    <div style={{
      position: 'absolute', inset: 0,
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      gap: 10,
      background: `
        repeating-linear-gradient(45deg,
          var(--asa-stone-200, #e6dfd1) 0 6px,
          var(--asa-stone-100, #f3efe7) 6px 12px)`,
      color: 'var(--asa-stone-500)',
      fontFamily: 'var(--ff-sans)', fontSize: 8.5, fontWeight: 600,
      letterSpacing: '0.22em', textTransform: 'uppercase',
      textAlign: 'center', padding: '0 12px',
    }}>
      <svg width="34" height="34" viewBox="0 0 32 32" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" style={{ opacity: 0.55 }}>
        <rect x="3.5" y="6" width="25" height="20" rx="1.5" />
        <circle cx="11" cy="13" r="2.2" />
        <path d="M3.5 22 L12 15 L18 20 L22 17 L28.5 22" />
      </svg>
      <span>{label}</span>
    </div>
  );
}

function HeroImg({ s, set, fallbackPosition, hintSmall }) {
  const src = s.heroSrc;
  const pos = s.heroPosition || fallbackPosition || '50% 50%';
  if (!src) {
    return <PhotoPlaceholder label="Upload front photo" />;
  }
  return (
    <DraggablePhoto
      src={src}
      position={pos}
      onChange={set ? (v) => set({ heroPosition: v }) : null}
      hintSmall={hintSmall}
    />
  );
}
function ThumbImg({ src, placeholder, set, position, posKey }) {
  if (!src) {
    return <PhotoPlaceholder label={placeholder || 'Install photo'} />;
  }
  return (
    <DraggablePhoto
      src={src}
      position={position || '50% 50%'}
      onChange={set && posKey ? (v) => set({ [posKey]: v }) : null}
      hintSmall={true}
    />
  );
}

/* Two-tier product taxonomy:
   TYPE → list of subcategories. The user picks a TYPE first, then a subcategory.
   First entry per type is the DEFAULT for that type. */
const CATEGORY_TYPES = {
  shutters:  { label: 'Shutters',         subs: ['shutters', 'custom_shutters', 'specialty_shape_shutters'] },
  shades:    { label: 'Shades',           subs: ['shades', 'motorized_shades', 'roller_shades', 'honeycomb_shades', 'roman_shades', 'woven_wood_shades', 'silhouette_sheer_shadings', 'pirouette_window_shadings', 'luminette_privacy_sheers'] },
  blinds:    { label: 'Blinds',           subs: ['blinds'] },
  drapery:   { label: 'Drapery',          subs: ['drapery', 'motorized_drapery', 'drapery_roman', 'drapery_roller', 'drapery_cornices'] },
  exterior:  { label: 'Exterior Shades',  subs: ['exterior_patio_shades', 'motorized_screen_shades'] },
};
const CATEGORY_TYPE_LIST = ['shutters', 'shades', 'blinds', 'drapery', 'exterior'];

/* Display-ready label, plus a short eyebrow form for tracked-caps usage */
const CATEGORY_LABEL = {
  shutters:                  'Locally Made Shutters',
  custom_shutters:           'Custom Shutters',
  specialty_shape_shutters:  'Specialty Shape Shutters',
  shades:                    'Custom Shades',
  motorized_shades:          'Motorized Shades',
  roller_shades:             'Roller Shades',
  honeycomb_shades:          'Honeycomb Shades',
  roman_shades:              'Roman Shades',
  woven_wood_shades:         'Woven Wood Shades',
  silhouette_sheer_shadings: 'Silhouette® Sheer Shadings',
  pirouette_window_shadings: 'Pirouette® Window Shadings',
  luminette_privacy_sheers:  'Luminette® Privacy Sheers',
  blinds:                    'Blinds',
  drapery:                   'Drapery',
  motorized_drapery:         'Motorized Drapery',
  drapery_roman:             'Drapery Panels & Roman Shades',
  drapery_roller:            'Drapery Panels & Roller Shades',
  drapery_cornices:          'Drapery & Fabric Cornices',
  exterior_patio_shades:     'Patio Shades',
  motorized_screen_shades:   'Motorized Screen Shades',
};
const CATEGORY_SHORT = {
  shutters:                  'Shutters',
  custom_shutters:           'Custom Shutters',
  specialty_shape_shutters:  'Specialty Shape',
  shades:                    'Custom Shades',
  motorized_shades:          'Motorized Shades',
  roller_shades:             'Roller Shades',
  honeycomb_shades:          'Honeycomb',
  roman_shades:              'Roman Shades',
  woven_wood_shades:         'Woven Wood',
  silhouette_sheer_shadings: 'Silhouette®',
  pirouette_window_shadings: 'Pirouette®',
  luminette_privacy_sheers:  'Luminette®',
  blinds:                    'Blinds',
  drapery:                   'Drapery',
  motorized_drapery:         'Motorized Drapery',
  drapery_roman:             'Drapery + Roman',
  drapery_roller:            'Drapery + Roller',
  drapery_cornices:          'Drapery + Cornices',
  exterior_patio_shades:     'Patio Shades',
  motorized_screen_shades:   'Motorized Screen',
};
/* Reverse lookup: which TYPE does a subcategory belong to? */
const CATEGORY_TYPE_OF = (() => {
  const m = {};
  for (const t of CATEGORY_TYPE_LIST) for (const s of CATEGORY_TYPES[t].subs) m[s] = t;
  return m;
})();

/* Intelligent product-name helper for body copy.
   - If no/unknown category, returns 'custom window treatments' (or 'Custom Window Treatments' for headline case)
   - Strips leading qualifiers like "Locally Made " so prose reads naturally
   - Always normalizes registered marks (PowerView®) preserved
   - Casing options: 'lower' (mid-sentence), 'sentence' (Capitalized first word), 'title' (Headline), 'asis' */
function productCopy(category, mode = 'lower') {
  let raw = (CATEGORY_LABEL[category] || '').trim();
  if (!raw) raw = mode === 'asis' ? 'Custom Window Treatments' : 'custom window treatments';
  if (mode === 'asis') return raw;
  // Strip qualifier prefixes for prose:
  raw = raw.replace(/^Locally Made /i, '').replace(/^Stunning New /i, '').replace(/^Brand New /i, '');
  if (mode === 'title') {
    // Capitalize each word but preserve & and prepositions lowercased
    const small = new Set(['and','&','of','for','the','with','to','in','on']);
    return raw.split(/(\s+|&)/).map((w, i) => {
      if (!w.trim()) return w;
      if (w === '&') return w;
      const lw = w.toLowerCase();
      if (i > 0 && small.has(lw)) return lw;
      return w[0].toUpperCase() + w.slice(1);
    }).join('');
  }
  if (mode === 'sentence') {
    const lower = raw.toLowerCase();
    return lower[0].toUpperCase() + lower.slice(1);
  }
  // 'lower' — full lowercase EXCEPT preserve registered/trademark caps and acronyms
  return raw.toLowerCase().replace(/powerview/gi, 'PowerView');
}

/* Strip a leading house number from a street address so only the street
   name itself prints. "1234 Cloister Dr" → "Cloister Dr". Used everywhere
   the card displays a location credit — full numeric address is a privacy
   liability for the homeowner being credited. */
function streetName(addr) {
  const t = String(addr || '').trim();
  if (!t) return '';
  // Drop leading digits (and any "1234A" / "1234-1/2" variants), then any
  // residual leading whitespace or punctuation.
  return t.replace(/^\s*\d+[\dA-Za-z\-/.]*\s+/, '').trim();
}

/* Build "The Thorntons" from a last name. Handles already-pluralized inputs gracefully. */
function familyName(last) {
  const t = (last || '').trim();
  if (!t) return 'Your neighbors';
  // Pluralize: Jones → Joneses, Thornton → Thorntons, Smith → Smiths
  const lower = t.toLowerCase();
  const ending = /(s|x|z|ch|sh)$/.test(lower) ? 'es' : 's';
  return `The ${t}${ending}`;
}

/* ========================================================================
   Shared building blocks
   ======================================================================== */

function Eyebrow({ children, color = 'inherit', tracking = '0.22em', weight = 500, size = 9, style }) {
  return (
    <span style={{
      fontFamily: 'var(--ff-sans)',
      fontSize: size,
      fontWeight: weight,
      letterSpacing: tracking,
      textTransform: 'uppercase',
      color,
      lineHeight: 1.1,
      ...style,
    }}>{children}</span>
  );
}

function RuleEyebrow({ children, color = 'currentColor', size = 10, tracking = '0.22em', ruleW = 24 }) {
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 10,
      fontFamily: 'var(--ff-sans)',
      fontSize: size,
      fontWeight: 500,
      letterSpacing: tracking,
      textTransform: 'uppercase',
      color,
      lineHeight: 1.1,
    }}>
      <span style={{ width: ruleW, height: 1, background: 'currentColor', opacity: 0.7 }} />
      <span>{children}</span>
      <span style={{ width: ruleW, height: 1, background: 'currentColor', opacity: 0.7 }} />
    </span>
  );
}

/* The cursive "A" mark from the ASA logo, drawn small so we can place it freely.
   We fetch the SVG once and inline its markup — html2canvas can't reliably
   rasterize SVG <img> tags but renders inlined <svg> faithfully. */
// Inlined ASA logo SVG — embedded directly in source so there is no fetch
// dependency at render time. html2canvas reliably rasterizes inlined <svg>;
// the prior fetch-and-inject path was racing the iframe lifecycle.
const asaLogoSvgMarkup = `<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 676.7 309.5"><defs></defs><g><path class="st1" d="M44,178.6h-2.9l-22,53.4h-6.4v2.5h15.3v-2.5h-6.2l7.6-18.3h16.7l7.9,18.3h-6.5v2.5h26.1v-2.5h-6.6l-23-53.4ZM30.4,211.3l7.2-17.5,7.6,17.5s-14.8,0-14.8,0Z"></path><path class="st1" d="M129.8,206c-1.5-1.2-3.3-2.2-5.2-3-1.9-.8-3.9-1.5-5.9-2.1s-3.9-1.3-5.8-1.8c-1.8-.6-3.5-1.3-4.9-2-1.4-.8-2.5-1.7-3.4-2.7-.8-1-1.2-2.3-1.2-3.8s.2-2,.7-3,1.2-1.9,2.2-2.7,2.2-1.5,3.6-2,3.1-.8,4.9-.8,4.2.4,5.6,1.2c1.5.8,2.7,1.8,3.7,3.1s1.8,2.8,2.4,4.4c.7,1.6,1.3,3.3,1.9,5l.2.5h2.2v-15.6h-2.4l-.2.4c-.3.7-.5.9-.5,1,0,0-.2.1-.9.1s-1.1-.1-1.7-.4c-.7-.3-1.5-.6-2.5-.9-1-.3-2.1-.6-3.3-.9-1.3-.3-2.8-.5-4.4-.5-2.6,0-5.1.5-7.4,1.3-2.3.9-4.2,2.1-5.8,3.5-1.6,1.5-2.9,3.1-3.8,5s-1.4,3.9-1.4,6,.5,5.1,1.4,6.9c.9,1.8,2.1,3.4,3.6,4.7,1.5,1.2,3.2,2.3,5,3.1s3.7,1.4,5.7,2c1.9.6,3.8,1.1,5.6,1.7,1.8.5,3.3,1.2,4.7,1.9,1.3.7,2.4,1.7,3.2,2.7.8,1,1.1,2.4,1.1,4.1s-.2,2-.6,3.2-1,2.4-1.9,3.5-2.1,2-3.7,2.8-3.6,1.1-6.1,1.1h-2.4c-.8,0-1.6-.2-2.5-.4-.9-.3-1.9-.7-2.9-1.2-1-.6-2-1.4-3.1-2.5-1-.9-1.8-2-2.5-3.1s-1.3-2.2-1.7-3.3-.8-2.1-1-3-.4-1.7-.5-2.3v-.6h-2.5v17.8h2.4l.2-.5c.3-.8.5-1,.6-1s.3-.2,1-.2.9.1,2,.4c1,.3,2.2.6,3.5.9,1.3.3,2.7.6,4.2.9,1.5.3,3.1.5,4.7.5,3.1,0,6-.5,8.5-1.5s4.8-2.3,6.6-4c1.9-1.7,3.3-3.7,4.3-5.9s1.5-4.6,1.5-7.1-.5-4.8-1.4-6.5-2.1-3.2-3.7-4.4"></path><path class="st1" d="M307.7,190.7c-1.7-2-3.7-3.7-5.9-5-2.2-1.3-4.5-2.3-7-3.1-2.4-.8-4.9-1.3-7.4-1.6s-4.8-.4-7.1-.4-5,0-7.5.1-5,.1-7.5.1h-7.5v2.5h6.8v48.7h-12.4l-23.2-53.4h-2.9l-21.8,53.4h-12.5v-48.8h6.8v-2.5h-25.6v2.5h6.8v22.7h-19.8v-22.7h6.8v-2.5h-25.6v2.5h6.8v48.8h-6.8v2.5h25.6v-2.5h-6.8v-23.7h19.8v23.7h-6.8v2.5h40.2v-2.5h-6.2l7.6-18.3h16.7l7.9,18.3h-6.5v2.5h32.6c2.8,0,5.6,0,8.3.1,2.8,0,5.6.1,8.4.1s4.1-.2,6.3-.4c2.3-.3,4.6-.8,6.9-1.6s4.6-1.8,6.7-3.1c2.2-1.3,4.1-3,5.8-5s3.1-4.4,4.1-7.2,1.5-6,1.5-9.7-.5-7-1.5-9.7c-1-2.9-2.4-5.3-4.1-7.3M276.7,183.1c.3,0,.8-.1,1.2-.1h2.6c4.1,0,7.5.6,10,1.8s4.4,2.8,5.8,5,2.3,4.9,2.8,8c.5,3.2.7,6.8.7,10.7s-.1,3.3-.2,5.1-.4,3.6-.9,5.4-1.1,3.5-2,5.1-2,3-3.5,4.2-3.3,2.2-5.5,3c-2.2.7-4.8,1.1-7.9,1.1s-2-.1-3.2-.2v-49.1h.1ZM215.5,211.3l7.2-17.5,7.6,17.5h-14.8Z"></path><path class="st1" d="M364.2,217.5c-.5,2-1.2,4-2.1,5.8s-2,3.3-3.4,4.6c-1.3,1.3-2.9,2.3-4.6,3s-3.7,1.1-5.8,1.1h-11.3v-25.1h4.2c1.6,0,2.9.3,4,.8s1.9,1.3,2.7,2.2c.7.9,1.3,2,1.8,3.3s.9,2.7,1.2,4v.6h2.3v-24.3h-2.3v.6c-.4,1.6-.9,3.1-1.4,4.4-.5,1.3-1.2,2.4-1.9,3.2-.8.9-1.7,1.5-2.9,2s-2.6.7-4.3.7h-3.4v-21.2h11c2.3,0,4.2.4,5.8,1.3,1.6.9,2.9,1.9,3.9,3,1,1.2,1.8,2.4,2.4,3.6.6,1.3,1,2.3,1.2,3.1l.2.5h2.2v-14h-45.4v2.5h6.8v48.8h-6.8v2.5h48.4v-17.6h-2.3l-.2.6h0Z"></path><path class="st1" d="M486.3,208.3c-1.8-1.1-3.7-1.9-5.9-2.4-.9-.2-1.7-.4-2.6-.5.3-.1.6-.2,1-.3,1.7-.5,3.4-1.2,5.1-2.2,1.6-1,3.1-2.2,4.2-3.7,1.2-1.6,1.8-3.5,1.8-5.8s-.6-4.6-1.8-6.3c-1.2-1.7-2.7-3-4.5-4s-3.8-1.7-6-2.1c-2.1-.4-4.2-.6-6.2-.6s-4.1,0-6.1.1-4,.1-6.1.1h-11.8v2.5h6.8v48.9h-12.6l-23-52.9-.2-.4h-3l-21.9,53.3h-6.4v2.5h15.3v-2.5h-6.2l7.6-18.3h16.7l7.9,18.3h-6.5v2.5h37.2c2.1,0,4.1,0,6.1.1,2.1,0,4.2.1,6.2.1s4.1-.2,6.4-.6c2.4-.4,4.7-1.2,6.9-2.3s4-2.7,5.5-4.6c1.5-2,2.3-4.5,2.3-7.5s-.6-5-1.8-6.9c-1.1-1.9-2.6-3.4-4.4-4.5M475.5,185.6c.6.9,1.1,2.1,1.4,3.6s.4,3.3.4,5.4-.1,2.6-.2,3.8c-.2,1.2-.5,2.2-1.1,3.1-.6.9-1.4,1.6-2.6,2.1s-2.8.8-4.9.8h-2.3v-21.3h1c.5-.1,1-.2,1.6-.2,1.8,0,3.2.2,4.3.7,1,.5,1.8,1.1,2.4,2M466.2,206.9h3.5c2.3,0,4.1.5,5.3,1.4,1.2.9,2.2,2,2.8,3.3s1,2.6,1.1,3.9c.1,1.4.2,2.5.2,3.3,0,2.9-.2,5.3-.7,7.1-.4,1.7-1.1,3.1-1.9,4s-1.7,1.5-2.9,1.9c-1.2.3-2.5.5-4,.5s-2.4-.1-3.5-.2v-25.2h.1ZM404.8,211.3l7.2-17.5,7.6,17.5h-14.8Z"></path><path class="st1" d="M545.9,187.1c-2.6-2.4-5.7-4.3-9.1-5.6-3.4-1.3-6.8-2-10.3-2s-6.5.7-9.9,2c-3.3,1.3-6.4,3.2-9.1,5.7s-4.9,5.4-6.7,8.9c-1.7,3.5-2.6,7.4-2.6,11.6s.8,7.6,2.3,11c1.5,3.3,3.6,6.3,6.2,8.8s5.6,4.5,9,5.9,7,2.2,10.7,2.2,7.4-.8,10.8-2.2c3.4-1.5,6.4-3.5,9-6.1s4.7-5.6,6.2-8.9c1.5-3.4,2.3-7,2.3-10.8s-.8-8.2-2.5-11.7c-1.6-3.5-3.7-6.4-6.3-8.8M515.2,228.1c-1.2-1.9-2.1-4.5-2.7-7.8-.6-3.3-.9-7.5-.9-12.6s.2-7.4.5-10.6c.3-3.1,1-5.9,2.1-8.2,1-2.2,2.5-4,4.4-5.2s4.6-1.8,7.8-1.8,6.3.7,8.2,2c2,1.3,3.4,3.1,4.4,5.3,1,2.3,1.6,5,1.8,8,.2,3.1.3,6.4.3,9.8s0,7.3-.4,10.6c-.3,3.2-.9,5.9-1.9,8.2-1,2.2-2.4,4-4.4,5.3-1.9,1.3-4.7,1.9-8.1,1.9s-4.9-.4-6.8-1.1c-1.7-.7-3.2-2-4.3-3.8"></path><path class="st1" d="M658.4,216.9v.6c-.6,2-1.3,4-2.2,5.8s-2,3.3-3.4,4.6c-1.3,1.3-2.9,2.3-4.6,3s-3.7,1.1-5.8,1.1h-11.3v-25.1h4.2c1.6,0,2.9.3,4,.8s1.9,1.3,2.7,2.2c.7.9,1.3,2,1.8,3.3s.9,2.7,1.2,4v.6h2.3v-24.3h-2.3v.6c-.4,1.6-.9,3.1-1.4,4.4-.5,1.3-1.2,2.4-1.9,3.3-.8.9-1.7,1.5-2.9,2s-2.6.7-4.3.7h-3.4v-21.3h11c2.3,0,4.2.4,5.8,1.3,1.6.9,2.9,1.9,3.9,3,1,1.2,1.8,2.4,2.4,3.6.6,1.3,1,2.3,1.2,3.1l.2.5h2.2v-14h-60.8v2.5h6.3l-14.5,37.1-14.4-37.1h6.7v-2.5h-26v2.5h6.4l20.3,53.3h3.1l20.7-53.3h13.2v48.8h-6.8v2.5h48.4v-17.6h-2Z"></path></g><path class="st1" d="M417.5,19.1c-.1-.1-.2-.3-.3-.3-30.8-8.7-53.7,33.7-72.2,67.9-3.9,7.1-7.6,13.9-11,19.7-14,23.4-31.1,28.3-43,28.3s-14.6-2.8-20.1-7.9-8.7-11.8-9.3-19.1c-.7-9.6,2.6-19.3,8.8-26.1,5.9-6.3,13.8-9.6,22.8-9.6s10.3,1.5,14,3.9c2.9,1.9,6.2,5.4,6.2,11s-3.4,11.6-8.4,11.6-3.7-1-3.9-3.2c0-.2-.2-.5-.5-.6-.2,0-.5,0-.7.2-.9,1.5-1,3.4-1,4.5,0,3.6,2.6,7.2,8.5,7.2s13.2-7,13.2-15.3c0-11.9-12.4-21.3-28.1-21.3s-21,3.8-28.3,10.6c-6.9,6.3-10.8,14.7-10.8,22.8s3.6,17.6,10,23.4c7.2,6.7,17.9,10.1,30.9,10.1,18.9,0,35.9-8.4,47.9-23.5,6.9-8.8,14.6-28.9,16.8-34.9.6-1.3,1.3-1.7,2.9-1.7h25.6c.7,0,1.1,0,1.3.3.2.2.1.8,0,1.3l-9,36.6c-1.7,7-6.4,10.6-14.1,10.6s-.6.2-.6.6v.7c-.1,0-.1.3,0,.5.1,0,.2.2.5.2h34.9c.2,0,.5,0,.6-.3l.2-.7c.1-.2,0-.3-.1-.6-.1,0-.3-.2-.5-.2-4.2,0-7.2-1-8.8-3.1-2-2.5-1.5-5.8-1.1-7.1l22.1-94.1c.4-1.1.9-1.6,2-1.5h.3c.2,0,.9.3,1.3.6.4.1.7,0,.8-.2l.4-.7c.1-.2.1-.5.1-.6M360.2,74.8s-.1-.2.2-1c3.2-8.2,6-15.1,10.8-24,9.2-16.3,19.9-26.5,31-29.5l-12.9,52.9c-.5,1.4-1,1.7-2.4,1.7h-25.6c-1,0-1.1,0-1.1,0"></path><g><path class="st1" d="M96,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8-1.6-.6-2.3-1-1.2-1-1.7-1.7-.6-1.6-.6-2.7c0-1.6.6-3,1.9-4.1,1.3-1.1,2.9-1.7,5-1.7s1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1c.7.4,1.2,1,1.7,1.7s.6,1.6.6,2.7c0,1.8-.6,3.3-1.9,4.4-1.3,1.1-3,1.7-5.3,1.7s-1.8-.1-2.6-.3c-.8-.2-1.5-.5-2-.8-.5-.3-1-.6-1.4-.9s-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path><path class="st1" d="M106.7,263.4h2.3v9.4h11.9v-9.4h2.3v21.3h-2.3v-9.7h-11.9v9.7h-2.3v-21.3Z"></path><path class="st1" d="M135.1,278.8l-2.4,5.9h-2.4l8.8-21.3h2.1l8.8,21.3h-2.4l-2.4-5.9h-10.1ZM140.1,266.6l-4.1,10.1h8.2l-4.1-10.1Z"></path><path class="st1" d="M163.7,263.4c3.1,0,5.7,1,7.9,3.1,2.2,2.1,3.2,4.6,3.2,7.5s-1.1,5.4-3.2,7.5c-2.2,2.1-4.8,3.1-7.9,3.1h-6.7v-21.3h6.7ZM159.3,265.5v17.1h4.4c2.5,0,4.6-.8,6.3-2.5,1.7-1.7,2.5-3.7,2.5-6s-.8-4.4-2.5-6-3.8-2.5-6.3-2.5h-4.4Z"></path><path class="st1" d="M197.8,282.6v2.1h-14.9v-21.3h14.6v2.1h-12.3v7h9.9v2.1h-9.9v7.9h12.6Z"></path><path class="st1" d="M215,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8-1.6-.6-2.3-1-1.2-1-1.7-1.7c-.4-.7-.6-1.6-.6-2.7,0-1.6.6-3,1.9-4.1s2.9-1.7,5-1.7,1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1c.7.4,1.2,1,1.7,1.7s.6,1.6.6,2.7c0,1.8-.6,3.3-1.9,4.4s-3,1.7-5.3,1.7-1.8-.1-2.6-.3-1.5-.5-2-.8-1-.6-1.4-.9-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path><path class="st1" d="M237.4,289.3h-2v-28h2v28Z"></path><path class="st1" d="M266.2,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8-1.6-.6-2.3-1-1.2-1-1.7-1.7-.6-1.6-.6-2.7c0-1.6.6-3,1.9-4.1,1.3-1.1,2.9-1.7,5-1.7s1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1c.7.4,1.2,1,1.7,1.7.4.7.6,1.6.6,2.7,0,1.8-.6,3.3-1.9,4.4-1.3,1.1-3,1.7-5.3,1.7s-1.8-.1-2.6-.3-1.5-.5-2-.8-1-.6-1.4-.9-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path><path class="st1" d="M276.9,263.4h2.3v9.4h11.9v-9.4h2.3v21.3h-2.3v-9.7h-11.9v9.7h-2.3v-21.3Z"></path><path class="st1" d="M315.7,281c1.2-1.3,1.9-2.8,1.9-4.6v-13h2.3v13c0,2.4-.9,4.4-2.6,6.1-1.7,1.7-3.8,2.6-6.1,2.6s-4.4-.9-6.1-2.6c-1.7-1.7-2.6-3.7-2.6-6.1v-13h2.3v13c0,1.8.6,3.4,1.9,4.6,1.2,1.3,2.8,1.9,4.5,1.9s3.3-.6,4.5-1.9Z"></path><path class="st1" d="M342.1,263.4v2.1h-6.7v19.2h-2.3v-19.2h-6.7v-2.1h15.7Z"></path><path class="st1" d="M363.8,263.4v2.1h-6.7v19.2h-2.3v-19.2h-6.7v-2.1h15.7Z"></path><path class="st1" d="M385.6,282.6v2.1h-14.9v-21.3h14.6v2.1h-12.3v7h9.9v2.1h-9.9v7.9h12.6Z"></path><path class="st1" d="M401.7,263.4c1.9,0,3.4.6,4.7,1.8s1.9,2.7,1.9,4.6-.2,1.7-.5,2.5-.7,1.3-1.1,1.8-.9.8-1.3,1.1-.8.5-1.1.6h-.5c0,.1,5,9,5,9h-2.6l-4.7-8.5h-6.1v8.5h-2.3v-21.3h8.5ZM395.5,265.5v8.5h6.2c1.3,0,2.3-.4,3.1-1.2s1.2-1.8,1.2-3.1-.4-2.3-1.2-3.1c-.8-.8-1.8-1.2-3.1-1.2h-6.2Z"></path><path class="st1" d="M426.4,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8-1.6-.6-2.3-1-1.2-1-1.7-1.7c-.4-.7-.6-1.6-.6-2.7,0-1.6.6-3,1.9-4.1,1.3-1.1,2.9-1.7,5-1.7s1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1,1.2,1,1.7,1.7.6,1.6.6,2.7c0,1.8-.6,3.3-1.9,4.4-1.3,1.1-3,1.7-5.3,1.7s-1.8-.1-2.6-.3-1.5-.5-2-.8-1-.6-1.4-.9-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path><path class="st1" d="M448.8,289.3h-2v-28h2v28Z"></path><path class="st1" d="M480.1,273.5c.1,0,.4.3.8.6s.8.6,1.1,1,.6.9.8,1.5c.3.6.4,1.3.4,2.1,0,1.8-.6,3.2-1.8,4.4s-2.8,1.7-4.7,1.7h-8.7v-21.3h8.2c1.7,0,3.1.5,4.3,1.6,1.1,1.1,1.7,2.4,1.7,3.9s0,1.2-.2,1.7-.4,1-.6,1.3-.5.6-.7.8-.4.4-.6.5h-.2c0,.2.2.2.4.3ZM470.3,265.5v6.8h5.9c1.1,0,2-.3,2.7-.9s1-1.5,1-2.6-.3-1.8-1-2.4c-.6-.6-1.5-.9-2.7-.9h-5.9ZM476.7,282.6c1.3,0,2.3-.4,3.1-1.1.8-.7,1.2-1.7,1.2-2.9s-.4-2.2-1.2-3-1.8-1.2-3.1-1.2h-6.4v8.2h6.4Z"></path><path class="st1" d="M491.5,284.7v-21.3h2.3v19.2h11v2.1h-13.3Z"></path><path class="st1" d="M511.6,263.4h2.3v21.3h-2.3v-21.3Z"></path><path class="st1" d="M540.6,263.4v21.3h-2l-13.1-17.2v17.2h-2.3v-21.3h2l13.1,17.2v-17.2h2.3Z"></path><path class="st1" d="M556.8,263.4c3.1,0,5.7,1,7.9,3.1,2.2,2.1,3.2,4.6,3.2,7.5s-1.1,5.4-3.2,7.5c-2.2,2.1-4.8,3.1-7.9,3.1h-6.7v-21.3h6.7ZM552.4,265.5v17.1h4.4c2.5,0,4.6-.8,6.3-2.5,1.7-1.7,2.5-3.7,2.5-6s-.8-4.4-2.5-6-3.8-2.5-6.3-2.5h-4.4Z"></path><path class="st1" d="M585.5,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8c-.8-.3-1.6-.6-2.3-1s-1.2-1-1.7-1.7-.6-1.6-.6-2.7c0-1.6.6-3,1.9-4.1,1.3-1.1,2.9-1.7,5-1.7s1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1c.7.4,1.2,1,1.7,1.7.4.7.6,1.6.6,2.7,0,1.8-.6,3.3-1.9,4.4s-3,1.7-5.3,1.7-1.8-.1-2.6-.3c-.8-.2-1.5-.5-2-.8s-1-.6-1.4-.9-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path></g><g><path class="st0" d="M-688.2,178.6h-2.9l-22,53.4h-6.4v2.5h15.3v-2.5h-6.2l7.6-18.3h16.7l7.9,18.3h-6.5v2.5h26.1v-2.5h-6.6l-23-53.4ZM-701.8,211.3l7.2-17.5,7.6,17.5s-14.8,0-14.8,0Z"></path><path class="st0" d="M-602.4,206c-1.5-1.2-3.3-2.2-5.2-3-1.9-.8-3.9-1.5-5.9-2.1s-3.9-1.3-5.8-1.8c-1.8-.6-3.5-1.3-4.9-2-1.4-.8-2.5-1.7-3.4-2.7-.8-1-1.2-2.3-1.2-3.8s.2-2,.7-3,1.2-1.9,2.2-2.7,2.2-1.5,3.6-2,3.1-.8,4.9-.8,4.2.4,5.6,1.2c1.5.8,2.7,1.8,3.7,3.1s1.8,2.8,2.4,4.4c.7,1.6,1.3,3.3,1.9,5l.2.5h2.2v-15.6h-2.4l-.2.4c-.3.7-.5.9-.5,1,0,0-.2.1-.9.1s-1.1-.1-1.7-.4c-.7-.3-1.5-.6-2.5-.9-1-.3-2.1-.6-3.3-.9-1.3-.3-2.8-.5-4.4-.5-2.6,0-5.1.5-7.4,1.3-2.3.9-4.2,2.1-5.8,3.5-1.6,1.5-2.9,3.1-3.8,5s-1.4,3.9-1.4,6,.5,5.1,1.4,6.9c.9,1.8,2.1,3.4,3.6,4.7,1.5,1.2,3.2,2.3,5,3.1s3.7,1.4,5.7,2c1.9.6,3.8,1.1,5.6,1.7,1.8.5,3.3,1.2,4.7,1.9,1.3.7,2.4,1.7,3.2,2.7.8,1,1.1,2.4,1.1,4.1s-.2,2-.6,3.2-1,2.4-1.9,3.5-2.1,2-3.7,2.8-3.6,1.1-6.1,1.1h-2.4c-.8,0-1.6-.2-2.5-.4-.9-.3-1.9-.7-2.9-1.2-1-.6-2-1.4-3.1-2.5-1-.9-1.8-2-2.5-3.1s-1.3-2.2-1.7-3.3-.8-2.1-1-3-.4-1.7-.5-2.3v-.6h-2.5v17.8h2.4l.2-.5c.3-.8.5-1,.6-1s.3-.2,1-.2.9.1,2,.4c1,.3,2.2.6,3.5.9,1.3.3,2.7.6,4.2.9,1.5.3,3.1.5,4.7.5,3.1,0,6-.5,8.5-1.5s4.8-2.3,6.6-4c1.9-1.7,3.3-3.7,4.3-5.9s1.5-4.6,1.5-7.1-.5-4.8-1.4-6.5-2.1-3.2-3.7-4.4"></path><path class="st0" d="M-424.5,190.7c-1.7-2-3.7-3.7-5.9-5-2.2-1.3-4.5-2.3-7-3.1-2.4-.8-4.9-1.3-7.4-1.6s-4.8-.4-7.1-.4-5,0-7.5.1-5,.1-7.5.1h-7.5v2.5h6.8v48.7h-12.4l-23.2-53.4h-2.9l-21.8,53.4h-12.5v-48.8h6.8v-2.5h-25.6v2.5h6.8v22.7h-19.8v-22.7h6.8v-2.5h-25.6v2.5h6.8v48.8h-6.8v2.5h25.6v-2.5h-6.8v-23.7h19.8v23.7h-6.8v2.5h40.2v-2.5h-6.2l7.6-18.3h16.7l7.9,18.3h-6.5v2.5h32.6c2.8,0,5.6,0,8.3.1,2.8,0,5.6.1,8.4.1s4.1-.2,6.3-.4c2.3-.3,4.6-.8,6.9-1.6s4.6-1.8,6.7-3.1c2.2-1.3,4.1-3,5.8-5s3.1-4.4,4.1-7.2,1.5-6,1.5-9.7-.5-7-1.5-9.7c-1-2.9-2.4-5.3-4.1-7.3M-455.5,183.1c.3,0,.8-.1,1.2-.1h2.6c4.1,0,7.5.6,10,1.8s4.4,2.8,5.8,5,2.3,4.9,2.8,8c.5,3.2.7,6.8.7,10.7s-.1,3.3-.2,5.1-.4,3.6-.9,5.4-1.1,3.5-2,5.1-2,3-3.5,4.2-3.3,2.2-5.5,3c-2.2.7-4.8,1.1-7.9,1.1s-2-.1-3.2-.2v-49.1h.1ZM-516.7,211.3l7.2-17.5,7.6,17.5h-14.8Z"></path><path class="st0" d="M-368,217.5c-.5,2-1.2,4-2.1,5.8s-2,3.3-3.4,4.6c-1.3,1.3-2.9,2.3-4.6,3s-3.7,1.1-5.8,1.1h-11.3v-25.1h4.2c1.6,0,2.9.3,4,.8s1.9,1.3,2.7,2.2c.7.9,1.3,2,1.8,3.3s.9,2.7,1.2,4v.6h2.3v-24.3h-2.3v.6c-.4,1.6-.9,3.1-1.4,4.4-.5,1.3-1.2,2.4-1.9,3.2-.8.9-1.7,1.5-2.9,2s-2.6.7-4.3.7h-3.4v-21.2h11c2.3,0,4.2.4,5.8,1.3,1.6.9,2.9,1.9,3.9,3,1,1.2,1.8,2.4,2.4,3.6.6,1.3,1,2.3,1.2,3.1l.2.5h2.2v-14h-45.4v2.5h6.8v48.8h-6.8v2.5h48.4v-17.6h-2.3l-.2.6h0Z"></path><path class="st0" d="M-245.9,208.3c-1.8-1.1-3.7-1.9-5.9-2.4-.9-.2-1.7-.4-2.6-.5.3-.1.6-.2,1-.3,1.7-.5,3.4-1.2,5.1-2.2,1.6-1,3.1-2.2,4.2-3.7,1.2-1.6,1.8-3.5,1.8-5.8s-.6-4.6-1.8-6.3c-1.2-1.7-2.7-3-4.5-4s-3.8-1.7-6-2.1c-2.1-.4-4.2-.6-6.2-.6s-4.1,0-6.1.1-4,.1-6.1.1h-11.8v2.5h6.8v48.9h-12.6l-23-52.9-.2-.4h-3l-21.9,53.3h-6.4v2.5h15.3v-2.5h-6.2l7.6-18.3h16.7l7.9,18.3h-6.5v2.5h37.2c2.1,0,4.1,0,6.1.1,2.1,0,4.2.1,6.2.1s4.1-.2,6.4-.6c2.4-.4,4.7-1.2,6.9-2.3s4-2.7,5.5-4.6c1.5-2,2.3-4.5,2.3-7.5s-.6-5-1.8-6.9c-1.1-1.9-2.6-3.4-4.4-4.5M-256.7,185.6c.6.9,1.1,2.1,1.4,3.6s.4,3.3.4,5.4-.1,2.6-.2,3.8c-.2,1.2-.5,2.2-1.1,3.1-.6.9-1.4,1.6-2.6,2.1s-2.8.8-4.9.8h-2.3v-21.3h1c.5-.1,1-.2,1.6-.2,1.8,0,3.2.2,4.3.7,1,.5,1.8,1.1,2.4,2M-266,206.9h3.5c2.3,0,4.1.5,5.3,1.4,1.2.9,2.2,2,2.8,3.3s1,2.6,1.1,3.9c.1,1.4.2,2.5.2,3.3,0,2.9-.2,5.3-.7,7.1-.4,1.7-1.1,3.1-1.9,4s-1.7,1.5-2.9,1.9c-1.2.3-2.5.5-4,.5s-2.4-.1-3.5-.2v-25.2h.1ZM-327.4,211.3l7.2-17.5,7.6,17.5h-14.8Z"></path><path class="st0" d="M-186.3,187.1c-2.6-2.4-5.7-4.3-9.1-5.6-3.4-1.3-6.8-2-10.3-2s-6.5.7-9.9,2c-3.3,1.3-6.4,3.2-9.1,5.7s-4.9,5.4-6.7,8.9c-1.7,3.5-2.6,7.4-2.6,11.6s.8,7.6,2.3,11c1.5,3.3,3.6,6.3,6.2,8.8s5.6,4.5,9,5.9,7,2.2,10.7,2.2,7.4-.8,10.8-2.2c3.4-1.5,6.4-3.5,9-6.1s4.7-5.6,6.2-8.9c1.5-3.4,2.3-7,2.3-10.8s-.8-8.2-2.5-11.7c-1.6-3.5-3.7-6.4-6.3-8.8M-217,228.1c-1.2-1.9-2.1-4.5-2.7-7.8-.6-3.3-.9-7.5-.9-12.6s.2-7.4.5-10.6c.3-3.1,1-5.9,2.1-8.2,1-2.2,2.5-4,4.4-5.2s4.6-1.8,7.8-1.8,6.3.7,8.2,2c2,1.3,3.4,3.1,4.4,5.3,1,2.3,1.6,5,1.8,8,.2,3.1.3,6.4.3,9.8s0,7.3-.4,10.6c-.3,3.2-.9,5.9-1.9,8.2-1,2.2-2.4,4-4.4,5.3-1.9,1.3-4.7,1.9-8.1,1.9s-4.9-.4-6.8-1.1c-1.7-.7-3.2-2-4.3-3.8"></path><path class="st0" d="M-73.8,216.9v.6c-.6,2-1.3,4-2.2,5.8s-2,3.3-3.4,4.6c-1.3,1.3-2.9,2.3-4.6,3s-3.7,1.1-5.8,1.1h-11.3v-25.1h4.2c1.6,0,2.9.3,4,.8s1.9,1.3,2.7,2.2c.7.9,1.3,2,1.8,3.3s.9,2.7,1.2,4v.6h2.3v-24.3h-2.3v.6c-.4,1.6-.9,3.1-1.4,4.4-.5,1.3-1.2,2.4-1.9,3.3-.8.9-1.7,1.5-2.9,2s-2.6.7-4.3.7h-3.4v-21.3h11c2.3,0,4.2.4,5.8,1.3,1.6.9,2.9,1.9,3.9,3,1,1.2,1.8,2.4,2.4,3.6.6,1.3,1,2.3,1.2,3.1l.2.5h2.2v-14h-60.8v2.5h6.3l-14.5,37.1-14.4-37.1h6.7v-2.5h-26v2.5h6.4l20.3,53.3h3.1l20.7-53.3h13.2v48.8h-6.8v2.5h48.4v-17.6h-2Z"></path></g><path class="st0" d="M-314.7,19.1c-.1-.1-.2-.3-.3-.3-30.8-8.7-53.7,33.7-72.2,67.9-3.9,7.1-7.6,13.9-11,19.7-14,23.4-31.1,28.3-43,28.3s-14.6-2.8-20.1-7.9-8.7-11.8-9.3-19.1c-.7-9.6,2.6-19.3,8.8-26.1,5.9-6.3,13.8-9.6,22.8-9.6s10.3,1.5,14,3.9c2.9,1.9,6.2,5.4,6.2,11s-3.4,11.6-8.4,11.6-3.7-1-3.9-3.2c0-.2-.2-.5-.5-.6-.2,0-.5,0-.7.2-.9,1.5-1,3.4-1,4.5,0,3.6,2.6,7.2,8.5,7.2s13.2-7,13.2-15.3c0-11.9-12.4-21.3-28.1-21.3s-21,3.8-28.3,10.6c-6.9,6.3-10.8,14.7-10.8,22.8s3.6,17.6,10,23.4c7.2,6.7,17.9,10.1,30.9,10.1,18.9,0,35.9-8.4,47.9-23.5,6.9-8.8,14.6-28.9,16.8-34.9.6-1.3,1.3-1.7,2.9-1.7h25.6c.7,0,1.1,0,1.3.3.2.2.1.8,0,1.3l-9,36.6c-1.7,7-6.4,10.6-14.1,10.6s-.6.2-.6.6v.7c-.1,0-.1.3,0,.5.1,0,.2.2.5.2h34.9c.2,0,.5,0,.6-.3l.2-.7c.1-.2,0-.3-.1-.6-.1,0-.3-.2-.5-.2-4.2,0-7.2-1-8.8-3.1-2-2.5-1.5-5.8-1.1-7.1l22.1-94.1c.4-1.1.9-1.6,2-1.5h.3c.2,0,.9.3,1.3.6.4.1.7,0,.8-.2l.4-.7c.1-.2.1-.5.1-.6M-372,74.8s-.1-.2.2-1c3.2-8.2,6-15.1,10.8-24,9.2-16.3,19.9-26.5,31-29.5l-12.9,52.9c-.5,1.4-1,1.7-2.4,1.7h-25.6c-1,0-1.1,0-1.1,0"></path><g><path class="st0" d="M-636.2,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8-1.6-.6-2.3-1-1.2-1-1.7-1.7-.6-1.6-.6-2.7c0-1.6.6-3,1.9-4.1,1.3-1.1,2.9-1.7,5-1.7s1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1c.7.4,1.2,1,1.7,1.7s.6,1.6.6,2.7c0,1.8-.6,3.3-1.9,4.4-1.3,1.1-3,1.7-5.3,1.7s-1.8-.1-2.6-.3c-.8-.2-1.5-.5-2-.8-.5-.3-1-.6-1.4-.9s-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path><path class="st0" d="M-625.5,263.4h2.3v9.4h11.9v-9.4h2.3v21.3h-2.3v-9.7h-11.9v9.7h-2.3v-21.3Z"></path><path class="st0" d="M-597.1,278.8l-2.4,5.9h-2.4l8.8-21.3h2.1l8.8,21.3h-2.4l-2.4-5.9h-10.1ZM-592.1,266.6l-4.1,10.1h8.2l-4.1-10.1Z"></path><path class="st0" d="M-568.5,263.4c3.1,0,5.7,1,7.9,3.1,2.2,2.1,3.2,4.6,3.2,7.5s-1.1,5.4-3.2,7.5c-2.2,2.1-4.8,3.1-7.9,3.1h-6.7v-21.3h6.7ZM-572.9,265.5v17.1h4.4c2.5,0,4.6-.8,6.3-2.5,1.7-1.7,2.5-3.7,2.5-6s-.8-4.4-2.5-6-3.8-2.5-6.3-2.5h-4.4Z"></path><path class="st0" d="M-534.4,282.6v2.1h-14.9v-21.3h14.6v2.1h-12.3v7h9.9v2.1h-9.9v7.9h12.6Z"></path><path class="st0" d="M-517.2,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8-1.6-.6-2.3-1-1.2-1-1.7-1.7c-.4-.7-.6-1.6-.6-2.7,0-1.6.6-3,1.9-4.1s2.9-1.7,5-1.7,1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1c.7.4,1.2,1,1.7,1.7s.6,1.6.6,2.7c0,1.8-.6,3.3-1.9,4.4s-3,1.7-5.3,1.7-1.8-.1-2.6-.3-1.5-.5-2-.8-1-.6-1.4-.9-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path><path class="st0" d="M-494.8,289.3h-2v-28h2v28Z"></path><path class="st0" d="M-466,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8-1.6-.6-2.3-1-1.2-1-1.7-1.7-.6-1.6-.6-2.7c0-1.6.6-3,1.9-4.1,1.3-1.1,2.9-1.7,5-1.7s1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1c.7.4,1.2,1,1.7,1.7.4.7.6,1.6.6,2.7,0,1.8-.6,3.3-1.9,4.4-1.3,1.1-3,1.7-5.3,1.7s-1.8-.1-2.6-.3-1.5-.5-2-.8-1-.6-1.4-.9-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path><path class="st0" d="M-455.3,263.4h2.3v9.4h11.9v-9.4h2.3v21.3h-2.3v-9.7h-11.9v9.7h-2.3v-21.3Z"></path><path class="st0" d="M-416.5,281c1.2-1.3,1.9-2.8,1.9-4.6v-13h2.3v13c0,2.4-.9,4.4-2.6,6.1-1.7,1.7-3.8,2.6-6.1,2.6s-4.4-.9-6.1-2.6c-1.7-1.7-2.6-3.7-2.6-6.1v-13h2.3v13c0,1.8.6,3.4,1.9,4.6,1.2,1.3,2.8,1.9,4.5,1.9s3.3-.6,4.5-1.9Z"></path><path class="st0" d="M-390.1,263.4v2.1h-6.7v19.2h-2.3v-19.2h-6.7v-2.1h15.7Z"></path><path class="st0" d="M-368.4,263.4v2.1h-6.7v19.2h-2.3v-19.2h-6.7v-2.1h15.7Z"></path><path class="st0" d="M-346.6,282.6v2.1h-14.9v-21.3h14.6v2.1h-12.3v7h9.9v2.1h-9.9v7.9h12.6Z"></path><path class="st0" d="M-330.5,263.4c1.9,0,3.4.6,4.7,1.8s1.9,2.7,1.9,4.6-.2,1.7-.5,2.5-.7,1.3-1.1,1.8-.9.8-1.3,1.1-.8.5-1.1.6h-.5c0,.1,5,9,5,9h-2.6l-4.7-8.5h-6.1v8.5h-2.3v-21.3h8.5ZM-336.7,265.5v8.5h6.2c1.3,0,2.3-.4,3.1-1.2s1.2-1.8,1.2-3.1-.4-2.3-1.2-3.1c-.8-.8-1.8-1.2-3.1-1.2h-6.2Z"></path><path class="st0" d="M-305.8,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8-1.6-.6-2.3-1-1.2-1-1.7-1.7c-.4-.7-.6-1.6-.6-2.7,0-1.6.6-3,1.9-4.1,1.3-1.1,2.9-1.7,5-1.7s1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1,1.2,1,1.7,1.7.6,1.6.6,2.7c0,1.8-.6,3.3-1.9,4.4-1.3,1.1-3,1.7-5.3,1.7s-1.8-.1-2.6-.3-1.5-.5-2-.8-1-.6-1.4-.9-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path><path class="st0" d="M-283.4,289.3h-2v-28h2v28Z"></path><path class="st0" d="M-252.1,273.5c.1,0,.4.3.8.6s.8.6,1.1,1,.6.9.8,1.5c.3.6.4,1.3.4,2.1,0,1.8-.6,3.2-1.8,4.4s-2.8,1.7-4.7,1.7h-8.7v-21.3h8.2c1.7,0,3.1.5,4.3,1.6,1.1,1.1,1.7,2.4,1.7,3.9s0,1.2-.2,1.7-.4,1-.6,1.3-.5.6-.7.8-.4.4-.6.5h-.2c0,.2.2.2.4.3ZM-261.9,265.5v6.8h5.9c1.1,0,2-.3,2.7-.9s1-1.5,1-2.6-.3-1.8-1-2.4c-.6-.6-1.5-.9-2.7-.9h-5.9ZM-255.5,282.6c1.3,0,2.3-.4,3.1-1.1.8-.7,1.2-1.7,1.2-2.9s-.4-2.2-1.2-3-1.8-1.2-3.1-1.2h-6.4v8.2h6.4Z"></path><path class="st0" d="M-240.7,284.7v-21.3h2.3v19.2h11v2.1h-13.3Z"></path><path class="st0" d="M-220.6,263.4h2.3v21.3h-2.3v-21.3Z"></path><path class="st0" d="M-191.6,263.4v21.3h-2l-13.1-17.2v17.2h-2.3v-21.3h2l13.1,17.2v-17.2h2.3Z"></path><path class="st0" d="M-175.4,263.4c3.1,0,5.7,1,7.9,3.1,2.2,2.1,3.2,4.6,3.2,7.5s-1.1,5.4-3.2,7.5c-2.2,2.1-4.8,3.1-7.9,3.1h-6.7v-21.3h6.7ZM-179.8,265.5v17.1h4.4c2.5,0,4.6-.8,6.3-2.5,1.7-1.7,2.5-3.7,2.5-6s-.8-4.4-2.5-6-3.8-2.5-6.3-2.5h-4.4Z"></path><path class="st0" d="M-146.7,277.1c-.4-.5-1-.9-1.7-1.2s-1.4-.5-2.3-.8-1.6-.5-2.5-.8c-.8-.3-1.6-.6-2.3-1s-1.2-1-1.7-1.7-.6-1.6-.6-2.7c0-1.6.6-3,1.9-4.1,1.3-1.1,2.9-1.7,5-1.7s1.5,0,2.2.3,1.3.4,1.7.7.9.5,1.2.8.6.5.8.7l.3.3-1.5,1.5c0,0,0-.1-.2-.2s-.3-.3-.6-.5-.6-.5-.9-.7-.8-.4-1.3-.5-1.1-.2-1.7-.2c-1.4,0-2.5.4-3.3,1.1s-1.2,1.6-1.2,2.6.2,1.4.6,1.9,1,.9,1.7,1.2,1.4.5,2.3.8,1.6.5,2.5.8,1.6.6,2.3,1c.7.4,1.2,1,1.7,1.7.4.7.6,1.6.6,2.7,0,1.8-.6,3.3-1.9,4.4s-3,1.7-5.3,1.7-1.8-.1-2.6-.3c-.8-.2-1.5-.5-2-.8s-1-.6-1.4-.9-.7-.6-.8-.8l-.3-.3,1.4-1.5c0,0,.1.2.2.3s.3.3.7.6.7.6,1.1.8.9.4,1.6.6,1.4.3,2.1.3c1.6,0,2.8-.4,3.6-1.1s1.3-1.7,1.3-2.9-.2-1.4-.6-1.9Z"></path></g></svg>`;
function loadAsaLogoSvg() { return Promise.resolve(asaLogoSvgMarkup); }

// Resolve `var(--asa-off-white)` → `#f3ede4`. CSS variables don't always
// propagate through dangerouslySetInnerHTML into an inline SVG's <style>
// block, and html2canvas definitely can't follow them during rasterization.
// Resolving to a literal hex bypasses both problems. We try the computed
// style first, then fall back to a hardcoded map (because in some iframe
// contexts getPropertyValue can return empty even when the var IS defined).
const ASA_HEX = {
  '--asa-white':       '#ffffff',
  '--asa-off-white':   '#f3ede4',
  '--asa-cream':       '#edd2b6',
  '--asa-navy':        '#15212f',
  '--asa-navy-print':  '#192946',
  '--asa-stone-200':   '#dfd9ce',
  '--asa-stone-500':   '#8e9aa6',
  '--asa-stone-700':   '#404a57',
};
function resolveCssColor(color) {
  if (!color) return '#000';
  const m = String(color).match(/var\((--[^,)\s]+)/);
  if (!m) return color;
  try {
    const v = getComputedStyle(document.documentElement).getPropertyValue(m[1]).trim();
    if (v) return v;
  } catch {}
  return ASA_HEX[m[1]] || '#000';
}

function ASAWordmark({ color = 'var(--asa-white)', size = 28 }) {
  // No async, no state — markup is a synchronous module-level constant.
  // Source SVG uses class="st1" with empty <defs>, so shapes default to black.
  // We inline `style="fill:..."` per path — inline style wins over <style>
  // blocks. Earlier we injected a <style>.st1{...}</style> block, but with
  // multiple ASAWordmark instances on the page (front variants + back), each
  // global .st1 rule clobbered the others — the last `!important` won and
  // painted every instance the same color. Inline style is per-element and
  // immune to that cross-instance interference.
  const hex = resolveCssColor(color);
  const styled = asaLogoSvgMarkup
    .replace(
      /<svg([^>]*)>/,
      `<svg$1 width="100%" height="100%" preserveAspectRatio="xMidYMid meet">`
    )
    .replace(/class="st1"/g, `class="st1" style="fill:${hex}"`);
  return (
    <div
      aria-label="A Shade Above"
      style={{
        height: size,
        width: size * (676.7 / 309.5),
        display: 'block',
      }}
      dangerouslySetInnerHTML={{ __html: styled }}
    />
  );
}

function CategoryBar({ items, color, size = 7.5, sep = '·', tracking = '0.32em' }) {
  return (
    <span style={{
      fontFamily: 'var(--ff-sans)', fontWeight: 500,
      fontSize: size, letterSpacing: tracking, textTransform: 'uppercase',
      color, lineHeight: 1.1,
    }}>
      {items.map((it, i) => (
        <React.Fragment key={i}>
          {i > 0 && <span style={{ opacity: 0.4, margin: '0 6px' }}>{sep}</span>}
          <span>{it}</span>
        </React.Fragment>
      ))}
    </span>
  );
}

function HairRule({ color = 'rgba(243,237,228,0.45)', w = '100%', mt = 0, mb = 0 }) {
  return <div style={{ width: w, height: 1, background: color, marginTop: mt, marginBottom: mb }} />;
}

/* ========================================================================
   FRONTS
   ======================================================================== */

function FrontPhotoLed({ s, set }) {
  // 60/40 split — navy panel left ~4.2", photo right ~6.8"
  const PANEL_W = 4.2 * SC;   // ~302px
  const photo = PHOTO_DEFAULTS[s.category] || PHOTO_DEFAULTS.shutters;
  const placeLine = [s.neighborhood, s.city].filter(Boolean).join(', ');
  return (
    <div style={{
      position: 'absolute', inset: 0,
      display: 'grid', gridTemplateColumns: `${PANEL_W}px 1fr`,
    }}>
      {/* LEFT navy panel */}
      <div style={{
        background: 'var(--asa-navy)',
        color: 'var(--asa-off-white)',
        padding: `${BLEED + 22}px ${28}px ${BLEED + 22}px ${BLEED + 28}px`,
        display: 'grid',
        gridTemplateRows: 'auto 1fr auto',
        gap: 16,
        position: 'relative',
      }}>
        <div>
          <ASAWordmark size={48} color="var(--asa-white)" />
        </div>

        <div style={{ alignSelf: 'center' }}>
          <div style={{ marginBottom: 14 }}>
            <Eyebrow color="var(--asa-cream)" tracking="0.18em" size={8}>
              Your neighborhood
            </Eyebrow>
            <div style={{ marginTop: 3 }}>
              <Eyebrow color="rgba(243,237,228,0.78)" tracking="0.16em" size={8}>
                {placeLine}
              </Eyebrow>
            </div>
          </div>
          <h1 style={{
            fontFamily: 'var(--ff-display)',
            fontStyle: 'italic',
            fontWeight: 400,
            fontSize: 31,
            lineHeight: 1.06,
            letterSpacing: '-0.01em',
            margin: 0,
            color: 'var(--asa-off-white)',
            textWrap: 'pretty',
          }}>{withAmp(s.headline)}</h1>
          <div style={{ marginTop: 18, width: 32, height: 1, background: 'var(--asa-cream)', opacity: 0.85 }} />
          <p style={{
            fontFamily: 'var(--ff-text)',
            fontStyle: 'italic',
            fontSize: 11,
            lineHeight: 1.5,
            letterSpacing: 0,
            margin: '14px 0 0',
            color: 'rgba(243,237,228,0.78)',
            maxWidth: 230,
          }}>
            {/* Always route off campaign location — ignore any stored
                s.subhead because the field isn't editable in the UI
                and old localStorage sessions had a hardcoded Charlotte
                default that was leaking onto NMB previews. */}
            {isNmbCampaign(s)
              ? 'A North Myrtle Beach tradition since 2017 — family-owned, hand-finished one home at a time.'
              : 'A Charlotte tradition since 1993, hand-finished one home at a time.'}
          </p>
        </div>

        <div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 3, whiteSpace: 'nowrap' }}>
            <span style={{
              fontFamily: 'var(--ff-sans)', fontSize: 11, fontWeight: 500,
              letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--asa-off-white)',
            }}>{s.phone}</span>
            <span style={{
              fontFamily: 'var(--ff-sans)', fontSize: 9.5, fontWeight: 500,
              letterSpacing: '0.14em', textTransform: 'uppercase', color: 'rgba(243,237,228,0.7)',
            }}>{s.url}</span>
          </div>
        </div>
      </div>

      {/* RIGHT photo */}
      <div style={{ position: 'relative', overflow: 'hidden' }}>
        <HeroImg s={s} set={set} />
        {/* tiny tracked-caps location credit lower-right — street name only,
            never the house number, to keep the credited home unidentifiable. */}
        {streetName(s.street) && (
          <div style={{
            position: 'absolute', right: BLEED + 14, bottom: BLEED + 12,
            padding: '5px 9px',
            background: 'rgba(21,33,47,0.78)',
            color: 'var(--asa-off-white)',
          }}>
            <Eyebrow color="rgba(243,237,228,0.85)" tracking="0.20em" size={8}>
              {streetName(s.street)}
            </Eyebrow>
          </div>
        )}
      </div>
    </div>
  );
}

function FrontPatternLed({ s, set }) {
  const photo = PHOTO_DEFAULTS[s.category] || PHOTO_DEFAULTS.shutters;
  const placeLine = [s.neighborhood, s.city].filter(Boolean).join(', ');
  return (
    <div style={{
      position: 'absolute', inset: 0,
      background: 'var(--asa-navy)',
      color: 'var(--asa-off-white)',
    }}>
      {/* Center composition — full width, two columns, copy spread vertically */}
      <div style={{
        position: 'absolute',
        top: BLEED + 28, bottom: BLEED + 28,
        left: BLEED + 28, right: BLEED + 24,
        display: 'grid',
        gridTemplateColumns: '1.05fr 1fr',
        gap: 28,
        alignItems: 'stretch',
      }}>
        {/* Left: copy — wordmark up top, headline mid, subhead/phone bottom; spread top-to-bottom */}
        <div style={{
          display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
          paddingRight: 4, minWidth: 0, textAlign: 'center', alignItems: 'center',
          height: '100%',
        }}>
          {/* Top — wordmark only */}
          <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
            <ASAWordmark size={56} color="var(--asa-off-white)" />
          </div>

          {/* Middle — eyebrow paired with the italic headline */}
          <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14 }}>
            <RuleEyebrow color="var(--asa-cream)" tracking="0.30em" size={9} ruleW={20}>
              Just up the street
            </RuleEyebrow>
            <h1 style={{
              fontFamily: 'var(--ff-display)', fontStyle: 'italic', fontWeight: 400,
              fontSize: 34, lineHeight: 1.05, letterSpacing: '-0.015em',
              margin: 0, color: 'var(--asa-off-white)', textWrap: 'pretty',
            }}>{withAmp(s.headline)}</h1>
            <div style={{ width: 44, height: 1, background: 'var(--asa-cream)', opacity: 0.55 }} />
            <p style={{
              fontFamily: 'var(--ff-text)', fontSize: 11.5, lineHeight: 1.55,
              margin: 0, color: 'rgba(243,237,228,0.78)', maxWidth: 260,
            }}>
              {isNmbCampaign(s)
                ? 'A North Myrtle Beach tradition since 2017 — family-owned, hand-finished one home at a time.'
                : (s.subhead || `We just finished ${productCopy(s.category, 'lower')} for one of your neighbors. Step inside and see what we can do for your home.`)}
            </p>
          </div>

          {/* Bottom — contact */}
          <Eyebrow size={8.5} color="rgba(243,237,228,0.6)" tracking="0.26em">
            {s.phone} · {s.url}
          </Eyebrow>
        </div>

        {/* Right: photo + product caption (location dropped per design refinement) */}
        <div style={{ position: 'relative', display: 'grid', gridTemplateRows: '1fr auto', gap: 2, minWidth: 0 }}>
          <div style={{ position: 'relative', overflow: 'hidden' }}>
            <HeroImg s={s} set={set} />
            {/* Slim navy address tag flush to the right edge of the photo —
                street name only, never the house number. */}
            {streetName(s.street) && (
              <div style={{
                position: 'absolute',
                right: 0, bottom: 14,
                height: 22,
                padding: '0 12px 0 14px',
                background: 'var(--asa-navy)',
                color: 'var(--asa-off-white)',
                boxShadow: '0 2px 6px rgba(0,0,0,0.18)',
                display: 'inline-flex',
                alignItems: 'center',
                lineHeight: 1,
              }}>
                <Eyebrow size={8} color="var(--asa-off-white)" tracking="0.18em">
                  {streetName(s.street)}
                </Eyebrow>
              </div>
            )}
          </div>
          {/* Bottom strip: product type only, left-aligned */}
          <div>
            <Eyebrow size={7.5} color="rgba(243,237,228,0.6)" tracking="0.28em">
              {productCopy(s.category, 'title')}
            </Eyebrow>
          </div>
        </div>
      </div>
    </div>
  );
}

function FrontQuoteLed({ s, set }) {
  const photo = PHOTO_DEFAULTS[s.category] || PHOTO_DEFAULTS.shutters;
  const family = familyName(s.lastName);
  const placeLine = [s.neighborhood, s.city].filter(Boolean).join(', ');
  return (
    <div style={{
      position: 'absolute', inset: 0,
      background: 'var(--asa-navy)',
      color: 'var(--asa-off-white)',
      display: 'grid',
      gridTemplateColumns: `${4.0 * SC}px 1fr`,
    }}>
      {/* Left photo */}
      <div style={{ position: 'relative', overflow: 'hidden' }}>
        <HeroImg s={s} set={set} />
      </div>

      {/* Right quote panel */}
      <div style={{
        position: 'relative',
        padding: `${BLEED + 22}px ${BLEED + 24}px ${BLEED + 22}px 28px`,
        display: 'grid', gridTemplateRows: 'auto 1fr auto',
        gap: 12, minWidth: 0,
      }}>
        {/* Top: ASA mark + neighborhood */}
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
          <ASAWordmark size={32} color="var(--asa-white)" />
          <Eyebrow size={8} color="var(--asa-cream)" tracking="0.28em">
            {placeLine}
          </Eyebrow>
        </div>

        {/* Center: quote */}
        <div style={{ alignSelf: 'center', minWidth: 0 }}>
          <span style={{
            fontFamily: 'var(--ff-display)', fontStyle: 'italic', fontWeight: 400,
            fontSize: 70, lineHeight: 0.6,
            color: 'var(--asa-cream)',
            display: 'block',
            opacity: 0.75,
            marginLeft: -4, marginBottom: 4,
          }}>“</span>
          <p style={{
            fontFamily: 'var(--ff-display)', fontStyle: 'italic', fontWeight: 400,
            fontSize: 22, lineHeight: 1.2, letterSpacing: '-0.005em',
            margin: 0, color: 'var(--asa-off-white)',
            textWrap: 'pretty',
          }}>{withAmp(s.quote)}</p>
          <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <span style={{ width: 22, height: 1, background: 'var(--asa-cream)', opacity: 0.7 }} />
            <Eyebrow size={9} color="var(--asa-cream)" tracking="0.24em">
              {family}
            </Eyebrow>
            {s.neighborhood && (
              <Eyebrow size={8} color="rgba(243,237,228,0.55)" tracking="0.22em">
                · {s.neighborhood}
              </Eyebrow>
            )}
          </div>
          <div style={{ marginTop: 4, paddingLeft: 32 }}>
            <span style={{
              fontFamily: 'var(--ff-text)', fontStyle: 'italic',
              fontSize: 9.5, color: 'rgba(243,237,228,0.5)', letterSpacing: 0,
            }}>
              — as told to our installer on {s.installDate || 'install day'}
            </span>
          </div>
        </div>

        {/* Bottom: footer */}
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', gap: 12 }}>
          <p style={{
            margin: 0,
            fontFamily: 'var(--ff-text)', fontStyle: 'italic',
            fontSize: 10.5, lineHeight: 1.4, color: 'rgba(243,237,228,0.72)',
            maxWidth: 220,
          }}>
            We just finished {family}' home with {productCopy(s.category, 'lower')}. We'd love to show you what we did.
          </p>
          <div style={{ textAlign: 'right', flex: '0 0 auto' }}>
            <Eyebrow size={8} color="rgba(243,237,228,0.55)" tracking="0.20em">Schedule</Eyebrow>
            <div style={{ marginTop: 4 }}>
              <span style={{
                fontFamily: 'var(--ff-sans)', fontSize: 11, fontWeight: 500,
                letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--asa-off-white)',
              }}>{s.phone}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ─── "Just Sold" variant ────────────────────────────────────────────
   Designed for the window-counter / Gemini-render workflow:
   - Left ~55%: hero photo (the listing's room, doctored from Zillow)
   - Right ~45%: navy panel with logo, tagline, and three render
     thumbnails captioned with the treatment names. */
function FrontJustSoldLed({ s, set }) {
  const PANEL_W = 4.4 * SC;   // ~317px navy column
  const tag1 = s.jsTagline    || 'Thought about your windows?';
  const tag2 = s.jsSubTagline || 'We have.';
  const eye  = s.jsEyebrow    || 'Welcome home';
  const heroLabel = s.heroLabel || '';

  return (
    <div style={{
      position: 'absolute', inset: 0,
      display: 'grid', gridTemplateColumns: `1fr ${PANEL_W}px`,
    }}>
      {/* LEFT photo — the chosen render of the front room */}
      <div style={{ position: 'relative', overflow: 'hidden' }}>
        <HeroImg s={s} set={set} />
        {/* Slim treatment-name chip — flush to the bottom-right of the photo,
            single line, low-key. The product chosen in the estimator. */}
        {heroLabel && (
          <div style={{
            position: 'absolute',
            right: 0, bottom: 14,
            height: 22,
            padding: '0 12px 0 14px',
            background: 'var(--asa-cream)',
            color: 'var(--asa-navy)',
            boxShadow: '0 2px 6px rgba(0,0,0,0.18)',
            display: 'inline-flex',
            alignItems: 'center',
            lineHeight: 1,
          }}>
            <Eyebrow size={8} color="var(--asa-navy)" tracking="0.18em">
              {heroLabel}
            </Eyebrow>
          </div>
        )}
      </div>

      {/* RIGHT navy panel — logo, tagline, copy, contact (NO alt thumbnails) */}
      <div style={{
        background: 'var(--asa-navy)',
        color: 'var(--asa-off-white)',
        padding: `${BLEED + 22}px ${BLEED + 24}px ${BLEED + 22}px 24px`,
        display: 'grid',
        gridTemplateRows: 'auto 1fr auto',
        gap: 14,
      }}>
        <div>
          <ASAWordmark size={42} color="var(--asa-off-white)" />
        </div>

        <div style={{ alignSelf: 'center' }}>
          <Eyebrow color="var(--asa-cream)" tracking="0.22em" size={9}>
            {eye}
          </Eyebrow>
          <h1 style={{
            fontFamily: 'var(--ff-display)',
            fontStyle: 'italic',
            fontWeight: 400,
            fontSize: 30,
            lineHeight: 1.04,
            letterSpacing: '-0.012em',
            margin: '8px 0 0',
            color: 'var(--asa-off-white)',
            textWrap: 'pretty',
          }}>{tag1}</h1>
          <div style={{
            marginTop: 6,
            fontFamily: 'var(--ff-display)',
            fontStyle: 'italic',
            fontWeight: 600,
            fontSize: 30,
            lineHeight: 1,
            color: 'var(--asa-cream)',
          }}>{tag2}</div>
          <div style={{ marginTop: 14, width: 32, height: 1, background: 'var(--asa-cream)', opacity: 0.7 }} />
          <p style={{
            fontFamily: 'var(--ff-text)',
            fontStyle: 'italic',
            fontSize: 11,
            lineHeight: 1.5,
            margin: '12px 0 0',
            color: 'rgba(243,237,228,0.82)',
            maxWidth: 240,
          }}>
            {isNmbCampaign(s)
              ? 'A family-owned North Myrtle Beach tradition, plus the latest AI — so you can fall in love with what your new home could become.'
              : 'Three generations of Carolina craftsmanship, plus the latest AI — so you can fall in love with what your new home could become.'}
          </p>
        </div>

        {/* Phone + URL */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 3, whiteSpace: 'nowrap' }}>
          <span style={{
            fontFamily: 'var(--ff-sans)', fontSize: 11, fontWeight: 500,
            letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--asa-off-white)',
          }}>{s.phone}</span>
          <span style={{
            fontFamily: 'var(--ff-sans)', fontSize: 9.5, fontWeight: 500,
            letterSpacing: '0.14em', textTransform: 'uppercase', color: 'rgba(243,237,228,0.7)',
          }}>{s.url}</span>
        </div>
      </div>
    </div>
  );
}

const VARIANTS = {
  photo:     { id: 'photo',     name: 'Primary layout',   sub: 'Magazine split — navy panel + full-bleed photograph.' },
  pattern:   { id: 'pattern',   name: 'Alternate layout', sub: 'Cream paper, ASA pattern strip, centered italic headline.' },
  justSold:  { id: 'justSold',  name: 'Just Sold',        sub: 'Listing photo + three AI-rendered treatment options. Pulls from the window-counter visualizer.' },
};

function PostcardFront({ s, set }) {
  const Cmp = s.variant === 'pattern'  ? FrontPatternLed
            : s.variant === 'justSold' ? FrontJustSoldLed
            : FrontPhotoLed;
  return (
    <div className="pc" style={{ width: FULL.w, height: FULL.h }}>
      <Cmp s={s} set={set} />
    </div>
  );
}

/* ========================================================================
   BACK (shared layout, variation accents)
   ======================================================================== */

function IndiciaBlock({ color = 'var(--asa-navy)' } = {}) {
  return (
    <div className="print-hide" style={{
      display: 'inline-block',
      border: `1px solid ${color}`,
      padding: '6px 10px 7px',
      textAlign: 'center',
      fontFamily: 'var(--ff-sans)', fontSize: 7.5, fontWeight: 500,
      letterSpacing: '0.18em', textTransform: 'uppercase',
      color: color,
      lineHeight: 1.4,
    }}>
      <div>PRSRT STD</div>
      <div>U.S. Postage</div>
      <div style={{ fontFamily: 'var(--ff-display)', fontStyle: 'italic', letterSpacing: 0, fontSize: 12 }}>Paid</div>
      <div>EDDM Retail</div>
    </div>
  );
}

function ShowroomList({ ids, variant, color = 'var(--asa-navy)', mute = 'var(--asa-stone-500)' }) {
  // Pre-ordered list — index 0 = top-left (matches the campaign's
  // return-address pick). Phones stripped (already on the front).
  // Layout: 2×2 for 2+ entries; single column for the NMB-only case
  // so the one card doesn't sit alone in a half-width grid cell with
  // dead space to its right.
  const list = ids.map(k => ({ id: k, ...SHOWROOMS[k] })).filter(s => s.name);
  const cols = list.length <= 1 ? '1fr' : '1fr 1fr';
  return (
    <div style={{
      display: 'grid', gridTemplateColumns: cols, gap: '7px 16px',
    }}>
      {list.map((sh, i) => (
        <div key={i} style={{
          // When the card has a photo (NMB), lay it out as a 2-column
          // mini-grid: photo on the left, address/hours on the right.
          // Without a photo (Charlotte-metro by-appointment showrooms),
          // address stacks on its own and reads as before.
          display: sh.photo ? 'grid' : 'block',
          gridTemplateColumns: sh.photo ? '60px 1fr' : undefined,
          gap: sh.photo ? 9 : 0,
          alignItems: sh.photo ? 'flex-start' : undefined,
        }}>
          {sh.photo && (
            <img
              src={sh.photo}
              alt={`${sh.name} storefront`}
              style={{
                width: 60, height: 45,
                objectFit: 'cover',
                borderRadius: 2,
                display: 'block',
              }}
            />
          )}
          <div style={{ minWidth: 0 }}>
            <div style={{
              fontFamily: 'var(--ff-sans)', fontSize: 8, fontWeight: 500,
              letterSpacing: '0.22em', textTransform: 'uppercase',
              color, lineHeight: 1.2,
            }}>{sh.name}</div>
            <div style={{
              fontFamily: 'var(--ff-text)', fontSize: 9, lineHeight: 1.35,
              color: mute, marginTop: 2,
            }}>
              {sh.addr}<br />{sh.sub}
              {sh.hours && (
                <>
                  <br />
                  <span style={{ fontFamily: 'var(--ff-sans)', fontSize: 8, letterSpacing: '0.08em', color: 'var(--asa-navy)', fontWeight: 600 }}>
                    {sh.hours}
                  </span>
                </>
              )}
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

function PostcardBack({ s, set }) {
  // LEFT half = message side; RIGHT half = postal/address side
  const HALF = TRIM.w / 2;          // 396 px
  const accentNavyTop = false;
  const accentPattern = s.variant === 'pattern';   // alternate layout — pattern motif on back
  const accentCream   = s.variant === 'photo';     // primary layout — cream stripe top
  const family = familyName(s.lastName);
  const placeLine = [s.neighborhood, s.city].filter(Boolean).join(', ');

  return (
    <div className="pc" style={{ width: FULL.w, height: FULL.h, background: 'var(--asa-off-white)', color: 'var(--asa-navy)' }}>
      {/* Navy spine bar — echoes the front. Full bleed, left edge. */}
      <div style={{
        position: 'absolute', left: 0, top: 0, bottom: 0,
        width: 22,
        background: 'var(--asa-navy)',
      }} />
      {/* dotted spine separator (cosmetic guide) */}
      <div style={{
        position: 'absolute', top: BLEED + 18, bottom: BLEED + 18,
        left: BLEED + HALF, width: 1,
        background: 'rgba(21,33,47,0.10)',
      }} />

      {/* === LEFT: message side ===
          Tweakable layout knobs:
            • gap         → vertical breathing room between blocks
            • padding 18  → top/bottom margin from card edge
            • accent bar minHeight → height of the colored top strip */}
      <div style={{
        position: 'absolute',
        left: 0, top: 0, bottom: 0, width: BLEED + HALF,
        padding: `${BLEED + 18}px 22px ${BLEED + 18}px ${BLEED + 22 + 14}px`,
        display: 'flex', flexDirection: 'column',
        gap: 14, minHeight: 0,
      }}>
        {/* Top accent strip — plain thin colored bar (no copy on either variant).
            The "A Shade Above Window Fashions · Since 1993" wordmark used to live
            here but felt redundant with the logo + indicia row above. */}
        {accentCream && (
          <div style={{
            margin: `-${BLEED + 18}px -22px 0 -${BLEED + 22 + 14}px`,
            padding: `4px ${BLEED + 22}px 4px ${BLEED + 22 + 14}px`,
            background: 'var(--asa-cream)',
            flex: '0 0 auto', minHeight: 6,
          }} />
        )}
        {accentNavyTop && (
          <div style={{
            margin: `-${BLEED + 18}px -22px 0 -${BLEED + 22 + 14}px`,
            padding: `4px ${BLEED + 22}px 4px ${BLEED + 22 + 14}px`,
            background: 'var(--asa-navy)',
            flex: '0 0 auto', minHeight: 6,
          }} />
        )}

        {/* Headline */}
        <div style={{ flex: '0 0 auto' }}>
          <h2 style={{
            fontFamily: 'var(--ff-display)', fontStyle: 'italic', fontWeight: 400,
            fontSize: 22, lineHeight: 1.05, letterSpacing: '-0.01em',
            margin: 0, color: 'var(--asa-navy)',
            textWrap: 'pretty',
          }}>{withAmp(s.backHeadline)}</h2>
        </div>

        {/* Body + thumbnail strip */}
        <div style={{ display: 'grid', gridTemplateColumns: '1.25fr 1fr', gap: 12, alignItems: 'flex-start', flex: '1 1 auto', minHeight: 0 }}>
          <div style={{ minWidth: 0 }}>
            <p style={{
              margin: 0, fontFamily: 'var(--ff-text)', fontSize: 10.5, lineHeight: 1.5,
              color: 'var(--asa-stone-700)',
            }} dangerouslySetInnerHTML={{ __html: s.body }} />
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
            {/* 4:3 instead of perfect square — the square was cresting the
                horizontal rule under the offer block on both variants. The
                landscape ratio chops a slice from the top (and a touch from
                the bottom) of a typical Gemini-rendered room photo, which
                also reads more like a magazine credit shot. */}
            <div style={{ position: 'relative', width: '100%', aspectRatio: '4 / 3', overflow: 'hidden', background: 'var(--asa-stone-200)' }}>
              <ThumbImg src={s.backPhotoSrc || s.heroSrc} placeholder="Install photo" set={set} position={s.backPhotoSrc ? (s.backPhotoPosition || '50% 50%') : (s.heroPosition || '50% 50%')} posKey={s.backPhotoSrc ? 'backPhotoPosition' : 'heroPosition'} />
              {/* Slim treatment-name chip — Just-Sold variant only.
                  Neighbor variants use the navy address chip below, never both.
                  Pulled flush to the bottom edge with reduced height +
                  smaller font + tighter tracking so it reads as a quiet
                  caption rather than a competing banner. */}
              {s.backLabel && s.variant === 'justSold' && (
                <div style={{
                  position: 'absolute',
                  right: 0, bottom: 0,
                  height: 14,
                  padding: '0 7px 0 8px',
                  background: 'var(--asa-cream)',
                  color: 'var(--asa-navy)',
                  boxShadow: '0 1px 3px rgba(0,0,0,0.14)',
                  display: 'inline-flex',
                  alignItems: 'center',
                  lineHeight: 1,
                }}>
                  <Eyebrow size={6.5} color="var(--asa-navy)" tracking="0.10em">
                    {s.backLabel}
                  </Eyebrow>
                </div>
              )}
              {/* Slim navy address chip — only on neighbor variants
                  (photo / pattern). Just-Sold uses the treatment chip alone.
                  Street name only — house numbers are a privacy liability. */}
              {streetName(s.street) && s.variant !== 'justSold' && (
                <div style={{
                  position: 'absolute',
                  right: 0, bottom: 14,
                  height: 22,
                  padding: '0 12px 0 14px',
                  background: 'var(--asa-navy)',
                  color: 'var(--asa-off-white)',
                  boxShadow: '0 2px 6px rgba(0,0,0,0.18)',
                  display: 'inline-flex',
                  alignItems: 'center',
                  lineHeight: 1,
                }}>
                  <Eyebrow size={8} color="var(--asa-off-white)" tracking="0.18em">
                    {streetName(s.street)}
                  </Eyebrow>
                </div>
              )}
            </div>
            {/* Photo caption — neighbor variants only ("Recently installed
                <product> at <STREET>"). Just-Sold drops it; the chip on
                the photo + the back body already speak to it. */}
            {s.variant !== 'justSold' && (
              <div style={{
                fontFamily: 'var(--ff-text)', fontSize: 9, lineHeight: 1.35,
                color: 'var(--asa-stone-500)', fontStyle: 'italic',
                overflow: 'visible', wordBreak: 'normal',
                display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
              }}>
                Recently installed {productCopy(s.category, 'asis')}
                {streetName(s.street)
                  ? <> on <strong style={{ color: 'var(--asa-navy)', fontWeight: 600, fontStyle: 'normal' }}>{streetName(s.street)}</strong></>
                  : (s.neighborhood
                    ? <> in <strong style={{ color: 'var(--asa-navy)', fontWeight: 600, fontStyle: 'normal' }}>{s.neighborhood}</strong></>
                    : null)
                }
              </div>
            )}
          </div>
        </div>

        {/* NMB-only: coastal team photo (transparent PNG) + caption
            ("Our Team — Chad, Jennifer, Matt & Carter") to the right.
            Right-aligned so it sits under the postcard photo column
            and doesn't overlap the "Recently installed …" italic
            caption that lives directly above it on the right side.
            Slightly larger than the first pass per design feedback. */}
        {isNmbCampaign(s) && (
          <div style={{
            flex: '0 0 auto',
            // Photo back to its earlier higher placement; the names
            // alone get pushed down via alignSelf: 'flex-end' below
            // so they clear the "Recently installed…" caption to
            // their left without dragging the photo down with them.
            marginTop: 12,
            marginBottom: -10,
            display: 'flex',
            alignItems: 'flex-start',
            justifyContent: 'flex-end',
            gap: 14,
          }}>
            <img
              src="assets/photos/asa-coastal-team-photo-transparent-bg_v2.png"
              alt="ASA Coastal team"
              style={{
                maxHeight: 95,
                width: 'auto',
                objectFit: 'contain',
                display: 'block',
                flex: '0 0 auto',
              }}
            />
            <div style={{
              flex: '0 1 auto',
              maxWidth: 190,
              color: 'var(--asa-navy)',
              // Pin the caption to the BOTTOM of the photo's height
              // so the text sits clear of the italic caption that
              // lives at the top-right of this row.
              alignSelf: 'flex-end',
              paddingBottom: 4,
            }}>
              <div style={{
                fontFamily: 'var(--ff-sans)', fontSize: 9, fontWeight: 500,
                letterSpacing: '0.22em', textTransform: 'uppercase',
                color: 'var(--asa-stone-500)', lineHeight: 1.2, marginBottom: 3,
              }}>
                Our Team
              </div>
              <div style={{
                fontFamily: 'var(--ff-display)', fontStyle: 'italic',
                fontWeight: 400, fontSize: 16, lineHeight: 1.25,
                color: 'var(--asa-navy)',
              }}>
                {withAmp('Chad, Jennifer, Matt & Carter')}
              </div>
            </div>
          </div>
        )}

        {/* Offer block — single condensed line.
            marginTop pulls it up a hair toward the photo above; the internal
            padding gives the rule lines themselves breathing room. */}
        {s.offerOn && (
          <div style={{
            marginTop: -8,
            borderTop: '1px solid rgba(21,33,47,0.18)',
            borderBottom: '1px solid rgba(21,33,47,0.18)',
            padding: '11px 0',
            display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 12,
            flex: '0 0 auto',
          }}>
            <div style={{ minWidth: 0, display: 'flex', alignItems: 'baseline', gap: 8 }}>
              <Eyebrow size={8} color="var(--asa-stone-500)" tracking="0.30em">
                {s.offerEyebrow || 'Complimentary'}
              </Eyebrow>
              <div style={{
                fontFamily: 'var(--ff-display)', fontStyle: 'italic', fontWeight: 400,
                fontSize: 14, lineHeight: 1.1, color: 'var(--asa-navy)',
              }}>{s.offerText}</div>
            </div>
            <div style={{
              fontFamily: 'var(--ff-sans)', fontSize: 11, fontWeight: 500,
              letterSpacing: '0.10em', color: 'var(--asa-navy)', lineHeight: 1,
              whiteSpace: 'nowrap',
            }}>{s.phone}</div>
          </div>
        )}

        {/* Showrooms — eyebrow pluralizes with the count. NMB
            campaigns mail with a single showroom and read singular;
            Charlotte-metro campaigns mail with all four and read plural. */}
        <div style={{ flex: '0 0 auto' }}>
          <Eyebrow size={8} color="var(--asa-stone-500)" tracking="0.30em">
            {(s.showrooms || []).length <= 1 ? 'Visit Our Showroom' : 'Visit Our Showrooms'}
          </Eyebrow>
          <div style={{ marginTop: 4 }}>
            <ShowroomList ids={s.showrooms} variant={s.variant} color="var(--asa-navy)" mute="var(--asa-stone-500)" />
          </div>
        </div>

        {/* Bottom: category bar — full width, evenly distributed to match divider above */}
        <div style={{
          borderTop: '1px solid rgba(21,33,47,0.18)',
          paddingTop: 7, flex: '0 0 auto',
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          gap: 8, whiteSpace: 'nowrap',
        }}>
          {['Shutters','Shades','Drapery','Exterior Shades'].map(it => (
            <Eyebrow key={it} size={8} color="var(--asa-navy)" tracking="0.26em">{it}</Eyebrow>
          ))}
        </div>
        {/* Just-Sold disclaimer — required because the renders are
            AI-generated approximations of the listing photos. Tiny, italic,
            low-contrast so it doesn't compete with the message but is
            legible at print size. */}
        {s.variant === 'justSold' && (
          <div style={{
            flex: '0 0 auto',
            marginTop: 4,
            fontFamily: 'var(--ff-text)',
            fontStyle: 'italic',
            fontSize: 5.5,
            lineHeight: 1.35,
            letterSpacing: '0.01em',
            color: 'rgba(21,33,47,0.5)',
          }}>
            Images may be AI-enhanced using publicly available imagery. Products shown are conceptual renderings and may not reflect exact colors, mounting, operation, or final installation.
          </div>
        )}
      </div>

      {/* === RIGHT: postal side === */}
      <div style={{
        position: 'absolute',
        left: BLEED + HALF, top: 0, bottom: 0, right: 0,
        padding: `${BLEED + 18}px ${BLEED + 22}px ${BLEED + 18}px 22px`,
        display: 'grid', gridTemplateRows: 'auto 1fr auto',
        gap: 10,
      }}>
        {/* alternate layout — no pattern accent on back; keep the message side clean */}

        {/* Top: big ASA logo + indicia. Both variants use the same large
            logo treatment now — typed return address dropped because LOB
            renders its own from the Matthews showroom we pass via API. */}
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
          <div>
            <ASAWordmark size={84} color="var(--asa-navy)" />
          </div>
          <IndiciaBlock />
        </div>

        {/* Middle: address panel — compact (PREVIEW ONLY; LOB applies real address) */}
        <div className="print-hide" style={{ alignSelf: 'center', textAlign: 'left' }}>
          <div style={{
            fontFamily: 'var(--ff-sans)', fontSize: 12, fontWeight: 500,
            letterSpacing: '0.18em', textTransform: 'uppercase',
            color: 'var(--asa-navy)', lineHeight: 1.3,
          }}>
            <div>****ECRWSS****</div>
            <div style={{ fontSize: 16, marginTop: 4, letterSpacing: '0.12em' }}>POSTAL CUSTOMER</div>
            <div style={{ fontSize: 10, marginTop: 4, color: 'var(--asa-stone-500)', letterSpacing: '0.10em' }}>
              Local · Carrier Route Pre-Sort
            </div>
          </div>
        </div>

        {/* Bottom: tracking only (PREVIEW ONLY) */}
        <div className="print-hide" style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'flex-end', gap: 8 }}>
          <div style={{ textAlign: 'right' }}>
            <Eyebrow size={7} color="var(--asa-stone-500)" tracking="0.30em">Tracking</Eyebrow>
            <div style={{
              fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
              fontSize: 8, color: 'var(--asa-stone-500)', marginTop: 2,
            }}>{s.dropCode || 'ASA-NB-2025-04-{ROUTE}'}</div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ========================================================================
   Print marks overlay
   ======================================================================== */

function PrintMarks({ on }) {
  if (!on) return null;
  // bleed at 0..FULL, trim at BLEED..BLEED+TRIM, safe at BLEED+SAFE..rest
  const bleedStyle = { left: 0, top: 0, right: 0, bottom: 0 };
  const trimStyle  = { left: BLEED, top: BLEED, right: BLEED, bottom: BLEED };
  const safeStyle  = { left: BLEED + SAFE, top: BLEED + SAFE, right: BLEED + SAFE, bottom: BLEED + SAFE };
  // crop ticks at trim corners (8 ticks total)
  const TickV = ({ side, end }) => (
    <div style={{
      position: 'absolute',
      [side]: 0, [end]: BLEED,
      width: 1, height: 8,
      background: 'rgba(255,77,77,0.95)',
    }} />
  );
  return (
    <div className="printmarks">
      {/* bleed edge */}
      <div style={{ position: 'absolute', ...bleedStyle, border: '1px dashed rgba(180,90,90,0.85)' }} />
      {/* trim edge */}
      <div style={{ position: 'absolute', ...trimStyle, border: '1px solid rgba(255,77,77,0.95)' }} />
      {/* safe edge */}
      <div style={{ position: 'absolute', ...safeStyle, border: '1px dotted rgba(56,180,120,0.95)' }} />

      {/* crop marks at trim corners */}
      {[
        { top: 0, left: BLEED, w: 1, h: BLEED },
        { top: 0, left: FULL.w - BLEED, w: 1, h: BLEED },
        { bottom: 0, left: BLEED, w: 1, h: BLEED },
        { bottom: 0, left: FULL.w - BLEED, w: 1, h: BLEED },
        { left: 0, top: BLEED, h: 1, w: BLEED },
        { right: 0, top: BLEED, h: 1, w: BLEED },
        { left: 0, bottom: BLEED, h: 1, w: BLEED },
        { right: 0, bottom: BLEED, h: 1, w: BLEED },
      ].map((p, i) => (
        <div key={i} style={{
          position: 'absolute',
          width: p.w, height: p.h,
          left: p.left, right: p.right, top: p.top, bottom: p.bottom,
          background: 'rgba(255,77,77,0.95)',
        }} />
      ))}

      {/* legend chips */}
      <div className="legend legend--bleed" style={{ position: 'absolute' }}>BLEED 0.125"</div>
      <div className="legend legend--trim"  style={{ position: 'absolute' }}>TRIM 11×6"</div>
      <div className="legend legend--safe"  style={{ position: 'absolute' }}>SAFE 0.25"</div>
      {/* dimension callout */}
      <div style={{
        position: 'absolute',
        right: 4, bottom: 4,
        fontSize: 8, padding: '3px 6px',
        background: 'rgba(11,20,29,0.9)',
        color: 'rgba(243,237,228,0.85)',
        letterSpacing: '0.20em', textTransform: 'uppercase',
      }}>11.25 × 6.25 in · 300dpi</div>
    </div>
  );
}

/* expose to window so app.jsx can import them */
Object.assign(window, {
  POSTCARD_FULL: FULL,
  POSTCARD_TRIM: TRIM,
  POSTCARD_BLEED: BLEED,
  POSTCARD_SAFE: SAFE,
  POSTCARD_VARIANTS: VARIANTS,
  POSTCARD_SHOWROOMS: SHOWROOMS,
  POSTCARD_ALL_SHOWROOMS: ALL_SHOWROOMS,
  POSTCARD_CATEGORY_LABEL: CATEGORY_LABEL,
  POSTCARD_CATEGORY_SHORT: CATEGORY_SHORT,
  POSTCARD_CATEGORY_TYPES: CATEGORY_TYPES,
  POSTCARD_CATEGORY_TYPE_LIST: CATEGORY_TYPE_LIST,
  POSTCARD_CATEGORY_TYPE_OF: CATEGORY_TYPE_OF,
  POSTCARD_DEFAULT_OFFER_TEXT: DEFAULT_OFFER_TEXT,
  postcardFamilyName: familyName,
  postcardStreetName: streetName,
  PostcardFront,
  PostcardBack,
  PrintMarks,
});
