// 타운카 제휴업체 — UI 프리미티브
// Design System: Toss × Towncar (TDS Mobile)
// 색상은 Towncar 시맨틱 토큰, 컴포넌트 어휘는 TDS Mobile.

// ── TDS × Towncar 디자인 토큰 (CSS 변수와 1:1)
const TC = {
  // Brand
  primary: '#0BB53B', // green-600 · primary action / selected
  primaryHover: '#0AA336', // green-650
  primaryPressed: '#06842B', // green-700
  primaryWeak: '#F0FCF3', // green-100
  primarySubtle: 'rgba(11,181,59,0.10)',
  primaryWeakBorder: 'rgba(11,181,59,0.22)',
  secondary: '#6D4BF6', // violet-500
  // Text (TDS: 본문 #171719, 순수 검정 사용 안 함)
  textStrong: '#171719', // neutral-800 — titles / strong
  textHeavy: '#171719',
  text: '#171719',
  textNeutral: '#515157', // neutral-600 — secondary
  textAlt: '#6F6E74', // neutral-500 — tertiary
  textAssist: '#939298', // neutral-300 — placeholder / chevron
  textDisabled: '#B8B8BF', // neutral-200
  // Background
  white: '#FFFFFF',
  surface: '#FFFFFF',
  bgBase: '#FFFFFF',
  bgAlt: '#F8F8F9', // neutral-50
  bgMuted: '#F0F0F3', // neutral-100
  // Line
  line: '#E9E9ED', // neutral-120 — default border
  lineSoft: 'rgba(23,23,25,0.07)',
  lineStrong: '#B8B8BF', // neutral-200
  // Fill
  fillSubtle: '#F8F8F9',
  fill: '#F0F0F3',
  fillStrong: '#E9E9ED',
  // Status
  positive: '#34CC64',
  negative: '#EC4337',
  cautionary: '#FF782A',
  info: '#0059FE',
  // Point accents (카테고리 톤)
  green: '#0BB53B',
  blue: '#0059FE',
  lightblue: '#008AED',
  yellow: '#E0AA00',
  orange: '#FD6106',
  purple: '#AD36E3',
  violet: '#6D4BF6',
  pink: '#E846CD',
  red: '#EC4337',
  // Dimmer
  dim: 'rgba(0,0,0,0.55)'
};

const FF = '"Pretendard Variable", "Pretendard", -apple-system, system-ui, sans-serif';

// 카테고리별 톤 (TDS Asset 타일: 옅은 틴트 배경 + 채도 높은 아이콘)
const CAT_ACCENT = {
  'new-domestic': { bg: '#F0FCF3', fg: '#0BB53B' },
  'new-imported': { bg: '#E9F6FF', fg: '#008AED' },
  'financing': { bg: '#E9F0FF', fg: '#0059FE' },
  'used': { bg: '#FFEEE4', fg: '#FD6106' },
  'accident': { bg: '#FFEBEB', fg: '#EC4337' },
  'service': { bg: '#FFF8DE', fg: '#C69600' },
  'inspection': { bg: '#F1EDFF', fg: '#6D4BF6' },
  'detailing': { bg: '#F9EDFF', fg: '#AD36E3' },
  'custom': { bg: '#FEECFB', fg: '#E846CD' },
  'gps': { bg: '#E9F6FF', fg: '#008AED' },
  'photo': { bg: '#E1F7F4', fg: '#00857A' }
};

// ── 마우스 드래그로 가로 스크롤 지원 (웹 PC용)
function useDragScroll() {
  const ref = React.useRef(null);
  const state = React.useRef({ down: false, startX: 0, scrollLeft: 0, moved: false, prevSnap: '' });

  const animateTo = (el, target) => {
    const start = el.scrollLeft;
    const distance = target - start;
    if (Math.abs(distance) < 1) return;
    const duration = 120; // AdCarousel snapTo와 동일한 빠른 ease
    const startTime = performance.now();
    const ease = (t) => 1 - Math.pow(1 - t, 3);
    const tick = (now) => {
      const t = Math.min(1, (now - startTime) / duration);
      el.scrollLeft = start + distance * ease(t);
      if (t < 1) requestAnimationFrame(tick);else
      el.style.scrollSnapType = state.current.prevSnap;
    };
    requestAnimationFrame(tick);
  };

  const findNearestSnap = (el) => {
    const children = el.children;
    if (!children.length) return el.scrollLeft;
    const current = el.scrollLeft;
    const padLeft = parseFloat(getComputedStyle(el).paddingLeft) || 0;
    let best = current,bestDist = Infinity;
    for (const c of children) {
      const snapX = c.offsetLeft - padLeft;
      const d = Math.abs(snapX - current);
      if (d < bestDist) {bestDist = d;best = snapX;}
    }
    return best;
  };

  const onPointerDown = (e) => {
    if (e.pointerType !== 'mouse') return;
    const el = ref.current;
    if (!el) return;
    state.current = {
      down: true, startX: e.clientX, scrollLeft: el.scrollLeft, moved: false,
      prevSnap: el.style.scrollSnapType || ''
    };
    el.style.scrollSnapType = 'none';
  };
  const finishDrag = () => {
    const s = state.current;
    if (!s.down) return;
    s.down = false;
    const el = ref.current;
    if (!el) return;
    const snap = s.prevSnap;
    if (snap && snap !== 'none' && s.moved) {
      const target = findNearestSnap(el);
      animateTo(el, target);
    } else {
      el.style.scrollSnapType = s.prevSnap;
    }
  };
  const onPointerMove = (e) => {
    if (e.pointerType !== 'mouse') return;
    const s = state.current;
    if (!s.down) return;
    const dx = e.clientX - s.startX;
    if (Math.abs(dx) > 3) s.moved = true;
    const el = ref.current;
    if (el) el.scrollLeft = s.scrollLeft - dx;
  };
  const onPointerUp = (e) => {
    if (e.pointerType !== 'mouse') return;
    finishDrag();
  };
  const onClickCapture = (e) => {
    if (state.current.moved) {
      e.stopPropagation();
      e.preventDefault();
      state.current.moved = false;
    }
  };
  return {
    ref,
    handlers: {
      onPointerDown, onPointerMove, onPointerUp,
      onPointerLeave: onPointerUp, onPointerCancel: onPointerUp,
      onClickCapture
    }
  };
}

// ── Lucide 아이콘 래퍼
function _toPascal(s) {
  return s.split('-').map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
}
function Icon({ name, size = 24, stroke = 1.8, color = 'currentColor', fill = 'none', style = {} }) {
  const key = _toPascal(name);
  const i = window.lucide && window.lucide[key];
  if (!i) return <span style={{ width: size, height: size, display: 'inline-block', ...style }} />;
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={fill} stroke={color}
    strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"
    style={{ flexShrink: 0, ...style }}
    dangerouslySetInnerHTML={{ __html: i.map(([tag, attrs]) =>
      `<${tag} ${Object.entries(attrs).map(([k, v]) => `${k}="${v}"`).join(' ')}/>`
      ).join('') }} />);


}

// ── 카테고리 톤 스쿼클 에셋 타일 (TDS Asset). mono=true면 색상 제거(뉴트럴), bg로 배경만 오버라이드
function AssetTile({ category, size = 44, iconSize, radius, mono = false, bg, circle = false }) {
  const tone = mono ?
  { bg: TC.fill, fg: TC.textNeutral } :
  CAT_ACCENT[category] || { bg: TC.fill, fg: TC.textNeutral };
  const cat = window.CATEGORIES && window.CATEGORIES.find((c) => c.id === category);
  return (
    <div style={{
      flexShrink: 0, width: size, height: size,
      borderRadius: circle ? 999 : radius != null ? radius : Math.round(size * 0.30),
      background: bg || tone.bg,
      display: 'flex', alignItems: 'center', justifyContent: 'center'
    }}>
      <Icon name={cat ? cat.icon : 'store'} size={iconSize || Math.round(size * 0.5)} color={tone.fg} stroke={1.9} />
    </div>);

}

// ── 브랜드 로고 매핑 (id → 이미지 URL). 비어있거나 로드 실패 시 카테고리 아이콘으로 폴백.
// Cardog Icons (MIT, icons.cardog.ai) 최적화 SVG를 jsDelivr로 핫링크.
const _CARDOG = (name) => 'https://cdn.jsdelivr.net/gh/cardog-ai/icons@master/core/optimized/' + encodeURIComponent(name + ' Icon.svg');
const PARTNER_BRAND_LOGOS = {
  hyundai: _CARDOG('Hyundai'),
  kia: _CARDOG('Kia'),
  audi: _CARDOG('Audi'),
  landrover: _CARDOG('Landrover'),
  benz: _CARDOG('MB'),
  bmw: _CARDOG('BMW'),
  porsche: _CARDOG('Porsche'),
  mini: _CARDOG('Mini'),
  lexus: _CARDOG('Lexus'),
  volvo: _CARDOG('Volvo'),
  vw: _CARDOG('Volkswagen'),
  byd: _CARDOG('BYD'),
  renault: 'assets/brand-logos/renault.svg'
};

const PARTNER_LOGO_SCALE = { renault: '56%' };

// ── 리스트 아이콘 (브랜드 로고 우선, 없으면 동근 카테고리 아이콘)
function PartnerAsset({ partner, size = 44 }) {
  const logo = PARTNER_BRAND_LOGOS[partner.sub];
  const scale = PARTNER_LOGO_SCALE[partner.sub] || '82%';
  const [failed, setFailed] = React.useState(false);
  if (logo && !failed) {
    return (
      <div style={{
        flexShrink: 0, width: size, height: size, borderRadius: 999,
        background: '#fff', border: '1px solid ' + TC.line, overflow: 'hidden',
        display: 'flex', alignItems: 'center', justifyContent: 'center'
      }}>
        <img src={logo} alt="" aria-hidden="true" onError={() => setFailed(true)}
        style={{ width: scale, height: scale, objectFit: 'contain' }} />
      </div>);

  }
  return <AssetTile category={partner.category} size={size} circle />;
}

// ── 페이지 헤더 (TDS Navigation)
function PageHeader({ title, onLeading, leadingIcon, trailing, trailingIcon, onTrailing, align = 'center', floating = false, scrolled = false, logoSrc }) {
  const between = align === 'between';

  const leadingNode = leadingIcon ?
  <button onClick={onLeading} style={{
    all: 'unset', cursor: 'pointer', width: 44, height: 44,
    display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0
  }} aria-label="뒤로/닫기">
      <Icon name={leadingIcon} size={leadingIcon === 'x' ? 24 : 26}
    color={TC.textStrong} stroke={leadingIcon === 'x' ? 2.2 : 2} />
    </button> :
  between ? null : <div style={{ width: 44, height: 44, flexShrink: 0 }} aria-hidden="true" />;

  const trailingNode = trailing ?
  <div style={{ minWidth: 44, height: 44, display: 'flex', alignItems: 'center', justifyContent: 'flex-end', flexShrink: 0 }}>
      {trailing}
    </div> :
  trailingIcon ?
  <button onClick={onTrailing} style={{
    all: 'unset', cursor: 'pointer', width: 44, height: 44,
    display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0
  }} aria-label={trailingIcon === 'search' ? '검색' : '닫기'}>
      <Icon name={trailingIcon} size={trailingIcon === 'x' ? 24 : 24}
    color={TC.textStrong} stroke={trailingIcon === 'x' ? 2.2 : 2.1} />
    </button> :
  between ? null : <div style={{ width: 44, height: 44, flexShrink: 0 }} aria-hidden="true" />;

  // chazm 스타일: 헤더는 상단 고정(sticky) 안 함 — 스크롤하면 같이 위로 올라가서
  // iOS 시계영역 자리에 페이지 콘텐츠가 보이도록(투명 통과). 배경도 투명/blur 없음.
  return (
    <div style={{ ...{
        height: floating ? 60 : 56, padding: between ? '0 6px 0 16px' : '0 4px',
        display: 'flex', alignItems: 'center', gap: 4,
        justifyContent: between ? 'space-between' : 'flex-start',
        background: 'transparent',
        position: 'relative', zIndex: 2, fontFamily: FF,
      }, padding: "0px 16px 0px 6px" }}>
      {between ?
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
          {/* 앱 웹뷰에서 앱으로 돌아가기 버튼 (iOS 스와이프 백 비활성 대응) */}
          {leadingIcon && onLeading &&
        <button onClick={onLeading} aria-label="앱으로 돌아가기" style={{
          all: 'unset', cursor: 'pointer',
          width: 44, height: 44, marginLeft: -10,
          display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
        }}>
              <Icon name={leadingIcon} size={26} color={TC.textStrong} stroke={2} />
            </button>
        }
          {logoSrc &&
        <img src={logoSrc} alt="타운카" style={{
          display: 'block', width: "32px", height: "32px",
          margin: leadingIcon ? '0' : '0 0 0 16px'
        }} />
        }
          <h1 style={{
          margin: 0, fontSize: 19, fontWeight: 700, letterSpacing: '-0.02em',
          whiteSpace: 'nowrap', color: "rgb(145, 145, 145)"
        }}>{title}</h1>
        </div> :

      <>
          {leadingNode}
          <h1 style={{
          margin: 0, flex: 1, fontSize: 17, fontWeight: 700, letterSpacing: '-0.02em',
          color: TC.textStrong, textAlign: 'center'
        }}>{title}</h1>
        </>
      }
      {trailingNode}
    </div>);

}

// ── 카테고리 탭 (TDS 인-스크린 언더라인 탭)
function CategoryTabs({ items, active, onChange, counts }) {
  const drag = useDragScroll();
  const scrollRef = drag.ref;
  const activeRef = React.useRef(null);
  const firstScrollRef = React.useRef(true);

  React.useLayoutEffect(() => {
    const container = scrollRef.current;
    if (!container) return;
    const compute = () => {
      const cur = activeRef.current;
      if (!cur || !container) return null;
      const target = cur.offsetLeft - (container.clientWidth - cur.offsetWidth) / 2;
      const max = Math.max(0, container.scrollWidth - container.clientWidth);
      return Math.max(0, Math.min(max, target));
    };
    if (firstScrollRef.current) {
      const v = compute();if (v != null) container.scrollLeft = v;
      firstScrollRef.current = false;
      requestAnimationFrame(() => requestAnimationFrame(() => {
        const v2 = compute();if (v2 != null) container.scrollLeft = v2;
      }));
      setTimeout(() => {const v4 = compute();if (v4 != null) container.scrollLeft = v4;}, 80);
      setTimeout(() => {const v5 = compute();if (v5 != null) container.scrollLeft = v5;}, 240);
      if (document.fonts && document.fonts.ready && document.fonts.ready.then) {
        document.fonts.ready.then(() => {const v3 = compute();if (v3 != null) container.scrollLeft = v3;});
      }
    } else {
      const v = compute();
      if (v != null) {
        if (window.tcSmoothScrollLeft) window.tcSmoothScrollLeft(container, v, 360);else
        container.scrollTo({ left: v, behavior: 'smooth' });
      }
    }
  }, [active]);

  return (
    <div style={{
      background: '#fff', borderBottom: '1px solid ' + TC.line,
      position: 'sticky', top: 56, zIndex: 25, fontFamily: FF
    }}>
      <div ref={scrollRef} {...drag.handlers} style={{
        display: 'flex', overflowX: 'auto', overflowY: 'hidden',
        scrollbarWidth: 'none', padding: '0 12px', cursor: 'grab'
      }} className="hide-scroll">
        {items.map((it) => {
          const on = it.id === active;
          return (
            <button key={it.id}
            ref={on ? activeRef : null}
            onClick={() => onChange(it.id)} style={{
              all: 'unset', cursor: 'pointer',
              padding: '15px 14px 13px',
              display: 'inline-flex', alignItems: 'center', gap: 4,
              position: 'relative', flexShrink: 0,
              color: on ? it.id === '__benefit' ? TC.primary : 'var(--tab-active-fg)' : TC.textAlt
            }}>
              <span style={{
                fontSize: 15, fontWeight: on ? 700 : 500, letterSpacing: '-0.01em', whiteSpace: 'nowrap'
              }}>{it.label}</span>
              {/* 탭 옆 카운트 숫자는 표시하지 않음 — 깔끔한 라벨만 노출 */}
              {on &&
              <span style={{
                position: 'absolute', bottom: 0, left: 8, right: 8,
                height: 2.5, background: it.id === '__benefit' ? TC.primary : 'var(--tab-underline)', borderRadius: "0px"
              }} />
              }
            </button>);

        })}
      </div>
    </div>);

}

// ── 서브 필터 칩 (TDS Chip — 선택 시 그린)
function SubChips({ items, active, onChange, showAll = true }) {
  const all = showAll ? [{ id: '__all', label: '전체' }, ...items] : items;
  const drag = useDragScroll();
  const activeChipRef = React.useRef(null);
  const firstScrollRef = React.useRef(true);

  React.useLayoutEffect(() => {
    const container = drag.ref.current;
    if (!container) return;
    const compute = () => {
      const cur = activeChipRef.current;
      if (!cur || !container) return null;
      const target = cur.offsetLeft - (container.clientWidth - cur.offsetWidth) / 2;
      const max = Math.max(0, container.scrollWidth - container.clientWidth);
      return Math.max(0, Math.min(max, target));
    };
    if (firstScrollRef.current) {
      const v = compute();if (v != null) container.scrollLeft = v;
      firstScrollRef.current = false;
      requestAnimationFrame(() => requestAnimationFrame(() => {
        const v2 = compute();if (v2 != null) container.scrollLeft = v2;
      }));
      setTimeout(() => {const v4 = compute();if (v4 != null) container.scrollLeft = v4;}, 80);
      setTimeout(() => {const v5 = compute();if (v5 != null) container.scrollLeft = v5;}, 240);
    } else {
      const v = compute();
      if (v != null) {
        if (window.tcSmoothScrollLeft) window.tcSmoothScrollLeft(container, v, 320);else
        container.scrollTo({ left: v, behavior: 'smooth' });
      }
    }
  }, [active]);

  return (
    <div style={{ background: '#fff', borderBottom: '1px solid ' + TC.line, fontFamily: FF }}>
      <div ref={drag.ref} {...drag.handlers} style={{
        display: 'flex', gap: 6, overflowX: 'auto', padding: '10px 16px',
        scrollbarWidth: 'none', cursor: 'grab'
      }} className="hide-scroll">
        {all.map((it) => {
          const on = active == null && it.id === '__all' || it.id === active;
          return (
            <button
              key={it.id}
              ref={on ? activeChipRef : null}
              onClick={() => onChange(it.id === '__all' ? null : it.id)}
              style={{
                all: 'unset', cursor: 'pointer', flexShrink: 0,
                height: 34, padding: '0 15px', borderRadius: 999,
                background: on ? 'var(--sel-bg)' : TC.bgAlt,
                border: on ? '1px solid var(--sel-border)' : '1px solid ' + TC.line,
                color: on ? 'var(--sel-fg)' : TC.textNeutral,
                fontSize: 13.5, fontWeight: on ? 700 : 500, letterSpacing: '-0.005em',
                display: 'inline-flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap',
                transition: 'all 140ms cubic-bezier(0.22,0.61,0.36,1)'
              }}>{it.label}</button>);

        })}
      </div>
    </div>);

}

// ── 검색바 (TDS TextField — 인셋 fill)
function SearchBar({ value, onChange, placeholder = '지점명·지역으로 검색', onFocus }) {
  return (
    <div style={{ padding: '10px 16px', background: '#fff', fontFamily: FF }}>
      <div style={{
        height: 46, padding: '0 14px', borderRadius: 12, background: TC.bgAlt,
        display: 'flex', alignItems: 'center', gap: 8
      }}>
        <Icon name="search" size={18} color={TC.textAlt} stroke={2} />
        <input
          value={value}
          onFocus={onFocus}
          onChange={(e) => onChange(e.target.value)}
          placeholder={placeholder}
          autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck={false}
          style={{
            all: 'unset', flex: 1, fontWeight: 500, fontSize: 16,
            fontFamily: FF, letterSpacing: '-0.01em', color: TC.textStrong, minWidth: 0
          }} />
        
        {value &&
        <button onClick={() => onChange('')} style={{
          all: 'unset', cursor: 'pointer', display: 'flex',
          width: 20, height: 20, borderRadius: 999, background: TC.lineStrong,
          alignItems: 'center', justifyContent: 'center'
        }}><Icon name="x" size={12} color="#fff" stroke={2.6} /></button>
        }
      </div>
    </div>);

}

// ── 뷰 토글 (지도/리스트)
function ViewToggle({ view, onChange }) {
  return (
    <div style={{
      display: 'inline-flex', borderRadius: 999, background: TC.fill, padding: 3, fontFamily: FF
    }}>
      {[
      { id: 'map', label: '지도', icon: 'map-pin' },
      { id: 'list', label: '리스트', icon: 'list' }].
      map((it) => {
        const on = view === it.id;
        return (
          <button key={it.id} onClick={() => onChange(it.id)} style={{
            all: 'unset', cursor: 'pointer', height: 32, padding: '0 12px', borderRadius: 999,
            background: on ? '#fff' : 'transparent',
            color: on ? TC.textStrong : TC.textNeutral,
            fontSize: 12, fontWeight: 600,
            display: 'inline-flex', alignItems: 'center', gap: 4,
            boxShadow: on ? '0 1px 2px rgba(0,19,43,0.08)' : 'none'
          }}>
            <Icon name={it.icon} size={14} stroke={2} />
            {it.label}
          </button>);

      })}
    </div>);

}

// ── 타운카 공식 뱃지
function OfficialBadge() {
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 3,
      height: 21, padding: '0 8px', borderRadius: 6,
      background: TC.primaryWeak, color: TC.primaryPressed,
      fontSize: 11, fontWeight: 700, letterSpacing: '-0.005em', fontFamily: FF
    }}>
      <Icon name="badge-check" size={12} color={TC.primaryPressed} stroke={2.4} />
      타운카 공식
    </span>);

}

// ── 일반 뱃지
function MiniBadge({ children, tone = 'neutral' }) {
  const tones = {
    neutral: { bg: TC.fill, fg: TC.textNeutral },
    benefit: { bg: TC.primaryWeak, fg: TC.primaryPressed },
    info: { bg: '#E9F6FF', fg: '#007AD1' },
    accent: { bg: '#FEECFB', fg: '#A81690' },
    yellow: { bg: '#FFF8DE', fg: '#A07300' },
    discount: { bg: '#FFEBEB', fg: '#CC1003' }
  };
  const t = tones[tone] || tones.neutral;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', height: 21, padding: '0 8px',
      borderRadius: 6, background: t.bg, color: t.fg,
      fontSize: 11, fontWeight: 700, letterSpacing: '-0.005em',
      fontFamily: FF, whiteSpace: 'nowrap'
    }}>{children}</span>);

}

// ── 파트너 카드 (TDS ListRow)
function PartnerCard({ partner, brandLabel, onClick, showAsset = false, index, last = false, showBenefit = false }) {
  const p = partner;
  const [pressed, setPressed] = React.useState(false);
  const handleClick = (e) => {
    if (onClick) onClick();
  };
  return (
    <div
      onClick={handleClick}
      role="button" tabIndex={0}
      onPointerDown={() => setPressed(true)}
      onPointerUp={() => setPressed(false)}
      onPointerLeave={() => setPressed(false)}
      onPointerCancel={() => setPressed(false)}
      style={{
        background: pressed ? TC.bgAlt : '#fff',
        padding: '15px 16px',
        cursor: 'pointer', position: 'relative',
        fontFamily: FF,
        display: 'flex', gap: 13, alignItems: 'flex-start',
        transition: 'background 100ms cubic-bezier(0.22,0.61,0.36,1)',
        WebkitTapHighlightColor: 'transparent'
      }}>
      
      {showAsset && <PartnerAsset partner={p} size={44} />}

      <div style={{ flex: 1, minWidth: 0 }}>
        {brandLabel &&
        <div style={{
          fontSize: 11.5, fontWeight: 600, color: TC.textAlt,
          letterSpacing: '0.01em', marginBottom: 2
        }}>{brandLabel}</div>
        }
        <div style={{
          fontSize: 16, fontWeight: 700, color: TC.textStrong,
          letterSpacing: '-0.02em', lineHeight: '21px',
          display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap'
        }}>
          {p.name}
          {p.flagship && <MiniBadge tone="yellow">플래그십</MiniBadge>}
          {p.badges && p.badges.filter((b) => /타운카 차주/.test(b)).map((b, i) => (
            <MiniBadge key={'top-' + i} tone="accent">{b}</MiniBadge>
          ))}
        </div>

        {(p.discount || (p.category === 'financing' && /대출로 분류/.test(p.note || '')) || (p.badges && p.badges.filter((b) => !/타운카 차주/.test(b)).length > 0)) &&
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 7 }}>
            {p.discount && <MiniBadge tone="benefit">타운카 전용 혜택</MiniBadge>}
            {p.category === 'financing' && /대출로 분류되는/.test(p.note || '') && <MiniBadge tone="neutral">대출 상품</MiniBadge>}
            {p.category === 'financing' && /대출로 분류되지 않는/.test(p.note || '') && <MiniBadge tone="yellow">비대출 상품</MiniBadge>}
            {p.badges && p.badges.filter((b) => !/타운카 차주/.test(b)).map((b, i) => (
              <MiniBadge key={i} tone="info">{b}</MiniBadge>
            ))}
          </div>
        }

        {showBenefit && p.discount &&
        <div style={{
          marginTop: 8, display: 'flex', alignItems: 'center', gap: 7,
          padding: '9px 11px', borderRadius: 10, background: TC.bgAlt
        }}>
            <Icon name="gift" size={14} color={TC.textNeutral} stroke={2.2} style={{ flexShrink: 0 }} />
            <span style={{ fontSize: 13, fontWeight: 700, color: TC.text, letterSpacing: '-0.005em', lineHeight: '17px', wordBreak: 'keep-all' }}>{p.discount}</span>
          </div>
        }

        {(p.address || p.branchPhone) &&
        <div style={{
          marginTop: 4, fontSize: 13, color: p.address ? TC.textNeutral : TC.textAssist,
          letterSpacing: '-0.005em', lineHeight: '18px',
          display: 'flex', alignItems: 'center', gap: 4, minWidth: 0
        }}>
            <svg width="13" height="13" viewBox="0 0 24 24" fill={TC.textAssist} style={{ flexShrink: 0 }} aria-hidden="true"><path fillRule="evenodd" clipRule="evenodd" d="M12 2c-3.87 0-7 3.13-7 7 0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 110-5 2.5 2.5 0 010 5z" /></svg>
            <span style={{
            flex: 1, minWidth: 0,
            whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis'
          }}>{p.address || '지점 내방 문의'}</span>
          </div>
        }
      </div>

      <Icon name="chevron-right" size={18} color={TC.textAssist} style={{ marginTop: 12 }} />
      {!last &&
      <span style={{ position: 'absolute', left: 16, right: 16, bottom: 0, height: 1, background: TC.lineSoft }} />
      }
    </div>);

}

// ── 빈 상태
function EmptyState({ title, subtitle, icon = 'search-x' }) {
  return (
    <div style={{
      padding: '56px 24px', display: 'flex', flexDirection: 'column',
      alignItems: 'center', gap: 8, fontFamily: FF
    }}>
      <div style={{
        width: 60, height: 60, borderRadius: 999, background: TC.bgAlt,
        display: 'flex', alignItems: 'center', justifyContent: 'center'
      }}>
        <Icon name={icon} size={27} color={TC.textAlt} stroke={1.8} />
      </div>
      <div style={{ fontSize: 16, fontWeight: 700, color: TC.textStrong, marginTop: 6, letterSpacing: '-0.01em' }}>{title}</div>
      {subtitle && <div style={{ fontSize: 14, color: TC.textNeutral, textAlign: 'center', lineHeight: '20px' }}>{subtitle}</div>}
    </div>);

}

// ── 토스트 (TDS Toast — 다크 라운드)
function Toast({ message, visible }) {
  return (
    <div style={{
      position: 'absolute', bottom: visible ? 96 : 64, left: '50%',
      transform: 'translateX(-50%)',
      background: TC.textStrong, color: '#fff',
      padding: '13px 18px', borderRadius: 12,
      fontSize: 14, fontWeight: 500, letterSpacing: '-0.01em',
      fontFamily: FF, whiteSpace: 'nowrap',
      opacity: visible ? 1 : 0, pointerEvents: 'none',
      boxShadow: '0 8px 24px rgba(0,19,43,0.20)',
      transition: 'all 240ms cubic-bezier(0.22,0.61,0.36,1)', zIndex: 200
    }}>{message}</div>);

}

// ── 네이버 지도 URL
function naverMapUrl(partner) {
  const q = partner.address || partner.name;
  return 'https://map.naver.com/p/search/' + encodeURIComponent(q);
}
// ── 전화 URL
function telUrl(phone) {
  return 'tel:' + String(phone || '').replace(/[^0-9+]/g, '');
}

Object.assign(window, {
  TC, FF, CAT_ACCENT, Icon, AssetTile, PartnerAsset, useDragScroll, PageHeader, CategoryTabs, SubChips,
  SearchBar, ViewToggle, OfficialBadge, MiniBadge, PartnerCard: React.memo(PartnerCard),
  EmptyState, Toast, naverMapUrl, telUrl
});