// FilterBar — período / plataforma / canal dropdowns (right), timestamp (left).
const Dropdown = ({ icon, label, value, options, onChange }) => {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(()=>{
    const h = (e)=>{ if(ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return ()=>document.removeEventListener('mousedown', h);
  },[]);
  const cur = options.find(o=>o.key===value);
  return (
    <div ref={ref} style={fb.ddWrap}>
      <button style={{...fb.dd, ...(open?fb.ddOpen:{})}} onClick={()=>setOpen(o=>!o)}
        onMouseEnter={e=>{if(!open)e.currentTarget.style.borderColor='var(--border-2)';}}
        onMouseLeave={e=>{if(!open)e.currentTarget.style.borderColor='var(--border-1)';}}>
        {icon && <Icon name={icon} size={15} color="var(--fg-3)"/>}
        <span style={fb.ddLabel}>{label}</span>
        <span style={fb.ddValue}>{cur?cur.label:'—'}</span>
        <Icon name="chevronDown" size={15} color="var(--fg-3)" style={{transform:open?'rotate(180deg)':'none', transition:'transform .18s'}}/>
      </button>
      {open && (
        <div style={fb.menu}>
          {options.map(o=>{
            const on = o.key===value;
            return (
              <button key={o.key} style={{...fb.opt, ...(on?fb.optOn:{})}}
                onMouseEnter={e=>{if(!on)e.currentTarget.style.background='var(--gray-50)';}}
                onMouseLeave={e=>{if(!on)e.currentTarget.style.background='transparent';}}
                onClick={()=>{ onChange(o.key); setOpen(false); }}>
                {o.dot && <span style={{...fb.optDot, background:o.dot}}/>}
                <span style={{flex:1}}>{o.label}</span>
                {on && <Icon name="dot" size={7} color="var(--fenix-orange)"/>}
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
};

// Dropdown de seleção MÚLTIPLA (spec 2026-07-29 — Leads & economics por oferta).
// O `Dropdown` acima é de escolha única; este marca vários itens ao mesmo tempo.
// REGRA DE NEGÓCIO: nenhum marcado = TODOS (é o default do filtro de produto no backend,
// que recebe `products` vazio e não filtra nada). Por isso não existe opção "Todos" na
// lista: limpar a seleção JÁ é "todos", e o botão de limpar é o caminho para voltar.
// `values` é array de keys; `onChange(next)` recebe o array novo (imutável).
const MultiDropdown = ({ icon, label, values = [], options, onChange, allLabel = 'Todos', maxChips = 3 }) => {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(()=>{
    const h = (e)=>{ if(ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return ()=>document.removeEventListener('mousedown', h);
  },[]);
  const sel = new Set(values);
  const toggle = (k)=> onChange(sel.has(k) ? values.filter(v=>v!==k) : [...values, k]);
  const labelOf = (k)=> (options.find(o=>o.key===k) || {}).label || k;
  return (
    <div ref={ref} style={fb.ddWrap}>
      <button style={{...fb.dd, ...(open?fb.ddOpen:{})}} onClick={()=>setOpen(o=>!o)}
        onMouseEnter={e=>{if(!open)e.currentTarget.style.borderColor='var(--border-2)';}}
        onMouseLeave={e=>{if(!open)e.currentTarget.style.borderColor='var(--border-1)';}}>
        {icon && <Icon name={icon} size={15} color="var(--fg-3)"/>}
        <span style={fb.ddLabel}>{label}</span>
        {/* Até maxChips: os chips dos selecionados. Acima disso vira contador — a lista
            inteira no botão empurraria a barra de filtros para outra linha. */}
        {values.length === 0
          ? <span style={fb.ddValue}>{allLabel}</span>
          : values.length <= maxChips
            ? <span style={fb.mdChips}>{values.map(v=><span key={v} style={fb.mdChip}>{labelOf(v)}</span>)}</span>
            : <span style={fb.mdChip}>{values.length} selecionados</span>}
        {values.length > 0 && (
          <span role="button" title="Limpar seleção" style={fb.mdX}
            onClick={e=>{ e.stopPropagation(); onChange([]); }}>
            <Icon name="x" size={13} color="var(--fg-3)"/>
          </span>
        )}
        <Icon name="chevronDown" size={15} color="var(--fg-3)"
          style={{transform:open?'rotate(180deg)':'none', transition:'transform .18s'}}/>
      </button>
      {open && (
        <div style={{...fb.menu, minWidth:230}}>
          {options.map(o=>{
            const on = sel.has(o.key);
            return (
              <button key={o.key} style={{...fb.opt, ...(on?fb.optOn:{})}}
                onMouseEnter={e=>{if(!on)e.currentTarget.style.background='var(--gray-50)';}}
                onMouseLeave={e=>{if(!on)e.currentTarget.style.background='transparent';}}
                onClick={()=>toggle(o.key)}>
                <span style={{...fb.mdBox, ...(on?fb.mdBoxOn:{})}}>{on && <span style={fb.mdCheck}/>}</span>
                {o.dot && <span style={{...fb.optDot, background:o.dot}}/>}
                <span style={{flex:1}}>{o.label}</span>
              </button>
            );
          })}
          {options.length === 0 && <div style={fb.mdEmpty}>Sem opções na janela.</div>}
          {values.length > 0 && (
            <button style={fb.mdClear} onClick={()=>onChange([])}
              onMouseEnter={e=>{e.currentTarget.style.background='var(--gray-50)';}}
              onMouseLeave={e=>{e.currentTarget.style.background='transparent';}}>
              Limpar seleção ({values.length}) · volta para {allLabel.toLowerCase()}
            </button>
          )}
        </div>
      )}
    </div>
  );
};

// Dropdown com busca (spec 2026-07-31 D9): para listas longas (roster de afiliados).
// Mesmo contrato do Dropdown ({icon,label,value,options,onChange}) + input de filtro.
// Reusa os mesmos tokens visuais do Dropdown (fb.dd/fb.menu/fb.opt/fb.mdEmpty) — só troca
// a lista por uma busca client-side (options já vêm carregadas do roster).
const SearchDropdown = ({ icon, label, value, options, onChange }) => {
  const [open, setOpen] = React.useState(false);
  const [q, setQ] = React.useState('');
  const ref = React.useRef(null);
  React.useEffect(()=>{
    const h = (e)=>{ if(ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return ()=>document.removeEventListener('mousedown', h);
  },[]);
  const cur = options.find(o=>o.key===value);
  const vis = options.filter(o=> !q || o.label.toLowerCase().includes(q.toLowerCase()));
  return (
    <div ref={ref} style={fb.ddWrap}>
      <button style={{...fb.dd, ...(open?fb.ddOpen:{})}} onClick={()=>{ setOpen(o=>!o); setQ(''); }}
        onMouseEnter={e=>{if(!open)e.currentTarget.style.borderColor='var(--border-2)';}}
        onMouseLeave={e=>{if(!open)e.currentTarget.style.borderColor='var(--border-1)';}}>
        {icon && <Icon name={icon} size={15} color="var(--fg-3)"/>}
        <span style={fb.ddLabel}>{label}</span>
        <span style={fb.ddValue}>{cur?cur.label:'—'}</span>
        <Icon name="chevronDown" size={15} color="var(--fg-3)" style={{transform:open?'rotate(180deg)':'none', transition:'transform .18s'}}/>
      </button>
      {open && (
        <div style={{...fb.menu, minWidth:240}}>
          <input autoFocus value={q} onChange={e=>setQ(e.target.value)} placeholder="Buscar afiliado…"
            style={{ width:'100%', boxSizing:'border-box', padding:'7px 10px', marginBottom:5,
              border:'1px solid var(--border-1)', borderRadius:'var(--r-sm)', font:'inherit', fontSize:13, outline:'none' }}/>
          <div style={{maxHeight:260, overflowY:'auto'}}>
            {vis.map(o=>{
              const on = o.key===value;
              return (
                <button key={o.key} style={{...fb.opt, ...(on?fb.optOn:{})}}
                  onMouseEnter={e=>{if(!on)e.currentTarget.style.background='var(--gray-50)';}}
                  onMouseLeave={e=>{if(!on)e.currentTarget.style.background='transparent';}}
                  onClick={()=>{ onChange(o.key); setOpen(false); }}>
                  {o.dot && <span style={{...fb.optDot, background:o.dot}}/>}
                  <span style={{flex:1}}>{o.label}</span>
                  {on && <Icon name="dot" size={7} color="var(--fenix-orange)"/>}
                </button>
              );
            })}
            {!vis.length && <div style={fb.mdEmpty}>Nenhum afiliado encontrado.</div>}
          </div>
        </div>
      )}
    </div>
  );
};

const Segmented = ({ options, value, onChange }) => (
  <div style={fb.seg}>
    {options.map(o=>{
      const on = o.key===value;
      return (
        <button key={o.key} style={{...fb.segBtn, ...(on?fb.segBtnOn:{})}} onClick={()=>onChange(o.key)}>
          {o.label}
        </button>
      );
    })}
  </div>
);

// Filtro de tempo PADRÃO do dashboard (spec 2026-07-21): estas 7 opções, nesta ordem, em
// TODOS os seletores de período (o PeriodSelector é compartilhado por todas as telas).
// O backend continua aceitando as chaves antigas (24h/mtd/qtd) por compat — só saem do menu.
const PERIOD_OPTIONS = [
  {key:'ontem',  label:'Ontem'},
  {key:'7d',     label:'Últimos 7 dias'},
  {key:'14d',    label:'Últimos 14 dias'},
  {key:'30d',    label:'Últimos 30 dias'},
  {key:'90d',    label:'Últimos 90 dias'},
  {key:'ytd',    label:'YTD · ano até hoje'},
  {key:'total',  label:'Todo período'},
  {key:'custom', label:'Personalizado...'},
];

const PeriodSelector = ({ period, setPeriod, customRange, setCustomRange }) => {
  const [open, setOpen]             = React.useState(false);
  const [showPicker, setShowPicker] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(()=>{
    const h = (e)=>{
      if(ref.current && !ref.current.contains(e.target)){
        setOpen(false);
        setShowPicker(false);
      }
    };
    document.addEventListener('mousedown', h);
    return ()=>document.removeEventListener('mousedown', h);
  },[]);

  const getPeriodLabel = () => {
    if(period==='custom' && customRange?.start && customRange?.end){
      const fmt = k => { const [,m,d]=k.split('-'); return `${d}/${m}`; };
      return `${fmt(customRange.start)} – ${fmt(customRange.end)}`;
    }
    const opt = PERIOD_OPTIONS.find(o=>o.key===period);
    return opt ? opt.label : period;
  };

  return (
    <div ref={ref} style={fb.ddWrap}>
      <button style={{...fb.dd, ...(open?fb.ddOpen:{})}} onClick={()=>setOpen(o=>!o)}
        onMouseEnter={e=>{if(!open)e.currentTarget.style.borderColor='var(--border-2)';}}
        onMouseLeave={e=>{if(!open)e.currentTarget.style.borderColor='var(--border-1)';}}>
        <Icon name="calendar" size={15} color="var(--fg-3)"/>
        <span style={fb.ddLabel}>Período</span>
        <span style={{...fb.ddValue, maxWidth:148, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>
          {getPeriodLabel()}
        </span>
        <Icon name="chevronDown" size={15} color="var(--fg-3)"
          style={{transform:open?'rotate(180deg)':'none', transition:'transform .18s'}}/>
      </button>

      {open && (
        <div style={{...fb.menu, padding:0, overflow:'hidden'}}>
          {!showPicker ? (
            <div style={{padding:5}}>
              {PERIOD_OPTIONS.map(o=>{
                const on       = o.key===period;
                const isCustom = o.key==='custom';
                return (
                  <button key={o.key} style={{...fb.opt, ...(on?fb.optOn:{})}}
                    onMouseEnter={e=>{if(!on)e.currentTarget.style.background='var(--gray-50)';}}
                    onMouseLeave={e=>{if(!on)e.currentTarget.style.background='transparent';}}
                    onClick={()=>{
                      if(isCustom){ setShowPicker(true); return; }
                      setPeriod(o.key);
                      setCustomRange(null);
                      setOpen(false);
                    }}>
                    <span style={{flex:1}}>{o.label}</span>
                    {on && !isCustom && <Icon name="dot" size={7} color="var(--fenix-orange)"/>}
                    {isCustom && <Icon name="chevron" size={12} color="var(--fg-3)"/>}
                  </button>
                );
              })}
            </div>
          ) : (
            <DateRangePicker
              range={customRange}
              onApply={range=>{
                setCustomRange(range);
                setPeriod('custom');
                setOpen(false);
                setShowPicker(false);
              }}
              onCancel={()=>setShowPicker(false)}
            />
          )}
        </div>
      )}
    </div>
  );
};

// Spinner do indicador de conexão (classe fenix-spin respeita prefers-reduced-motion)
const Spinner = ({ color='var(--warning)', size=12, bw=2 }) => (
  <span className="fenix-spin" style={{ width:size, height:size, borderRadius:'50%',
    border:`${bw}px solid ${color}`, borderTopColor:'transparent', display:'inline-block', flex:'none' }}/>
);

// Tela de carregamento (spec 2026-06-25): mostrada na área de conteúdo enquanto não há
// dado real. connecting = 1º load; offline = caiu antes de qualquer dado.
const LoadingScreen = ({ conn='connecting' }) => {
  const offline = conn === 'offline';
  return (
    <div style={ls.wrap}>
      <Spinner color={offline ? 'var(--warning)' : 'var(--fenix-orange)'} size={44} bw={4}/>
      <div style={ls.title}>{offline ? 'Sem conexão' : 'Conectando à API'}</div>
      <div style={ls.sub}>{offline ? 'Reconectando…' : 'Carregando seus dados…'}</div>
    </div>
  );
};
const ls = {
  wrap:{ minHeight:'60vh', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap:16 },
  title:{ font:'var(--text-h3)', color:'var(--fg-1)' },
  sub:{ font:'var(--text-body-sm)', color:'var(--fg-3)' },
};

// --- Loading cheio por interação (spec 2026-07-23) ---
// mark(): chamado pelos setters de filtro/aba embrulhados — registra "o próximo fetch é do
// usuário" (janela de 300ms; efeitos do React rodam logo após o commit, sobra folga).
// begin(): chamado no INÍCIO do load() de cada efeito — liga o overlay só se veio de mark().
// end(began): chamado no FIM (sucesso OU erro) — desliga respeitando o mínimo de 400ms
// (anti-flash: cache do backend responde em ~100-300ms). Contador `pending` cobre o caso
// de 2 efeitos dispararem pela mesma interação (ex.: afiliados+trend e produtos na Visão).
function useFullLoading() {
  const markTs = React.useRef(0);
  const pending = React.useRef(0);
  const since = React.useRef(0);
  const timer = React.useRef(null);
  const [show, setShow] = React.useState(false);
  React.useEffect(() => () => clearTimeout(timer.current), []);   // cleanup no unmount
  const mark = () => { markTs.current = Date.now(); };
  const begin = () => {
    if (Date.now() - markTs.current > 300) return false;   // polling/retry: não é do usuário
    pending.current += 1;
    if (pending.current === 1) {
      since.current = Date.now();
      clearTimeout(timer.current);
      setShow(true);
    }
    return true;
  };
  const end = (began) => {
    if (!began) return;
    pending.current = Math.max(0, pending.current - 1);
    if (pending.current === 0) {
      const rest = Math.max(0, 400 - (Date.now() - since.current));
      clearTimeout(timer.current);
      timer.current = setTimeout(() => setShow(false), rest);
    }
  };
  return { mark, begin, end, show };
}

// --- Retry de reconexão (spec 2026-07-30-reconexao-self-contained) ---
// Falha de primeira carga NÃO pode ser terminal: enquanto `active`, agenda nova
// tentativa a cada 15s (mesma cadência do App global) até a API voltar. Uso:
//   const retry = useAutoRetry(err);          // active = err, não err && !data:
//   React.useEffect(() => {...}, [..., retry]) // com dado velho o retry roda em
// silêncio (o refire não passa por mark(), então não pisca overlay). OBRIGATÓRIO
// em toda página self-contained — card de offline estático sem retry é proibido.
function useAutoRetry(active, delayMs = 15000) {
  const [tick, setTick] = React.useState(0);
  React.useEffect(() => {
    if (!active) return;
    const t = setTimeout(() => setTick(n => n + 1), delayMs);
    return () => clearTimeout(t);
  }, [active, tick, delayMs]);
  return tick;
}

// Overlay cheio: mesmo miolo da LoadingScreen sobre fundo OPACO, cobrindo a área de
// conteúdo (o pai precisa de position:relative). Conteúdo continua montado por baixo
// (scroll preservado). Cobre a barra de filtros de propósito: impede troca dupla no
// meio de um fetch.
const FullLoadingOverlay = ({ show, conn = 'connecting' }) => (
  <div style={{ position: 'absolute', inset: 0, zIndex: 25, background: 'var(--bg-app)',
      opacity: show ? 1 : 0, pointerEvents: show ? 'auto' : 'none', transition: 'opacity .15s' }}>
    {show && (
      <div style={{ position: 'sticky', top: 0, height: '100vh', maxHeight: '100%',
          display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <LoadingScreen conn={conn}/>
      </div>
    )}
  </div>
);

// Selo de status: connecting | live | offline (spec 2026-06-25)
const ConnStamp = ({ conn, lastUpdate }) => {
  if (conn === 'live') {
    return (
      <div style={fb.stamp}>
        <span style={{...fb.stampDot, background:'var(--positive)'}}/>
        Atualizado às <span style={fb.stampTime}>{lastUpdate ? lastUpdate.toLocaleTimeString('pt-BR',{hour:'2-digit',minute:'2-digit'}) : '—'}</span>
      </div>
    );
  }
  if (conn === 'connecting') {
    return (
      <div style={fb.stamp}>
        <Spinner color="var(--fg-3)"/>
        Conectando à API<span style={fb.stampTime}>…</span>
      </div>
    );
  }
  return (
    <div style={fb.stamp}>
      <Spinner color="var(--warning)"/>
      API offline <span style={fb.stampTime}>reconectando…</span>
    </div>
  );
};

const FilterBar = ({ period, setPeriod, platform, setPlatform, traffic, setTraffic, currency, setCurrency, customRange, setCustomRange, lastUpdate, conn='live', product, onProductChange, funil, setFunil, families, channelsMode=false, productOnly=false, refundBy='pedido', setRefundBy, refundByOn=false }) => {
  const sortedFamilies = [...families].sort((a, b) => a.name.localeCompare(b.name));
  const variants = product !== 'todos' ? (families.find(f => f.name === product)?.variants || []) : [];
  return (
    <div style={fb.root}>
      <ConnStamp conn={conn} lastUpdate={lastUpdate}/>
      <div style={fb.filters}>
        <div style={fb.segGroup}>
          <span style={fb.segLabel}>Moeda</span>
          <Segmented value={currency} onChange={setCurrency} options={[
            {key:'USD',label:'USD'},{key:'BRL',label:'BRL'},
          ]}/>
        </div>
        <PeriodSelector period={period} setPeriod={setPeriod}
          customRange={customRange} setCustomRange={setCustomRange}/>
        {/* AOV Tracker: só Período + Produto (buygoods, sem plataforma/tráfego/funil) */}
        {productOnly && (
          <Dropdown icon="cart" label="Produto" value={product} onChange={onProductChange} options={[
            {key:'todos', label:'Todos'},
            ...sortedFamilies.map(f=>({key:f.name, label:f.name})),
          ]}/>
        )}
        {/* Canais não têm dimensão de plataforma/produto/tráfego — só período se aplica */}
        {!channelsMode && !productOnly && (
          <>
            <Dropdown icon="layers" label="Plataforma" value={platform} onChange={setPlatform} options={[
              {key:'todas',    label:'Todas'},
              {key:'buygoods',    label:'Buygoods',    dot:'#FD7119'},
              {key:'clickbank',   label:'Clickbank',   dot:'#3B82F6'},
              {key:'digistore24', label:'Digistore24', dot:'#8B5CF6'},
              {key:'pagamerican', label:'PagAmerican', dot:'#1F9D6B'},
            ]}/>
            <Dropdown icon="cart" label="Produto" value={product} onChange={onProductChange} options={[
              {key:'todos', label:'Todos'},
              ...sortedFamilies.map(f=>({key:f.name, label:f.name})),
            ]}/>
            {product !== 'todos' && (
              <Dropdown icon="layers" label="Funil" value={funil} onChange={setFunil} options={[
                {key:'todos', label:'Todas variações'},
                ...variants.map(v=>({
                  key: v,
                  label: v.slice(product.length).replace(/^\s*[|\-]\s*/, '').trim() || v,
                })),
              ]}/>
            )}
            <Dropdown icon="globe" label="Canal de Tráfego" value={traffic} onChange={setTraffic} options={[
              {key:'todos',  label:'Todos'},
              {key:'fb',     label:'FB-heavy',     dot:'var(--positive)'},
              {key:'google', label:'Google-heavy', dot:'var(--info)'},
            ]}/>
          </>
        )}
        {/* Filtro Reembolso (spec 2026-07-22): visível também em Produtos/Nichos (channelsMode) */}
        {refundByOn && setRefundBy && (
          <Dropdown icon="layers" label="Reembolso" value={refundBy} onChange={setRefundBy}
            options={REFUND_BY_OPTIONS}/>
        )}
      </div>
    </div>
  );
};

const fb = {
  root:     { display:'flex', alignItems:'center', justifyContent:'space-between', gap:16, margin:'4px 0 18px', flexWrap:'wrap' },
  stamp:    { display:'inline-flex', alignItems:'center', gap:8, font:'var(--text-body-sm)', color:'var(--fg-3)', whiteSpace:'nowrap' },
  stampDot: { width:7, height:7, borderRadius:'50%', background:'var(--positive)', boxShadow:'0 0 0 3px rgba(31,157,107,.2)' },
  stampTime:{ font:'var(--text-mono-sm)', color:'var(--fg-2)' },
  filters:  { display:'flex', alignItems:'center', gap:10, flexWrap:'wrap' },
  segGroup: { display:'inline-flex', alignItems:'center', gap:8 },
  segLabel: { font:'600 12px/1 var(--font-sans)', color:'var(--fg-3)' },
  seg:      { display:'inline-flex', background:'var(--bg-sunken)', border:'1px solid var(--border-1)', borderRadius:'var(--r-md)', padding:3, gap:2 },
  segBtn:   { border:0, background:'transparent', cursor:'pointer', font:'700 12px/1 var(--font-mono)', color:'var(--fg-3)',
              padding:'7px 11px', borderRadius:'var(--r-sm)', transition:'all .15s' },
  segBtnOn: { background:'#fff', color:'var(--fenix-navy)', boxShadow:'var(--shadow-sm)' },
  ddWrap:   { position:'relative' },
  dd:       { display:'inline-flex', alignItems:'center', gap:8, background:'#fff', borderWidth:'1px', borderStyle:'solid', borderColor:'var(--border-1)',
              borderRadius:'var(--r-md)', padding:'9px 12px', cursor:'pointer', font:'var(--font-sans)', boxShadow:'var(--shadow-sm)', transition:'border-color .15s' },
  ddOpen:   { borderColor:'var(--fenix-orange)', boxShadow:'var(--shadow-focus)' },
  ddLabel:  { font:'600 12px/1 var(--font-sans)', color:'var(--fg-3)' },
  ddValue:  { font:'700 13px/1 var(--font-sans)', color:'var(--fg-1)' },
  menu:     { position:'absolute', top:'calc(100% + 6px)', right:0, minWidth:190, maxHeight:320, overflowY:'auto', background:'#fff',
              border:'1px solid var(--border-1)', borderRadius:'var(--r-md)', boxShadow:'var(--shadow-lg)', padding:5, zIndex:40 },
  opt:      { display:'flex', alignItems:'center', gap:9, width:'100%', border:0, background:'transparent',
              borderRadius:'var(--r-sm)', padding:'9px 10px', cursor:'pointer', font:'600 13px/1 var(--font-sans)', color:'var(--fg-1)', textAlign:'left', transition:'background .12s' },
  optOn:    { background:'var(--fenix-orange-100)', color:'var(--fenix-orange-600)' },
  optDot:   { width:8, height:8, borderRadius:'50%', flex:'none' },
  // MultiDropdown (spec 2026-07-29): mesmos tokens do Dropdown + caixa de marcação e chips.
  mdChips:  { display:'inline-flex', alignItems:'center', gap:4, maxWidth:260, overflow:'hidden' },
  mdChip:   { font:'700 11px/1 var(--font-sans)', color:'var(--fenix-orange-600)', background:'var(--fenix-orange-100)',
              borderRadius:'var(--r-pill)', padding:'4px 8px', whiteSpace:'nowrap' },
  mdX:      { display:'inline-flex', alignItems:'center', justifyContent:'center', width:18, height:18,
              borderRadius:'var(--r-sm)', cursor:'pointer', flex:'none' },
  mdBox:    { width:15, height:15, flex:'none', borderRadius:'var(--r-xs)', border:'1.5px solid var(--border-2)',
              background:'#fff', display:'inline-flex', alignItems:'center', justifyContent:'center' },
  mdBoxOn:  { background:'var(--fenix-orange)', borderColor:'var(--fenix-orange)' },
  mdCheck:  { width:4, height:8, borderRight:'2px solid #fff', borderBottom:'2px solid #fff',
              transform:'rotate(45deg) translate(-1px,-1px)' },
  mdClear:  { display:'block', width:'100%', border:0, borderTop:'1px solid var(--border-1)', background:'transparent',
              marginTop:4, padding:'9px 10px', cursor:'pointer', font:'600 11.5px/1.3 var(--font-sans)',
              color:'var(--fg-3)', textAlign:'left', borderRadius:'var(--r-sm)' },
  mdEmpty:  { padding:'14px 10px', font:'var(--text-body-sm)', color:'var(--fg-4)', textAlign:'center' },
};

Object.assign(window, { FilterBar, Dropdown, MultiDropdown, SearchDropdown, Segmented, PeriodSelector, Spinner, ConnStamp,
  LoadingScreen, useFullLoading, FullLoadingOverlay, useAutoRetry });
