// =============================================================================
// eCash — ENDORSEMENTS page (production)  ·  sets window.EndorsementsTab
// =============================================================================
// Drop-in replacement for docs/endorsements.jsx. Implements the Claude Design
// endorsements layout (left rail · type chips · search · featured · masonry of
// typed cards) wired to your REAL data (the static eCash list + Notion via
// /api/endorsements, plus the Drivechain archive from /drivechain-endorsements.json).
//
// IMAGE / LOGO STRATEGY (as requested): pull a real image wherever possible,
// otherwise fall back to the source's logo, otherwise a procedural eCash tile.
//   · tweet/quote/nostr avatar : item.photo → unavatar.io/twitter/<handle> → initials tile
//   · video thumbnail          : item.photo → YouTube thumb from link → abstract tile
//   · article / podcast image  : item.photo → unavatar.io/<domain> (publisher logo,
//                                 e.g. a Yahoo article with no image shows the Yahoo
//                                 logo) → google favicon → abstract tile
// Each <img> degrades through the chain via onError, so a broken/missing image
// never leaves an empty box.
//
// NOTE: true article HERO images (og:image) require the backend to scrape OG tags;
// without that we use the publisher LOGO as the "no image" fallback, which is what
// gives you "Yahoo article → Yahoo logo". Everything else uses real media images.
// =============================================================================

var STATIC_ECASH_ENDORSEMENTS = [
  {
    id: 'ecash-static-xximpod-1',
    type: 'Tweet',
    person: 'xximpod',
    handle: '@xximpod',
    snippet: '🚨 Episode w @Truthcoin — "Bitcoin Lightning & Bitcoin Cash Failed Mission, Now Hard Forking Bitcoin to Launch ECash"\n\n@Sehgal_ankit sits down with @Truthcoin, Bitcoin researcher and CEO of @LayerTwoLabs, to discuss his decade-long push for Bitcoin scaling through Drivechain (BIP 300 & 301) and why he\'s now launching eCash: a Bitcoin hard fork scheduled for August that activates the sidechain vision Bitcoin Core refused to enable.',
    link: 'https://x.com/xximpod/status/2058537729714164107',
    date: '2026-05-24',
  },
  {
    id: 'ecash-static-coinbureau-1',
    type: 'Video',
    person: 'Coin Bureau',
    snippet: "Satoshi's Stash Gets Grabbed in eCash Bitcoin Fork",
    link: 'https://www.youtube.com/watch?v=itJpwsK3WVg',
    date: '2026-05-02',
  },
  {
    id: 'ecash-static-alphastack-1',
    type: 'Video',
    person: 'Alphastack',
    snippet: 'Bitcoin Fork EXPLAINED: 7 New Chains Coming in August?',
    link: 'https://www.youtube.com/watch?v=buhJpD5ddoE',
    date: '2026-05-03',
  },
  {
    id: 'ecash-static-truthcoin-1',
    type: 'Tweet',
    person: 'Paul Sztorc',
    handle: '@Truthcoin',
    role: 'Creator of BIP 300/301, Lead Developer of eCash',
    snippet: 'BREAKING: New Bitcoin Fork\n\nI am helping create a new Bitcoin Hardfork — dropping this August, called "eCash".\n\nYour coins will split. If you have 4.19 BTC, you will get 4.19 eCash. You may sell your eCash — or keep it. Or ignore it!',
    link: 'https://x.com/Truthcoin/status/2047639261453680838',
    date: '2026-04-24',
  },
  {
    id: 'ecash-static-layertwolabs-1',
    type: 'Tweet',
    person: 'Layer Two Labs',
    handle: '@LayerTwoLabs',
    role: 'Company behind BIP 300/301 development',
    snippet: 'WATCH: @reardencode admits @Truthcoin has a point about L2 fees on a podcast with @jimmysong and @ToneVays',
    link: 'https://x.com/LayerTwoLabs/status/2015509852647235624',
    date: '2026-01-25',
  },
  {
    id: 'ecash-static-yahoofinance-video-1',
    type: 'Video',
    person: 'Yahoo Finance',
    snippet: "The eCash Hard Fork on Bitcoin Could Be 'Bizarre' For Miners: Here's How",
    link: 'https://finance.yahoo.com/video/ecash-hard-fork-bitcoin-could-211801875.html',
    date: '2026-05-01',
  },
  {
    id: 'ecash-static-reddit-1',
    type: 'Article',
    person: 'r/CryptoTechnology',
    snippet: 'Technical Analysis of the eCash Hard Fork',
    link: 'https://www.reddit.com/r/CryptoTechnology/comments/1sxsnic/technical_analysis_of_the_ecash_hard_fork/',
    date: '2026-05-01',
  },
  {
    id: 'ecash-static-bitcoincom-1',
    type: 'Article',
    person: 'Bitcoin.com',
    snippet: "Bitcoin's August Hard Fork May Dwarf Every Previous Split Combined — Here's Why",
    link: 'https://news.bitcoin.com/bitcoins-august-hard-fork-may-dwarf-every-previous-split-combined-heres-why/',
    date: '2026-04-29',
  },
  {
    id: 'ecash-static-cryptotimes-1',
    type: 'Article',
    person: 'CryptoTimes',
    snippet: "Paul Sztorc Pushes Back on eCash 'Theft' Claims: 'We Don't Take a Single BTC'",
    link: 'https://www.cryptotimes.io/2026/04/28/paul-sztorc-pushes-back-on-ecash-theft-claims-we-dont-take-a-single-btc/',
    date: '2026-04-28',
  },
  {
    id: 'ecash-static-yahoofinance-1',
    type: 'Article',
    person: 'Yahoo Finance',
    snippet: "Bitcoin Developer Plans to 'Reassign' Coins Linked to Satoshi Nakamoto in Hard Fork",
    link: 'https://finance.yahoo.com/markets/crypto/articles/bitcoin-developer-plans-reassign-coins-201529807.html',
    date: '2026-04-27',
  },
  {
    id: 'ecash-static-bitcoinmanual-1',
    type: 'Article',
    person: 'The Bitcoin Manual',
    snippet: 'What Is The eCash Fork?',
    link: 'https://thebitcoinmanual.com/articles/ecash-fork/',
    date: '2026-04-27',
  },
  {
    id: 'ecash-static-bitcoinnewscom-1',
    type: 'Tweet',
    person: 'Bitcoin News',
    handle: '@BitcoinNewsCom',
    snippet: 'NEW BITCOIN FORK PROPOSED SET FOR AUGUST LAUNCH\n\nA new Bitcoin hard fork dubbed "eCash" is being developed by Paul Sztorc. Holders would receive a 1:1 split of their BTC — 4.19 BTC would result in 4.19 eCash. One of the most contentious elements is the proposed reassignment of a portion of Satoshi\'s estimated 1.1M BTC as a premine for investors on the new chain.',
    link: 'https://x.com/BitcoinNewsCom/status/2048020029350858845',
    date: '2026-04-25',
  },
  {
    id: 'ecash-static-binance-1',
    type: 'Article',
    person: 'Binance Square',
    snippet: 'eCash hard fork coverage — Binance Square',
    link: 'https://www.binance.com/en/square/post/317501666142529',
    date: '2026-04-25',
  },
  {
    id: 'ecash-static-cryptorank-1',
    type: 'Article',
    person: 'CryptoRank',
    snippet: 'Top Bitcoin Dev Is Launching a New BTC Fork That Will Give Holders New eCash',
    link: 'https://cryptorank.io/news/feed/2323a-top-bitcoin-dev-is-launching-a-new-btc-fork-that-will-give-holders-new-ecash-but-claiming',
    date: '2026-04-25',
  },
  {
    id: 'ecash-static-mexc-1',
    type: 'Article',
    person: 'MEXC',
    snippet: 'eCash hard fork coverage — MEXC News',
    link: 'https://www.mexc.com/news/1053036',
    date: '2026-04-25',
  },
  {
    id: 'ecash-static-tradingview-1',
    type: 'Article',
    person: 'CoinTelegraph',
    source: 'CoinTelegraph',
    snippet: 'Bitcoin developer Paul Sztorc announces BTC hard fork called eCash',
    link: 'https://www.tradingview.com/news/cointelegraph:ecd9e1072094b:0-bitcoin-developer-paul-sztorc-announces-btc-hard-fork-called-ecash/',
    date: '2026-04-24',
  },
  {
    id: 'ecash-static-cryptoeconomy-1',
    type: 'Article',
    person: 'Crypto Economy',
    snippet: "Bitcoin Dev Paul Sztorc Unveils New 'eCash' Hard Fork Proposal",
    link: 'https://crypto-economy.com/bitcoin-dev-paul-sztorc-unveils-new-ecash-hard-fork-proposal/',
    date: '2026-04-24',
  },
  {
    id: 'ecash-static-cryptonews-1',
    type: 'Article',
    person: 'CryptoNews',
    snippet: 'Bitcoin Hard Fork eCash Set to Launch in August: Drivechain Proposer Reveals Plan',
    link: 'https://cryptonews.net/news/bitcoin/32762233/',
    date: '2026-04-24',
  },
  {
    id: 'ecash-static-binance-2',
    type: 'Article',
    person: 'Binance Square',
    snippet: 'eCash hard fork coverage — Binance Square',
    link: 'https://www.binance.com/en/square/post/316121980126737',
    date: '2026-04-24',
  },
  {
    id: 'ecash-static-cryptoslate-1',
    type: 'Article',
    person: 'CryptoSlate',
    snippet: "Top Bitcoin Dev Is Launching a New BTC Fork That Will Give Holders New eCash",
    link: 'https://cryptoslate.com/top-bitcoin-dev-is-launching-a-new-btc-fork-that-will-give-holders-new-ecash-but-claiming-it-may-be-the-real-risk/',
    date: '2026-04-25',
  },
  {
    id: 'ecash-static-forklog-1',
    type: 'Article',
    person: 'ForkLog',
    snippet: "Developer Announces Bitcoin Hard Fork and Distribution of Satoshi's Coins",
    link: 'https://forklog.com/en/developer-announces-bitcoin-hard-fork-and-distribution-of-satoshis-coins/',
    date: '2026-04-25',
  },
];

const E_CSS = `
#endorse-root .ecard__arthead{display:flex;align-items:center;gap:10px;margin-bottom:12px;}
#endorse-root .ecard__artlogo{position:relative;width:38px;height:38px;flex-shrink:0;border:1px solid var(--line);border-radius:2px;overflow:hidden;background:#0d0d10;display:flex;align-items:center;justify-content:center;}
#endorse-root .ecard__artimg{width:100%;height:100%;object-fit:contain;padding:18%;display:block;}
#endorse-root .ecard__artlogo--txt{font-size:16px;font-weight:700;color:var(--amber);background:#141418;}
#endorse-root .ecard--article .ecard__outlet{margin-bottom:0;}
#endorse-root{--bg:#0A0A0C; --panel:#0d0d10; --ink:#c9c9cf; --dim:#76767e; --dimmer:#4a4a52;
  --amber:#E8A84A; --amber-soft:rgba(232,168,74,0.14); --amber-dim:rgba(232,168,74,0.5);
  --line:#1f1f23; --line2:#161619; --r:3px;
  --fs-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-family:var(--fs-mono); color:var(--ink); position:relative; padding-top:60px; padding-bottom:40px;}
#endorse-root .statusbar{display:flex;justify-content:space-between;align-items:center;padding:7px 22px;
    border-bottom:1px solid var(--line);font-size:10.5px;letter-spacing:.06em;color:var(--amber-dim);background:var(--panel);}
#endorse-root .statusbar__r{display:flex;align-items:center;gap:7px;}
#endorse-root .dot{width:6px;height:6px;border-radius:50%;background:var(--amber);box-shadow:0 0 7px var(--amber);animation:pulse 2.4s ease-in-out infinite;}
  @keyframes pulse{0%,100%{opacity:1;}50%{opacity:.25;}}#endorse-root .nav{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;padding:16px 22px;
    border-bottom:1px solid var(--line);position:sticky;top:0;z-index:30;
    background:rgba(10,10,12,0.86);backdrop-filter:blur(8px);}
#endorse-root .nav__logo{font-size:19px;font-weight:700;letter-spacing:-.01em;color:#f2f2f4;}
#endorse-root .nav__caret{color:var(--amber);animation:blink 1.2s steps(1) infinite;}
  @keyframes blink{0%,49%{opacity:1;}50%,100%{opacity:0;}}#endorse-root .nav__links{display:flex;gap:26px;justify-content:center;}
#endorse-root .nav__links a{font-size:11.5px;letter-spacing:.18em;color:var(--dim);text-decoration:none;cursor:pointer;transition:color .15s;white-space:nowrap;}
#endorse-root .nav__links a:hover{color:var(--ink);}
#endorse-root .nav__links a.is-active{color:var(--amber);}
#endorse-root .shell{display:grid;grid-template-columns:218px 1fr;max-width:1480px;margin:0 auto;}
#endorse-root .rail{position:sticky;top:65px;align-self:start;height:calc(100vh - 65px);border-right:1px solid var(--line);
    padding:30px 0 20px;display:flex;flex-direction:column;gap:30px;}
#endorse-root .rail__group{display:flex;flex-direction:column;}
#endorse-root .rail__head{font-size:10.5px;letter-spacing:.08em;color:var(--amber);padding:0 22px 10px;}
#endorse-root .rail__item{appearance:none;background:none;border:none;text-align:left;color:var(--dim);font-size:13px;
    padding:6px 22px;border-left:2px solid transparent;transition:all .14s;display:flex;justify-content:space-between;align-items:center;gap:8px;}
#endorse-root .rail__item--view{font-size:12.5px;line-height:1.3;}
#endorse-root .rail__item:hover:not(:disabled){color:var(--ink);background:var(--line2);}
#endorse-root .rail__item.is-active{color:var(--amber);border-left-color:var(--amber);background:var(--amber-soft);}
#endorse-root .rail__n{color:var(--dimmer);font-size:11px;}
#endorse-root .rail__item.is-active .rail__n{color:var(--amber-dim);}
#endorse-root .rail__foot{margin-top:auto;padding:14px 22px 0;border-top:1px solid var(--line2);font-size:10px;color:var(--dimmer);letter-spacing:.04em;}
#endorse-root .rail__foot>div:first-child{color:var(--dim);}
#endorse-root .rail__footdim{margin-top:3px;}
#endorse-root .main{padding:26px 28px 60px;min-width:0;}
#endorse-root .toolbar{display:flex;justify-content:space-between;align-items:center;gap:20px;flex-wrap:wrap;margin-bottom:24px;}
#endorse-root .chips{display:flex;gap:8px;flex-wrap:wrap;}
#endorse-root .chip{background:none;border:1px solid var(--line);color:var(--dim);font-size:12px;padding:6px 12px;border-radius:var(--r);
    transition:all .14s;display:flex;align-items:center;gap:7px;}
#endorse-root .chip:hover:not(:disabled){border-color:#33333a;color:var(--ink);}
#endorse-root .chip__n{color:var(--dimmer);font-size:11px;}
#endorse-root .chip--on{border-color:var(--amber);color:var(--amber);background:var(--amber-soft);}
#endorse-root .chip--on .chip__n{color:var(--amber-dim);}
#endorse-root .search{display:flex;align-items:center;gap:8px;border:1px solid var(--line);border-radius:var(--r);padding:7px 12px;min-width:280px;transition:border-color .15s;}
#endorse-root .search:focus-within{border-color:var(--amber-dim);}
#endorse-root .search__ico{color:var(--dim);font-size:14px;}
#endorse-root .search__in{flex:1;background:none;border:none;outline:none;color:var(--ink);font-family:inherit;font-size:13px;}
#endorse-root .search__in::placeholder{color:var(--dimmer);}
#endorse-root .search__clr{background:none;border:1px solid var(--line);color:var(--dim);font-size:10px;padding:2px 6px;border-radius:2px;}
#endorse-root .search__clr:hover{color:var(--amber);border-color:var(--amber-dim);}
#endorse-root .feat{border:1px solid var(--line);border-left:2px solid var(--amber);border-radius:var(--r);
    padding:24px 28px 22px;margin-bottom:34px;background:linear-gradient(180deg,#0c0c0f,#0a0a0c);}
#endorse-root .feat__label{font-size:11px;color:var(--amber);letter-spacing:.05em;margin-bottom:16px;}
#endorse-root .feat__row{display:flex;gap:14px;}
#endorse-root .feat__mark{font-size:64px;line-height:.7;color:var(--amber);opacity:.5;font-weight:700;}
#endorse-root .feat__body{flex:1;min-width:0;}
#endorse-root .feat__quote{font-size:25px;line-height:1.34;color:#f0f0f2;font-weight:500;margin:6px 0 22px;text-wrap:pretty;}
#endorse-root .feat__by{display:flex;align-items:center;gap:13px;}
#endorse-root .feat__name{font-size:13.5px;color:#e9e9ec;font-weight:500;}
#endorse-root .feat__meta{font-size:12px;color:var(--dim);}
#endorse-root .feat__sep{margin:0 7px;color:var(--dimmer);}
#endorse-root .feat__link{margin-left:auto;font-size:12px;color:var(--dim);text-decoration:none;cursor:pointer;white-space:nowrap;transition:color .15s;}
#endorse-root .feat__link:hover{color:var(--amber);}
#endorse-root .libhead{display:flex;justify-content:space-between;align-items:baseline;gap:16px;
    padding-bottom:14px;margin-bottom:20px;border-bottom:1px solid var(--line);flex-wrap:wrap;}
#endorse-root .libhead__l{font-size:13px;color:var(--ink);}
#endorse-root .libhead__sep{margin:0 9px;color:var(--dimmer);}
#endorse-root .libhead__filt{color:var(--amber);}
#endorse-root .libhead__view{color:var(--dim);}
#endorse-root .libhead__yt{font-size:12px;color:var(--dim);text-decoration:none;cursor:pointer;transition:color .15s;white-space:nowrap;}
#endorse-root .libhead__yt:hover{color:var(--amber);}
#endorse-root .masonry{column-count:3;column-gap:18px;}
  @media (max-width:1100px){#endorse-root .masonry{column-count:2;}
}
  @media (max-width:680px){#endorse-root .masonry{column-count:1;}
}#endorse-root .vgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:20px 18px;}#endorse-root .ecard{position:relative;break-inside:avoid;display:inline-block;width:100%;margin:0 0 18px;
    border:1px solid var(--line);border-left:2px solid #26211a;border-radius:var(--r);
    background:var(--panel);padding:16px 16px 14px;transition:border-color .16s,transform .16s,background .16s;}
#endorse-root .ecard:hover{border-color:var(--amber-dim);border-left-color:var(--amber);transform:translateY(-2px);background:#0e0e12;}
#endorse-root .ecard__top{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:13px;}
#endorse-root .ecard__plat{display:block;font-size:10px;color:var(--dimmer);letter-spacing:.04em;margin-bottom:6px;}
#endorse-root .ecard__who{min-width:0;}
#endorse-root .ecard__name{display:block;font-size:13.5px;color:#e9e9ec;font-weight:500;}
#endorse-root .ecard__handle{display:block;font-size:12px;color:var(--amber-dim);margin-top:1px;word-break:break-all;}
#endorse-root .ecard__quote{font-size:14px;line-height:1.5;color:var(--ink);margin:0 0 16px;text-wrap:pretty;}
#endorse-root .ecard__quote::before{content:"";}
#endorse-root .av{flex:0 0 40px;width:40px;height:40px;border:1px solid var(--line);border-radius:2px;
    display:flex;align-items:center;justify-content:center;overflow:hidden;}
#endorse-root .av span{font-size:13px;font-weight:700;color:var(--amber);letter-spacing:.02em;}
#endorse-root .ecard__foot{display:flex;align-items:center;gap:11px;}
#endorse-root .ecard__tag{font-size:10px;color:var(--amber);border:1px solid #2c2620;background:rgba(232,168,74,0.06);
    padding:2px 7px;border-radius:2px;letter-spacing:.03em;}
#endorse-root .ecard__date{font-size:11px;color:var(--dimmer);}
#endorse-root .ecard__link{margin-left:auto;font-size:11.5px;color:var(--dim);text-decoration:none;cursor:pointer;white-space:nowrap;transition:color .15s;}
#endorse-root .ecard__link:hover{color:var(--amber);}
#endorse-root .ecard--video{padding:0;overflow:hidden;border-left:1px solid var(--line);}
#endorse-root .ecard__media{display:block;position:relative;aspect-ratio:16/9;border-bottom:1px solid var(--line);overflow:hidden;background:#0f0f12;}
#endorse-root .ecard__vplay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .16s;z-index:3;}
#endorse-root .ecard--video:hover .ecard__vplay{opacity:1;}
#endorse-root .ecard__vplay span{width:46px;height:46px;border:1px solid var(--amber);border-radius:50%;display:flex;align-items:center;justify-content:center;color:var(--amber);font-size:15px;background:rgba(10,10,12,.55);padding-left:3px;}
#endorse-root .ecard__vcat{position:absolute;left:8px;bottom:8px;font-size:10.5px;color:var(--ink);background:rgba(10,10,12,.82);border:1px solid var(--line);padding:2px 6px;border-radius:2px;z-index:3;}
#endorse-root .ecard__vbody{padding:14px 16px 13px;}
#endorse-root .ecard__title{font-size:14px;line-height:1.34;font-weight:500;color:#e9e9ec;margin:0 0 7px;text-wrap:pretty;}
#endorse-root .ecard__src{font-size:12px;color:var(--dim);margin-bottom:13px;}
#endorse-root .ecard__dur{position:absolute;right:8px;bottom:8px;font-size:10.5px;color:#e7e7ea;background:rgba(0,0,0,.78);padding:2px 6px;border-radius:2px;}
#endorse-root .ecard__outlet{font-size:10.5px;color:var(--amber);letter-spacing:.04em;margin-bottom:11px;}
#endorse-root .ecard__title--art{font-size:16px;line-height:1.3;font-weight:700;margin-bottom:11px;}
#endorse-root .ecard__excerpt{font-size:13px;line-height:1.5;color:var(--dim);margin:0 0 16px;text-wrap:pretty;}
#endorse-root .ecard__podrow{display:flex;gap:13px;margin-bottom:14px;}
#endorse-root .ecard__podtxt{min-width:0;display:flex;flex-direction:column;}
#endorse-root .ecard__show{font-size:10.5px;color:var(--amber);letter-spacing:.04em;margin-bottom:6px;}
#endorse-root .ecard__title--pod{font-size:13.5px;margin-bottom:8px;}
#endorse-root .ecard__dur--inline{position:static;background:none;padding:0;color:var(--dimmer);font-size:11px;align-self:flex-start;}
#endorse-root .mthumb{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;}
#endorse-root .mthumb--sq{position:relative;flex:0 0 78px;width:78px;height:78px;border:1px solid var(--line);border-radius:2px;overflow:hidden;}
#endorse-root .mthumb__glyph{font-size:54px;font-weight:700;color:rgba(232,168,74,0.13);line-height:1;}
#endorse-root .mthumb--sq .mthumb__glyph{font-size:30px;}
#endorse-root .mthumb__play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .16s;}
#endorse-root .ecard--video:hover .mthumb__play{opacity:1;}
#endorse-root .mthumb__play span{width:44px;height:44px;border:1px solid var(--amber);border-radius:50%;display:flex;align-items:center;justify-content:center;
    color:var(--amber);font-size:14px;background:rgba(10,10,12,.55);padding-left:3px;}
#endorse-root .mthumb--sq .mthumb__play{display:none;}
#endorse-root .empty{padding:80px 20px;text-align:center;color:var(--dim);border:1px dashed var(--line);border-radius:var(--r);}
#endorse-root .empty__big{font-size:42px;color:var(--dimmer);margin-bottom:14px;}
#endorse-root .empty__q{color:var(--amber);}
#endorse-root .empty__reset{margin-top:18px;background:none;border:1px solid var(--amber-dim);color:var(--amber);padding:7px 14px;border-radius:var(--r);font-size:12px;}
#endorse-root .empty__reset:hover{background:var(--amber-soft);}
#endorse-root .endline{margin-top:42px;text-align:center;font-size:11px;color:var(--dimmer);letter-spacing:.05em;}
#endorse-root .mchips{display:none;gap:8px;padding:12px 18px;border-bottom:1px solid var(--line);overflow-x:auto;-webkit-overflow-scrolling:touch;}
#endorse-root .mchips .chip{flex:0 0 auto;}
  @media (max-width:880px){#endorse-root .shell{grid-template-columns:1fr;}
#endorse-root .rail{display:none;}
#endorse-root .mchips{display:flex;}
#endorse-root .nav__links{display:none;}
#endorse-root .nav{grid-template-columns:1fr auto;}
#endorse-root .feat__quote{font-size:20px;}
#endorse-root .feat__mark{font-size:48px;}
#endorse-root .toolbar{flex-direction:column;align-items:stretch;}
#endorse-root .search{min-width:0;}
}
#endorse-root .imgwrap{position:absolute;inset:0;}
#endorse-root .img--cover{width:100%;height:100%;object-fit:cover;display:block;}
#endorse-root .img--logo{width:100%;height:100%;object-fit:contain;padding:22%;background:#0d0d10;display:block;}
#endorse-root .av{position:relative;}
#endorse-root .av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:inherit;}
#endorse-root .av img.av--logo{object-fit:contain;padding:18%;}
#endorse-root .mthumb img{position:absolute;inset:0;z-index:1;}
#endorse-root .mthumb .mthumb__glyph,#endorse-root .mthumb .mthumb__play{z-index:2;}
`;

const { useState, useMemo, useEffect } = React;

const E_TYPE_TAG = { tweet:"tweet", video:"video", article:"article" };
const E_TYPES = [
  { key:"all", label:"all" }, { key:"article", label:"article" },
  { key:"video", label:"video" }, { key:"tweet", label:"tweet" }
];
const VISIBLE_TYPES = new Set(["article", "video", "tweet"]);
const E_VIEWS = [ { key:"current", label:"ecash endorsements" }, { key:"archive", label:"drivechain archive (2018–2024)" } ];

function seedOf(s){ return (s||"x").split("").reduce((a,c)=>a+c.charCodeAt(0),0); }
function rootDomain(link){ try{ var h=new URL(link).hostname.replace(/^www\./,""); var p=h.split("."); return p.length>2?p.slice(-2).join("."):h; }catch(e){ return ""; } }
function ytId(link){ if(!link) return null; var m=link.match(/(?:youtube\.com\/(?:live|watch|embed)\/|youtu\.be\/)([A-Za-z0-9_-]{11})/); if(!m) m=link.match(/[?&]v=([A-Za-z0-9_-]{11})/); return m?m[1]:null; }
function platformOf(item){
  var t=item.type;
  if(t==="nostr") return "nostr";
  if(t==="tweet"||t==="quote") return "X (formerly Twitter)";
  if(t==="video") return item.source || "YouTube";
  if(t==="article") return item.outlet || rootDomain(item.link) || "Article";
  if(t==="podcast") return item.show || item.source || "Podcast";
  return item.source || "X (formerly Twitter)";
}

/* image with multi-step fallback (real → logo → favicon → procedural tile) */
function FallbackImg({ sources, className, onAllFail }) {
  const [i, setI] = useState(0);
  if (!sources.length || i >= sources.length) { if (onAllFail) onAllFail(); return null; }
  const cur = sources[i];
  return <img className={className + (cur.logo ? " img--logo" : "")} src={cur.url} alt="" loading="lazy"
    onError={() => setI(i + 1)} />;
}

/* avatar: real profile pic → initials tile */
function Avatar({ item }) {
  const [failed, setFailed] = useState(false);
  const name = item.author || "?";
  const initials = name.split(/\s+/).slice(0,2).map(w=>w[0]).join("").toUpperCase();
  const seed = seedOf(name); const ang = 120 + (seed % 5) * 24;
  const tile = { backgroundColor:"#141418", backgroundImage:`repeating-linear-gradient(${ang}deg, rgba(232,168,74,0.10) 0 1px, transparent 1px 7px)` };
  const srcs = [];
  if (item.photo) srcs.push({ url:item.photo });
  var h = (item.handle||"").replace(/^@/,"");
  if (h && (item.type==="tweet"||item.type==="quote")) srcs.push({ url:"https://unavatar.io/twitter/"+h+"?fallback=false" });
  return (
    <div className="av" style={tile}>
      <span>{initials}</span>
      {!failed && srcs.length > 0 && <FallbackImg sources={srcs} className="" onAllFail={()=>setFailed(true)} />}
    </div>
  );
}

/* media thumbnail (video/podcast/article): real image → publisher logo → favicon → abstract */
function MediaImg({ item, square }) {
  const seed = seedOf(item.title || item.text || "m"); const ang = 90 + (seed % 5) * 20;
  const glyph = square ? "♪" : "▸";
  const tile = { backgroundColor:"#0f0f12", backgroundImage:[
    `repeating-linear-gradient(${ang}deg, rgba(232,168,74,0.05) 0 1px, transparent 1px ${14+(seed%4)*3}px)`,
    `radial-gradient(120% 130% at ${10+(seed%50)}% -10%, rgba(232,168,74,0.10), transparent 55%)`].join(", ") };
  const srcs = [];
  if (item.photo) srcs.push({ url:item.photo });
  if (item.type === "video") { var v = ytId(item.link); if (v) srcs.push({ url:"https://img.youtube.com/vi/"+v+"/hqdefault.jpg" }); }
  var dom = rootDomain(item.link);
  if (dom && item.type !== "video") { srcs.push({ url:"https://unavatar.io/"+dom+"?fallback=false", logo:true }); srcs.push({ url:"https://www.google.com/s2/favicons?domain="+dom+"&sz=128", logo:true }); }
  return (
    <div className={"mthumb" + (square ? " mthumb--sq" : "")} style={tile}>
      <div className="mthumb__glyph">{glyph}</div>
      {srcs.length > 0 && <FallbackImg sources={srcs} className="img--cover" />}
      <div className="mthumb__play"><span>▶</span></div>
    </div>
  );
}

function TextCard({ e }) {
  const handleLine = (e.type === "nostr" || e.npub) ? e.npub : e.handle;
  return (
    <article className="ecard ecard--text">
      <header className="ecard__top">
        <div className="ecard__id">
          <span className="ecard__plat">{e.platform}</span>
          <div className="ecard__who">
            <span className="ecard__name">{e.author}</span>
            {handleLine && <span className="ecard__handle">{handleLine}</span>}
          </div>
        </div>
        <Avatar item={e} />
      </header>
      {e.text && <p className="ecard__quote">{e.text}</p>}
      <footer className="ecard__foot">
        <span className="ecard__tag">{E_TYPE_TAG[e.type]}</span>
        {e.date && <span className="ecard__date">{e.date}</span>}
        {e.link && <a className="ecard__link" href={e.link} target="_blank" rel="noopener noreferrer">view source →</a>}
      </footer>
    </article>
  );
}

function VideoCard({ e }) {
  const [thumbOk, setThumbOk] = useState(true);
  const vid = ytId(e.link);
  const seed = seedOf(e.title || "v"); const ang = 90 + (seed % 5) * 20;
  const tileBg = { backgroundImage:[
    `repeating-linear-gradient(${ang}deg,rgba(232,168,74,0.05) 0 1px,transparent 1px ${14+(seed%4)*3}px)`,
    `radial-gradient(120% 130% at ${10+(seed%50)}% -10%,rgba(232,168,74,0.10),transparent 55%)`].join(",") };
  return (
    <article className="ecard ecard--video" style={{display:"flex",flexDirection:"column"}}>
      <a className="ecard__media" href={e.link} target="_blank" rel="noopener noreferrer" style={tileBg}>
        {(!vid || !thumbOk) && (
          <div style={{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",
            fontSize:62,fontWeight:700,color:"rgba(232,168,74,0.12)",lineHeight:1}}>▸</div>
        )}
        {vid && <img src={"https://img.youtube.com/vi/"+vid+"/hqdefault.jpg"} onError={()=>setThumbOk(false)}
          style={{position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover",display:"block",
            transition:"transform .38s cubic-bezier(.25,.46,.45,.94)"}} alt="" loading="lazy" />}
        <span className="ecard__vcat">{e.outlet || "video"}</span>
        {e.duration && <span className="ecard__dur">{e.duration}</span>}
        <div className="ecard__vplay"><span>▶</span></div>
      </a>
      <div className="ecard__vbody" style={{flex:1,display:"flex",flexDirection:"column"}}>
        <h3 className="ecard__title">{e.title}</h3>
        <div className="ecard__src" style={{marginBottom:"auto"}}>{e.author}</div>
        <footer className="ecard__foot" style={{marginTop:10}}>
          <span className="ecard__tag">video</span>
          {e.date && <span className="ecard__date">{e.date}</span>}
          {e.link && <a className="ecard__link" href={e.link} target="_blank" rel="noopener noreferrer">watch →</a>}
        </footer>
      </div>
    </article>
  );
}

/* small publisher logo for article cards: photo → domain logo → favicon → initial */
function PubLogo({ item }) {
  const [i, setI] = useState(0);
  const [dead, setDead] = useState(false);
  var dom = rootDomain(item.link);
  var srcs = [];
  if (item.photo) srcs.push(item.photo);
  if (dom) { srcs.push("https://unavatar.io/" + dom + "?fallback=false"); srcs.push("https://www.google.com/s2/favicons?domain=" + dom + "&sz=128"); }
  if (dead || !srcs.length) return <div className="ecard__artlogo ecard__artlogo--txt">{((item.outlet||"?")[0]||"?").toUpperCase()}</div>;
  return (
    <div className="ecard__artlogo">
      <img className="ecard__artimg" src={srcs[Math.min(i, srcs.length-1)]} alt="" loading="lazy"
        onError={() => (i + 1 < srcs.length ? setI(i + 1) : setDead(true))} />
    </div>
  );
}

function ArticleCard({ e }) {
  return (
    <article className="ecard ecard--article">
      <div className="ecard__arthead">
        <PubLogo item={e} />
        <div className="ecard__outlet">{e.outlet}</div>
      </div>
      <h3 className="ecard__title ecard__title--art">{e.title}</h3>
      {e.excerpt && <p className="ecard__excerpt">{e.excerpt}</p>}
      <footer className="ecard__foot">
        <span className="ecard__tag">{E_TYPE_TAG[e.type]}</span>
        {e.date && <span className="ecard__date">{e.date}</span>}
        {e.link && <a className="ecard__link" href={e.link} target="_blank" rel="noopener noreferrer">read →</a>}
      </footer>
    </article>
  );
}

function ECard({ e }) {
  if (e.type === "video") return <VideoCard e={e} />;
  if (e.type === "article") return <ArticleCard e={e} />;
  if (e.type === "tweet") return <TextCard e={e} />;
  return null;
}

function Featured({ e }) {
  if (!e) return null;
  return (
    <section className="feat">
      <div className="feat__label">§ featured endorsement</div>
      <div className="feat__row">
        <span className="feat__mark">“</span>
        <div className="feat__body">
          <p className="feat__quote">{e.text || e.title}</p>
          <div className="feat__by">
            <Avatar item={e} />
            <div>
              <div className="feat__name">{e.author}</div>
              <div className="feat__meta">{e.handle}{e.handle && <span className="feat__sep">·</span>}{e.platform}{e.date && <span className="feat__sep">·</span>}{e.date}</div>
            </div>
            {e.link && <a className="feat__link" href={e.link} target="_blank" rel="noopener noreferrer">view source →</a>}
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---- normalize the existing data items into the card shape ---- */
function normalize(item, view) {
  var type = (item.type || "tweet").toLowerCase();
  if (type === "tweet" || type === "x") type = "tweet";
  var text = item.snippet || item.text || "";
  var n = {
    id: item.id || (view + "-" + Math.random().toString(36).slice(2)),
    type: type, view: view,
    author: item.person || item.author || item.handle || "",
    handle: item.handle || "",
    npub: item.npub || (type === "nostr" ? (item.handle || item.person) : ""),
    text: text,
    title: item.title || text,
    source: item.source || "",
    outlet: item.outlet || item.source || rootDomain(item.link),
    excerpt: item.excerpt || "",
    show: item.show || item.source || "",
    duration: item.duration || "",
    date: item.date || "",
    link: item.link || "",
    photo: item.photo || ""
  };
  n.platform = platformOf(n);
  return n;
}

function EndorsementsTab({ accent }) {
  const [current, setCurrent] = useState(null);
  const [archive, setArchive] = useState(null);
  const [view, setView] = useState("current");
  const [cat, setCat] = useState("all");
  const [query, setQuery] = useState("");

  useEffect(() => {
    var statics = (typeof STATIC_ECASH_ENDORSEMENTS !== "undefined") ? STATIC_ECASH_ENDORSEMENTS : [];
    fetch(API_BASE + "/api/endorsements").then(r=>r.json())
      .then(j => setCurrent(statics.concat(j.data || []).map(it => normalize(it, "current"))))
      .catch(() => setCurrent(statics.map(it => normalize(it, "current"))));
    fetch("/drivechain-endorsements.json").then(r=>r.json())
      .then(j => setArchive(((j.data || j) || []).map(it => normalize(it, "archive"))))
      .catch(() => setArchive([]));
  }, []);

  const inView = (view === "current" ? current : archive) || [];
  const counts = useMemo(() => {
    var c = { all: 0 };
    inView.forEach(e => { if (!VISIBLE_TYPES.has(e.type)) return; c.all++; c[e.type] = (c[e.type] || 0) + 1; });
    return c;
  }, [inView]);
  const q = query.trim().toLowerCase();
  const TYPE_ORDER = { article: 0, video: 1, tweet: 2 };
  const filtered = inView.filter(e => {
    if (!VISIBLE_TYPES.has(e.type)) return false;
    var catOk = cat === "all" || e.type === cat;
    var hay = [e.author,e.handle,e.text,e.title,e.source,e.outlet,e.excerpt].filter(Boolean).join(" ").toLowerCase();
    return catOk && (!q || hay.includes(q));
  }).sort((a, b) => (TYPE_ORDER[a.type] ?? 9) - (TYPE_ORDER[b.type] ?? 9));
  useEffect(() => { if (cat !== "all" && !counts[cat]) setCat("all"); }, [view]); // eslint-disable-line
  const featured = (current || []).find(e => e.link && e.link.includes("thebitcoinmanual.com"))
    || (current || []).find(e => e.type === "article" && e.text)
    || (current || [])[0];
  const dcFeatured = (archive || []).find(e => e.type === "tweet" && e.author && e.author.toLowerCase().includes("adam back"))
    || (archive || [])[0];

  const loading = current === null;

  return (
    <div id="endorse-root">
      <style dangerouslySetInnerHTML={{ __html: E_CSS }} />

      <div className="mchips">
        {E_TYPES.map(c => (
          <button key={c.key} disabled={c.key!=="all" && !counts[c.key]} className={"chip"+(cat===c.key?" chip--on":"")} onClick={()=>setCat(c.key)} type="button">{c.label} · {counts[c.key]||0}</button>
        ))}
      </div>

      <div className="shell">
        <aside className="rail">
          <div className="rail__group">
            <div className="rail__head">§ views</div>
            {E_VIEWS.map(v => (
              <button key={v.key} className={"rail__item rail__item--view"+(view===v.key?" is-active":"")} onClick={()=>setView(v.key)} type="button">{v.label}</button>
            ))}
          </div>
          <div className="rail__group">
            <div className="rail__head">§ types</div>
            {E_TYPES.filter(t=>t.key!=="all").map(t => (
              <button key={t.key} disabled={!counts[t.key]} className={"rail__item"+(cat===t.key?" is-active":"")} onClick={()=>setCat(cat===t.key?"all":t.key)} type="button">{t.label}<span className="rail__n">{counts[t.key]||0}</span></button>
            ))}
          </div>
          <div className="rail__foot">
            <div>idx synced</div>
            <div className="rail__footdim">{inView.length} records · rev E8538</div>
          </div>
        </aside>

        <main className="main">
          <div className="toolbar">
            <div className="chips">
              {E_TYPES.map(c => (
                <button key={c.key} disabled={c.key!=="all" && !counts[c.key]} className={"chip"+(cat===c.key?" chip--on":"")} onClick={()=>setCat(c.key)} type="button">{c.label}<span className="chip__n">{counts[c.key]||0}</span></button>
              ))}
            </div>
            <div className="search">
              <span className="search__ico">⌕</span>
              <input className="search__in" placeholder="search endorsements…" value={query} onChange={e=>setQuery(e.target.value)} />
              {query && <button className="search__clr" onClick={()=>setQuery("")} type="button">esc</button>}
            </div>
          </div>

          {!query && cat === "all" && <Featured e={view === "current" ? featured : dcFeatured} />}

          <div className="libhead">
            <span className="libhead__l">§ endorsements<span className="libhead__sep">·</span>{filtered.length}
              {cat !== "all" && <span className="libhead__filt"> / {cat}</span>}
              <span className="libhead__view"> · {E_VIEWS.find(v=>v.key===view).label}</span>
            </span>
            <a className="libhead__yt" href={"https://x.com/intent/post?text="+encodeURIComponent("Bitcoin is forking August 21.\n\nDrivechains are real. Sidechains are real. The wait is over.\n\nI'm with #eCash. ecash.com")} target="_blank" rel="noopener noreferrer">submit an endorsement →</a>
          </div>

          {loading ? (
            <div className="empty"><div className="empty__big">⋯</div><div>loading endorsements…</div></div>
          ) : filtered.length > 0 ? (
            <div className={cat === "video" ? "vgrid" : "masonry"}>{filtered.map(e => <ECard key={e.id} e={e} />)}</div>
          ) : (
            <div className="empty">
              <div className="empty__big">∅</div>
              <div>no records match <span className="empty__q">"{query}"</span></div>
              <button className="empty__reset" onClick={()=>{setQuery("");setCat("all");}} type="button">reset filters</button>
            </div>
          )}

          <footer className="endline">— end of index · {filtered.length} of {inView.length} records · {E_VIEWS.find(v=>v.key===view).label} —</footer>
        </main>
      </div>
    </div>
  );
}

window.EndorsementsTab = EndorsementsTab;
