/* Shared helpers for the L3V3L site kit. */
window.Kit = window.Kit || {};
(function () {
  const { useEffect, useRef, useState } = React;

  function Icon({ name, size = 20, strokeWidth = 2, ...p }) {
    const icons = window.lucide && window.lucide.icons;
    let node = icons && (icons[name] || icons.Globe);
    if (!node) return <svg width={size} height={size} viewBox="0 0 24 24" aria-hidden="true" />;
    const children = node[0] === 'svg' ? (node[2] || []) : node;
    return (
      <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...p}>
        {children.map(([tag, attrs], i) => React.createElement(tag, { ...attrs, key: i }))}
      </svg>
    );
  }

  function Photo({ id, label = 'Drop a clean studio photo', ratio = '16 / 10', radius = 24, flush = false, className = '', style, children }) {
    return (
      <div className={'photo ' + (flush ? 'photo--flush ' : '') + className} style={{ aspectRatio: ratio, borderRadius: flush ? 0 : radius, ...style }}>
        <div className="photo__placeholder" role="img" aria-label={label} data-photo-id={id}>
          <span>{label}</span>
        </div>
        {children}
      </div>
    );
  }

  function useReveal(dep) {
    useEffect(() => {
      const els = Array.from(document.querySelectorAll('.reveal:not(.in)'));
      const showAll = () => els.forEach((el) => el.classList.add('in'));
      if (document.hidden || !('IntersectionObserver' in window)) { showAll(); return; }
      // Anything already in the viewport reveals on the next frame (staggered via CSS delays).
      const vh = window.innerHeight || 900;
      let pending = els.filter((el) => { const r = el.getBoundingClientRect(); if (r.top < vh) { requestAnimationFrame(() => el.classList.add('in')); return false; } return true; });
      const io = new IntersectionObserver((entries) => {
        entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } });
      }, { threshold: 0.1, rootMargin: '0px 0px -6% 0px' });
      pending.forEach((el) => io.observe(el));
      // Safety net: hidden tab, thumbnails, exports — never leave content at opacity 0.
      const onVis = () => { if (document.hidden) showAll(); };
      document.addEventListener('visibilitychange', onVis);
      const t = setTimeout(() => { if (document.hidden) showAll(); }, 1000);
      return () => { io.disconnect(); clearTimeout(t); document.removeEventListener('visibilitychange', onVis); };
    }, [dep]);
  }

  function parseStat(v) {
    const m = String(v).match(/^([+\-−]?)([\d,]*\.?\d+)(.*)$/);
    if (!m) return null;
    const raw = m[2]; const num = parseFloat(raw.replace(/,/g, ''));
    const decimals = (raw.split('.')[1] || '').length;
    return { prefix: m[1], num, decimals, comma: raw.includes(','), suffix: m[3] };
  }
  function CountStat({ value, ...rest }) {
    const { Stat } = window.DS;
    const ref = useRef(null);
    const [shown, setShown] = useState(() => { const p = parseStat(value); return p ? p.prefix + (0).toFixed(p.decimals) + p.suffix : value; });
    useEffect(() => {
      const p = parseStat(value);
      const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      if (!p || !ref.current || document.hidden || reduce || !('IntersectionObserver' in window)) { setShown(value); return; }
      let raf; const el = ref.current; let started = false;
      const finish = () => { if (!started) { started = true; io.disconnect(); setShown(value); } };
      const io = new IntersectionObserver((entries) => {
        if (!entries[0].isIntersecting || started) return;
        started = true; io.disconnect();
        const t0 = performance.now(); const dur = 1400;
        const tick = (t) => {
          const k = Math.min(1, (t - t0) / dur); const e = 1 - Math.pow(1 - k, 4);
          let n = (p.num * e).toFixed(p.decimals);
          if (p.comma) n = Number(n).toLocaleString('en-US', { minimumFractionDigits: p.decimals });
          setShown(p.prefix + n + p.suffix);
          if (k < 1) raf = requestAnimationFrame(tick);
        };
        raf = requestAnimationFrame(tick);
      }, { threshold: 0.4 });
      io.observe(el);
      const onVis = () => { if (document.hidden) finish(); };
      document.addEventListener('visibilitychange', onVis);
      const t = setTimeout(() => { if (document.hidden) finish(); }, 1000);
      return () => { io.disconnect(); cancelAnimationFrame(raf); clearTimeout(t); document.removeEventListener('visibilitychange', onVis); };
    }, [value]);
    return <div ref={ref}><Stat value={shown} {...rest} /></div>;
  }

  function Mask({ text, delay = 0, step = 60 }) {
    return text.split(' ').map((w, i) => (
      <React.Fragment key={i}>
        <span className="mask"><span style={{ animationDelay: (delay + i * step) + 'ms' }}>{w}</span></span>{' '}
      </React.Fragment>
    ));
  }

  const data = {
    services: [
      { id: 'domain', phase: 'build', icon: 'Globe', kicker: 'Own it', title: 'Domain', desc: 'We find, buy and manage your domain, DNS and email. Registered in your name — it stays yours forever.', bullets: ['Domain search & purchase', 'DNS, SSL and email setup', 'Renewals handled'], photo: 'Studio photo: laptop with a domain search on screen' },
      { id: 'website', phase: 'build', icon: 'LayoutTemplate', kicker: 'Build it', title: 'Website', desc: 'Designed, built, hosted and kept fast. You never touch a line of code, and you never wait on a ticket.', bullets: ['Custom design on your brand', 'Hosting, backups, uptime', 'Edits turned around in 48h'], photo: 'Studio photo: website on a large monitor, bright desk' },
      { id: 'content', phase: 'grow', icon: 'PenLine', kicker: 'Say it', title: 'Content', desc: 'Pages, posts, photos and copy that sound like you and rank. A monthly calendar, written and published.', bullets: ['Monthly content calendar', 'Copy, photography direction', 'Published and tracked'], photo: 'Studio photo: writer and photographer on a seamless set' },
      { id: 'seo', phase: 'grow', icon: 'TrendingUp', kicker: 'Rank for it', title: 'SEO', desc: 'Technical fixes, local listings, keywords and links. The slow, compounding work most agencies skip — with your rankings visible live, every day.', bullets: ['Technical audit & fixes', 'Local + national keywords', 'Live rank tracking, daily, by location', 'Monthly ranking report'], photo: 'Studio photo: phone showing a map result, clean background' },
      { id: 'social', phase: 'grow', icon: 'Share2', kicker: 'Post it', title: 'Social media', desc: 'Posts drafted in your voice, approved in one tap, published to every channel from the L3V3L platform — then every comment, mention and number watched for you.', bullets: ['AI-drafted posts in your voice, you approve', 'Scheduling & publishing to Instagram, Facebook, LinkedIn, X, TikTok, Google', 'Unified inbox: comments, DMs, mentions, reviews', 'Monitoring: brand mentions & competitor watch', 'Analytics: reach, engagement, best time to post', 'Link-in-bio page, hashtag & first-comment tools'], photo: 'Studio photo: phone on a tripod mid-shoot, bright seamless set' },
      { id: 'ads', phase: 'grow', icon: 'Megaphone', kicker: 'Buy it', title: 'Google Ads', desc: 'Search and Maps campaigns built, run and tuned weekly — with every call and form tracked back to the keyword that paid for it. Spend billed at cost, never marked up.', bullets: ['Campaign build: keywords, ads, landing pages', 'Weekly bid, budget & negative-keyword tuning', 'Local Services Ads & Maps ads', 'Call & form tracking to the keyword', 'Plain-English spend report — ad spend billed at cost'], photo: 'Studio photo: phone showing a sponsored map result, soft light' },
      { id: 'reviews', phase: 'run', icon: 'Star', kicker: 'Earn it', title: 'Reviews', desc: 'After every job we text and email the customer a one-tap link to the right Google, Yelp or Facebook listing for that location — then reply to every review in your voice.', bullets: ['Text & email review requests, automatic after each job', 'Routing to Google, Yelp, Facebook — per location', 'Every review replied to within 24h, in your voice', 'Negative-review alerts & private recovery flow', 'Reviews widget on your website', 'Monitoring across 20+ review sites'], photo: 'Studio photo: owner reading a review on a phone, shop counter behind' },
      { id: 'listings', phase: 'run', icon: 'MapPin', kicker: 'Be found', title: 'Google listings & maps', desc: 'Your Google Business Profile run like a storefront — posts, photos, hours, Q&A, products — plus Apple, Bing, Yelp and 40+ directories kept in sync.', bullets: ['Google Business Profile: posts, photos, hours, Q&A', 'Multi-location management', 'Apple, Bing, Yelp + 40 directories in sync', 'Spam & duplicate listing removal', 'Map-pack ranking tracked by location'], photo: 'Studio photo: storefront sign, shallow depth of field' },
      { id: 'analytics', phase: 'run', icon: 'BarChart3', kicker: 'Measure it', title: 'Analytics', desc: 'Every call, form, visit, review and ranking tracked from day one. One dashboard, by location. Plain-language monthly summary.', bullets: ['GA4, tag manager, call tracking', 'Rankings, reviews, ads & social in one dashboard', 'Live, by location, with history', 'Monthly plain-English report'], photo: 'Studio photo: tablet dashboard on a wooden table' },
    ],
    steps: [
      { title: 'Audit', desc: 'Twenty minutes on a call. We look at your site, your search presence and your numbers, then tell you what to fix first.', time: '20 minutes' },
      { title: 'Build', desc: 'Domain, website, content and analytics go live together. You approve designs; we do everything else.', time: '~3 weeks' },
      { title: 'Grow', desc: 'Monthly SEO, fresh content, social posting, review outreach, Google Ads and a report you can actually read. The number goes up; you run the business.', time: 'Every month' },
    ],
    stats: [
      { value: '+128%', label: 'Organic traffic', note: 'median, first 90 days', trend: 'up' },
      { value: '42', label: 'Sites launched', note: 'in 2026 so far' },
      { value: '2.4M', label: 'Search impressions', note: 'across clients, last 30 days' },
      { value: '1,180', label: 'Posts published', note: 'through the platform, last 30 days' },
      { value: '19', label: 'Days to launch', note: 'average, audit to live' },
    ],
    clients: ['Northwind Bakery', 'Atlas Dental', 'Pine & Co', 'Harbor Fitness', 'Mesa Legal', 'Bloom Florals', 'Summit Roofing', 'Juniper Studio'],
    cases: [
      { client: 'Northwind Bakery', delta: '+212%', title: 'From a Facebook page to #1 for "bakery near me".', tags: ['Website', 'Listings', 'SEO'], photo: 'Studio photo: bakery products on a seamless backdrop' },
      { client: 'Atlas Dental', delta: '+64 calls/mo', title: 'Three locations, one site, every call tracked.', tags: ['Website', 'Analytics', 'Content'], photo: 'Studio photo: dental office reception, bright and clean' },
    ],
    terms: ['No commitment', 'Cancel anytime · 30 days’ notice', '30-day billing periods', 'Download your website 24/7'],
    reputation: {
      funnel: [['48', 'Requests sent'], ['31', 'Opened'], ['12', 'New 5★ reviews']],
      review: { name: 'Dana P.', location: 'Northwind · Main St', text: 'Finally a bakery that answers the phone. The rye was perfect and they remembered my order.', reply: 'Thank you, Dana — the rye is back every Saturday at 7am. See you then.' },
      rankings: [['bakery near me', 1, 4], ['sourdough bread portland', 3, 2], ['custom cakes portland', 7, 5], ['wedding cake bakery', 12, -1]],
    },
    social: {
      channels: [['IG', 'Instagram'], ['FB', 'Facebook'], ['in', 'LinkedIn'], ['X', 'X'], ['YT', 'YouTube']],
      draft: 'Sourdough Saturday is back. First 50 loaves out of the oven at 7am — and yes, the rye is finally here. Come hungry.',
      schedule: 'Sat 7:05 AM · best time for your audience',
      stats: [['+41%', 'Reach'], ['128', 'Comments, 100% replied'], ['+312', 'Followers, 30d']],
      inbox: [['Instagram', 'love the rye!! shipping?', '2m'], ['Google', 'New 5★ review from Dana P.', '14m'], ['Mention', '@citypaper tagged you in a story', '1h']],
    },
    plans: [
      { id: 'launch', name: 'Launch', monthly: 490, yearly: 4900, blurb: 'Get online properly.', features: ['Domain, DNS, email', 'Website — up to 6 pages', 'Hosting, SSL, backups', 'Google Business Profile set up & verified', 'Review requests — 50 / mo', 'Social profiles set up & branded', 'Analytics installed'], off: ['Monthly content', 'SEO program', 'Social posting & monitoring', 'Google Ads management'] },
      { id: 'grow', name: 'Grow', monthly: 1490, yearly: 14900, popular: true, blurb: 'Get found and keep growing.', features: ['Everything in Launch', 'Website — unlimited pages', '4 content pieces / month', 'Social: 12 posts / mo, 3 channels, inbox & monitoring', 'Reviews: unlimited requests, routing, replies', 'Google Ads management (spend billed at cost)', 'SEO program + live rank tracking', 'Google listings + 40 directories in sync', 'Call & form tracking'], off: ['Dedicated strategist'] },
      { id: 'scale', name: 'Scale', monthly: 3900, yearly: 39000, blurb: 'Multiple locations or brands.', features: ['Everything in Grow', 'Up to 5 locations / brands', '12 content pieces / month', 'Social: daily posting, all channels, competitor watch', 'Reviews, listings & ads across every location', 'Dedicated strategist', 'Quarterly photo shoot', 'Weekly reporting'], off: [] },
    ],
    faq: [
      ['Who owns the domain and the website?', 'You do. The domain is registered in your name and the site is yours. Download a full copy of your website anytime, 24/7, from your dashboard — no request, no waiting.'],
      ['How long until we are live?', 'Average is 19 days from the audit call to launch. Content and SEO start the same week the site goes live.'],
      ['Do I need to supply photos or copy?', 'No. We write the copy, draft the social posts in your voice and direct photography. Send us whatever you have and we take it from there.'],
      ['Do I post on social myself?', 'Only if you want to. We draft, you approve from your phone, and the L3V3L platform publishes to every channel. Comments, DMs and mentions land in one inbox we watch for you.'],
      ['How do you get reviews?', 'After each job we text and email the customer a one-tap link that lands on the right Google, Yelp or Facebook listing for that location. We reply to every review within 24 hours in your voice, and unhappy customers are routed to you privately first.'],
      ['Do you run Google Ads too?', 'Yes, on Grow and Scale. We build and manage the campaigns; ad spend is billed by Google at cost, never marked up. Every call and form is tracked back to the keyword that paid for it.'],
      ['Is there a contract?', 'No commitment. L3V3L is a subscription billed in 30-day periods. Cancel anytime with 30 days’ notice — your current period finishes, nothing renews, and your domain and site go with you.'],
      ['Can I cancel?', 'Yes, anytime, with 30 days’ notice. Yearly plans are the same subscription paid up front with two months free.'],
    ],
  };

  // Wordmark: the two Ls face outward; the first "3" is mirrored. Use <Brand/> in JSX; brand(text) swaps every "L3V3L" inside a plain string.
  function Brand(p) { return <span {...p}><span className="wm-flip">L</span><span className="wm-flip">3</span>V3L</span>; }
  function brand(text) {
    const parts = String(text).split('L3V3L');
    return parts.flatMap((t, i) => i === 0 ? [t] : [<Brand key={'b' + i} />, t]);
  }

  Object.assign(window.Kit, { Icon, Photo, useReveal, CountStat, Mask, Brand, brand, data });
})();
