${moodInfo(m)[1
const readState=(k,d)=>{try{return JSON.parse(localStorage.getItem('cmc-'+k))??d}catch{return d}};
const asIds=v=>new Set(Array.isArray(v)?v.filter(x=>typeof x==='string'):[]);
let saved=asIds(readState('saved',[])),seen=asIds(readState('seen',[])),created=[],library=[],page='discover',mood='all',query='',collection='',limit=18,order=[];
-let viewerQueue=[],viewerIndex=0,autoTimer=null,toastTimer=null,editing=null,uploadedTemplate=null,dbPromise=null;
+function currentSeason(){const d=new Date();return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}`}
+function seasonLabel(s){const[y,m]=s.split('-').map(Number);return new Date(y,m-1,1).toLocaleDateString([],{month:'long',year:'numeric'})}
+function daysLeftInSeason(){const d=new Date();return new Date(d.getFullYear(),d.getMonth()+1,0).getDate()-d.getDate()}
+function readBattles(){const d=readState('battles',{wins:{},plays:{},battles:0,streak:0,best:0,lastWinner:null,lastChamp:null,season:currentSeason(),hall:[]});return {wins:{...d.wins||{}},plays:{...d.plays||{}},battles:d.battles|0,streak:d.streak|0,best:d.best|0,lastWinner:typeof d.lastWinner==='string'?d.lastWinner:null,lastChamp:typeof d.lastChamp==='string'?d.lastChamp:null,season:typeof d.season==='string'?d.season:currentSeason(),hall:Array.isArray(d.hall)?d.hall:[]}}
+function rollSeason(){const cur=currentSeason();if(battleStats.season===cur)return;const prev=champId();
+ if(prev&&battleStats.battles>0){const pm=allMemes().find(m=>m.id===prev);battleStats.hall=[{season:battleStats.season,champ:prev,wins:battleWins(prev)},...battleStats.hall].slice(0,12);setTimeout(()=>toast(prev?`📜 New season! “${pm?.title||'Cat'}” enters the hall of fame.`:'📜 New season! Fresh board.'),600)}
+ battleStats={wins:{},plays:{},battles:0,streak:0,best:0,lastWinner:null,lastChamp:null,season:cur,hall:battleStats.hall};setLocal('battles',battleStats)}
+let battleStats=readBattles(),battleLeft=null,battleRight=null;
+const battleWins=id=>battleStats.wins[id]|0;
+const battlePlays=id=>battleStats.plays[id]|0;
+const battleLosses=id=>Math.max(0,battlePlays(id)-battleWins(id));
+const battleRate=id=>{const p=battlePlays(id);return p?Math.round(battleWins(id)/p*100):null};
+function pickBattle(){const pool=allMemes();if(pool.length<2)return;let a=battleLeft,b=Math.random()<0.5?battleLeft:battleRight;
+ for(let i=0;i<20;i++){const x=pool[Math.floor(Math.random()*pool.length)],y=pool[Math.floor(Math.random()*pool.length)];if(x.id!==y.id&&(x.id!==a?.id||y.id!==b?.id)){a=x;b=y;break}}
+ if(!a||!b||a.id===b.id){const s=shuffle(pool);a=s[0];b=s[1]}
+ battleLeft=a;battleRight=b;
+}
+const battleTaunts=['The crowd goes mild.','A historic moment for cats.','Democracy works. No recount needed.','The chimp nods approvingly.','Questionable behavior, rewarded.','A landslide. Somehow still about naps.','The other cat demands a recount. Denied.','Instant classic. Frame it.','The judges (you) have spoken.','That one had main-character energy.','Science can’t explain this one.','Straight into the hall of fame.'];
+function voteBattle(winnerId){if(!battleLeft||!battleRight)return;
+ battleStats.plays[battleLeft.id]=battlePlays(battleLeft.id)+1;battleStats.plays[battleRight.id]=battlePlays(battleRight.id)+1;
+ battleStats.wins[winnerId]=battleWins(winnerId)+1;battleStats.battles++;battleStats.streak++;battleStats.best=Math.max(battleStats.best,battleStats.streak);battleStats.lastWinner=winnerId;
+ setLocal('battles',battleStats);checkAwards();
+ const w=allMemes().find(m=>m.id===winnerId);
+ const ms=streakMilestones[battleStats.streak];
+ const champ=champId(),dethroned=champ&&battleStats.lastChamp&&champ!==battleStats.lastChamp&&battleStats.battles>3;
+ battleStats.lastChamp=champ;setLocal('battles',battleStats);
+ if(dethroned){const c=allMemes().find(m=>m.id===champ);sfx('fanfare');confetti();toast(`👑 DETHRONED! “${c?.title||'Cat'}” seizes the crown!`)}
+ else{sfx(ms?'fanfare':'pop');if(ms)confetti();toast(ms||`“${w?.title||'Cat'}” takes the crown. ${battleTaunts[Math.floor(Math.random()*battleTaunts.length)]}`)}
+ pickBattle();renderArena();
+}
+function skipBattle(){if(battleStats.streak>0){toast('Streak reset. The cats judge your indecision.');sfx('chaos')}battleStats.streak=0;setLocal('battles',battleStats);pickBattle();renderArena()}
+function resetArena(){if(!battleStats.battles){toast('No battles yet — crown a cat first.');return}if(!window.confirm('Reset your Arena record? Wins, streaks and the leaderboard go back to zero.'))return;battleStats={wins:{},plays:{},battles:0,streak:0,best:0,lastWinner:null,lastChamp:null,season:battleStats.season,hall:battleStats.hall};setLocal('battles',battleStats);pickBattle();renderArena();toast('Fresh slate. The cats forgive you.')}
+function rankBoard(){return [...allMemes()].map(m=>({m,w:battleWins(m.id),p:battlePlays(m.id)})).filter(x=>x.p>0).sort((a,b)=>b.w-a.w||(b.w/b.p)-(a.w/a.p)||a.m.title.localeCompare(b.m.title))}
+const champId=()=>rankBoard()[0]?.m.id||null;
+const streakMilestones={5:'Five crowns in a row. The cats whisper your name.',10:'TEN STREAK. Certified cat judge.',25:'25 straight crowns. Unhinged. Legendary.',50:'50?! Touch grass. Then crown more cats.'};
+let labTop=null,labBottom=null,labCat=null,labEngine=null;
+const randMeme=()=>allMemes()[Math.floor(Math.random()*allMemes().length)];
+function rollLab(part){if(!allMemes().length)return;labEngine=null;const withTop=()=>{let m=randMeme();for(let i=0;i<10&&!m.top;i++)m=randMeme();return m};const withBottom=()=>{let m=randMeme();for(let i=0;i<10&&!m.bottom;i++)m=randMeme();return m};
+ if(!labTop||part==='top'||part==='all')labTop=withTop();if(!labBottom||part==='bottom'||part==='all')labBottom=withBottom();if(!labCat||part==='cat'||part==='all')labCat=randMeme();renderLab()}
+function labMeme(){return {title:`${labTop?.title||'Top'} × ${labBottom?.title||'Bottom'}`,top:labEngine?.top||labTop?.top||'',bottom:labEngine?.bottom||labBottom?.bottom||'',image:labCat?.image||'',template:labCat?`Frankenmeme (${labCat.template})`:'Frankenmeme',mood:labCat?.mood||'chaos'}}
+function curseRating(){const n=(labTop?.top.length||0)+(labBottom?.bottom.length||0)*3+(labCat?.id.length||0)*7;return (n%5)+1}
+function renderLab(){if(!labTop||!labBottom||!labCat){rollLab();return}const m=labMeme();$('#lab-preview').innerHTML=visual(m,'eager');
+ const stars='★'.repeat(curseRating())+'☆'.repeat(5-curseRating());
+ const names=['blessed','mildly cursed','cursed','deeply cursed','FORBIDDEN'];
+ $('#lab-provenance').innerHTML=`Above the cat: ${labEngine?'🤖 The Engine':esc(labTop.title)} Below the cat: ${labEngine?'🤖 The Engine':esc(labBottom.title)} The cat: ${esc(labCat.title)} ${stars} ${names[curseRating()-1]} `}
+function inspireLab(){if(!labTop||!labBottom||!labCat)rollLab();labEngine=inspireCaption();renderLab();sfx('blip');toast('The machine dreamed this. No refunds.')}
+async function saveLab(){const m={...labMeme(),id:'own-'+crypto.randomUUID(),kind:'creation',added:new Date().toISOString(),tags:['frankenmeme','my creation']};try{await dbPut(m);created.push(m);labSaves++;setLocal('stats',{labSaves});sfx('fanfare');checkAwards();navigate('studio');toast('Abomination preserved for future generations.')}catch{toast('Could not save in this browser. Use PNG to keep your creation.')}}
+function readSets(){const d=readState('sets',[]);return Array.isArray(d)?d.filter(s=>s&&typeof s.id==='string'&&typeof s.name==='string'&&Array.isArray(s.ids)):[]}
+let userSets=readSets();
+const saveSets=()=>setLocal('sets',userSets);
+const setById=id=>userSets.find(s=>s.id===id);
+function createSet(name){const s={id:'set-'+crypto.randomUUID(),name:String(name||'').trim().slice(0,40)||'Untitled set',ids:[]};userSets.push(s);saveSets();return s}
+function deleteSet(id){userSets=userSets.filter(s=>s.id!==id);if(collection===id)collection='';saveSets()}
+function toggleInSet(setId,memeId){const s=setById(setId);if(!s)return false;const has=s.ids.includes(memeId);s.ids=has?s.ids.filter(x=>x!==memeId):[...s.ids,memeId];saveSets();return !has}
+function renderSets(){const p=$('#sets-panel');if(!p)return;
+ p.innerHTML=`Your sets Hoard with purpose. Sets live in this browser — or in a file, if you export them.
⤓ Export sets ⤒ Import sets
${userSets.length?userSets.map(s=>`${esc(s.name)} ${s.ids.length} ${s.ids.length===1?'cat':'cats'} Delete
`).join(''):'No sets yet. Name one above — future you says thanks.
'}`}
+function exportSets(){if(!userSets.length){toast('No sets to export. Hoard first.');return}const blob=new Blob([JSON.stringify({app:'CATMEMECHIMP-sets',version:1,exported:new Date().toISOString(),sets:userSets.map(s=>({name:s.name,ids:s.ids}))},null,2)],{type:'application/json'});const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download='catmeme-sets.json';document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),5000);toast(`Exported ${userSets.length} ${userSets.length===1?'set':'sets'}. Send it to a friend.`)}
+async function importSets(file){if(!file)return;try{const data=JSON.parse(await file.text());const arr=Array.isArray(data)?data:data.sets;if(!Array.isArray(arr))throw new Error('bad file');const known=new Set(allMemes().map(m=>m.id));let added=0;
+ for(const s of arr){if(!s||typeof s.name!=='string'||!Array.isArray(s.ids))continue;const ids=[...new Set(s.ids.filter(id=>typeof id==='string'&&known.has(id)))];let name=s.name.trim().slice(0,40)||'Untitled set';if(userSets.some(u=>u.name===name))name=`${name} (imported)`;userSets.push({id:'set-'+crypto.randomUUID(),name,ids});added++}
+ if(!added)throw new Error('bad file');saveSets();if(page==='collections')render();toast(`Imported ${added} ${added===1?'set':'sets'}. Sharing is caring.`)}catch{toast('That file isn’t a CatMemeChimp set export.')}}
+function renderCollect(){const m=currentMeme();const box=$('#collect-list');if(!box||!m)return;
+ box.innerHTML=userSets.length?userSets.map(s=>`${s.ids.includes(m.id)?'✓':'+'} ${esc(s.name)} ${s.ids.length} `).join(''):'No sets yet — create one below.
'}
+const PACK_MS=24*60*60*1000;
+function readPack(){const d=readState('pack',{lastOpened:0,streak:0,best:0,haul:{},lastPull:null,scrap:0});return {lastOpened:d.lastOpened|0,streak:d.streak|0,best:d.best|0,haul:{...d.haul||{}},lastPull:d.lastPull||null,scrap:d.scrap|0}}
+let pack=readPack(),packStage='sealed',packReveal=null,keepTab='sets';
+const savePack=()=>setLocal('pack',pack);
+const haulSize=()=>Object.values(pack.haul).reduce((a,b)=>a+(b|0),0);
+const canOpenPack=()=>Date.now()-(pack.lastOpened||0)>=PACK_MS;
+function packCountdown(){const left=PACK_MS-(Date.now()-(pack.lastOpened||0));if(left<=0)return 'Ready!';const h=Math.floor(left/3600000),m=Math.floor(left%3600000/60000),s=Math.floor(left%60000/1000);return `${h}h ${String(m).padStart(2,'0')}m ${String(s).padStart(2,'0')}s`}
+function rollRarity(){const r=Math.random();return r<0.08?'legendary':r<0.3?'rare':'common'}
+function openPack(){if(!canOpenPack()){toast(`Next pack in ${packCountdown()}. Patience, goblin.`);return}doPull(false)}
+function forgePack(){if((pack.scrap|0)<3){toast('Need 3 scrap. Melt spare copies below.');return}pack.scrap-=3;doPull(true)}
+function meltDupe(id){if((pack.haul[id]|0)<2){toast('Only spare copies can be melted. The original stays.');return}pack.haul[id]--;pack.scrap=(pack.scrap|0)+1;savePack();renderPacks();sfx('blip');toast('+1 scrap. The furnace hungers.')}
+function doPull(bonus){
+ const pool=allMemes();if(!pool.length)return;
+ const unseen=pool.filter(m=>!pack.haul[m.id]&&!seen.has(m.id));
+ const lucky=Math.random()<0.7&&unseen.length?unseen[Math.floor(Math.random()*unseen.length)]:pool[Math.floor(Math.random()*pool.length)];
+ const rarity=rollRarity(),wasOwned=(pack.haul[lucky.id]|0)>0;
+ pack.haul[lucky.id]=(pack.haul[lucky.id]|0)+1;
+ if(!bonus){const gap=Date.now()-(pack.lastOpened||0);pack.streak=pack.lastOpened&&gap<2*PACK_MS?pack.streak+1:1;pack.best=Math.max(pack.best,pack.streak);pack.lastOpened=Date.now()}
+ pack.lastPull={id:lucky.id,rarity,shiny:wasOwned,at:Date.now(),bonus:!!bonus};
+ savePack();packStage='opening';packReveal=pack.lastPull;renderPacks();sfx('chaos');
+ setTimeout(()=>{if(page!=='packs')return;packStage='revealed';renderPacks();
+ if(rarity==='legendary'){sfx('fanfare');confetti()}else sfx('save');
+ checkAwards();if(!bonus&&pack.streak>=2)buddySay('streak');
+ toast(wasOwned?`✨ SHINY ${lucky.title}! A duplicate, but make it fashion.`:`${bonus?'Forged pull! ':rarity==='legendary'?'🌟 LEGENDARY PULL! ':rarity==='rare'?'💎 Rare pull! ':''}“${lucky.title}” joins your haul.`)}
+ ,1100)}
+function renderPacks(){const panel=$('#pack-panel');if(!panel)return;
+ const ready=canOpenPack();
+ panel.innerHTML=packStage==='opening'
+ ?``
+ :ready
+ ?`📦 Today's drop is here! Tap to rip it open `
+ :`Next drop in ${packCountdown()}
one pack per human per 24h. rules are rules. ${pack.lastPull?packMini(allMemes().find(m=>m.id===pack.lastPull.id),pack.lastPull):''}
`;
+ if(packStage==='revealed'&&packReveal){const m=allMemes().find(m=>m.id===packReveal.id);if(m){panel.insertAdjacentHTML('beforeend',packMini(m,packReveal,true));panel.insertAdjacentHTML('beforeend',`⏫ Share this pull
`)}}
+ $('#pack-stats').innerHTML=`0 🔥 day streak
0 🏆 best streak
0 cats hauled
`;countUp($('#pack-stats'));
+ renderForge();
+ const bc=$('#daily-count');if(bc)bc.textContent=ready?'🎁':haulSize()}
+function renderKeep(){const tabs=$('#keep-tabs');if(!tabs)return;const loved=allMemes().filter(m=>saved.has(m.id)).length;
+ tabs.innerHTML=[['sets','📁 Sets'],['haul',`📦 Haul · ${haulSize()}`],['loved',`♥ Loved · ${loved}`],['trophies',`🏆 Trophies · ${awards.length}/${ACH.length}`]].map(([id,label])=>`${label} `).join('');
+ const show={sets:['collection-grid','sets-panel'],haul:['keep-haul'],loved:['keep-loved'],trophies:['trophies']}[keepTab]||[];
+ for(const id of ['collection-grid','sets-panel','keep-haul','keep-loved','trophies'])document.getElementById(id).hidden=!show.includes(id);
+ if(keepTab==='sets'){$('#collection-grid').innerHTML=collections.map(c=>`${library.filter(c.filter).length} CATS · CURATED COLLECTION ${c.title} ${c.description}
`).join('');renderSets()}
+ if(keepTab==='haul'){const list=allMemes().filter(m=>pack.haul[m.id]).slice(0,60);$('#keep-haul').innerHTML=list.length?list.map((m,i)=>packCard(m,i)).join(''):'No pulls yet. Rip a pack on the Daily Drop page.
'}
+ if(keepTab==='loved'){const list=allMemes().filter(m=>saved.has(m.id)).slice(0,60);$('#keep-loved').innerHTML=list.length?list.map(card).join(''):'No loved cats yet — tap ♥ on any meme.
'}
+ if(keepTab==='trophies')renderTrophies()}
+function renderForge(){const f=$('#forge');if(!f)return;const dupes=allMemes().filter(m=>(pack.haul[m.id]|0)>1);
+ f.innerHTML=`The Forge Melt spare copies into scrap. 3 scrap = 1 bonus pack , no waiting.
🪙 ${pack.scrap|0} scrap Forge bonus pack · 3 🪙
${dupes.length?dupes.map(m=>`${esc(m.title)} ×${pack.haul[m.id]} owned Melt +1 `).join(''):'No spares yet. Duplicates you pull become meltable here.
'}`}
+function packMini(m,pull,fresh){if(!m)return '';return `${pull.shiny?'✨ SHINY ':''}${esc(m.title)} ${pull.rarity}${pull.shiny?' · dupe upgrade':''} `}
+async function sharePull(id){const m=allMemes().find(m=>m.id===id);if(!m)return;
+ await document.fonts.ready;let img;try{img=await loadImage(m.image)}catch{toast('That cat refuses to travel.');return}
+ const width=1000,pad=48,fontSize=44,lineHeight=56,headH=150,footH=120;
+ const canvas=document.createElement('canvas');canvas.width=width;
+ let ctx=canvas.getContext('2d');const sht=memeTheme(m);ctx.font=`800 ${fontSize}px ${sht.font}`;
+ const top=m.top?wrapText(ctx,m.top.toUpperCase(),width-pad*2):[],bottom=m.bottom?wrapText(ctx,m.bottom.toUpperCase(),width-pad*2):[];
+ const imageH=Math.round(Math.min(img.height*(width/img.width),1100)),th=top.length?top.length*lineHeight+40:0,bh=bottom.length?bottom.length*lineHeight+40:0;
+ canvas.height=headH+th+imageH+bh+footH;ctx=canvas.getContext('2d');ctx.textBaseline='top';
+ ctx.fillStyle='#4A412A';ctx.fillRect(0,0,width,headH);
+ ctx.fillStyle='#F7F5EE';ctx.font='800 34px "DM Sans", sans-serif';ctx.fillText('CATMEMECHIMP · DAILY DROP',pad,40);
+ const pull=pack.lastPull&&pack.lastPull.id===id?pack.lastPull:null,tag=pull?`${pull.rarity.toUpperCase()}${pull.shiny?' · SHINY':''}`:'PACK PULL';
+ ctx.fillStyle='#B9C5F3';ctx.font='700 26px "DM Sans", sans-serif';ctx.fillText(tag,pad,86);
+ ctx.fillStyle=sht.bg;ctx.fillRect(0,headH,width,th+imageH+bh);
+ ctx.fillStyle=sht.fg;ctx.font=`800 ${fontSize}px ${sht.font}`;
+ top.forEach((s,i)=>ctx.fillText(s,pad,headH+20+i*lineHeight));
+ const ratio=Math.min(width/img.width,imageH/img.height),iw=img.width*ratio,ih=img.height*ratio;
+ ctx.drawImage(img,(width-iw)/2,headH+th+(imageH-ih)/2,iw,ih);
+ bottom.forEach((s,i)=>ctx.fillText(s,pad,headH+th+imageH+20+i*lineHeight));
+ ctx.fillStyle='#4A412A';ctx.fillRect(0,headH+th+imageH+bh,width,footH);
+ ctx.fillStyle='#F7F5EE';ctx.font='700 28px "DM Sans", sans-serif';ctx.fillText(m.title.slice(0,48),pad,headH+th+imageH+bh+28);
+ ctx.fillStyle='#B9C5F3';ctx.font='500 22px "DM Sans", sans-serif';ctx.fillText('ripped from a daily pack · good cats, questionable behavior',pad,headH+th+imageH+bh+66);
+ const blob=await new Promise(res=>canvas.toBlob(res,'image/png'));if(!blob){toast('The card couldn’t be made.');return}
+ const file=new File([blob],'catmeme-drop.png',{type:'image/png'});
+ try{if(navigator.canShare&&navigator.canShare({files:[file]})){await navigator.share({files:[file],title:'My daily cat'});toast('Shared! Go forth and convert friends.');return}}catch(e){if(e&&e.name==='AbortError')return}
+ const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=`catmeme-drop-${m.id}.png`;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),5000);toast('Card downloaded. Spread the gospel.')}
+function packCard(m,i){const n=pack.haul[m.id]|0;return `${n>1?'✨ SHINY ':''}${visual(m,i<6?'eager':'lazy')} `}
+setInterval(()=>{const el=$('#pack-timer');if(el&&page==='packs'&&!canOpenPack())el.textContent=packCountdown();if(el&&page==='packs'&&canOpenPack()&&packStage!=='opening')renderPacks()},1000);
+const ACH=[
+ {id:'first-crown',icon:'👑',name:'First blood (but cats)',desc:'Crown your first Arena champion'},
+ {id:'streak-5',icon:'🔥',name:'Warming up',desc:'Reach a 5-crown streak'},
+ {id:'streak-10',icon:'🔥',name:'Certified judge',desc:'Reach a 10-crown streak'},
+ {id:'streak-25',icon:'🏆',name:'Unhinged',desc:'Reach a 25-crown streak'},
+ {id:'brawler-100',icon:'⚔️',name:'Centurion',desc:'Judge 100 battles'},
+ {id:'collector',icon:'💾',name:'Hoarder',desc:'Save 10 cats'},
+ {id:'creator',icon:'✨',name:'Meme parent',desc:'Create your first meme'},
+ {id:'prolific',icon:'🏭',name:'Meme factory',desc:'Create 5 memes'},
+ {id:'frankenstein',icon:'🧪',name:'It’s alive',desc:'Save a Frankenmeme'},
+ {id:'explorer',icon:'🧭',name:'Completionist-ish',desc:'View 75 different cats'},
+ {id:'first-pull',icon:'📦',name:'Unboxer',desc:'Open your first daily pack'},
+ {id:'week-streak',icon:'📅',name:'Regular',desc:'Open packs 7 days in a row'},
+ {id:'dex-half',icon:'📖',name:'Halfway there',desc:'Discover half the archive'},
+ {id:'dex-full',icon:'🌟',name:'Living dex',desc:'Discover all 290 cats'}];
+let awards=readState('awards',[]),labSaves=readState('stats',{labSaves:0}).labSaves|0;
+function checkAwards(){const earned=[];
+ const need=(id,ok)=>{if(ok&&!awards.includes(id)&&!earned.includes(id))earned.push(id)};
+ need('first-crown',battleStats.battles>0);need('streak-5',battleStats.best>=5);need('streak-10',battleStats.best>=10);need('streak-25',battleStats.best>=25);need('brawler-100',battleStats.battles>=100);
+ need('collector',allMemes().filter(m=>saved.has(m.id)).length>=10);need('creator',created.length>=1);need('prolific',created.length>=5);
+ need('frankenstein',labSaves>=1);need('explorer',seen.size>=75);
+ need('first-pull',haulSize()>=1);need('week-streak',pack.best>=7);
+ need('dex-half',discoveredCount()>=Math.ceil(library.length/2));need('dex-full',library.length>0&&discoveredCount()>=library.length);
+ if(!earned.length)return;awards=[...awards,...earned];setLocal('awards',awards);sfx('fanfare');
+ toast(earned.length===1?`🏆 Achievement: ${ACH.find(a=>a.id===earned[0]).name}!`:`🏆 ${earned.length} achievements: ${earned.map(id=>ACH.find(a=>a.id===id).name).join(' · ')}!`);
+ renderTrophies()}
+function renderTrophies(){const t=$('#trophies');if(!t)return;
+ t.innerHTML=`Trophies ${awards.length} of ${ACH.length} unlocked · stored in this browser
${ACH.map(a=>{const has=awards.includes(a.id);return `
${has?a.icon:'🔒'} ${esc(a.name)} ${esc(a.desc)}
`}).join('')}
`}
+function battleCard(m,side){const wins=battleWins(m.id),plays=battlePlays(m.id),rate=battleRate(m.id),label=moodInfo(m.mood)[2];
+ return `${side==='left'?'← CONTENDER':'CONTENDER →'} ${visual(m,'eager')}${moodTag(m.mood)}${esc(m.title)} ${plays?`${wins}W · ${plays-wins}L · ${rate}%`:'No battles yet — be its first fan'} ${rate!==null?` `:''}👑 Crown this cat `}
+function renderArena(){if(!battleLeft||!battleRight)pickBattle();if(!battleLeft||!battleRight)return;
+ $('#arena-stats').innerHTML=`0 battles judged
0 🔥 current streak
0 🏆 best streak
${seasonLabel(battleStats.season).split(' ')[0]} ${daysLeftInSeason()}d left
`;countUp($('#arena-stats'));
+ const bc=$('#battle-count');if(bc)bc.textContent=battleStats.battles;
+ const last=battleStats.lastWinner?allMemes().find(m=>m.id===battleStats.lastWinner):null;
+ const lc=$('#last-crowned');if(last){lc.hidden=false;lc.innerHTML=`Last crowned: ${esc(last.title)} · ${battleWins(last.id)}W · ${battleRate(last.id)}% `}else{lc.hidden=true;lc.innerHTML=''}
+ $('#battle-grid').innerHTML=`${battleCard(battleLeft,'left')}VS
${battleCard(battleRight,'right')}`;
+ const ranked=rankBoard().slice(0,5),top3=ranked.slice(0,3),rest=ranked.slice(3);
+ const medalFor=i=>['🥇','🥈','🥉'][i];
+ const podiumOrder=[1,0,2].filter(i=>top3[i]);
+ const podiumCard=x=>{const i=top3.indexOf(x),r=Math.round(x.w/x.p*100);return `${medalFor(i)} ${esc(x.m.title)} ${x.w}W · ${x.p-x.w}L · ${r}% `};
+ const hall=battleStats.hall.filter(h=>allMemes().some(m=>m.id===h.champ)).slice(0,6);
+ $('#leaderboard').innerHTML=`Local legends ${battleStats.battles?`From ${battleStats.battles} judged ${battleStats.battles===1?'battle':'battles'} · stored only in this browser`:'No champions yet — your votes build this board. Stored only in this browser.'}
${ranked.length?`${top3.length?`${podiumOrder.map(i=>podiumCard(top3[i])).join('')}
`:''}${rest.map((x,i)=>{const r=Math.round(x.w/x.p*100);return `${i+4}. ${esc(x.m.title)} ${x.w}W · ${x.p-x.w}L · ${r}% View `}).join('')}`:'Crown your first cat above and it will show up here.
'}${hall.length?`📜 Hall of fame ${hall.map(h=>{const m=allMemes().find(m=>m.id===h.champ);return `👑 ${esc(m.title)} ${esc(seasonLabel(h.season))} · ${h.wins}W View `}).join('')}`:''}`;
+}
+let viewerQueue=[],viewerIndex=0,autoTimer=null,tvMode=false,toastTimer=null,editing=null,inspired=false,uploadedTemplate=null,dbPromise=null;
const collections=[{id:'office',title:'The office survival kit',description:'For meetings that could have been naps.',image:'1bh7.jpg',filter:m=>m.tags.some(t=>['work','email','meeting','career','deadline'].includes(t))},{id:'battery',title:'The low battery club',description:'A safe space for professional nappers.',image:'11wis1.jpg',filter:m=>m.mood==='sleepy'},{id:'serotonin',title:'A little serotonin',description:'Small cats. Unreasonably big feelings.',image:'amuvy.jpg',filter:m=>m.mood==='wholesome'}];
function toast(message){clearTimeout(toastTimer);$('#toast').textContent=message;$('#toast').classList.add('visible');toastTimer=setTimeout(()=>$('#toast').classList.remove('visible'),3300);}
function setLocal(k,value){try{localStorage.setItem('cmc-'+k,JSON.stringify(value));return true}catch{toast('Browser storage is unavailable. Download to keep a copy.');return false}}
-function database(){if(!dbPromise)dbPromise=new Promise((res,rej)=>{const req=indexedDB.open('CATMEMECHIMP',1);req.onupgradeneeded=()=>req.result.createObjectStore('creations',{keyPath:'id'});req.onsuccess=()=>res(req.result);req.onerror=()=>rej(req.error)});return dbPromise}
+function database(){if(!dbPromise)dbPromise=new Promise((res,rej)=>{const req=indexedDB.open('CATMEMECHIMP',2);req.onupgradeneeded=()=>{const db=req.result;if(!db.objectStoreNames.contains('creations'))db.createObjectStore('creations',{keyPath:'id'});if(!db.objectStoreNames.contains('clips'))db.createObjectStore('clips',{keyPath:'id'})};req.onsuccess=()=>res(req.result);req.onerror=()=>rej(req.error)});return dbPromise}
+async function dbReadClips(){try{const db=await database();return await new Promise((res,rej)=>{const r=db.transaction('clips').objectStore('clips').getAll();r.onsuccess=()=>res(r.result||[]);r.onerror=()=>rej(r.error)})}catch{return[]}}
+async function dbPutClip(value){const db=await database();return new Promise((res,rej)=>{const tx=db.transaction('clips','readwrite');tx.objectStore('clips').put(value);tx.oncomplete=res;tx.onerror=()=>rej(tx.error);tx.onabort=()=>rej(tx.error)})}
async function dbRead(){const db=await database();return new Promise((res,rej)=>{const r=db.transaction('creations').objectStore('creations').getAll();r.onsuccess=()=>res(r.result);r.onerror=()=>rej(r.error)})}
async function dbPut(value){const db=await database();return new Promise((res,rej)=>{const tx=db.transaction('creations','readwrite');tx.objectStore('creations').put(value);tx.oncomplete=res;tx.onerror=()=>rej(tx.error);tx.onabort=()=>rej(tx.error)})}
const allMemes=()=>[...library,...created];
+const hotScore=id=>battlePlays(id)+2*battleWins(id);
+function hotIds(){return new Set([...allMemes()].filter(m=>battlePlays(m.id)>=3).sort((a,b)=>hotScore(b.id)-hotScore(a.id)).slice(0,12).map(m=>m.id))}
+let templateFilter=null,hotCache=new Set();
+function showTemplate(image){navigate('discover');templateFilter=image;limit=18;render();const m=library.find(m=>m.image===image);toast(m?`Every “${m.template}” in the house.`:'Template view.')}
function filterMemes(){let list=page==='studio'?[...created].reverse():page==='saved'?allMemes().filter(m=>saved.has(m.id)):[...library];
- if(collection){const c=collections.find(c=>c.id===collection);if(c)list=list.filter(c.filter)}
+ if(templateFilter)list=list.filter(m=>m.image===templateFilter);
+ if(collection){const c=collections.find(c=>c.id===collection);if(c)list=list.filter(c.filter);else{const u=setById(collection);if(u){const keep=new Set(u.ids);list=allMemes().filter(m=>keep.has(m.id))}}}
if(mood!=='all')list=list.filter(m=>m.mood===mood);
if(query){const terms=query.toLowerCase().trim().split(/\s+/);list=list.filter(m=>{const haystack=[m.title,m.top,m.bottom,m.template,m.mood,moodInfo(m.mood)[2],...m.tags].join(' ').toLowerCase();return terms.every(t=>haystack.includes(t))})}
- const sort=$('#sort').value;
- if(sort==='az')list.sort((a,b)=>a.title.localeCompare(b.title));else if(sort==='unseen')list.sort((a,b)=>Number(seen.has(a.id))-Number(seen.has(b.id)));else if(order.length){const map=new Map(order.map((x,i)=>[x,i]));list.sort((a,b)=>(map.get(a.id)??999)-(map.get(b.id)??999))}
+ const sort=$('#sort').value;
+ if(sort==='az')list.sort((a,b)=>a.title.localeCompare(b.title));else if(sort==='hot')list.sort((a,b)=>hotScore(b.id)-hotScore(a.id)||b.title.localeCompare(a.title));else if(sort==='unseen')list.sort((a,b)=>Number(seen.has(a.id))-Number(seen.has(b.id)));else if(order.length){const map=new Map(order.map((x,i)=>[x,i]));list.sort((a,b)=>(map.get(a.id)??999)-(map.get(b.id)??999))}
return list;
}
-function visual(m,loading='lazy'){return `${m.top?`
${esc(m.top)}
`:''}
${m.bottom?`
${esc(m.bottom)}
`:''}
`}
-function card(m,i){return `${visual(m,i<6?'eager':'lazy')} `}
-function updateCounts(){$('#all-count').textContent=library.length;$('#saved-count').textContent=allMemes().filter(m=>saved.has(m.id)).length;$('#created-count').textContent=created.length}
+function memeTheme(m){const fonts={dm:'"DM Sans", sans-serif',fraunces:'Fraunces, Georgia, serif',mono:'ui-monospace, SFMono-Regular, Menlo, monospace'};const styles={light:{bg:'#fff',fg:'#24231e'},dark:{bg:'#171410',fg:'#F7F5EE'},banana:{bg:'#FFD75E',fg:'#3A2F10'}};return {font:fonts[m?.font]||fonts.dm,...(styles[m?.style]||styles.light)}}
+function visual(m,loading='lazy'){const st=m.sticker&&m.sticker.emoji?`${esc(m.sticker.emoji)} `:'';return `${m.top?`
${esc(m.top)}
`:''}
${m.bottom?`
${esc(m.bottom)}
`:''}${st}
`}
+function card(m,i){const hot=hotCache.has(m.id);return `${visual(m,i<6?'eager':'lazy')} `}
+function updateCounts(){$('#all-count').textContent=library.length;const dc=$('#daily-count');if(dc)dc.textContent=canOpenPack()?'🎁':haulSize();const dx=$('#dex-count');if(dx)dx.textContent=discoveredCount();$('#created-count').textContent=created.length;const bc=$('#battle-count');if(bc)bc.textContent=battleStats.battles}
function render(){updateCounts();$$('[data-page]').forEach(b=>{b.classList.toggle('active',b.dataset.page===page);if(b.dataset.page===page)b.setAttribute('aria-current','page');else b.removeAttribute('aria-current')});
- $('#hero').hidden=page!=='discover'||!!query;
- $('#breadcrumb').textContent=({discover:'THE CAT ARCHIVE',saved:'SAVED FOR A RAINY DAY',collections:'HANDPICKED CAT COLLECTIONS',studio:'THE NONSENSE LAB'})[page];
- $('#section-eyebrow').textContent=({discover:'THE GOOD STUFF',saved:'YOUR PERSONAL SEROTONIN STASH',collections:'A CAT FOR EVERY OCCASION',studio:'MADE BY YOU. APPROVED BY YOU.'})[page];
- $('#library-title').textContent=query?`Cats matching “${query}”`:({discover:'Find your feline frequency.',saved:'The keepers.',collections:'Good things come in collections.',studio:'Your little masterpieces.'})[page];
- const desc=({discover:'',saved:'Every cat you’ve hearted, right here when you need one.',collections:'Small, handpicked corners of the archive. Pick one and settle in.',studio:'Your captions. Your cats. Your very questionable sense of humor.'})[page];$('#page-description').textContent=desc;$('#page-description').hidden=!desc;
- $('#collection-grid').hidden=page!=='collections';
- if(page==='collections')$('#collection-grid').innerHTML=collections.map(c=>`${library.filter(c.filter).length} CATS · CURATED COLLECTION ${c.title} ${c.description}
`).join('');
+ $('#hero').hidden=page!=='discover'||!!query;$('#ticker').hidden=$('#hero').hidden;
+ $('#breadcrumb').textContent=({discover:'THE CAT ARCHIVE',arena:'THE BATTLE ARENA',lab:'THE FRANKENMEME LAB',packs:'THE DAILY DROP',album:'THE COMPLETE DEX',saved:'SAVED FOR A RAINY DAY',collections:'HANDPICKED CAT COLLECTIONS',studio:'THE NONSENSE LAB'})[page]||'THE CAT ARCHIVE';
+ $('#section-eyebrow').textContent=({discover:'THE GOOD STUFF',arena:'TWO CATS ENTER. ONE CAT LEAVES.',lab:'MAD SCIENCE, BUT CATS.',packs:'A LITTLE GAMBLE.',album:'GOTTA PET ’EM ALL.',saved:'YOUR PERSONAL SEROTONIN STASH',collections:'A CAT FOR EVERY OCCASION',studio:'MADE BY YOU. APPROVED BY YOU.'})[page];
+ const tmplName=templateFilter?(library.find(m=>m.image===templateFilter)?.template||'Template'):'';
+ $('#library-title').textContent=page==='arena'?'Crown the funniest cat.':page==='lab'?'Build an abomination.':page==='packs'?"Today's drop.":page==='album'?'Every cat. Yours to find.':templateFilter?`More ${tmplName}.`:query?`Cats matching “${query}”`:({discover:'Find your feline frequency.',saved:'The keepers.',collections:'Good things come in collections.',studio:'Your little masterpieces.'})[page];
+ const desc=({discover:'',arena:'290 memes. Zero mercy. Pick a winner, build a streak — skips reset the streak.',lab:'Three cats walk in. One meme walks out. Reroll parts until it feels illegal.',packs:'One sealed pack per human per 24 hours. Rip it, keep the cat, chase the streak.',album:'Open, pull, or battle the whole archive. Silhouettes are personal failures.',saved:'Every cat you’ve hearted, right here when you need one.',collections:'Small, handpicked corners of the archive. Pick one and settle in.',studio:'Your captions. Your cats. Your very questionable sense of humor.'})[page];$('#page-description').textContent=desc;$('#page-description').hidden=!desc;
+ const inArena=page==='arena',inLab=page==='lab',inPacks=page==='packs',inAlbum=page==='album',hideGrid=inArena||inLab||inPacks||inAlbum;
+ $('#arena').hidden=!inArena;$('#lab').hidden=!inLab;$('#packs').hidden=!inPacks;$('#album').hidden=!inAlbum;$('#shuffle').hidden=hideGrid;
+ $('#filter-row').hidden=hideGrid;$('#collection-grid').hidden=page!=='collections';
+ document.querySelector('.results-bar').hidden=hideGrid;$('#meme-grid').hidden=hideGrid;$('#empty').hidden=hideGrid?true:!!0;$('#load-more').hidden=hideGrid;$('#end-note').hidden=hideGrid;
+ if(inArena){if(!allMemes().length){$('#arena-stats').innerHTML='';$('#battle-grid').innerHTML='';$('#leaderboard').innerHTML='The cats haven’t arrived yet.
'}else renderArena();return}
+ if(inLab){renderLab();return}
+ if(inPacks){renderPacks();return}
+ if(inAlbum){renderDex();return}
+ if(page==='collections'){renderKeep()}
+ $('#stash').hidden=page!=='collections';
$('#mood-filters').innerHTML=moods.map(([id,symbol,label])=>`${symbol} ${label} `).join('');
- const list=filterMemes(),display=list.slice(0,limit);$('#meme-grid').innerHTML=display.map(card).join('');
- $('#result-count').innerHTML=list.length?`${list.length} ${list.length===1?'cat':'cats'} ${page==='saved'?'worth keeping':mood==='all'&&!query&&!collection?'and not a single thought':collection?'in this collection':'in this corner'} · showing ${display.length} `:'0 cats found';
+ const list=filterMemes(),display=list.slice(0,limit);hotCache=hotIds();$('#meme-grid').innerHTML=display.map(card).join('');
+ if(setById(collection)){$$('#meme-grid .meme-card').forEach(c=>{const id=c.querySelector('[data-open]')?.dataset.open;if(!id)return;const b=document.createElement('button');b.className='remove-from-set';b.dataset.removeFromSet=id;b.textContent='× Remove from set';b.setAttribute('aria-label','Remove from set');c.append(b)})}
+ $('#result-count').innerHTML=list.length?`${list.length} ${list.length===1?'cat':'cats'} ${page==='saved'?'worth keeping':mood==='all'&&!query&&!collection?'and not a single thought':collection?'in this collection':'in this corner'} · showing ${display.length} ${templateFilter?' · all templates ':''}`:'0 cats found';
$('#empty').hidden=!!list.length;$('#load-more').hidden=list.length<=limit;$('#end-note').hidden=!list.length||list.length>limit;
const unfiltered=!query&&mood==='all'&&!collection;
$('#empty-title').textContent=page==='saved'&&unfiltered?'Your favorites are waiting to happen.':page==='studio'&&unfiltered?'Every masterpiece starts with a cat.':'No cats in this corner.';
- $('#empty-copy').textContent=page==='saved'&&unfiltered?'Tap the heart on any meme. We’ll keep it warm for you.':page==='studio'&&unfiltered?'Pick a cat, add a caption, make someone’s day.':'Try a different search or let all the cats back in.';
+ $('#empty-copy').textContent=page==='saved'&&unfiltered?'Tap the heart on any meme. We’ll keep it warm for you.':page==='studio'&&unfiltered?'Pick a cat, add a caption, make someone’s day.':`Try a different search — ${library.length} cats are hiding somewhere.`;
$('#empty-action').textContent=page==='studio'&&unfiltered?'Make your first meme':page==='saved'&&unfiltered?'Find some favorites':'Show all cats';
}
-function navigate(p){page=p;mood='all';query='';collection='';limit=18;$('#search').value='';render();window.scrollTo({top:0,behavior:'smooth'})}
-function toggleSave(id){const next=new Set(saved);next.has(id)?next.delete(id):next.add(id);if(!setLocal('saved',[...next]))return;saved=next;
- if(page==='saved')render();else{$$(`[data-save="${id}"]`).forEach(b=>{b.classList.toggle('saved',saved.has(id));b.setAttribute('aria-pressed',saved.has(id));const m=allMemes().find(m=>m.id===id);b.setAttribute('aria-label',`${saved.has(id)?'Unsave':'Save'} ${m?.title||'meme'}`)});updateCounts()}
+const BUDDY_QUIPS={
+ hello:['Psst. I saved you the good cats. You’re welcome.','New here? Rule one: trust the banana cat. Rule two: see rule one.','I curate. The cats approve. Mostly.'],
+ idle:['Still here? Elite behavior.','You’ve been staring for a while. Hydrate, then resume cats.','I counted your blinks. Rookie numbers.','The cats noticed you stayed. They’re pretending not to care.'],
+ click:['Boop received. Filing it under “morale”.','Yes, I’m real. No, I won’t share my snacks.','Poke me again and I’ll tell the cats.','I’m the reason the vibes are like this.'],
+ night:['Past midnight, huh? The forbidden browsing hours. Respect.','Shh. The day-people must never know about this.'],
+ packReady:['Your daily pack is RIPE. Go rip it.','Psst — fresh pack upstairs. Don’t let it age.'],
+ streak:['Streak looking tasty. Don’t fumble it.','One more day keeps the streak dream alive.']};
+let buddyMuted=readState('buddy',{muted:false}).muted===true,buddyLast=0,buddyHideT=null,buddyPressT=null;
+function buddySay(kind){if(buddyMuted&&kind!=='force')return;const now=Date.now();if(kind!=='force'&&now-buddyLast<25000)return;buddyLast=now;
+ const lines=BUDDY_QUIPS[kind]||BUDDY_QUIPS.idle;const b=$('#buddy-bubble');if(!b)return;
+ b.textContent=lines[Math.floor(Math.random()*lines.length)];b.hidden=false;
+ clearTimeout(buddyHideT);buddyHideT=setTimeout(()=>{b.hidden=true},4500)}
+function buddyInit(){const w=$('#chimp-buddy');if(!w)return;w.hidden=false;w.classList.toggle('muted',buddyMuted);
+ setTimeout(()=>{if(page==='discover'&&!location.hash.slice(1)){buddySay('hello');sfx('meow')}},2500);
+ $('#buddy-btn').onclick=()=>{sfx('meow');buddySay('force')};
+ $('#buddy-btn').addEventListener('pointerdown',()=>{clearTimeout(buddyPressT);buddyPressT=setTimeout(()=>{buddyMuted=!buddyMuted;setLocal('buddy',{muted:buddyMuted});w.classList.toggle('muted',buddyMuted);$('#buddy-bubble').hidden=true;toast(buddyMuted?'Biscuit muted. She’ll pretend it doesn’t hurt.':'Biscuit is back. She missed you terribly.')},650)});
+ $('#buddy-btn').addEventListener('pointerup',()=>clearTimeout(buddyPressT));
+ $('#buddy-btn').addEventListener('pointerleave',()=>clearTimeout(buddyPressT));
+ let idleT=null;const poke=()=>{clearTimeout(idleT);idleT=setTimeout(()=>buddySay('idle'),90000);const h=new Date().getHours();if((h>=23||h<5)&&Math.random()<0.3)buddySay('night')};
+ for(const ev of ['pointerdown','keydown'])document.addEventListener(ev,poke,{passive:true,capture:true});poke();buddy3D()}
+function buddy3D(){const btn=$('#buddy-btn');if(!btn||!matchMedia('(pointer:fine)').matches||matchMedia('(prefers-reduced-motion: reduce)').matches)return;let raf=null;addEventListener('pointermove',e=>{if(raf)return;raf=requestAnimationFrame(()=>{raf=null;const r=btn.getBoundingClientRect(),cx=r.left+r.width/2,cy=r.top+r.height/2,dx=Math.max(-1,Math.min(1,(e.clientX-cx)/220)),dy=Math.max(-1,Math.min(1,(e.clientY-cy)/220));btn.style.transform=`rotateY(${(dx*22).toFixed(1)}deg) rotateX(${(-dy*22).toFixed(1)}deg)`;btn.style.setProperty('--gx',`${50+dx*40}%`);btn.style.setProperty('--gy',`${50+dy*40}%`)})},{passive:true})}
+function countUp(root){if(!root)return;if(matchMedia('(prefers-reduced-motion: reduce)').matches){root.querySelectorAll('[data-count]').forEach(s=>{s.textContent=s.dataset.count});return}root.querySelectorAll('[data-count]').forEach(s=>{const target=+s.dataset.count||0,t0=performance.now();const step=t=>{const p=Math.min(1,(t-t0)/600);s.textContent=Math.round(target*(1-Math.pow(1-p,3)));if(p<1)requestAnimationFrame(step)};requestAnimationFrame(step)})}
+function fillTicker(){const el=$('#ticker-inner');if(!el||!library.length)return;const picks=Array.from({length:12},()=>library[Math.floor(Math.random()*library.length)].title);const half=picks.map(t=>`BREAKING: ${esc(t)}`).join(' ✳ ');el.innerHTML=`${half} ✳ ${half} ✳ `}
+function heroParallax(){const hero=document.querySelector('.hero'),art=document.querySelector('.hero-art');if(!hero||!art||!matchMedia('(pointer:fine)').matches||matchMedia('(prefers-reduced-motion: reduce)').matches)return;let raf=null;hero.addEventListener('pointermove',e=>{const r=e.currentTarget.getBoundingClientRect(),dx=(e.clientX-r.left)/r.width-0.5,dy=(e.clientY-r.top)/r.height-0.5;if(raf)return;raf=requestAnimationFrame(()=>{raf=null;art.style.transform=`translate(${dx*14}px,${dy*10}px)`})});hero.addEventListener('pointerleave',()=>{art.style.transform=''})}
+function discoveredCount(){return library.filter(m=>seen.has(m.id)||pack.haul[m.id]).length}
+function renderDex(){const total=library.length,done=discoveredCount(),pct=total?Math.round(done/total*100):0;
+ $('#album').innerHTML=`${done} / ${total} ${pct}% discovered
`+moods.slice(1).map(([id,sym,label])=>{const ms=library.map((m,i)=>({m,idx:i})).filter(x=>x.m.mood===id),got=ms.filter(x=>seen.has(x.m.id)||pack.haul[x.m.id]).length;
+ return `${sym} ${label} · ${got}/${ms.length} ${ms.map(x=>{const known=seen.has(x.m.id)||pack.haul[x.m.id];return known?`
#${String(x.idx+1).padStart(3,'0')} ${esc(x.m.title)} `:`
#${String(x.idx+1).padStart(3,'0')} ? ${esc(label)} mystery
`}).join('')}
`}).join('');
+ const bc=$('#dex-count');if(bc)bc.textContent=done}
+function navigate(p){page=p;mood='all';query='';collection='';templateFilter=null;limit=18;$('#search').value='';if(p==='arena'&&(!battleLeft||!battleRight))pickBattle();if(p==='lab'&&(!labTop||!labBottom||!labCat))rollLab();if(p==='packs'&&canOpenPack())setTimeout(()=>buddySay('packReady'),800);render();window.scrollTo({top:0,behavior:'smooth'})}
+function toggleSave(id){const added=!saved.has(id);const next=new Set(saved);next.has(id)?next.delete(id):next.add(id);if(!setLocal('saved',[...next]))return;saved=next;if(added)sfx('save');checkAwards();
+ if(page==='packs')render();else{$$(`[data-save="${id}"]`).forEach(b=>{b.classList.toggle('saved',saved.has(id));b.setAttribute('aria-pressed',saved.has(id));const m=allMemes().find(m=>m.id===id);b.setAttribute('aria-label',`${saved.has(id)?'Unsave':'Save'} ${m?.title||'meme'}`)});updateCounts()}
if($('#viewer').open)updateViewerSave();toast(saved.has(id)?'A good cat, safely tucked away.':'Released back into the wild.');
}
function shuffle(list){const out=[...list];for(let i=out.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[out[i],out[j]]=[out[j],out[i]]}return out}
-function shuffledGrid(){order=shuffle(allMemes()).map(m=>m.id);$('#sort').value='curated';limit=18;render();toast('Same good cats. A little new chaos.')}
+function shuffledGrid(){order=shuffle(allMemes()).map(m=>m.id);$('#sort').value='curated';limit=18;sfx('chaos');render();toast('Same good cats. A little new chaos.')}
function currentMeme(){return viewerQueue[viewerIndex]}
function updateViewerSave(){const on=saved.has(currentMeme()?.id);$('#viewer-save').classList.toggle('saved',on);$('#viewer-save').setAttribute('aria-pressed',on);$('#viewer-save').innerHTML=icon(on?'check':'heart')+`${on?'Saved':'Save'} `}
-function showViewer(){const m=currentMeme();if(!m)return;$('#viewer-image-wrap').innerHTML=visual(m,'eager');$('#viewer-title').textContent=m.title;$('#viewer-mood').outerHTML=moodTag(m.mood).replace('class="mood-label"','id="viewer-mood" class="mood-label"');$('#viewer-template').textContent=m.kind==='creation'?'Your original creation':`${m.template} · A CATMEMECHIMP remix`;
- $('#viewer-progress').textContent=`${String(viewerIndex+1).padStart(2,'0')} / ${String(viewerQueue.length).padStart(2,'0')}`;
+function showViewer(){const m=currentMeme();if(!m)return;tvClip=false;clearTimeout(clipT);$('#viewer-image-wrap').innerHTML=visual(m,'eager');$('#viewer-title').textContent=m.title;$('#viewer-mood').outerHTML=moodTag(m.mood).replace('class="mood-label"','id="viewer-mood" class="mood-label"');$('#viewer-template').textContent=m.kind==='creation'?'Your original creation':`${m.template} · ${m.status||'remix'} · est. ${m.origin||'unknown'}`;$('#viewer-template').title=m.lore||'';const vm=$('#viewer-more');if(vm){vm.hidden=m.kind==='creation';if(m.kind!=='creation')vm.textContent=`More ${m.template}`}
+ $('#viewer-progress').textContent=`${String(viewerIndex+1).padStart(2,'0')} / ${String(viewerQueue.length).padStart(2,'0')}`;
+ $('#viewer').classList.toggle('tv',tvMode);const ov=$('#tv-overlay');ov.hidden=!tvMode;
+ if(tvMode)updateTVOverlay(m)
$('#viewer-source').hidden=!m.source;if(m.source)$('#viewer-source').href=m.source;updateViewerSave();
- seen.add(m.id);try{localStorage.setItem('cmc-seen',JSON.stringify([...seen]))}catch{}
+ seen.add(m.id);try{localStorage.setItem('cmc-seen',JSON.stringify([...seen]))}catch{}checkAwards();
history.replaceState(null,'',m.kind==='creation'?location.pathname:location.pathname+'#meme='+encodeURIComponent(m.id));
if(autoTimer)restartAutoplay();
}
function openViewer(id,list=filterMemes()){stopAutoplay();viewerQueue=list.length?[...list]:[...library];viewerIndex=Math.max(0,viewerQueue.findIndex(m=>m.id===id));if(!viewerQueue.length)return;showViewer();$('#viewer').showModal()}
-function browse(delta){if(!viewerQueue.length)return;viewerIndex=(viewerIndex+delta+viewerQueue.length)%viewerQueue.length;showViewer()}
+function browse(delta){if(!viewerQueue.length)return;viewerIndex=(viewerIndex+delta+viewerQueue.length)%viewerQueue.length;if(tvMode){tvVJ++;if(tvVJ>=4){tvVJ=0;vjSlot()}}showViewer()}
function randomCat(){const list=filterMemes();if(!list.length){toast('No cats here yet. Discover a few first.');return}const unseen=list.filter(m=>!seen.has(m.id));const pool=unseen.length?unseen:list;const m=pool[Math.floor(Math.random()*pool.length)];openViewer(m.id,shuffle(list))}
function closeViewer(){$('#viewer').close()}
+function enterTV(){if(!viewerQueue.length)return;tvReturn={queue:[...viewerQueue],index:viewerIndex};tvMode=true;const blk=tvBlock();tvChannel=blk.mood==='prime'?'all':blk.mood;tvPrime=blk.mood==='prime';tvBlockName=blk.name;
+ if(tvPrime){viewerQueue=[...allMemes()].sort((a,b)=>hotScore(b.id)-hotScore(a.id)||a.title.localeCompare(b.title))}else{const pool=tvChannel==='all'?[...allMemes()]:allMemes().filter(m=>m.mood===tvChannel);viewerQueue=pool.length?shuffle(pool):shuffle([...allMemes()])}
+ viewerIndex=0;tvPlays=0;tvVJ=0;const vj0=$('#tv-vj');if(vj0)vj0.textContent='';showViewer();restartAutoplay();zap();ident('📺 CAT TV',`${blk.name.toUpperCase()}${tvPrime?' · HOTTEST FIRST':''}`)}
+function exitTV(){tvMode=false;tvSleep=0;tvClip=false;clearTimeout(clipT);$('#viewer').classList.remove('tv');$('#tv-overlay').hidden=true;$('#screen').hidden=true;stopAutoplay();if(tvReturn){viewerQueue=tvReturn.queue;viewerIndex=Math.min(tvReturn.index,Math.max(0,viewerQueue.length-1));tvReturn=null;if($('#viewer').open)showViewer()}}
+const TV_SPEEDS=[3,6,10],TV_SLEEPS=[0,10,20,30];
+let tvChannel='all',tvSpeedIdx=1,tvSleepIdx=0,tvSleep=0,tvReturn=null,tvPlays=0,tvVJ=0,tvPrime=false,tvBlockName='',identT=null;
+function tvBlock(h=new Date().getHours()){const order=[[2,'snacks','Night Snacks'],[5,'wholesome','Morning Cuddles'],[10,'relatable','Midday Relatables'],[13,'chaos','Afternoon Chaos'],[17,'prime','Prime Time'],[22,'sleepy','After Dark']];let b=order[order.length-1];for(const x of order)if(h>=x[0])b=x;return {mood:b[1],name:b[2]}}
+const VJ_QUIPS=['That last one broke me. In a good way.','I would like the record to show: I laughed.','My whiskers are tingling. Good sign.','Note to self: steal that one for the group chat.','Ten out of ten. No notes. Next.','I pay zero rent and judge this highly.','Somewhere, a dog is furious about this broadcast.','This channel is my Roman empire.','Shh. The good part is coming. Probably.','I approved this message. I approve most messages.'];
+function vjSlot(){const vj=$('#tv-vj');if(!vj)return;vj.textContent='🐱 Biscuit: '+VJ_QUIPS[Math.floor(Math.random()*VJ_QUIPS.length)];sfx('meow')}
+const reducedMotion=()=>matchMedia('(prefers-reduced-motion: reduce)').matches;
+function ident(big,small){if(reducedMotion()){toast(`📺 ${big} — ${small}`);return}const s=$('#screen');if(!s)return;s.innerHTML=``;s.hidden=false;s.classList.remove('show');void s.offsetWidth;s.classList.add('show');clearTimeout(identT);identT=setTimeout(()=>{s.hidden=true},1600)}
+function signOff(){const s=$('#screen');if(!reducedMotion()&&s){s.innerHTML=`
PLEASE STAND BYNAPPING IN PROGRESS…
`;s.hidden=false;s.classList.remove('show');void s.offsetWidth;s.classList.add('show');setTimeout(()=>{closeViewer();toast('💤 TV off. Dream of potassium.')},2600)}else{closeViewer();toast('💤 TV off. Dream of potassium.')}}
+const ADS=[
+ {brand:'Potassium™',line:'For cats with places to be.',img:'/assets/memes/7s8bhl.jpg',cta:'ASK YOUR HUMAN'},
+ {brand:'THE BOX',line:'Now with 20% more box.',img:'/assets/memes/tau4.jpg',cta:'FITS (ALLEGEDLY)'},
+ {brand:'NapCo™',line:'Horizontal is a lifestyle.',img:'/assets/memes/lp2ba.jpg',cta:'DO NOT PERCEIVE'},
+ {brand:'Sunbeam Reserve',line:'Moves one inch. So do you.',img:'/assets/memes/11wis1.jpg',cta:'CHASE IT'},
+ {brand:'Crünch™',line:'Louder means hungrier.',img:'/assets/memes/4p3h78.jpg',cta:'SECOND BREAKFAST'},
+ {brand:'Grump™',line:'Judgment you can trust.',img:'/assets/memes/8p0a.jpg',cta:'NO REFUNDS'},
+ {brand:'Zoomies Energy',line:'3AM. No destination.',img:'/assets/memes/24uu.jpg',cta:'FEEL THE RUSH'},
+ {brand:'Smug™',line:'The absence of vegetables.',img:'/assets/memes/39binw.jpg',cta:'ORDER ANYWAY'}];
+function showAd(){const ad=ADS[Math.floor(Math.random()*ADS.length)];$('#viewer-image-wrap').innerHTML=``;$('#tv-name').textContent='📢 '+ad.brand;$('#tv-next').textContent='Your show resumes shortly · click to skip';sfx('save')}
+function tvTick(){if(tvSleep>0){tvSleep--;const m=currentMeme();if(m)updateTVOverlay(m);if(tvSleep<=0){signOff();return}}tvPlays++;const clips=[...BUNDLED_CLIPS,...userClips];if(clips.length&&tvPlays%8===0){showClip(clips[Math.floor(Math.random()*clips.length)])}else if(tvPlays%5===0){showAd()}else{browse(1)}}
+const BUNDLED_CLIPS=[
+ {src:'/assets/clips/calico.mp4',title:'Calico Cam',credit:'CC BY-SA · Wikimedia Commons'},
+ {src:'/assets/clips/tokyo.mp4',title:'Tokyo Kitten',credit:'CC BY · Wikimedia Commons'},
+ {src:'/assets/clips/gatos.mp4',title:'Gatos Playing',credit:'CC BY-SA · Wikimedia Commons'}];
+let userClips=[],tvClip=false,clipT=null;
+function showClip(clip){if(!clip)return;tvClip=true;stopAutoplay();$('#viewer-image-wrap').innerHTML=` `;$('#tv-name').textContent='🎥 '+clip.title;$('#tv-next').textContent='Back to the show shortly · click to skip';sfx('blip');clearTimeout(clipT);clipT=setTimeout(()=>{if(tvMode&&$('#viewer').open){tvClip=false;browse(1);restartAutoplay()}},5500)}
+function skipClip(){clearTimeout(clipT);tvClip=false;browse(1);restartAutoplay()}
+async function importClip(file){if(!file)return;if(!['video/mp4','video/webm'].includes(file.type)){toast('MP4 or WebM only, please.');return}if(file.size>15*1024*1024){toast('That’s a big clip. Under 15 MB, please.');return}if(userClips.length>=10){toast('Ten personal clips max. The schedule is full.');return}
+ try{const rec={id:'clip-'+crypto.randomUUID(),name:file.name,blob:file,added:new Date().toISOString()};await dbPutClip(rec);userClips.push({id:rec.id,title:file.name.replace(/\.[^.]+$/,''),src:URL.createObjectURL(file)});toast('Your clip joins the rotation. Stardom awaits.')}catch{toast('Could not store that clip in this browser.')}}
+function cycleVol(){volIdx=(volIdx+1)%VOLS.length;masterVol=VOLS[volIdx];setLocal('vol',masterVol);applySoundIcon();updateTVOverlay(currentMeme());sfx('blip')}
+function zap(){if(matchMedia('(prefers-reduced-motion: reduce)').matches)return;const z=$('#zap');if(!z)return;z.classList.remove('on');void z.offsetWidth;z.classList.add('on')}
+function setTVChannel(d){const i=(moods.findIndex(m=>m[0]===tvChannel)+d+moods.length)%moods.length;tvChannel=moods[i][0];tvPrime=false;const pool=tvChannel==='all'?[...allMemes()]:allMemes().filter(m=>m.mood===tvChannel);if(!pool.length)return;viewerQueue=shuffle(pool);viewerIndex=0;showViewer();zap();sfx('blip');ident(`CH${i+1} · ${moods[i][1]} ${moods[i][2]}`,tvChannel==='all'?`${library.length} CATS · NO MERCY`:`${pool.length} CATS · ALL ${moods[i][2].toUpperCase()}`)}
+function cycleTVSpeed(){tvSpeedIdx=(tvSpeedIdx+1)%TV_SPEEDS.length;if(autoTimer)restartAutoplay();updateTVOverlay(currentMeme());sfx('blip')}
+function cycleTVSleep(){tvSleepIdx=(tvSleepIdx+1)%TV_SLEEPS.length;tvSleep=TV_SLEEPS[tvSleepIdx];updateTVOverlay(currentMeme());toast(tvSleep?`💤 Sleep timer: TV off in ${tvSleep} cats.`:'💤 Sleep timer off. Eternal broadcast.')}
+function updateTVOverlay(m){if(!m)return;const i=Math.max(0,moods.findIndex(x=>x[0]===tvChannel));$('#tv-chan').textContent=tvPrime?`⭐ PRIME · Trending`:`CH${i+1} ${moods[i][1]} ${moods[i][2]}`;$('#tv-clock').textContent=new Date().toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'});$('#tv-count').textContent=`${viewerIndex+1} / ${viewerQueue.length}`;$('#tv-name').textContent=m.title;const nx=viewerQueue[(viewerIndex+1)%viewerQueue.length];$('#tv-next').textContent=nx&&viewerQueue.length>1?`Up next: ${nx.title}`:'';$('#tv-speed').textContent=TV_SPEEDS[tvSpeedIdx]+'s';$('#tv-sleep').textContent=tvSleep?`💤 ${tvSleep}`:'💤 Off';$('#tv-vol').textContent=`🔊 ${Math.round(masterVol*100)}%`}
function stopAutoplay(){clearInterval(autoTimer);autoTimer=null;$('#autoplay').innerHTML=icon('play')+'Autoplay ';$('#autoplay').setAttribute('aria-pressed','false');$('#autoplay-fill').classList.remove('running')}
-function restartAutoplay(){clearInterval(autoTimer);autoTimer=setInterval(()=>browse(1),6000);$('#autoplay').innerHTML=icon('pause')+'Pause ';$('#autoplay').setAttribute('aria-pressed','true');$('#autoplay-fill').classList.remove('running');void $('#autoplay-fill').offsetWidth;$('#autoplay-fill').classList.add('running')}
+function restartAutoplay(){clearInterval(autoTimer);const secs=tvMode?TV_SPEEDS[tvSpeedIdx]:6;autoTimer=setInterval(()=>{if(tvMode)tvTick();else browse(1)},secs*1000);$('#autoplay').innerHTML=icon('pause')+'Pause ';$('#autoplay').setAttribute('aria-pressed','true');const fill=$('#autoplay-fill');fill.style.animationDuration=secs+'s';fill.classList.remove('running');void fill.offsetWidth;fill.classList.add('running')}
function templates(){return [...new Map(library.map(m=>[m.image,m])).values()]}
-function editorMeme(){return {...editing,title:$('#meme-name').value.trim()||'My little masterpiece',top:$('#top-caption').value.trim(),bottom:$('#bottom-caption').value.trim(),mood:$('#editor-mood').value}}
+function editorMeme(){const sv=$('#editor-sticker').value;return {...editing,title:$('#meme-name').value.trim()||'My little masterpiece',top:$('#top-caption').value.trim(),bottom:$('#bottom-caption').value.trim(),mood:$('#editor-mood').value,font:$('#editor-font').value,style:$('#editor-style').value,sticker:sv==='none'?null:{emoji:sv,corner:$('#editor-corner').value}}}
function preview(){$('#editor-preview').innerHTML=visual(editorMeme(),'eager')}
-function openEditor(m){if($('#viewer').open)closeViewer();editing={...(m||library[0])};uploadedTemplate=editing.image.startsWith('data:')?{image:editing.image,template:editing.template,source:editing.source}:null;$('#template-select').innerHTML=templates().map(t=>`${esc(t.template)} `).join('');
+function openEditor(m){if($('#viewer').open)closeViewer();inspired=false;editing={...(m||library[0])};uploadedTemplate=editing.image.startsWith('data:')?{image:editing.image,template:editing.template,source:editing.source}:null;$('#template-select').innerHTML=templates().map(t=>`${esc(t.template)} `).join('');
if(editing.image.startsWith('data:')){$('#template-select').insertAdjacentHTML('afterbegin','Your uploaded cat ');$('#template-select').value='custom'}else $('#template-select').value=editing.image;
- $('#top-caption').value=m?.top||'';$('#bottom-caption').value=m?.bottom||'';$('#meme-name').value=m?m.title+' (my version)':'';$('#editor-mood').innerHTML=moods.slice(1).map(([id,s,label])=>`${s} ${label} `).join('');$('#editor-mood').value=m?.mood||'relatable';$('#upload').value='';preview();$('#editor').showModal()
+ $('#top-caption').value=m?.top||'';$('#bottom-caption').value=m?.bottom||'';$('#meme-name').value=m?m.title+' (my version)':'';$('#editor-mood').innerHTML=moods.slice(1).map(([id,s,label])=>`${s} ${label} `).join('');$('#editor-mood').value=m?.mood||'relatable';$('#editor-font').value=m?.font||'dm';$('#editor-style').value=m?.style||'light';$('#editor-sticker').value=m?.sticker?.emoji||'none';$('#editor-corner').value=m?.sticker?.corner||'tr';$('#upload').value='';preview();$('#editor').showModal()
}
const loadImage=src=>new Promise((resolve,reject)=>{const img=new Image();img.onload=()=>resolve(img);img.onerror=()=>reject(new Error('This image could not be loaded.'));img.src=src});
async function uploadCat(file){if(!file)return;if(!['image/jpeg','image/png','image/webp','image/gif'].includes(file.type)){toast('Please choose a JPG, PNG, WebP, or GIF.');return}if(file.size>12*1024*1024){toast('That’s a big cat. Choose an image under 12 MB.');return}
const url=URL.createObjectURL(file);try{const img=await loadImage(url);const scale=Math.min(1,1400/Math.max(img.width,img.height));const c=document.createElement('canvas');c.width=Math.round(img.width*scale);c.height=Math.round(img.height*scale);const ctx=c.getContext('2d');ctx.fillStyle='#fff';ctx.fillRect(0,0,c.width,c.height);ctx.drawImage(img,0,0,c.width,c.height);uploadedTemplate={image:c.toDataURL('image/jpeg',.9),template:file.name,source:null};editing={...editing,...uploadedTemplate};if(!$('#template-select option[value="custom"]'))$('#template-select').insertAdjacentHTML('afterbegin','Your uploaded cat ');$('#template-select').value='custom';preview();toast(file.type==='image/gif'?'Your cat is in. GIFs use a still frame.':'Your cat has entered the chat.')}catch(e){toast(e.message)}finally{URL.revokeObjectURL(url)}
}
function wrapText(ctx,text,width){const lines=[];for(const paragraph of text.split('\n')){let line='';for(const word of paragraph.split(/\s+/)){const next=line?line+' '+word:word;if(ctx.measureText(next).width<=width){line=next;continue}if(line)lines.push(line);line='';for(const char of word){if(ctx.measureText(line+char).width>width){lines.push(line);line=char}else line+=char}}lines.push(line)}return lines}
-async function exportPNG(m){await document.fonts.ready;const img=await loadImage(m.image);const canvas=document.createElement('canvas'),width=1000,fontSize=42,lineHeight=55,pad=42;canvas.width=width;let ctx=canvas.getContext('2d');ctx.font=`700 ${fontSize}px "DM Sans", sans-serif`;
+function chainFrom(lines){const starts=[],map=new Map(),known=new Set();
+ for(const raw of lines){const line=String(raw||'').trim();if(!line)continue;known.add(line.toLowerCase());const words=line.split(/\s+/).filter(Boolean);if(words.length<2)continue;starts.push(words[0]);
+ for(let i=0;i3)break}
+ let s=out.join(' ').replace(/\s+([,.!?…])/g,'$1');if(s.length<8||s.length>110)continue;const low=s.toLowerCase();
+ if(ch.known.has(low))continue;if(out.length<3)continue;
+ return s.charAt(0).toUpperCase()+s.slice(1)}
+ return null}
+function inspireCaption(){const pool=allMemes();const tops=pool.map(m=>m.top).filter(Boolean),bottoms=pool.map(m=>m.bottom).filter(Boolean);
+ const top=walkChain(chainFrom(tops))||tops[Math.floor(Math.random()*tops.length)]||'';
+ const bottom=walkChain(chainFrom(bottoms))||bottoms[Math.floor(Math.random()*bottoms.length)]||'';
+ return {top,bottom}}
+async function exportPNG(m){await document.fonts.ready;const img=await loadImage(m.image);const th_=memeTheme(m);const canvas=document.createElement('canvas'),width=1000,fontSize=42,lineHeight=55,pad=42;canvas.width=width;let ctx=canvas.getContext('2d');ctx.font=`700 ${fontSize}px ${th_.font}`;
const top=m.top?wrapText(ctx,m.top,width-pad*2):[],bottom=m.bottom?wrapText(ctx,m.bottom,width-pad*2):[];
- const imageH=Math.round(Math.min(img.height*(width/img.width),1200));const th=top.length?top.length*lineHeight+pad:0,bh=bottom.length?bottom.length*lineHeight+pad:0;canvas.height=th+imageH+bh;ctx=canvas.getContext('2d');ctx.fillStyle='#fff';ctx.fillRect(0,0,width,canvas.height);ctx.fillStyle='#24231e';ctx.font=`700 ${fontSize}px "DM Sans", sans-serif`;ctx.textBaseline='top';
+ const imageH=Math.round(Math.min(img.height*(width/img.width),1200));const th=top.length?top.length*lineHeight+pad:0,bh=bottom.length?bottom.length*lineHeight+pad:0;canvas.height=th+imageH+bh;ctx=canvas.getContext('2d');ctx.fillStyle=th_.bg;ctx.fillRect(0,0,width,canvas.height);ctx.fillStyle=th_.fg;ctx.font=`700 ${fontSize}px ${th_.font}`;ctx.textBaseline='top';
top.forEach((s,i)=>ctx.fillText(s,pad,pad/2+i*lineHeight));const ratio=Math.min(width/img.width,imageH/img.height),iw=img.width*ratio,ih=img.height*ratio;ctx.drawImage(img,(width-iw)/2,th+(imageH-ih)/2,iw,ih);bottom.forEach((s,i)=>ctx.fillText(s,pad,th+imageH+pad/2+i*lineHeight));
+ if(m.sticker&&m.sticker.emoji){ctx.font='130px serif';const sx=m.sticker.corner==='tl'||m.sticker.corner==='bl'?pad:width-pad-130,sy=m.sticker.corner==='tl'||m.sticker.corner==='tr'?th+10:th+imageH-140;ctx.fillText(m.sticker.emoji,sx,sy)}
const blob=await new Promise(res=>canvas.toBlob(res,'image/png'));if(!blob)throw new Error('The PNG couldn’t be created. Please try again.');return blob;
}
async function download(m,button){if(button)button.disabled=true;try{const blob=await exportPNG(m);const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download='catmemechimp-'+m.title.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'')+'.png';document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),5000);toast('One portable cat. PNG downloaded.')}catch(e){toast(e.message||'Download failed. Please try again.')}finally{if(button)button.disabled=false}}
+async function exportMotion(m,button){if(button)button.disabled=true;try{
+ if(!('MediaRecorder' in window)||!HTMLCanvasElement.prototype.captureStream)throw new Error('Motion export needs a modern browser. PNG still works.');
+ const still=await exportPNG(m);const url=URL.createObjectURL(still);let frame;try{frame=await loadImage(url)}finally{setTimeout(()=>URL.revokeObjectURL(url),5000)}
+ const width=640,Hs=Math.round(frame.height*(width/frame.width));
+ const canvas=document.createElement('canvas');canvas.width=width;canvas.height=Hs;const ctx=canvas.getContext('2d');
+ const stream=canvas.captureStream(30);const mime=MediaRecorder.isTypeSupported('video/webm;codecs=vp9')?'video/webm;codecs=vp9':'video/webm';
+ const rec=new MediaRecorder(stream,{mimeType:mime,videoBitsPerSecond:2500000});const chunks=[];rec.ondataavailable=e=>{if(e.data&&e.data.size)chunks.push(e.data)};
+ const done=new Promise(res=>{rec.onstop=res});rec.start();toast('Rolling… three seconds of cinema.');
+ const DUR=3000,t0=performance.now();
+ await new Promise(res=>{const step=()=>{const p=Math.min(1,(performance.now()-t0)/DUR),s=1+p*0.12,dw=width*s,dh=Hs*s;ctx.drawImage(frame,(width-dw)/2,(Hs-dh)/2-p*24,dw,dh);if(p<1)requestAnimationFrame(step);else res()};requestAnimationFrame(step)});
+ rec.stop();await done;
+ const blob=new Blob(chunks,{type:'video/webm'});if(!blob.size)throw new Error('The video came out empty. PNG still works.');
+ const vurl=URL.createObjectURL(blob),a=document.createElement('a');a.href=vurl;a.download='catmemechimp-'+(m.title||'motion').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'')+'.webm';document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(vurl),5000);toast('Motion meme exported. Hollywood calls.')}catch(e){toast(e.message||'Motion export failed. PNG still works.')}finally{if(button)button.disabled=false}}
function info(title,content){$('#info-title').textContent=title;$('#info-content').innerHTML=content;$('#info').showModal()}
function about(){info('An unserious archive. A serious color.',`CATMEMECHIMP is a small, lovingly assembled corner of the internet for taking a cat break.
448 C · #4A412A
PERIWINKLE
WARM CREAM
The pixel chimp wears #4A412A , a screen approximation of Pantone 448 C. Deep olive-brown anchors the app; complementary blue and periwinkle bring a little lightness. Its silhouette comes from the canonical Chimp facekit.
Made with creative direction and code by GPT-6 Astra . No accounts, no subscription, no paid API, no tracking. Your favorites and creations stay in this browser.
Color reference ↗
`)}
function sources(){info('A little credit for the cats.',`The starter archive contains ${library.length} original CATMEMECHIMP caption remixes using ${templates().length} community cat templates from Imgflip. Every meme’s viewer links to its individual image source.
The captions were written for this app. Source photographs and templates belong to their respective creators; this app does not claim their ownership or grant reuse rights. Files are stored locally so the collection keeps working when you’re offline.
This is a curated collection, not a live trending feed. The starter collection was assembled on September 8, 2026 .
Your favorites use browser storage and your creations use IndexedDB. Clearing this site’s browser data clears them; download your creations if you want a separate copy.
Explore the source templates on Imgflip ↗
`)}
-function shortcuts(){info('Less clicking. More cats.',`/ Jump to search R A random cat from this view ← / → Previous / next cat in the viewer S Save the cat in the viewer Space Pause / resume autoplay in the viewer Esc Close the current panel ? This little cheat sheet
On your phone, swipe left or right on a meme in the viewer. Autoplay changes cats every six seconds and pauses when you leave the tab.
`)}
-document.addEventListener('click',e=>{const nav=e.target.closest('[data-page]');if(nav){navigate(nav.dataset.page);return}const open=e.target.closest('[data-open]');if(open){openViewer(open.dataset.open);return}const save=e.target.closest('[data-save]');if(save){toggleSave(save.dataset.save);return}const mb=e.target.closest('[data-mood]');if(mb?.classList.contains('mood-filter')){mood=mb.dataset.mood;limit=18;render();return}const cb=e.target.closest('[data-collection]');if(cb){collection=collection===cb.dataset.collection?'':cb.dataset.collection;mood='all';limit=18;render()}});
+function shortcuts(){info('Less clicking. More cats.',`/ Jump to search R A random cat from this view ← / → Previous / next cat in the viewer, or vote in the Arena T / B / C / F Reroll Frankenmeme top / bottom / cat / full chaos ↑ / ↓ Change TV channel in Cat TV mode O Open today's pack on the Daily Drop page M Mute / unmute sounds S Save the cat in the viewer Space Pause / resume autoplay in the viewer Esc Close the current panel ? This little cheat sheet
On your phone, swipe left or right on a meme in the viewer. Tap a cat to crown it in the Arena. Autoplay changes cats every six seconds and pauses when you leave the tab.
`)}
+document.addEventListener('click',e=>{const nav=e.target.closest('[data-page]');if(nav){navigate(nav.dataset.page);return}const vt=e.target.closest('[data-vote]');if(vt){voteBattle(vt.dataset.vote);return}const open=e.target.closest('[data-open]');if(open){openViewer(open.dataset.open,page==='arena'?allMemes():filterMemes());return}const save=e.target.closest('[data-save]');if(save){toggleSave(save.dataset.save);return}const mb=e.target.closest('[data-mood]');if(mb?.classList.contains('mood-filter')){mood=mb.dataset.mood;limit=18;render();return}const cb=e.target.closest('[data-collection]');if(cb){collection=collection===cb.dataset.collection?'':cb.dataset.collection;mood='all';limit=18;render();return}const ds=e.target.closest('[data-delset]');if(ds){const s=setById(ds.dataset.delset);deleteSet(ds.dataset.delset);render();toast(s?`“${s.name}” released into the wild.`:'Set deleted.');return}const cr=e.target.closest('[data-collect]');if(cr){const added=toggleInSet(cr.dataset.collect,currentMeme()?.id);renderCollect();updateCounts();toast(added?'Stashed. A fine addition.':'Removed from that set.');return}const rm=e.target.closest('[data-remove-from-set]');if(rm){toggleInSet(collection,rm.dataset.removeFromSet);render();return}const po=e.target.closest('#pack-open');if(po){openPack();return}const pt=e.target.closest('[data-keeptab]');if(pt){keepTab=pt.dataset.keeptab;renderKeep();return}const tc=e.target.closest('[data-tclear]');if(tc){templateFilter=null;render();return}const lk=e.target.closest('[data-locked]');if(lk){toast('Undiscovered. Prowl Discover, packs, and the Arena…');return}const sh=e.target.closest('[data-share]');if(sh){sharePull(sh.dataset.share);return}const mz=e.target.closest('[data-melt]');if(mz){meltDupe(mz.dataset.melt);return}if(e.target.closest('[data-forge]')){forgePack();return}const xs=e.target.closest('[data-exportsets]');if(xs){exportSets();return}});
$('.brand').addEventListener('click',e=>{e.preventDefault();navigate('discover')});
$('#search').addEventListener('input',()=>{query=$('#search').value.trim();limit=18;render()});$('#sort').addEventListener('change',()=>{limit=18;render()});
-$('#shuffle').onclick=shuffledGrid;$('#hero-random').onclick=randomCat;$('#side-random').onclick=randomCat;$('#side-focus').onclick=()=>{const list=filterMemes();if(list.length)openViewer((list.find(m=>!seen.has(m.id))||list[0]).id);else toast('This corner needs a few cats first.')};
+$('#shuffle').onclick=shuffledGrid;$('#arena-skip').onclick=skipBattle;$('#arena-reset').onclick=resetArena;$('#lab-top').onclick=()=>{rollLab('top');sfx('blip')};$('#lab-bottom').onclick=()=>{rollLab('bottom');sfx('blip')};$('#lab-cat').onclick=()=>{rollLab('cat');sfx('blip')};$('#lab-chaos').onclick=()=>{rollLab('all');sfx('chaos')};$('#lab-inspire').onclick=inspireLab;$('#lab-save').onclick=saveLab;$('#lab-download').onclick=()=>download(labMeme(),$('#lab-download'));$('#hero-random').onclick=randomCat;$('#side-random').onclick=randomCat;$('#side-focus').onclick=()=>{const list=filterMemes();if(list.length)openViewer((list.find(m=>!seen.has(m.id))||list[0]).id);else toast('This corner needs a few cats first.')};
$('#load-more').onclick=()=>{const before=$$('#meme-grid .meme-card').length;limit+=18;render();const first=$$('#meme-grid .meme-open')[before];first?.focus({preventScroll:true})};
$('#back-top').onclick=()=>window.scrollTo({top:0,behavior:'smooth'});
$('#empty-action').onclick=()=>{if(page==='studio'&&!query&&mood==='all')openEditor();else navigate('discover')};
function setDensity(dense){$('#meme-grid').classList.toggle('dense',dense);$('#view-comfy').classList.toggle('selected',!dense);$('#view-dense').classList.toggle('selected',dense);$('#view-comfy').setAttribute('aria-pressed',!dense);$('#view-dense').setAttribute('aria-pressed',dense)}
setDensity(readState('dense',false)===true);$('#view-comfy').onclick=()=>{setDensity(false);setLocal('dense',false)};$('#view-dense').onclick=()=>{setDensity(true);setLocal('dense',true)};
+function applyTheme(t){document.documentElement.dataset.theme=t;const b=$('#theme-toggle');if(!b)return;b.innerHTML=icon(t==='dark'?'sun':'moon');b.setAttribute('aria-label',t==='dark'?'Switch to light mode':'Switch to dark mode')}
+function toggleTheme(){const next=document.documentElement.dataset.theme==='dark'?'light':'dark';setLocal('theme',next);applyTheme(next);toast(next==='dark'?'Lights out. The cats can see in the dark.':'Lights on. The cats judge you clearly again.')}
+applyTheme(readState('theme',matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'));$('#theme-toggle').onclick=toggleTheme;
+let soundOn=readState('sound',true),audioCtx=null,audioReady=false;
+const _vol=readState('vol',1);let masterVol=typeof _vol==='number'&&_vol>=0&&_vol<=1?_vol:1;
+const VOLS=[1,0.6,0.3,0];let volIdx=Math.max(0,VOLS.indexOf(masterVol));if(VOLS[volIdx]!==masterVol)volIdx=0;
+for(const ev of ['pointerdown','keydown'])document.addEventListener(ev,()=>{audioReady=true;try{if(!audioCtx)audioCtx=new (window.AudioContext||window.webkitAudioContext)();if(audioCtx.state==='suspended')audioCtx.resume()}catch{}},{once:true,capture:true});
+function tone(freq,dur,{type='sine',vol=0.12,at=0,slide=null}={}){const t0=audioCtx.currentTime+at;const o=audioCtx.createOscillator(),g=audioCtx.createGain();o.type=type;o.frequency.setValueAtTime(freq,t0);if(slide)o.frequency.exponentialRampToValueAtTime(slide,t0+dur);g.gain.setValueAtTime(0.0001,t0);g.gain.exponentialRampToValueAtTime(Math.max(0.0001,vol*masterVol),t0+0.015);g.gain.exponentialRampToValueAtTime(0.0001,t0+dur);o.connect(g);g.connect(audioCtx.destination);o.start(t0);o.stop(t0+dur+0.05)}
+function sfx(name){if(!soundOn||!audioReady)return;try{if(!audioCtx)audioCtx=new (window.AudioContext||window.webkitAudioContext)();if(audioCtx.state==='suspended')audioCtx.resume();
+ if(name==='pop')tone(520,0.12,{slide:880});else if(name==='meow'){tone(500,0.15,{slide:760,type:'triangle',vol:0.1});tone(760,0.28,{at:0.14,slide:420,type:'triangle',vol:0.1})}else if(name==='save'){tone(660,0.1);tone(880,0.14,{at:0.09})}else if(name==='fanfare')[523,659,784,1047].forEach((f,i)=>tone(f,0.16,{at:i*0.09,type:'triangle'}));else if(name==='blip')tone(440,0.07,{type:'square',vol:0.05});else if(name==='chaos')tone(300,0.2,{slide:150,type:'sawtooth',vol:0.06})}catch{}}
+function applySoundIcon(){const b=$('#sound-toggle');if(!b)return;const on=soundOn&&masterVol>0;b.innerHTML=icon(on?'volume':'mute');b.setAttribute('aria-label',on?'Mute sounds':'Unmute sounds')}
+function toggleSound(){soundOn=!soundOn;setLocal('sound',soundOn);applySoundIcon();if(soundOn)sfx('save');toast(soundOn?'Sound on. The cats are audible.':'Sound off. Silent judgment continues.')}
+applySoundIcon();$('#sound-toggle').onclick=toggleSound;
+function confetti(){if(matchMedia('(prefers-reduced-motion: reduce)').matches)return;const c=$('#confetti');if(!c||!c.getContext)return;const x=c.getContext('2d');c.width=innerWidth;c.height=innerHeight;const cols=['#B9C5F3','#eee2b7','#f4e0db','#e3ebd7','#ffffff'];const ps=Array.from({length:90},()=>({x:innerWidth/2+(Math.random()-0.5)*220,y:innerHeight*0.35,vx:(Math.random()-0.5)*9,vy:Math.random()*-7-2,g:0.35,s:Math.random()*7+4,r:Math.random()*Math.PI,vr:(Math.random()-0.5)*0.3,c:cols[Math.floor(Math.random()*cols.length)],l:1}));let f=0;const iv=setInterval(()=>{x.clearRect(0,0,c.width,c.height);f++;ps.forEach(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=p.g;p.r+=p.vr;p.l-=0.008;x.save();x.globalAlpha=Math.max(0,p.l);x.translate(p.x,p.y);x.rotate(p.r);x.fillStyle=p.c;x.fillRect(-p.s/2,-p.s/2,p.s,p.s*0.6);x.restore()});if(f>90){clearInterval(iv);x.clearRect(0,0,c.width,c.height)}},16)}
$('#viewer-close').onclick=closeViewer;$('#viewer-prev').onclick=()=>browse(-1);$('#viewer-next').onclick=()=>browse(1);$('#viewer-save').onclick=()=>toggleSave(currentMeme().id);$('#viewer-edit').onclick=()=>openEditor(currentMeme());$('#viewer-download').onclick=()=>download(currentMeme(),$('#viewer-download'));$('#autoplay').onclick=()=>autoTimer?stopAutoplay():restartAutoplay();
-$('#viewer').addEventListener('close',()=>{stopAutoplay();history.replaceState(null,'',location.pathname);if($('#sort').value==='unseen')render()});
+$('#viewer').addEventListener('close',()=>{tvMode=false;$('#viewer').classList.remove('tv');stopAutoplay();history.replaceState(null,'',location.pathname);if($('#sort').value==='unseen')render()});
+$('#viewer').addEventListener('cancel',e=>{if(tvMode){e.preventDefault();exitTV()}});
+$('#viewer-image-wrap').addEventListener('click',()=>{if(tvMode){if(tvClip)skipClip();else browse(1)}});
+$('#tv-toggle').onclick=enterTV;$('#tv-exit').onclick=exitTV;$('#tv-ch-up').onclick=()=>setTVChannel(-1);$('#tv-ch-down').onclick=()=>setTVChannel(1);$('#tv-speed').onclick=cycleTVSpeed;$('#tv-sleep').onclick=cycleTVSleep;$('#tv-vol').onclick=cycleVol;
$('#viewer-share').onclick=async()=>{if(currentMeme().kind==='creation'){toast('This creation lives here. Download its PNG to share it.');return}try{await navigator.clipboard.writeText(location.href);toast('Link copied. It opens on this computer while the app is running.')}catch{toast('Copy the meme link from your address bar.')}};
+$('#viewer-more').onclick=()=>{const m=currentMeme();if(!m||m.kind==='creation')return;const img=m.image;closeViewer();showTemplate(img)};
+$('#viewer-collect').onclick=()=>{renderCollect();$('#collect-new-name').value='';$('#collect-dialog').showModal()};$('#collect-close').onclick=()=>$('#collect-dialog').close();
+document.addEventListener('submit',e=>{if(e.target.id==='new-set-form'){e.preventDefault();const s=createSet($('#new-set-name').value);render();toast(`Set “${s.name}” created. Start hoarding.`)}if(e.target.id==='collect-new'){e.preventDefault();const s=createSet($('#collect-new-name').value);$('#collect-new-name').value='';renderCollect();toast(`Set “${s.name}” created. Tap it to stash this cat.`)}});
+document.addEventListener('change',e=>{if(e.target.id==='import-sets'){importSets(e.target.files[0]);e.target.value=''}if(e.target.id==='tv-clip-upload'){importClip(e.target.files[0]);e.target.value=''}});
$('#create-open').onclick=()=>openEditor();$('#editor-close').onclick=()=>$('#editor').close();$('#top-caption').oninput=preview;$('#bottom-caption').oninput=preview;
$('#template-select').onchange=()=>{const m=$('#template-select').value==='custom'?uploadedTemplate:library.find(m=>m.image===$('#template-select').value);if(m){editing={...editing,image:m.image,template:m.template,source:m.source};preview()}};
-$('#upload').onchange=()=>uploadCat($('#upload').files[0]);$('#editor-download').onclick=()=>download(editorMeme(),$('#editor-download'));
-$('#editor-form').onsubmit=async e=>{e.preventDefault();const button=e.submitter;button.disabled=true;const m={...editorMeme(),id:'own-'+crypto.randomUUID(),kind:'creation',added:new Date().toISOString(),tags:['my creation']};try{await dbPut(m);created.push(m);$('#editor').close();navigate('studio');toast('A masterpiece has been born. Saved in My creations.')}catch{toast('Could not save in this browser. Use PNG to keep your creation.')}finally{button.disabled=false}};
+for(const id of ['editor-font','editor-style','editor-sticker','editor-corner'])document.getElementById(id).addEventListener('change',preview);
+$('#upload').onchange=()=>uploadCat($('#upload').files[0]);$('#editor-download').onclick=()=>download(editorMeme(),$('#editor-download'));$('#editor-motion').onclick=()=>exportMotion(editorMeme(),$('#editor-motion'));$('#lab-motion').onclick=()=>exportMotion(labMeme(),$('#lab-motion'));
+$('#editor-inspire').onclick=()=>{const g=inspireCaption();$('#top-caption').value=g.top;$('#bottom-caption').value=g.bottom;inspired=true;preview();sfx('blip');toast('The machine dreamed this. Edit freely, take credit.')};
+$('#editor-form').onsubmit=async e=>{e.preventDefault();const button=e.submitter;button.disabled=true;const m={...editorMeme(),id:'own-'+crypto.randomUUID(),kind:'creation',added:new Date().toISOString(),tags:inspired?['my creation','robot-assisted']:['my creation']};try{await dbPut(m);created.push(m);$('#editor').close();sfx('fanfare');checkAwards();navigate('studio');toast('A masterpiece has been born. Saved in My creations.')}catch{toast('Could not save in this browser. Use PNG to keep your creation.')}finally{button.disabled=false}};
$('#about-open').onclick=about;$('#shortcuts-open').onclick=shortcuts;$('#sources-open').onclick=sources;$('#info-close').onclick=()=>$('#info').close();
for(const d of $$('dialog'))d.addEventListener('click',e=>{if(e.target===d){const r=d.getBoundingClientRect();if(e.clientXr.right||e.clientYr.bottom)d.close()}});
document.addEventListener('keydown',e=>{if(e.ctrlKey||e.altKey||e.metaKey||/INPUT|TEXTAREA|SELECT/.test(e.target.tagName)||e.target.isContentEditable)return;
- if($('#viewer').open){if(e.key==='ArrowRight'){e.preventDefault();browse(1)}else if(e.key==='ArrowLeft'){e.preventDefault();browse(-1)}else if(e.key.toLowerCase()==='s'){e.preventDefault();toggleSave(currentMeme().id)}else if(e.code==='Space'){e.preventDefault();autoTimer?stopAutoplay():restartAutoplay()}return}
- if($('dialog[open]'))return;if(e.key==='/'){e.preventDefault();$('#search').focus()}else if(e.key.toLowerCase()==='r'){e.preventDefault();randomCat()}else if(e.key==='?'){e.preventDefault();shortcuts()}});
+ if($('#viewer').open){if(tvMode&&e.key==='ArrowUp'){e.preventDefault();setTVChannel(-1)}else if(tvMode&&e.key==='ArrowDown'){e.preventDefault();setTVChannel(1)}else if(e.key==='ArrowRight'){e.preventDefault();browse(1)}else if(e.key==='ArrowLeft'){e.preventDefault();browse(-1)}else if(e.key.toLowerCase()==='s'){e.preventDefault();toggleSave(currentMeme().id)}else if(e.code==='Space'){e.preventDefault();autoTimer?stopAutoplay():restartAutoplay()}return}
+ if($('dialog[open]'))return;if(e.key==='/'){e.preventDefault();$('#search').focus()}else if(e.key.toLowerCase()==='r'){e.preventDefault();randomCat()}else if(e.key==='?'){e.preventDefault();shortcuts()}else if(page==='arena'&&e.key==='ArrowLeft'&&battleLeft){e.preventDefault();voteBattle(battleLeft.id)}else if(page==='arena'&&e.key==='ArrowRight'&&battleRight){e.preventDefault();voteBattle(battleRight.id)}else if(page==='lab'&&e.key.toLowerCase()==='t'){e.preventDefault();rollLab('top');sfx('blip')}else if(page==='lab'&&e.key.toLowerCase()==='b'){e.preventDefault();rollLab('bottom');sfx('blip')}else if(page==='lab'&&e.key.toLowerCase()==='c'){e.preventDefault();rollLab('cat');sfx('blip')}else if(page==='lab'&&e.key.toLowerCase()==='f'){e.preventDefault();rollLab('all');sfx('chaos')}else if(page==='packs'&&e.key.toLowerCase()==='o'){e.preventDefault();openPack()}else if(e.key.toLowerCase()==='m'){e.preventDefault();toggleSound()}});
document.addEventListener('visibilitychange',()=>{if(document.hidden&&autoTimer)stopAutoplay()});
let swipe=null;$('#viewer-image-wrap').addEventListener('touchstart',e=>{swipe={x:e.changedTouches[0].clientX,y:e.changedTouches[0].clientY}},{passive:true});$('#viewer-image-wrap').addEventListener('touchend',e=>{if(!swipe)return;const dx=e.changedTouches[0].clientX-swipe.x,dy=e.changedTouches[0].clientY-swipe.y;if(Math.abs(dx)>55&&Math.abs(dx)>Math.abs(dy)*1.5)browse(dx<0?1:-1);swipe=null},{passive:true});
-async function init(){try{const response=await fetch('/library.json');if(!response.ok)throw new Error('The archive could not load.');library=await response.json();try{created=await dbRead()}catch{toast('Creations storage is unavailable; PNG downloads still work.')}render();const deepId=new URLSearchParams(location.hash.slice(1)).get('meme');if(deepId){const m=library.find(m=>m.id===deepId);if(m)openViewer(m.id,library);else toast('That cat link is not in this archive.')}
+let battleSwipe=null;$('#battle-grid').addEventListener('touchstart',e=>{battleSwipe={x:e.changedTouches[0].clientX,y:e.changedTouches[0].clientY}},{passive:true});$('#battle-grid').addEventListener('touchend',e=>{if(!battleSwipe||page!=='arena')return;const dx=e.changedTouches[0].clientX-battleSwipe.x,dy=e.changedTouches[0].clientY-battleSwipe.y;battleSwipe=null;if(Math.abs(dx)>55&&Math.abs(dx)>Math.abs(dy)*1.5){if(dx<0&&battleRight)voteBattle(battleRight.id);else if(dx>0&&battleLeft)voteBattle(battleLeft.id)}},{passive:true});
+async function init(){if('serviceWorker' in navigator){try{await navigator.serviceWorker.register('/sw.js')}catch{}}try{const response=await fetch('/library.json');if(!response.ok)throw new Error('The archive could not load.');library=await response.json();try{created=await dbRead()}catch{toast('Creations storage is unavailable; PNG downloads still work.')}battleStats=readBattles();rollSeason();pickBattle();rollLab();try{for(const c of await dbReadClips())userClips.push({id:c.id,title:(c.name||'clip').replace(/\.[^.]+$/,''),src:URL.createObjectURL(c.blob)})}catch{}render();buddyInit();fillTicker();heroParallax();const deepId=new URLSearchParams(location.hash.slice(1)).get('meme');if(deepId){const m=library.find(m=>m.id===deepId);if(m)openViewer(m.id,library);else toast('That cat link is not in this archive.')}
+ if(!deepId&&!readState('welcomed',false)){setLocal('welcomed',true);setTimeout(()=>info('Welcome to the cat archive.',`Follow the thread: 🔍 Find → 🏟 Crown → 🧪 Stitch → 📦 Keep.
290 memes. Rip a daily pack, battle the arena, stitch lab abominations, fill the album dex.
/ search · R random cat · O open pack · ? everything else. All local, all yours.
`),450)}
}catch(e){$('#result-count').textContent='The cats couldn’t arrive.';$('#empty').hidden=false;$('#empty-title').textContent='A small cat-astrophe.';$('#empty-copy').textContent='The local library could not load. Reload to try again.';$('#empty-action').textContent='Try again';$('#empty-action').onclick=()=>location.reload();console.error(e)}}
window.addEventListener('hashchange',()=>{const id=new URLSearchParams(location.hash.slice(1)).get('meme');if(!id){if($('#viewer').open)closeViewer();return}const m=library.find(m=>m.id===id);if(!m){toast('That cat link is not in this archive.');return}if($('#viewer').open){viewerQueue=[...library];viewerIndex=viewerQueue.findIndex(m=>m.id===id);showViewer()}else openViewer(id,library)});
await init();
diff --git a/public/assets/clips/calico.mp4 b/public/assets/clips/calico.mp4
new file mode 100644
index 0000000..636051b
Binary files /dev/null and b/public/assets/clips/calico.mp4 differ
diff --git a/public/assets/clips/gatos.mp4 b/public/assets/clips/gatos.mp4
new file mode 100644
index 0000000..67a679c
Binary files /dev/null and b/public/assets/clips/gatos.mp4 differ
diff --git a/public/assets/clips/tokyo.mp4 b/public/assets/clips/tokyo.mp4
new file mode 100644
index 0000000..1de9d8a
Binary files /dev/null and b/public/assets/clips/tokyo.mp4 differ
diff --git a/public/assets/memes/22vtk.jpg b/public/assets/memes/22vtk.jpg
new file mode 100644
index 0000000..a0ccd3c
Binary files /dev/null and b/public/assets/memes/22vtk.jpg differ
diff --git a/public/assets/memes/345v97.jpg b/public/assets/memes/345v97.jpg
new file mode 100644
index 0000000..c096d89
Binary files /dev/null and b/public/assets/memes/345v97.jpg differ
diff --git a/public/assets/memes/3m5mp.jpg b/public/assets/memes/3m5mp.jpg
new file mode 100644
index 0000000..97b0212
Binary files /dev/null and b/public/assets/memes/3m5mp.jpg differ
diff --git a/public/assets/memes/7rf5z.jpg b/public/assets/memes/7rf5z.jpg
new file mode 100644
index 0000000..ad21305
Binary files /dev/null and b/public/assets/memes/7rf5z.jpg differ
diff --git a/public/assets/memes/8tlh.jpg b/public/assets/memes/8tlh.jpg
new file mode 100644
index 0000000..e57f8e0
Binary files /dev/null and b/public/assets/memes/8tlh.jpg differ
diff --git a/public/assets/memes/jru7x.jpg b/public/assets/memes/jru7x.jpg
new file mode 100644
index 0000000..c27a381
Binary files /dev/null and b/public/assets/memes/jru7x.jpg differ
diff --git a/public/assets/memes/zl7we.jpg b/public/assets/memes/zl7we.jpg
new file mode 100644
index 0000000..6ae2955
Binary files /dev/null and b/public/assets/memes/zl7we.jpg differ
diff --git a/public/index.html b/public/index.html
index bff6543..dd8c9b8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,7 +2,7 @@
-
+
CATMEMECHIMP — Good cats. Questionable behavior.
@@ -12,13 +12,22 @@
-
+
- ✳ A SMALL BREAK FROM BEING A PERSON
Serious archive.Unserious cats. The finest feline nonsense, all in one happy place. Find your mood. Save your favorites. Lose five minutes.
Find me a good cat
+ ✳ A SMALL BREAK FROM BEING A PERSON
Serious archive.Unserious cats. The finest feline nonsense, all in one happy place. Find your mood. Save your favorites. Lose five minutes.
Find me a good cat or follow the thread: 🏟 Crown 🧪 Stitch ✨ Make 📦 Keep
✳ no thoughts. only potassium.
me: “i’m fine” also me:
100%GOOD CATS ✦
a very serious collection ↗
-
-
+
+
THE GOOD STUFF
Find your feline frequency.
Shuffle the cats
-
+ How the Arena works Two cats enter. Tap the funnier one — or press ← / → .Build a streak. Every crown adds to your judging streak.Skips reset the streak. Cowardice has consequences. Stats stay in this browser.
Skip this matchupReset my stats ← vote left → vote right · swipe on phone
+
+
+ The Frankenmeme Lab Stitch a top from one cat, a bottom from another, and a third cat entirely. No ethics committee was consulted.
🎲 Top 🎲 Bottom 🎲 Cat ⚡ Full chaos 🤖 Inspire me
Save creation PNG 🎬 Motion
T top B bottom C cat F full chaos
+
- Rounding up the cats… Sort by Chimp’s picks A–Z Unseen first
+ Rounding up the cats… Sort by Chimp’s picks Trending A–Z Unseen first
ᓚᘏᗢ No cats in this corner. Try another mood or a different search.
Show all cats
More cats, please
@@ -48,9 +62,12 @@
A little less doom. A little more meow.The archive & its sources ↗
- Discover Saved Collections Creations
-
-THE NONSENSE LAB
Make a little mischief.
+ Discover Arena Lab Make Drop Sets Dex
+
● LIVE
▲ ▼ 🔊 100% 6s 💤 Off 📹 ✕ Exit
Image source ↗ More of this template ← → browse S save Esc closeCat TV Autoplay
+THE NONSENSE LAB
Make a little mischief.
-
+
+
+
+
diff --git a/public/library.json b/public/library.json
index ea77c12..886021d 100644
--- a/public/library.json
+++ b/public/library.json
@@ -13,7 +13,10 @@
"template": "sad thumbs up cat",
"source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
},
{
"id": "cat-002",
@@ -29,7 +32,10 @@
"template": "Huh Cat",
"source": "https://imgflip.com/meme/488919824/Huh-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2024,
+ "status": "alive",
+ "lore": "Confused in high definition."
},
{
"id": "cat-003",
@@ -46,7 +52,10 @@
"template": "I Should Buy A Boat Cat",
"source": "https://imgflip.com/meme/I-Should-Buy-A-Boat-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Boat questionable. Box undeniable."
},
{
"id": "cat-004",
@@ -62,7 +71,10 @@
"template": "Grumpy Cat",
"source": "https://imgflip.com/meme/Grumpy-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
},
{
"id": "cat-005",
@@ -78,7 +90,10 @@
"template": "Banana cat",
"source": "https://imgflip.com/meme/470680617/Banana-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Potassium with a fanbase."
},
{
"id": "cat-006",
@@ -94,7 +109,10 @@
"template": "Sleepy Kitten",
"source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
},
{
"id": "cat-007",
@@ -110,7 +128,10 @@
"template": "Smiling Cat",
"source": "https://imgflip.com/meme/Smiling-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
},
{
"id": "cat-008",
@@ -126,7 +147,10 @@
"template": "Business Cat",
"source": "https://imgflip.com/meme/Business-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
},
{
"id": "cat-009",
@@ -142,7 +166,10 @@
"template": "Smudge the cat",
"source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
},
{
"id": "cat-010",
@@ -158,7 +185,10 @@
"template": "vibing cat",
"source": "https://imgflip.com/meme/277365922/vibing-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Headphones on. World off."
},
{
"id": "cat-011",
@@ -174,7 +204,10 @@
"template": "Crying cat",
"source": "https://imgflip.com/meme/42003405/Crying-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2014,
+ "status": "classic",
+ "lore": "Crying professionally for a decade."
},
{
"id": "cat-012",
@@ -190,7 +223,10 @@
"template": "Pop Cat",
"source": "https://imgflip.com/meme/284017364/Pop-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
},
{
"id": "cat-013",
@@ -206,7 +242,10 @@
"template": "Cool Cat Stroll",
"source": "https://imgflip.com/meme/Cool-Cat-Stroll",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
},
{
"id": "cat-014",
@@ -222,7 +261,10 @@
"template": "I'm awake, but at what cost?",
"source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
},
{
"id": "cat-015",
@@ -238,7 +280,10 @@
"template": "Cute Cat",
"source": "https://imgflip.com/meme/Cute-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
},
{
"id": "cat-016",
@@ -254,7 +299,10 @@
"template": "Scared Cat",
"source": "https://imgflip.com/meme/Scared-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Startled since 2019."
},
{
"id": "cat-017",
@@ -270,7 +318,10 @@
"template": "Cat newspaper",
"source": "https://imgflip.com/meme/19259689/Cat-newspaper",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Reports the news. Is the news."
},
{
"id": "cat-018",
@@ -286,7 +337,10 @@
"template": "Grumpy Cat Not Amused",
"source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
},
{
"id": "cat-019",
@@ -302,7 +356,10 @@
"template": "Sad Cat Holding Dog",
"source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
},
{
"id": "cat-020",
@@ -318,7 +375,10 @@
"template": "Crying cat on phone",
"source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
},
{
"id": "cat-021",
@@ -334,7 +394,10 @@
"template": "Sleepy Cat",
"source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
},
{
"id": "cat-022",
@@ -350,7 +413,10 @@
"template": "Chemistry Cat",
"source": "https://imgflip.com/meme/Chemistry-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2011,
+ "status": "classic",
+ "lore": "Peer reviews your snacks."
},
{
"id": "cat-023",
@@ -366,7 +432,10 @@
"template": "white cat table",
"source": "https://imgflip.com/meme/191245022/white-cat-table",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
},
{
"id": "cat-024",
@@ -382,7 +451,10 @@
"template": "Excited Cat",
"source": "https://imgflip.com/meme/Excited-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
},
{
"id": "cat-025",
@@ -398,7 +470,10 @@
"template": "Huh Cat",
"source": "https://imgflip.com/meme/488919824/Huh-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2024,
+ "status": "alive",
+ "lore": "Confused in high definition."
},
{
"id": "cat-026",
@@ -414,7 +489,10 @@
"template": "Banana cat",
"source": "https://imgflip.com/meme/470680617/Banana-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Potassium with a fanbase."
},
{
"id": "cat-027",
@@ -430,7 +508,10 @@
"template": "Grumpy Cat",
"source": "https://imgflip.com/meme/Grumpy-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
},
{
"id": "cat-028",
@@ -446,7 +527,10 @@
"template": "Sleepy Kitten",
"source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
},
{
"id": "cat-029",
@@ -462,7 +546,10 @@
"template": "vibing cat",
"source": "https://imgflip.com/meme/277365922/vibing-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Headphones on. World off."
},
{
"id": "cat-030",
@@ -478,7 +565,10 @@
"template": "Smiling Cat",
"source": "https://imgflip.com/meme/Smiling-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
},
{
"id": "cat-031",
@@ -494,7 +584,10 @@
"template": "I Should Buy A Boat Cat",
"source": "https://imgflip.com/meme/I-Should-Buy-A-Boat-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Boat questionable. Box undeniable."
},
{
"id": "cat-032",
@@ -510,7 +603,10 @@
"template": "Pop Cat",
"source": "https://imgflip.com/meme/284017364/Pop-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
},
{
"id": "cat-033",
@@ -526,7 +622,10 @@
"template": "sad thumbs up cat",
"source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
},
{
"id": "cat-034",
@@ -542,7 +641,10 @@
"template": "Cool Cat Stroll",
"source": "https://imgflip.com/meme/Cool-Cat-Stroll",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
},
{
"id": "cat-035",
@@ -558,7 +660,10 @@
"template": "Smudge the cat",
"source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
},
{
"id": "cat-036",
@@ -574,7 +679,10 @@
"template": "I'm awake, but at what cost?",
"source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
},
{
"id": "cat-037",
@@ -590,7 +698,10 @@
"template": "Cute Cat",
"source": "https://imgflip.com/meme/Cute-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
},
{
"id": "cat-038",
@@ -606,7 +717,10 @@
"template": "Cat newspaper",
"source": "https://imgflip.com/meme/19259689/Cat-newspaper",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Reports the news. Is the news."
},
{
"id": "cat-039",
@@ -622,7 +736,10 @@
"template": "Scared Cat",
"source": "https://imgflip.com/meme/Scared-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Startled since 2019."
},
{
"id": "cat-040",
@@ -638,7 +755,10 @@
"template": "Business Cat",
"source": "https://imgflip.com/meme/Business-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
},
{
"id": "cat-041",
@@ -654,7 +774,10 @@
"template": "Sleepy Cat",
"source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
},
{
"id": "cat-042",
@@ -670,7 +793,10 @@
"template": "Grumpy Cat Not Amused",
"source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
},
{
"id": "cat-043",
@@ -686,7 +812,10 @@
"template": "Sad Cat Holding Dog",
"source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
},
{
"id": "cat-044",
@@ -702,7 +831,10 @@
"template": "Chemistry Cat",
"source": "https://imgflip.com/meme/Chemistry-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2011,
+ "status": "classic",
+ "lore": "Peer reviews your snacks."
},
{
"id": "cat-045",
@@ -718,7 +850,10 @@
"template": "white cat table",
"source": "https://imgflip.com/meme/191245022/white-cat-table",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
},
{
"id": "cat-046",
@@ -734,7 +869,10 @@
"template": "Crying cat",
"source": "https://imgflip.com/meme/42003405/Crying-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2014,
+ "status": "classic",
+ "lore": "Crying professionally for a decade."
},
{
"id": "cat-047",
@@ -750,7 +888,10 @@
"template": "Excited Cat",
"source": "https://imgflip.com/meme/Excited-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
},
{
"id": "cat-048",
@@ -766,7 +907,10 @@
"template": "Crying cat on phone",
"source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
},
{
"id": "cat-049",
@@ -782,7 +926,10 @@
"template": "Grumpy Cat",
"source": "https://imgflip.com/meme/Grumpy-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
},
{
"id": "cat-050",
@@ -798,7 +945,10 @@
"template": "Banana cat",
"source": "https://imgflip.com/meme/470680617/Banana-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Potassium with a fanbase."
},
{
"id": "cat-051",
@@ -814,7 +964,10 @@
"template": "Sleepy Kitten",
"source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
},
{
"id": "cat-052",
@@ -830,7 +983,10 @@
"template": "Smiling Cat",
"source": "https://imgflip.com/meme/Smiling-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
},
{
"id": "cat-053",
@@ -846,7 +1002,10 @@
"template": "I Should Buy A Boat Cat",
"source": "https://imgflip.com/meme/I-Should-Buy-A-Boat-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Boat questionable. Box undeniable."
},
{
"id": "cat-054",
@@ -862,7 +1021,10 @@
"template": "Smudge the cat",
"source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
},
{
"id": "cat-055",
@@ -878,7 +1040,10 @@
"template": "Grumpy Cat Not Amused",
"source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
},
{
"id": "cat-056",
@@ -894,7 +1059,10 @@
"template": "Pop Cat",
"source": "https://imgflip.com/meme/284017364/Pop-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
},
{
"id": "cat-057",
@@ -910,7 +1078,10 @@
"template": "Sleepy Cat",
"source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
},
{
"id": "cat-058",
@@ -926,7 +1097,10 @@
"template": "white cat table",
"source": "https://imgflip.com/meme/191245022/white-cat-table",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
},
{
"id": "cat-059",
@@ -942,7 +1116,10 @@
"template": "I'm awake, but at what cost?",
"source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
},
{
"id": "cat-060",
@@ -958,6 +1135,4379 @@
"template": "Huh Cat",
"source": "https://imgflip.com/meme/488919824/Huh-Cat",
"kind": "remix",
- "added": "2026-09-08"
+ "added": "2026-09-08",
+ "origin": 2024,
+ "status": "alive",
+ "lore": "Confused in high definition."
+ },
+ {
+ "id": "cat-061",
+ "title": "Read at 9:03 PM",
+ "top": "leaves me on read",
+ "bottom": "posts a story 4 minutes later",
+ "mood": "relatable",
+ "tags": [
+ "texting",
+ "friends"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-062",
+ "title": "The group chat lore",
+ "top": "everyone laughing at messages",
+ "bottom": "from while I was asleep",
+ "mood": "relatable",
+ "tags": [
+ "friends",
+ "confused"
+ ],
+ "image": "/assets/memes/8338y8.jpg",
+ "template": "Huh Cat",
+ "source": "https://imgflip.com/meme/488919824/Huh-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2024,
+ "status": "alive",
+ "lore": "Confused in high definition."
+ },
+ {
+ "id": "cat-063",
+ "title": "Inbox zero speedrun",
+ "top": "0 unread emails",
+ "bottom": "147 unread life decisions",
+ "mood": "relatable",
+ "tags": [
+ "work",
+ "email"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-064",
+ "title": "The Sunday reset",
+ "top": "new week, new me",
+ "bottom": "same me, new snacks",
+ "mood": "relatable",
+ "tags": [
+ "weekend",
+ "snacks"
+ ],
+ "image": "/assets/memes/p0a19.jpg",
+ "template": "Crying cat",
+ "source": "https://imgflip.com/meme/42003405/Crying-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2014,
+ "status": "classic",
+ "lore": "Crying professionally for a decade."
+ },
+ {
+ "id": "cat-065",
+ "title": "Performance review face",
+ "top": "“we value work-life balance”",
+ "bottom": "sends 11pm slack anyway",
+ "mood": "relatable",
+ "tags": [
+ "work",
+ "slack"
+ ],
+ "image": "/assets/memes/3u04h5.jpg",
+ "template": "sad thumbs up cat",
+ "source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
+ },
+ {
+ "id": "cat-066",
+ "title": "The rent conversation",
+ "top": "landlord: just a small increase",
+ "bottom": "my salary: absolutely not",
+ "mood": "relatable",
+ "tags": [
+ "money",
+ "home"
+ ],
+ "image": "/assets/memes/35v1lq.jpg",
+ "template": "white cat table",
+ "source": "https://imgflip.com/meme/191245022/white-cat-table",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
+ },
+ {
+ "id": "cat-067",
+ "title": "Trust the science",
+ "top": "studies show naps improve",
+ "bottom": "literally everything",
+ "mood": "relatable",
+ "tags": [
+ "science",
+ "nap"
+ ],
+ "image": "/assets/memes/1bif.jpg",
+ "template": "Chemistry Cat",
+ "source": "https://imgflip.com/meme/Chemistry-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "classic",
+ "lore": "Peer reviews your snacks."
+ },
+ {
+ "id": "cat-068",
+ "title": "HR has been notified",
+ "top": "me leaving the meeting",
+ "bottom": "I was never invited to",
+ "mood": "chaos",
+ "tags": [
+ "work",
+ "trouble"
+ ],
+ "image": "/assets/memes/24uu.jpg",
+ "template": "Cool Cat Stroll",
+ "source": "https://imgflip.com/meme/Cool-Cat-Stroll",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
+ },
+ {
+ "id": "cat-069",
+ "title": "The doorbell cinematic universe",
+ "top": "package for you",
+ "bottom": "fight or flight: activated",
+ "mood": "chaos",
+ "tags": [
+ "surprised",
+ "door"
+ ],
+ "image": "/assets/memes/2hgfw.jpg",
+ "template": "Scared Cat",
+ "source": "https://imgflip.com/meme/Scared-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Startled since 2019."
+ },
+ {
+ "id": "cat-070",
+ "title": "Emotional support fruit",
+ "top": "peeling good today",
+ "bottom": "potassium levels: unhinged",
+ "mood": "chaos",
+ "tags": [
+ "banana",
+ "feelings"
+ ],
+ "image": "/assets/memes/7s8bhl.jpg",
+ "template": "Banana cat",
+ "source": "https://imgflip.com/meme/470680617/Banana-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Potassium with a fanbase."
+ },
+ {
+ "id": "cat-071",
+ "title": "The 2am duet",
+ "top": "smoke detector: chirp",
+ "bottom": "me: chirp back, but louder",
+ "mood": "chaos",
+ "tags": [
+ "night",
+ "drama"
+ ],
+ "image": "/assets/memes/4p3h78.jpg",
+ "template": "Pop Cat",
+ "source": "https://imgflip.com/meme/284017364/Pop-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
+ },
+ {
+ "id": "cat-072",
+ "title": "Math again?",
+ "top": "quick mafs",
+ "bottom": "slow regrets",
+ "mood": "chaos",
+ "tags": [
+ "math",
+ "confused"
+ ],
+ "image": "/assets/memes/8338y8.jpg",
+ "template": "Huh Cat",
+ "source": "https://imgflip.com/meme/488919824/Huh-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2024,
+ "status": "alive",
+ "lore": "Confused in high definition."
+ },
+ {
+ "id": "cat-073",
+ "title": "DJ Fridge Hum",
+ "top": "headlining my kitchen",
+ "bottom": "one night only (every night)",
+ "mood": "chaos",
+ "tags": [
+ "music",
+ "night"
+ ],
+ "image": "/assets/memes/4l4wwy.jpg",
+ "template": "vibing cat",
+ "source": "https://imgflip.com/meme/277365922/vibing-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Headphones on. World off."
+ },
+ {
+ "id": "cat-074",
+ "title": "The five-year plan",
+ "top": "step 1: get box",
+ "bottom": "step 2: sit in box. done.",
+ "mood": "chaos",
+ "tags": [
+ "box",
+ "classic"
+ ],
+ "image": "/assets/memes/tau4.jpg",
+ "template": "I Should Buy A Boat Cat",
+ "source": "https://imgflip.com/meme/I-Should-Buy-A-Boat-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Boat questionable. Box undeniable."
+ },
+ {
+ "id": "cat-075",
+ "title": "The dinner audit",
+ "top": "you made pasta",
+ "bottom": "where is MY pasta",
+ "mood": "snacks",
+ "tags": [
+ "dinner",
+ "drama"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-076",
+ "title": "Crunch-based communication",
+ "top": "if I crunch louder",
+ "bottom": "second breakfast appears",
+ "mood": "snacks",
+ "tags": [
+ "hungry",
+ "breakfast"
+ ],
+ "image": "/assets/memes/4p3h78.jpg",
+ "template": "Pop Cat",
+ "source": "https://imgflip.com/meme/284017364/Pop-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
+ },
+ {
+ "id": "cat-077",
+ "title": "Peer-reviewed snackology",
+ "top": "hypothesis: fridge snacks",
+ "bottom": "taste better at midnight",
+ "mood": "snacks",
+ "tags": [
+ "science",
+ "night"
+ ],
+ "image": "/assets/memes/1bif.jpg",
+ "template": "Chemistry Cat",
+ "source": "https://imgflip.com/meme/Chemistry-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "classic",
+ "lore": "Peer reviews your snacks."
+ },
+ {
+ "id": "cat-078",
+ "title": "Snack market update",
+ "top": "treat stocks rising",
+ "bottom": "analysts recommend screaming",
+ "mood": "snacks",
+ "tags": [
+ "news",
+ "treat"
+ ],
+ "image": "/assets/memes/bgsvd.jpg",
+ "template": "Cat newspaper",
+ "source": "https://imgflip.com/meme/19259689/Cat-newspaper",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Reports the news. Is the news."
+ },
+ {
+ "id": "cat-079",
+ "title": "Lunch and learn",
+ "top": "today’s lesson:",
+ "bottom": "your sandwich is communal",
+ "mood": "snacks",
+ "tags": [
+ "work",
+ "lunch"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-080",
+ "title": "The dessert defense",
+ "top": "I’m full",
+ "bottom": "(of dessert-adjacent room)",
+ "mood": "snacks",
+ "tags": [
+ "dinner",
+ "dessert"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-081",
+ "title": "Smoothie era",
+ "top": "blended my feelings",
+ "bottom": "still lumpy",
+ "mood": "snacks",
+ "tags": [
+ "banana",
+ "feelings"
+ ],
+ "image": "/assets/memes/7s8bhl.jpg",
+ "template": "Banana cat",
+ "source": "https://imgflip.com/meme/470680617/Banana-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Potassium with a fanbase."
+ },
+ {
+ "id": "cat-082",
+ "title": "The gym membership",
+ "top": "paying monthly",
+ "bottom": "to be judged by a cat",
+ "mood": "judgment",
+ "tags": [
+ "gym",
+ "grumpy"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-083",
+ "title": "The code review",
+ "top": "“just one small comment”",
+ "bottom": "47 comments later",
+ "mood": "judgment",
+ "tags": [
+ "work",
+ "code"
+ ],
+ "image": "/assets/memes/35v1lq.jpg",
+ "template": "white cat table",
+ "source": "https://imgflip.com/meme/191245022/white-cat-table",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
+ },
+ {
+ "id": "cat-084",
+ "title": "The salad verdict",
+ "top": "you ordered salad",
+ "bottom": "in THIS economy of flavor?",
+ "mood": "judgment",
+ "tags": [
+ "salad",
+ "dinner"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-085",
+ "title": "The morning people",
+ "top": "“rise and grind”",
+ "bottom": "I rise and judge",
+ "mood": "judgment",
+ "tags": [
+ "morning",
+ "grumpy"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-086",
+ "title": "The neighbor report",
+ "top": "mows lawn at 7am",
+ "bottom": "noted. forever.",
+ "mood": "judgment",
+ "tags": [
+ "neighbors",
+ "grudge"
+ ],
+ "image": "/assets/memes/24uu.jpg",
+ "template": "Cool Cat Stroll",
+ "source": "https://imgflip.com/meme/Cool-Cat-Stroll",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
+ },
+ {
+ "id": "cat-087",
+ "title": "The software update",
+ "top": "“a quick restart”",
+ "bottom": "45 minutes of betrayal",
+ "mood": "judgment",
+ "tags": [
+ "tech",
+ "phone"
+ ],
+ "image": "/assets/memes/3u04h5.jpg",
+ "template": "sad thumbs up cat",
+ "source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
+ },
+ {
+ "id": "cat-088",
+ "title": "The photo shoot",
+ "top": "say cheese",
+ "bottom": "I said what I said",
+ "mood": "judgment",
+ "tags": [
+ "photo",
+ "attitude"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-089",
+ "title": "The elevator nap",
+ "top": "blinked once",
+ "bottom": "missed my floor",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "tired"
+ ],
+ "image": "/assets/memes/11wis1.jpg",
+ "template": "Sleepy Kitten",
+ "source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
+ },
+ {
+ "id": "cat-090",
+ "title": "The ambitious weekend",
+ "top": "so many plans",
+ "bottom": "so horizontal",
+ "mood": "sleepy",
+ "tags": [
+ "weekend",
+ "nap"
+ ],
+ "image": "/assets/memes/lp2ba.jpg",
+ "template": "Sleepy Cat",
+ "source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
+ },
+ {
+ "id": "cat-091",
+ "title": "The 6am club",
+ "top": "member since never",
+ "bottom": "meetings attended: against will",
+ "mood": "sleepy",
+ "tags": [
+ "morning",
+ "work"
+ ],
+ "image": "/assets/memes/3e5sun.jpg",
+ "template": "I'm awake, but at what cost?",
+ "source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
+ },
+ {
+ "id": "cat-092",
+ "title": "Do not perceive",
+ "top": "bedtime: 10pm",
+ "bottom": "doomscroll: until 3am",
+ "mood": "sleepy",
+ "tags": [
+ "phone",
+ "night"
+ ],
+ "image": "/assets/memes/3vnurc.jpg",
+ "template": "Crying cat on phone",
+ "source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
+ },
+ {
+ "id": "cat-093",
+ "title": "The nap negotiation",
+ "top": "just 20 minutes",
+ "bottom": "wakes up employed-ish",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "work"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-094",
+ "title": "Low power mode",
+ "top": "battery: 2%",
+ "bottom": "vibe: airplane mode",
+ "mood": "sleepy",
+ "tags": [
+ "battery",
+ "tired"
+ ],
+ "image": "/assets/memes/11wis1.jpg",
+ "template": "Sleepy Kitten",
+ "source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
+ },
+ {
+ "id": "cat-095",
+ "title": "The good morning text",
+ "top": "no reason",
+ "bottom": "just thought you’d like this cat",
+ "mood": "wholesome",
+ "tags": [
+ "friends",
+ "love"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-096",
+ "title": "The buddy system",
+ "top": "bad day?",
+ "bottom": "hug deployment initiated",
+ "mood": "wholesome",
+ "tags": [
+ "support",
+ "hug"
+ ],
+ "image": "/assets/memes/3ex8oy.jpg",
+ "template": "Sad Cat Holding Dog",
+ "source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
+ },
+ {
+ "id": "cat-097",
+ "title": "The airport pickup",
+ "top": "flight landed",
+ "bottom": "emotional support human en route",
+ "mood": "wholesome",
+ "tags": [
+ "friends",
+ "excited"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-098",
+ "title": "The compliment heist",
+ "top": "stealing this moment",
+ "bottom": "to say you’re doing great",
+ "mood": "wholesome",
+ "tags": [
+ "kindness",
+ "win"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-099",
+ "title": "The soup protocol",
+ "top": "you sound sick",
+ "bottom": "soup is already simmering",
+ "mood": "wholesome",
+ "tags": [
+ "care",
+ "love"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-100",
+ "title": "The Friday feeling",
+ "top": "you survived the week",
+ "bottom": "gold star. nap next.",
+ "mood": "wholesome",
+ "tags": [
+ "weekend",
+ "win"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-101",
+ "title": "The read receipt spiral",
+ "top": "seen 2:14pm",
+ "bottom": "reply drafted. deleted. drafted.",
+ "mood": "relatable",
+ "tags": [
+ "texting",
+ "anxiety"
+ ],
+ "image": "/assets/memes/3vnurc.jpg",
+ "template": "Crying cat on phone",
+ "source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
+ },
+ {
+ "id": "cat-102",
+ "title": "The tutorial skipper",
+ "top": "skips tutorial",
+ "bottom": "immediately needs tutorial",
+ "mood": "relatable",
+ "tags": [
+ "gaming",
+ "confused"
+ ],
+ "image": "/assets/memes/8338y8.jpg",
+ "template": "Huh Cat",
+ "source": "https://imgflip.com/meme/488919824/Huh-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2024,
+ "status": "alive",
+ "lore": "Confused in high definition."
+ },
+ {
+ "id": "cat-103",
+ "title": "The calendar invite",
+ "top": "“quick sync?”",
+ "bottom": "90 minutes. 14 people. no agenda.",
+ "mood": "relatable",
+ "tags": [
+ "work",
+ "meeting"
+ ],
+ "image": "/assets/memes/3u04h5.jpg",
+ "template": "sad thumbs up cat",
+ "source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
+ },
+ {
+ "id": "cat-104",
+ "title": "The subscription audit",
+ "top": "$4.99 here, $9.99 there",
+ "bottom": "net worth: streaming services",
+ "mood": "relatable",
+ "tags": [
+ "money",
+ "tech"
+ ],
+ "image": "/assets/memes/35v1lq.jpg",
+ "template": "white cat table",
+ "source": "https://imgflip.com/meme/191245022/white-cat-table",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
+ },
+ {
+ "id": "cat-105",
+ "title": "The adulting formula",
+ "top": "taxes + laundry +",
+ "bottom": "existential dread = tuesday",
+ "mood": "relatable",
+ "tags": [
+ "science",
+ "adulting"
+ ],
+ "image": "/assets/memes/1bif.jpg",
+ "template": "Chemistry Cat",
+ "source": "https://imgflip.com/meme/Chemistry-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "classic",
+ "lore": "Peer reviews your snacks."
+ },
+ {
+ "id": "cat-106",
+ "title": "The haircut gamble",
+ "top": "“just a trim”",
+ "bottom": "new identity unlocked",
+ "mood": "relatable",
+ "tags": [
+ "hair",
+ "regret"
+ ],
+ "image": "/assets/memes/p0a19.jpg",
+ "template": "Crying cat",
+ "source": "https://imgflip.com/meme/42003405/Crying-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2014,
+ "status": "classic",
+ "lore": "Crying professionally for a decade."
+ },
+ {
+ "id": "cat-107",
+ "title": "The out-of-office",
+ "top": "set OOO. feel peace.",
+ "bottom": "phone buzzes anyway",
+ "mood": "relatable",
+ "tags": [
+ "work",
+ "vacation"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-108",
+ "title": "The alarm snooze treaty",
+ "top": "5 more minutes",
+ "bottom": "signed every single morning",
+ "mood": "relatable",
+ "tags": [
+ "morning",
+ "sleep"
+ ],
+ "image": "/assets/memes/3e5sun.jpg",
+ "template": "I'm awake, but at what cost?",
+ "source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
+ },
+ {
+ "id": "cat-109",
+ "title": "The hallway strut",
+ "top": "heard my song",
+ "bottom": "in the grocery store",
+ "mood": "chaos",
+ "tags": [
+ "music",
+ "strut"
+ ],
+ "image": "/assets/memes/24uu.jpg",
+ "template": "Cool Cat Stroll",
+ "source": "https://imgflip.com/meme/Cool-Cat-Stroll",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
+ },
+ {
+ "id": "cat-110",
+ "title": "The microwave beep",
+ "top": "3… 2… 1…",
+ "bottom": "JUMPSCARE AT MIDNIGHT",
+ "mood": "chaos",
+ "tags": [
+ "night",
+ "surprised"
+ ],
+ "image": "/assets/memes/2hgfw.jpg",
+ "template": "Scared Cat",
+ "source": "https://imgflip.com/meme/Scared-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Startled since 2019."
+ },
+ {
+ "id": "cat-111",
+ "title": "The potassium prophecy",
+ "top": "dreams in yellow",
+ "bottom": "wakes up curved",
+ "mood": "chaos",
+ "tags": [
+ "banana",
+ "dreams"
+ ],
+ "image": "/assets/memes/7s8bhl.jpg",
+ "template": "Banana cat",
+ "source": "https://imgflip.com/meme/470680617/Banana-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Potassium with a fanbase."
+ },
+ {
+ "id": "cat-112",
+ "title": "The keyboard warrior",
+ "top": "types one email",
+ "bottom": "mouth open the whole time",
+ "mood": "chaos",
+ "tags": [
+ "work",
+ "drama"
+ ],
+ "image": "/assets/memes/4p3h78.jpg",
+ "template": "Pop Cat",
+ "source": "https://imgflip.com/meme/284017364/Pop-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
+ },
+ {
+ "id": "cat-113",
+ "title": "The elevator DJ",
+ "top": "no aux. no problem.",
+ "bottom": "humming the lobby anthem",
+ "mood": "chaos",
+ "tags": [
+ "music",
+ "vibe"
+ ],
+ "image": "/assets/memes/4l4wwy.jpg",
+ "template": "vibing cat",
+ "source": "https://imgflip.com/meme/277365922/vibing-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Headphones on. World off."
+ },
+ {
+ "id": "cat-114",
+ "title": "The laundry mountain",
+ "top": "will fold tomorrow",
+ "bottom": "tomorrow is a myth",
+ "mood": "chaos",
+ "tags": [
+ "laundry",
+ "classic"
+ ],
+ "image": "/assets/memes/tau4.jpg",
+ "template": "I Should Buy A Boat Cat",
+ "source": "https://imgflip.com/meme/I-Should-Buy-A-Boat-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Boat questionable. Box undeniable."
+ },
+ {
+ "id": "cat-115",
+ "title": "The plot twist",
+ "top": "read the same page",
+ "bottom": "three times. still lost.",
+ "mood": "chaos",
+ "tags": [
+ "reading",
+ "confused"
+ ],
+ "image": "/assets/memes/8338y8.jpg",
+ "template": "Huh Cat",
+ "source": "https://imgflip.com/meme/488919824/Huh-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2024,
+ "status": "alive",
+ "lore": "Confused in high definition."
+ },
+ {
+ "id": "cat-116",
+ "title": "The vet receipt",
+ "top": "“just a checkup”",
+ "bottom": "$400 and one betrayed stare",
+ "mood": "chaos",
+ "tags": [
+ "vet",
+ "drama"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-117",
+ "title": "The fry tax",
+ "top": "you ordered fries",
+ "bottom": "I collect the toll",
+ "mood": "snacks",
+ "tags": [
+ "fries",
+ "dinner"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-118",
+ "title": "The cereal summit",
+ "top": "emergency meeting:",
+ "bottom": "milk first or cereal first?",
+ "mood": "snacks",
+ "tags": [
+ "breakfast",
+ "debate"
+ ],
+ "image": "/assets/memes/4p3h78.jpg",
+ "template": "Pop Cat",
+ "source": "https://imgflip.com/meme/284017364/Pop-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
+ },
+ {
+ "id": "cat-119",
+ "title": "The midnight experiment",
+ "top": "control group: asleep",
+ "bottom": "test group: tacos",
+ "mood": "snacks",
+ "tags": [
+ "science",
+ "night"
+ ],
+ "image": "/assets/memes/1bif.jpg",
+ "template": "Chemistry Cat",
+ "source": "https://imgflip.com/meme/Chemistry-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "classic",
+ "lore": "Peer reviews your snacks."
+ },
+ {
+ "id": "cat-120",
+ "title": "The crumb report",
+ "top": "kitchen floor: 40% snack",
+ "bottom": "cleanup crew dispatched (me)",
+ "mood": "snacks",
+ "tags": [
+ "news",
+ "snack"
+ ],
+ "image": "/assets/memes/bgsvd.jpg",
+ "template": "Cat newspaper",
+ "source": "https://imgflip.com/meme/19259689/Cat-newspaper",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Reports the news. Is the news."
+ },
+ {
+ "id": "cat-121",
+ "title": "The snack drawer memo",
+ "top": "per my last email:",
+ "bottom": "these are MY crackers",
+ "mood": "snacks",
+ "tags": [
+ "work",
+ "snack"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-122",
+ "title": "The pizza crust connoisseur",
+ "top": "saves the best",
+ "bottom": "for last. then cries it’s over.",
+ "mood": "snacks",
+ "tags": [
+ "pizza",
+ "dinner"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-123",
+ "title": "The banana standoff",
+ "top": "perfect yellow today",
+ "bottom": "brown spots tomorrow. pressure’s on.",
+ "mood": "snacks",
+ "tags": [
+ "banana",
+ "pressure"
+ ],
+ "image": "/assets/memes/7s8bhl.jpg",
+ "template": "Banana cat",
+ "source": "https://imgflip.com/meme/470680617/Banana-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Potassium with a fanbase."
+ },
+ {
+ "id": "cat-124",
+ "title": "The drive-thru regular",
+ "top": "“the usual?”",
+ "bottom": "fame tastes like fries",
+ "mood": "snacks",
+ "tags": [
+ "fastfood",
+ "fame"
+ ],
+ "image": "/assets/memes/24uu.jpg",
+ "template": "Cool Cat Stroll",
+ "source": "https://imgflip.com/meme/Cool-Cat-Stroll",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
+ },
+ {
+ "id": "cat-125",
+ "title": "The reply-all",
+ "top": "replies all",
+ "bottom": "just to say “thanks”",
+ "mood": "judgment",
+ "tags": [
+ "work",
+ "email"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-126",
+ "title": "The loud chewer",
+ "top": "crunch. crunch.",
+ "bottom": "court is now in session",
+ "mood": "judgment",
+ "tags": [
+ "food",
+ "grumpy"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-127",
+ "title": "The group project",
+ "top": "“we should all contribute”",
+ "bottom": "— sent from my couch",
+ "mood": "judgment",
+ "tags": [
+ "school",
+ "work"
+ ],
+ "image": "/assets/memes/35v1lq.jpg",
+ "template": "white cat table",
+ "source": "https://imgflip.com/meme/191245022/white-cat-table",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
+ },
+ {
+ "id": "cat-128",
+ "title": "The hotel breakfast",
+ "top": "“continental breakfast”",
+ "bottom": "this is toast with confidence",
+ "mood": "judgment",
+ "tags": [
+ "travel",
+ "breakfast"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-129",
+ "title": "The parking spot",
+ "top": "waits 10 minutes",
+ "bottom": "for the close spot. walks anyway.",
+ "mood": "judgment",
+ "tags": [
+ "driving",
+ "logic"
+ ],
+ "image": "/assets/memes/24uu.jpg",
+ "template": "Cool Cat Stroll",
+ "source": "https://imgflip.com/meme/Cool-Cat-Stroll",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
+ },
+ {
+ "id": "cat-130",
+ "title": "The terms and conditions",
+ "top": "read 0 of 47 pages",
+ "bottom": "agreed to everything",
+ "mood": "judgment",
+ "tags": [
+ "tech",
+ "phone"
+ ],
+ "image": "/assets/memes/3u04h5.jpg",
+ "template": "sad thumbs up cat",
+ "source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
+ },
+ {
+ "id": "cat-131",
+ "title": "The influencer morning",
+ "top": "“my 5am routine”",
+ "bottom": "posted at noon",
+ "mood": "judgment",
+ "tags": [
+ "morning",
+ "social"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-132",
+ "title": "The spoiler",
+ "top": "“I won’t spoil it”",
+ "bottom": "spoils it immediately",
+ "mood": "judgment",
+ "tags": [
+ "movies",
+ "grumpy"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-133",
+ "title": "The couch gravity",
+ "top": "sat down for a sec",
+ "bottom": "woke up in 2027",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "couch"
+ ],
+ "image": "/assets/memes/11wis1.jpg",
+ "template": "Sleepy Kitten",
+ "source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
+ },
+ {
+ "id": "cat-134",
+ "title": "The blanket burrito",
+ "top": "rolled tight",
+ "bottom": "do not unroll",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "cozy"
+ ],
+ "image": "/assets/memes/lp2ba.jpg",
+ "template": "Sleepy Cat",
+ "source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
+ },
+ {
+ "id": "cat-135",
+ "title": "The Monday loading screen",
+ "top": "buffering…",
+ "bottom": "please wait indefinitely",
+ "mood": "sleepy",
+ "tags": [
+ "morning",
+ "monday"
+ ],
+ "image": "/assets/memes/3e5sun.jpg",
+ "template": "I'm awake, but at what cost?",
+ "source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
+ },
+ {
+ "id": "cat-136",
+ "title": "The 1% club",
+ "top": "phone dying",
+ "bottom": "me: same, honestly",
+ "mood": "sleepy",
+ "tags": [
+ "phone",
+ "battery"
+ ],
+ "image": "/assets/memes/3vnurc.jpg",
+ "template": "Crying cat on phone",
+ "source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
+ },
+ {
+ "id": "cat-137",
+ "title": "The sunbeam schedule",
+ "top": "sunbeam moves one inch",
+ "bottom": "I move with it",
+ "mood": "sleepy",
+ "tags": [
+ "sun",
+ "nap"
+ ],
+ "image": "/assets/memes/11wis1.jpg",
+ "template": "Sleepy Kitten",
+ "source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
+ },
+ {
+ "id": "cat-138",
+ "title": "The meeting nap",
+ "top": "camera off",
+ "bottom": "soul off too",
+ "mood": "sleepy",
+ "tags": [
+ "work",
+ "meeting"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-139",
+ "title": "The yawn contagion",
+ "top": "one yawn",
+ "bottom": "whole office infected",
+ "mood": "sleepy",
+ "tags": [
+ "work",
+ "tired"
+ ],
+ "image": "/assets/memes/lp2ba.jpg",
+ "template": "Sleepy Cat",
+ "source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
+ },
+ {
+ "id": "cat-140",
+ "title": "The daylight savings",
+ "top": "lost one hour",
+ "bottom": "will mourn for weeks",
+ "mood": "sleepy",
+ "tags": [
+ "time",
+ "tired"
+ ],
+ "image": "/assets/memes/3e5sun.jpg",
+ "template": "I'm awake, but at what cost?",
+ "source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
+ },
+ {
+ "id": "cat-141",
+ "title": "The bedtime story",
+ "top": "one more video",
+ "bottom": "47 videos later",
+ "mood": "sleepy",
+ "tags": [
+ "phone",
+ "night"
+ ],
+ "image": "/assets/memes/3vnurc.jpg",
+ "template": "Crying cat on phone",
+ "source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
+ },
+ {
+ "id": "cat-142",
+ "title": "The sidewalk flower",
+ "top": "picked for you",
+ "bottom": "slightly crushed. full of love.",
+ "mood": "wholesome",
+ "tags": [
+ "kindness",
+ "love"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-143",
+ "title": "The rain rescue",
+ "top": "shares umbrella",
+ "bottom": "gets wet. worth it.",
+ "mood": "wholesome",
+ "tags": [
+ "kindness",
+ "rain"
+ ],
+ "image": "/assets/memes/3ex8oy.jpg",
+ "template": "Sad Cat Holding Dog",
+ "source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
+ },
+ {
+ "id": "cat-144",
+ "title": "The good news sprint",
+ "top": "heard your news",
+ "bottom": "already celebrating",
+ "mood": "wholesome",
+ "tags": [
+ "friends",
+ "win"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-145",
+ "title": "The tiny victory",
+ "top": "made the bed",
+ "bottom": "unstoppable now",
+ "mood": "wholesome",
+ "tags": [
+ "win",
+ "morning"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-146",
+ "title": "The playlist gift",
+ "top": "made you 47 songs",
+ "bottom": "all say “you got this”",
+ "mood": "wholesome",
+ "tags": [
+ "music",
+ "friends"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-147",
+ "title": "The couch counselor",
+ "top": "no advice. just snacks.",
+ "bottom": "plus premium listening",
+ "mood": "wholesome",
+ "tags": [
+ "support",
+ "snacks"
+ ],
+ "image": "/assets/memes/3ex8oy.jpg",
+ "template": "Sad Cat Holding Dog",
+ "source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
+ },
+ {
+ "id": "cat-148",
+ "title": "The reunion zoomies",
+ "top": "haven’t seen you in DAYS",
+ "bottom": "(it was 3 hours)",
+ "mood": "wholesome",
+ "tags": [
+ "friends",
+ "love"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-149",
+ "title": "The window sunrise",
+ "top": "saved you a seat",
+ "bottom": "best view at 7am",
+ "mood": "wholesome",
+ "tags": [
+ "morning",
+ "cozy"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-150",
+ "title": "The thank-you note",
+ "top": "you helped me once",
+ "bottom": "now I tell everyone",
+ "mood": "wholesome",
+ "tags": [
+ "gratitude",
+ "love"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-151",
+ "title": "The typing Olympics",
+ "top": "drafting the perfect reply",
+ "bottom": "sends “ok”",
+ "mood": "relatable",
+ "tags": [
+ "texting",
+ "friends"
+ ],
+ "image": "/assets/memes/3vnurc.jpg",
+ "template": "Crying cat on phone",
+ "source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
+ },
+ {
+ "id": "cat-152",
+ "title": "The meeting math",
+ "top": "“quick question”",
+ "bottom": "27 follow-ups later",
+ "mood": "relatable",
+ "tags": [
+ "work",
+ "meeting"
+ ],
+ "image": "/assets/memes/8338y8.jpg",
+ "template": "Huh Cat",
+ "source": "https://imgflip.com/meme/488919824/Huh-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2024,
+ "status": "alive",
+ "lore": "Confused in high definition."
+ },
+ {
+ "id": "cat-153",
+ "title": "The wifi funeral",
+ "top": "one bar. one dream.",
+ "bottom": "buffering my personality",
+ "mood": "relatable",
+ "tags": [
+ "tech",
+ "phone"
+ ],
+ "image": "/assets/memes/3u04h5.jpg",
+ "template": "sad thumbs up cat",
+ "source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
+ },
+ {
+ "id": "cat-154",
+ "title": "The plant parent",
+ "top": "buys a fern",
+ "bottom": "funeral by friday",
+ "mood": "relatable",
+ "tags": [
+ "plants",
+ "fail"
+ ],
+ "image": "/assets/memes/35v1lq.jpg",
+ "template": "white cat table",
+ "source": "https://imgflip.com/meme/191245022/white-cat-table",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
+ },
+ {
+ "id": "cat-155",
+ "title": "The group chat economist",
+ "top": "venmo request for $2.14",
+ "bottom": "plus emotional damages",
+ "mood": "relatable",
+ "tags": [
+ "money",
+ "friends"
+ ],
+ "image": "/assets/memes/1bif.jpg",
+ "template": "Chemistry Cat",
+ "source": "https://imgflip.com/meme/Chemistry-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "classic",
+ "lore": "Peer reviews your snacks."
+ },
+ {
+ "id": "cat-156",
+ "title": "The password saga",
+ "top": "incorrect password",
+ "bottom": "it was the same password",
+ "mood": "relatable",
+ "tags": [
+ "tech",
+ "fail"
+ ],
+ "image": "/assets/memes/p0a19.jpg",
+ "template": "Crying cat",
+ "source": "https://imgflip.com/meme/42003405/Crying-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2014,
+ "status": "classic",
+ "lore": "Crying professionally for a decade."
+ },
+ {
+ "id": "cat-157",
+ "title": "The lunch thief suspect",
+ "top": "someone ate my yogurt",
+ "bottom": "trust exercises cancelled",
+ "mood": "relatable",
+ "tags": [
+ "work",
+ "lunch"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-158",
+ "title": "The snooze loop",
+ "top": "alarm at 7. 7:09. 7:18.",
+ "bottom": "up at 7:47 somehow",
+ "mood": "relatable",
+ "tags": [
+ "morning",
+ "sleep"
+ ],
+ "image": "/assets/memes/3e5sun.jpg",
+ "template": "I'm awake, but at what cost?",
+ "source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
+ },
+ {
+ "id": "cat-159",
+ "title": "The compliment dodge",
+ "top": "“you look nice today”",
+ "bottom": "this? old thing. thanks though",
+ "mood": "relatable",
+ "tags": [
+ "social",
+ "friends"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-160",
+ "title": "The friday save",
+ "top": "email at 4:59pm",
+ "bottom": "see you never",
+ "mood": "relatable",
+ "tags": [
+ "work",
+ "weekend"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-161",
+ "title": "The self-checkout",
+ "top": "unexpected item",
+ "bottom": "it’s me. i’m the item.",
+ "mood": "relatable",
+ "tags": [
+ "shopping",
+ "fail"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-162",
+ "title": "The autocorrect apology",
+ "top": "meant “love you”",
+ "bottom": "sent “live you”. standing by it.",
+ "mood": "relatable",
+ "tags": [
+ "texting",
+ "fail"
+ ],
+ "image": "/assets/memes/4p3h78.jpg",
+ "template": "Pop Cat",
+ "source": "https://imgflip.com/meme/284017364/Pop-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
+ },
+ {
+ "id": "cat-163",
+ "title": "The small talk",
+ "top": "“crazy weather, huh”",
+ "bottom": "performed flawlessly. retiring.",
+ "mood": "relatable",
+ "tags": [
+ "social",
+ "win"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-164",
+ "title": "The ‘k’ text",
+ "top": "replies “k”",
+ "bottom": "war has begun",
+ "mood": "relatable",
+ "tags": [
+ "texting",
+ "drama"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-165",
+ "title": "The horoscope",
+ "top": "“today you’ll thrive”",
+ "bottom": "naps aggressively to comply",
+ "mood": "relatable",
+ "tags": [
+ "nap",
+ "stars"
+ ],
+ "image": "/assets/memes/bgsvd.jpg",
+ "template": "Cat newspaper",
+ "source": "https://imgflip.com/meme/19259689/Cat-newspaper",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Reports the news. Is the news."
+ },
+ {
+ "id": "cat-166",
+ "title": "The monday outfit",
+ "top": "dressed for success",
+ "bottom": "success is sweatpants",
+ "mood": "relatable",
+ "tags": [
+ "work",
+ "monday"
+ ],
+ "image": "/assets/memes/lp2ba.jpg",
+ "template": "Sleepy Cat",
+ "source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
+ },
+ {
+ "id": "cat-167",
+ "title": "The pep talk",
+ "top": "“you got this”",
+ "bottom": "source: me, crying too",
+ "mood": "relatable",
+ "tags": [
+ "support",
+ "friends"
+ ],
+ "image": "/assets/memes/3ex8oy.jpg",
+ "template": "Sad Cat Holding Dog",
+ "source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
+ },
+ {
+ "id": "cat-168",
+ "title": "The cartwheel",
+ "top": "nobody: ...",
+ "bottom": "absolutely nobody: ...",
+ "mood": "chaos",
+ "tags": [
+ "random",
+ "classic"
+ ],
+ "image": "/assets/memes/24uu.jpg",
+ "template": "Cool Cat Stroll",
+ "source": "https://imgflip.com/meme/Cool-Cat-Stroll",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
+ },
+ {
+ "id": "cat-169",
+ "title": "The plot armor",
+ "top": "trips on nothing",
+ "bottom": "in front of everyone",
+ "mood": "chaos",
+ "tags": [
+ "fail",
+ "public"
+ ],
+ "image": "/assets/memes/2hgfw.jpg",
+ "template": "Scared Cat",
+ "source": "https://imgflip.com/meme/Scared-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Startled since 2019."
+ },
+ {
+ "id": "cat-170",
+ "title": "The banana phone",
+ "top": "ring ring",
+ "bottom": "potassium calling",
+ "mood": "chaos",
+ "tags": [
+ "banana",
+ "phone"
+ ],
+ "image": "/assets/memes/7s8bhl.jpg",
+ "template": "Banana cat",
+ "source": "https://imgflip.com/meme/470680617/Banana-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Potassium with a fanbase."
+ },
+ {
+ "id": "cat-171",
+ "title": "The echo",
+ "top": "says “hello?” in tunnel",
+ "bottom": "tunnel says it better",
+ "mood": "chaos",
+ "tags": [
+ "tunnel",
+ "drama"
+ ],
+ "image": "/assets/memes/4p3h78.jpg",
+ "template": "Pop Cat",
+ "source": "https://imgflip.com/meme/284017364/Pop-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
+ },
+ {
+ "id": "cat-172",
+ "title": "The hold music",
+ "top": "on hold 40 minutes",
+ "bottom": "vibing unironically now",
+ "mood": "chaos",
+ "tags": [
+ "music",
+ "phone"
+ ],
+ "image": "/assets/memes/4l4wwy.jpg",
+ "template": "vibing cat",
+ "source": "https://imgflip.com/meme/277365922/vibing-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Headphones on. World off."
+ },
+ {
+ "id": "cat-173",
+ "title": "The junk drawer",
+ "top": "contains: 3 batteries",
+ "bottom": "0 working pens. 1 mystery key.",
+ "mood": "chaos",
+ "tags": [
+ "home",
+ "mystery"
+ ],
+ "image": "/assets/memes/tau4.jpg",
+ "template": "I Should Buy A Boat Cat",
+ "source": "https://imgflip.com/meme/I-Should-Buy-A-Boat-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Boat questionable. Box undeniable."
+ },
+ {
+ "id": "cat-174",
+ "title": "The captcha",
+ "top": "select all traffic lights",
+ "bottom": "fails. am robot now.",
+ "mood": "chaos",
+ "tags": [
+ "tech",
+ "fail"
+ ],
+ "image": "/assets/memes/8338y8.jpg",
+ "template": "Huh Cat",
+ "source": "https://imgflip.com/meme/488919824/Huh-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2024,
+ "status": "alive",
+ "lore": "Confused in high definition."
+ },
+ {
+ "id": "cat-175",
+ "title": "The dramatic exit",
+ "top": "storms out",
+ "bottom": "forgets keys. returns.",
+ "mood": "chaos",
+ "tags": [
+ "drama",
+ "fail"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-176",
+ "title": "The elevator performance",
+ "top": "alone in elevator",
+ "bottom": "grammy-worthy mirror check",
+ "mood": "chaos",
+ "tags": [
+ "music",
+ "alone"
+ ],
+ "image": "/assets/memes/11wis1.jpg",
+ "template": "Sleepy Kitten",
+ "source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
+ },
+ {
+ "id": "cat-177",
+ "title": "The reply guy",
+ "top": "first to reply-all",
+ "bottom": "“adding myself to this thread”",
+ "mood": "chaos",
+ "tags": [
+ "work",
+ "email"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-178",
+ "title": "The update",
+ "top": "“update tonight?”",
+ "bottom": "phone is now a brick",
+ "mood": "chaos",
+ "tags": [
+ "tech",
+ "phone"
+ ],
+ "image": "/assets/memes/3u04h5.jpg",
+ "template": "sad thumbs up cat",
+ "source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
+ },
+ {
+ "id": "cat-179",
+ "title": "The group photo",
+ "top": "“one more!”",
+ "bottom": "47 photos later",
+ "mood": "chaos",
+ "tags": [
+ "photo",
+ "friends"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-180",
+ "title": "The wave back",
+ "top": "waves at someone",
+ "bottom": "behind me. always behind me.",
+ "mood": "chaos",
+ "tags": [
+ "social",
+ "fail"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-181",
+ "title": "The plot hole",
+ "top": "“it was all a dream”",
+ "bottom": "worst. trope. ever.",
+ "mood": "chaos",
+ "tags": [
+ "movies",
+ "opinion"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-182",
+ "title": "The jump scare",
+ "top": "own reflection",
+ "bottom": "in a dark hallway",
+ "mood": "chaos",
+ "tags": [
+ "night",
+ "surprised"
+ ],
+ "image": "/assets/memes/2hgfw.jpg",
+ "template": "Scared Cat",
+ "source": "https://imgflip.com/meme/Scared-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Startled since 2019."
+ },
+ {
+ "id": "cat-183",
+ "title": "The breaking news",
+ "top": "local cat naps",
+ "bottom": "through entire eclipse",
+ "mood": "chaos",
+ "tags": [
+ "news",
+ "nap"
+ ],
+ "image": "/assets/memes/bgsvd.jpg",
+ "template": "Cat newspaper",
+ "source": "https://imgflip.com/meme/19259689/Cat-newspaper",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Reports the news. Is the news."
+ },
+ {
+ "id": "cat-184",
+ "title": "The hype man",
+ "top": "friend got a haircut",
+ "bottom": "screaming like it’s a wedding",
+ "mood": "chaos",
+ "tags": [
+ "friends",
+ "hair"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-185",
+ "title": "The chip hierarchy",
+ "top": "big chip for me",
+ "bottom": "broken one for… also me",
+ "mood": "snacks",
+ "tags": [
+ "chips",
+ "snack"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-186",
+ "title": "The popcorn protocol",
+ "top": "movie starts in 5",
+ "bottom": "already half the bowl",
+ "mood": "snacks",
+ "tags": [
+ "movies",
+ "popcorn"
+ ],
+ "image": "/assets/memes/4p3h78.jpg",
+ "template": "Pop Cat",
+ "source": "https://imgflip.com/meme/284017364/Pop-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Two frames. Infinite volume."
+ },
+ {
+ "id": "cat-187",
+ "title": "The toast theorem",
+ "top": "butter side always down",
+ "bottom": "peer reviewed. devastating.",
+ "mood": "snacks",
+ "tags": [
+ "science",
+ "breakfast"
+ ],
+ "image": "/assets/memes/1bif.jpg",
+ "template": "Chemistry Cat",
+ "source": "https://imgflip.com/meme/Chemistry-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "classic",
+ "lore": "Peer reviews your snacks."
+ },
+ {
+ "id": "cat-188",
+ "title": "The menu investigation",
+ "top": "reads entire menu",
+ "bottom": "orders the usual",
+ "mood": "snacks",
+ "tags": [
+ "dinner",
+ "habit"
+ ],
+ "image": "/assets/memes/bgsvd.jpg",
+ "template": "Cat newspaper",
+ "source": "https://imgflip.com/meme/19259689/Cat-newspaper",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Reports the news. Is the news."
+ },
+ {
+ "id": "cat-189",
+ "title": "The office cake",
+ "top": "“help yourself!”",
+ "bottom": "takes the corner piece. power move.",
+ "mood": "snacks",
+ "tags": [
+ "work",
+ "cake"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-190",
+ "title": "The cookie swap",
+ "top": "one cookie each",
+ "bottom": "mine had more chips. justice.",
+ "mood": "snacks",
+ "tags": [
+ "cookies",
+ "justice"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-191",
+ "title": "The smoothie regret",
+ "top": "added spinach",
+ "bottom": "tastes like lawn. healthy lawn.",
+ "mood": "snacks",
+ "tags": [
+ "health",
+ "regret"
+ ],
+ "image": "/assets/memes/7s8bhl.jpg",
+ "template": "Banana cat",
+ "source": "https://imgflip.com/meme/470680617/Banana-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Potassium with a fanbase."
+ },
+ {
+ "id": "cat-192",
+ "title": "The taco balance",
+ "top": "eating tacos gracefully",
+ "bottom": "a myth. delicious myth.",
+ "mood": "snacks",
+ "tags": [
+ "tacos",
+ "dinner"
+ ],
+ "image": "/assets/memes/24uu.jpg",
+ "template": "Cool Cat Stroll",
+ "source": "https://imgflip.com/meme/Cool-Cat-Stroll",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
+ },
+ {
+ "id": "cat-193",
+ "title": "The diet starts",
+ "top": "monday. definitely monday.",
+ "bottom": "orders fries. it’s sunday.",
+ "mood": "snacks",
+ "tags": [
+ "diet",
+ "fries"
+ ],
+ "image": "/assets/memes/3u04h5.jpg",
+ "template": "sad thumbs up cat",
+ "source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
+ },
+ {
+ "id": "cat-194",
+ "title": "The waiter watch",
+ "top": "sees waiter coming",
+ "bottom": "suddenly mid-sentence",
+ "mood": "snacks",
+ "tags": [
+ "dinner",
+ "social"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-195",
+ "title": "The delivery tracker",
+ "top": "driver is 2 stops away",
+ "bottom": "staring out window like a dog",
+ "mood": "snacks",
+ "tags": [
+ "delivery",
+ "hungry"
+ ],
+ "image": "/assets/memes/3vnurc.jpg",
+ "template": "Crying cat on phone",
+ "source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
+ },
+ {
+ "id": "cat-196",
+ "title": "The secret stash",
+ "top": "hiding chocolate",
+ "bottom": "from myself. forgot where.",
+ "mood": "snacks",
+ "tags": [
+ "chocolate",
+ "fail"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-197",
+ "title": "The brunch rush",
+ "top": "table for brunch",
+ "bottom": "45 min wait. worth it.",
+ "mood": "snacks",
+ "tags": [
+ "brunch",
+ "weekend"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-198",
+ "title": "The recipe review",
+ "top": "“i swapped everything”",
+ "bottom": "one star. recipe’s fault.",
+ "mood": "snacks",
+ "tags": [
+ "cooking",
+ "review"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-199",
+ "title": "The fancy dinner",
+ "top": "tiny portions",
+ "bottom": "stops for tacos after",
+ "mood": "snacks",
+ "tags": [
+ "dinner",
+ "tacos"
+ ],
+ "image": "/assets/memes/35v1lq.jpg",
+ "template": "white cat table",
+ "source": "https://imgflip.com/meme/191245022/white-cat-table",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
+ },
+ {
+ "id": "cat-200",
+ "title": "The ice cream",
+ "top": "“just one scoop”",
+ "bottom": "the tub is gone",
+ "mood": "snacks",
+ "tags": [
+ "dessert",
+ "fail"
+ ],
+ "image": "/assets/memes/p0a19.jpg",
+ "template": "Crying cat",
+ "source": "https://imgflip.com/meme/42003405/Crying-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2014,
+ "status": "classic",
+ "lore": "Crying professionally for a decade."
+ },
+ {
+ "id": "cat-201",
+ "title": "The midnight cereal",
+ "top": "bowl at 1am",
+ "bottom": "hits different. scientifically.",
+ "mood": "snacks",
+ "tags": [
+ "cereal",
+ "night"
+ ],
+ "image": "/assets/memes/lp2ba.jpg",
+ "template": "Sleepy Cat",
+ "source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
+ },
+ {
+ "id": "cat-202",
+ "title": "The loud typer",
+ "top": "mechanical keyboard",
+ "bottom": "in a quiet café",
+ "mood": "judgment",
+ "tags": [
+ "noise",
+ "laptop"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-203",
+ "title": "The spoiler, part II",
+ "top": "“you finished it, right?”",
+ "bottom": "hadn’t. have now. thanks.",
+ "mood": "judgment",
+ "tags": [
+ "movies",
+ "spoiler"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-204",
+ "title": "The meeting monologue",
+ "top": "“i’ll keep it brief”",
+ "bottom": "narrates entire quarter",
+ "mood": "judgment",
+ "tags": [
+ "work",
+ "meeting"
+ ],
+ "image": "/assets/memes/35v1lq.jpg",
+ "template": "white cat table",
+ "source": "https://imgflip.com/meme/191245022/white-cat-table",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
+ },
+ {
+ "id": "cat-205",
+ "title": "The restaurant critic",
+ "top": "“the vibe is off”",
+ "bottom": "it’s a tuesday, kevin",
+ "mood": "judgment",
+ "tags": [
+ "dinner",
+ "attitude"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-206",
+ "title": "The slow walkers",
+ "top": "group of five",
+ "bottom": "entire sidewalk. all of it.",
+ "mood": "judgment",
+ "tags": [
+ "walking",
+ "city"
+ ],
+ "image": "/assets/memes/24uu.jpg",
+ "template": "Cool Cat Stroll",
+ "source": "https://imgflip.com/meme/Cool-Cat-Stroll",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "alive",
+ "lore": "Struts like rent is due."
+ },
+ {
+ "id": "cat-207",
+ "title": "The software license",
+ "top": "“accept all cookies”",
+ "bottom": "sells soul for 10% off",
+ "mood": "judgment",
+ "tags": [
+ "tech",
+ "cookies"
+ ],
+ "image": "/assets/memes/3u04h5.jpg",
+ "template": "sad thumbs up cat",
+ "source": "https://imgflip.com/meme/231792809/sad-thumbs-up-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Thumbs up. Spirits down."
+ },
+ {
+ "id": "cat-208",
+ "title": "The gym selfie",
+ "top": "posts leg day",
+ "bottom": "skipped legs",
+ "mood": "judgment",
+ "tags": [
+ "gym",
+ "social"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-209",
+ "title": "The phone call",
+ "top": "calls instead of texting",
+ "bottom": "a bold, hostile choice",
+ "mood": "judgment",
+ "tags": [
+ "phone",
+ "social"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-210",
+ "title": "The desk neighbor",
+ "top": "eats tuna at desk",
+ "bottom": "hr has been notified",
+ "mood": "judgment",
+ "tags": [
+ "work",
+ "food"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-211",
+ "title": "The early bird",
+ "top": "“morning people win”",
+ "bottom": "win what. exactly.",
+ "mood": "judgment",
+ "tags": [
+ "morning",
+ "debate"
+ ],
+ "image": "/assets/memes/3e5sun.jpg",
+ "template": "I'm awake, but at what cost?",
+ "source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
+ },
+ {
+ "id": "cat-212",
+ "title": "The horror movie",
+ "top": "“don’t go in there!”",
+ "bottom": "goes in there",
+ "mood": "judgment",
+ "tags": [
+ "movies",
+ "logic"
+ ],
+ "image": "/assets/memes/2hgfw.jpg",
+ "template": "Scared Cat",
+ "source": "https://imgflip.com/meme/Scared-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Startled since 2019."
+ },
+ {
+ "id": "cat-213",
+ "title": "The opinion column",
+ "top": "“unpopular opinion:”",
+ "bottom": "extremely popular opinion",
+ "mood": "judgment",
+ "tags": [
+ "opinion",
+ "social"
+ ],
+ "image": "/assets/memes/bgsvd.jpg",
+ "template": "Cat newspaper",
+ "source": "https://imgflip.com/meme/19259689/Cat-newspaper",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Reports the news. Is the news."
+ },
+ {
+ "id": "cat-214",
+ "title": "The playlist thief",
+ "top": "steals my playlist",
+ "bottom": "gets more likes",
+ "mood": "judgment",
+ "tags": [
+ "music",
+ "betrayal"
+ ],
+ "image": "/assets/memes/4l4wwy.jpg",
+ "template": "vibing cat",
+ "source": "https://imgflip.com/meme/277365922/vibing-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2020,
+ "status": "classic",
+ "lore": "Headphones on. World off."
+ },
+ {
+ "id": "cat-215",
+ "title": "The compliment fish",
+ "top": "“new haircut, don’t care”",
+ "bottom": "posted 4 angles",
+ "mood": "judgment",
+ "tags": [
+ "hair",
+ "social"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-216",
+ "title": "The over-packer",
+ "top": "weekend trip",
+ "bottom": "3 suitcases. 2 outfits.",
+ "mood": "judgment",
+ "tags": [
+ "travel",
+ "packing"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-217",
+ "title": "The baby voice",
+ "top": "talks to dog like baby",
+ "bottom": "in public. no shame.",
+ "mood": "judgment",
+ "tags": [
+ "dogs",
+ "public"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-218",
+ "title": "The advice",
+ "top": "“just be yourself”",
+ "bottom": "myself is the problem",
+ "mood": "judgment",
+ "tags": [
+ "advice",
+ "truth"
+ ],
+ "image": "/assets/memes/3ex8oy.jpg",
+ "template": "Sad Cat Holding Dog",
+ "source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
+ },
+ {
+ "id": "cat-219",
+ "title": "The blink",
+ "top": "rests eyes for a sec",
+ "bottom": "2 hours. drool.",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "oops"
+ ],
+ "image": "/assets/memes/11wis1.jpg",
+ "template": "Sleepy Kitten",
+ "source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
+ },
+ {
+ "id": "cat-220",
+ "title": "The snooze math",
+ "top": "9 more minutes × 6",
+ "bottom": "still tired. shocking.",
+ "mood": "sleepy",
+ "tags": [
+ "morning",
+ "math"
+ ],
+ "image": "/assets/memes/lp2ba.jpg",
+ "template": "Sleepy Cat",
+ "source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
+ },
+ {
+ "id": "cat-221",
+ "title": "The 3pm wall",
+ "top": "hits like a truck",
+ "bottom": "truck full of pillows",
+ "mood": "sleepy",
+ "tags": [
+ "afternoon",
+ "tired"
+ ],
+ "image": "/assets/memes/3e5sun.jpg",
+ "template": "I'm awake, but at what cost?",
+ "source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
+ },
+ {
+ "id": "cat-222",
+ "title": "The scroll hole",
+ "top": "“last video”",
+ "bottom": "sunrise. birds.",
+ "mood": "sleepy",
+ "tags": [
+ "phone",
+ "night"
+ ],
+ "image": "/assets/memes/3vnurc.jpg",
+ "template": "Crying cat on phone",
+ "source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
+ },
+ {
+ "id": "cat-223",
+ "title": "The cozy trap",
+ "top": "blanket + rain sounds",
+ "bottom": "plans: cancelled",
+ "mood": "sleepy",
+ "tags": [
+ "cozy",
+ "rain"
+ ],
+ "image": "/assets/memes/11wis1.jpg",
+ "template": "Sleepy Kitten",
+ "source": "https://imgflip.com/meme/63663121/Sleepy-Kitten",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Eepy before eepy was a word."
+ },
+ {
+ "id": "cat-224",
+ "title": "The conference coma",
+ "top": "camera on. brain off.",
+ "bottom": "nodding professionally",
+ "mood": "sleepy",
+ "tags": [
+ "work",
+ "meeting"
+ ],
+ "image": "/assets/memes/1bh7.jpg",
+ "template": "Business Cat",
+ "source": "https://imgflip.com/meme/Business-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2011,
+ "status": "immortal",
+ "lore": "Climbed the corporate ladder. Knocked it over."
+ },
+ {
+ "id": "cat-225",
+ "title": "The nap math",
+ "top": "20 min power nap",
+ "bottom": "4 hours. oops.",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "oops"
+ ],
+ "image": "/assets/memes/lp2ba.jpg",
+ "template": "Sleepy Cat",
+ "source": "https://imgflip.com/meme/36441334/Sleepy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "classic",
+ "lore": "Horizontal since forever."
+ },
+ {
+ "id": "cat-226",
+ "title": "The timezone",
+ "top": "flies 3 hours",
+ "bottom": "jet lagged for a month",
+ "mood": "sleepy",
+ "tags": [
+ "travel",
+ "tired"
+ ],
+ "image": "/assets/memes/3e5sun.jpg",
+ "template": "I'm awake, but at what cost?",
+ "source": "https://imgflip.com/meme/205183823/Im-awake-but-at-what-cost",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Awake. At what cost."
+ },
+ {
+ "id": "cat-227",
+ "title": "The low battery",
+ "top": "phone at 4%",
+ "bottom": "no charger. this is the end.",
+ "mood": "sleepy",
+ "tags": [
+ "phone",
+ "battery"
+ ],
+ "image": "/assets/memes/3vnurc.jpg",
+ "template": "Crying cat on phone",
+ "source": "https://imgflip.com/meme/234579576/Crying-cat-on-phone",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Doomscrolling before it was cool."
+ },
+ {
+ "id": "cat-228",
+ "title": "The good stretch",
+ "top": "morning stretch",
+ "bottom": "cracks like glow sticks",
+ "mood": "sleepy",
+ "tags": [
+ "morning",
+ "stretch"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-229",
+ "title": "The warm laundry",
+ "top": "hugs fresh laundry",
+ "bottom": "becomes laundry",
+ "mood": "sleepy",
+ "tags": [
+ "laundry",
+ "cozy"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-230",
+ "title": "The friday nap",
+ "top": "“quick nap before dinner”",
+ "bottom": "wakes up saturday",
+ "mood": "sleepy",
+ "tags": [
+ "weekend",
+ "nap"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-231",
+ "title": "The snorer",
+ "top": "“i don’t snore”",
+ "bottom": "recorded evidence attached",
+ "mood": "sleepy",
+ "tags": [
+ "sleep",
+ "evidence"
+ ],
+ "image": "/assets/memes/5do96.jpg",
+ "template": "Grumpy Cat Not Amused",
+ "source": "https://imgflip.com/meme/Grumpy-Cat-Not-Amused",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "The gold standard of not-amused."
+ },
+ {
+ "id": "cat-232",
+ "title": "The monday alarm",
+ "top": "5 alarms set",
+ "bottom": "sleeps through all 5",
+ "mood": "sleepy",
+ "tags": [
+ "morning",
+ "monday"
+ ],
+ "image": "/assets/memes/8p0a.jpg",
+ "template": "Grumpy Cat",
+ "source": "https://imgflip.com/meme/Grumpy-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2012,
+ "status": "immortal",
+ "lore": "Grumpy since 2012. Forever unimpressed."
+ },
+ {
+ "id": "cat-233",
+ "title": "The night owl",
+ "top": "“so productive at night”",
+ "bottom": "produces: anxiety",
+ "mood": "sleepy",
+ "tags": [
+ "night",
+ "truth"
+ ],
+ "image": "/assets/memes/35v1lq.jpg",
+ "template": "white cat table",
+ "source": "https://imgflip.com/meme/191245022/white-cat-table",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "Sits at tables. Judges at tables."
+ },
+ {
+ "id": "cat-234",
+ "title": "The pillow war",
+ "top": "steals all pillows",
+ "bottom": "denies everything",
+ "mood": "sleepy",
+ "tags": [
+ "sleep",
+ "denial"
+ ],
+ "image": "/assets/memes/39binw.jpg",
+ "template": "Smudge the cat",
+ "source": "https://imgflip.com/meme/197052476/Smudge-the-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2018,
+ "status": "immortal",
+ "lore": "Yells back at dinner since 2018."
+ },
+ {
+ "id": "cat-235",
+ "title": "The proud parent",
+ "top": "kid drew a potato",
+ "bottom": "framing it. obviously.",
+ "mood": "wholesome",
+ "tags": [
+ "kids",
+ "love"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-236",
+ "title": "The lost tourist",
+ "top": "looks lost",
+ "bottom": "walks them there anyway",
+ "mood": "wholesome",
+ "tags": [
+ "kindness",
+ "city"
+ ],
+ "image": "/assets/memes/3ex8oy.jpg",
+ "template": "Sad Cat Holding Dog",
+ "source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
+ },
+ {
+ "id": "cat-237",
+ "title": "The promotion",
+ "top": "friend got promoted",
+ "bottom": "louder than the promotion",
+ "mood": "wholesome",
+ "tags": [
+ "friends",
+ "win"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-238",
+ "title": "The first snow",
+ "top": "first snowflake",
+ "bottom": "acts like it’s magic. it is.",
+ "mood": "wholesome",
+ "tags": [
+ "winter",
+ "wonder"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-239",
+ "title": "The grandma call",
+ "top": "calls grandma",
+ "bottom": "stays on for an hour",
+ "mood": "wholesome",
+ "tags": [
+ "family",
+ "love"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-240",
+ "title": "The shelter visit",
+ "top": "“just looking”",
+ "bottom": "leaves with a cat",
+ "mood": "wholesome",
+ "tags": [
+ "cats",
+ "love"
+ ],
+ "image": "/assets/memes/3ex8oy.jpg",
+ "template": "Sad Cat Holding Dog",
+ "source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
+ },
+ {
+ "id": "cat-241",
+ "title": "The home run",
+ "top": "kid’s first goal",
+ "bottom": "entire stands erupt",
+ "mood": "wholesome",
+ "tags": [
+ "kids",
+ "sports"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-242",
+ "title": "The thank-you wave",
+ "top": "lets car merge",
+ "bottom": "gets the wave. best feeling.",
+ "mood": "wholesome",
+ "tags": [
+ "driving",
+ "kindness"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-243",
+ "title": "The birthday song",
+ "top": "off-key singing",
+ "bottom": "loudest in the room",
+ "mood": "wholesome",
+ "tags": [
+ "birthday",
+ "friends"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-244",
+ "title": "The study buddy",
+ "top": "brings snacks",
+ "bottom": "to the library grind",
+ "mood": "wholesome",
+ "tags": [
+ "friends",
+ "snacks"
+ ],
+ "image": "/assets/memes/3ex8oy.jpg",
+ "template": "Sad Cat Holding Dog",
+ "source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
+ },
+ {
+ "id": "cat-245",
+ "title": "The finish line",
+ "top": "cheers strangers",
+ "bottom": "at mile 20. all of them.",
+ "mood": "wholesome",
+ "tags": [
+ "sports",
+ "kindness"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-246",
+ "title": "The lucky penny",
+ "top": "finds a penny",
+ "bottom": "day = made",
+ "mood": "wholesome",
+ "tags": [
+ "luck",
+ "joy"
+ ],
+ "image": "/assets/memes/4xgqu.jpg",
+ "template": "Cute Cat",
+ "source": "https://imgflip.com/meme/Cute-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2015,
+ "status": "classic",
+ "lore": "Certified cute. No notes."
+ },
+ {
+ "id": "cat-247",
+ "title": "The neighbor cookies",
+ "top": "bakes too much",
+ "bottom": "doorbell ditch with love",
+ "mood": "wholesome",
+ "tags": [
+ "neighbors",
+ "baking"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-248",
+ "title": "The old dog",
+ "top": "slow on walks",
+ "bottom": "we go slow. gladly.",
+ "mood": "wholesome",
+ "tags": [
+ "dogs",
+ "love"
+ ],
+ "image": "/assets/memes/3ex8oy.jpg",
+ "template": "Sad Cat Holding Dog",
+ "source": "https://imgflip.com/meme/206464066/Sad-Cat-Holding-Dog",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Emotional support, holding."
+ },
+ {
+ "id": "cat-249",
+ "title": "The standing ovation",
+ "top": "school play",
+ "bottom": "front row. tears.",
+ "mood": "wholesome",
+ "tags": [
+ "kids",
+ "proud"
+ ],
+ "image": "/assets/memes/72k5g.jpg",
+ "template": "Excited Cat",
+ "source": "https://imgflip.com/meme/Excited-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Excitement levels: unsafe."
+ },
+ {
+ "id": "cat-250",
+ "title": "The 250th cat",
+ "top": "you made it here",
+ "bottom": "the cats are proud of you",
+ "mood": "wholesome",
+ "tags": [
+ "milestone",
+ "love"
+ ],
+ "image": "/assets/memes/amuvy.jpg",
+ "template": "Smiling Cat",
+ "source": "https://imgflip.com/meme/Smiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2022,
+ "status": "alive",
+ "lore": "Smiling through the chaos."
+ },
+ {
+ "id": "cat-251",
+ "title": "The deadline approaches",
+ "top": "heavy breathing intensifies",
+ "bottom": "it’s due in 20 minutes",
+ "mood": "relatable",
+ "tags": [
+ "deadline",
+ "work"
+ ],
+ "image": "/assets/memes/7rf5z.jpg",
+ "template": "Heavy Breathing Cat",
+ "source": "https://imgflip.com/meme/Heavy-Breathing-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Inhales deadlines. Exhales panic."
+ },
+ {
+ "id": "cat-252",
+ "title": "The gossip arrives",
+ "top": "WAIT. tell me",
+ "bottom": "EVERYTHING. now.",
+ "mood": "chaos",
+ "tags": [
+ "gossip",
+ "drama"
+ ],
+ "image": "/assets/memes/7rf5z.jpg",
+ "template": "Heavy Breathing Cat",
+ "source": "https://imgflip.com/meme/Heavy-Breathing-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Inhales deadlines. Exhales panic."
+ },
+ {
+ "id": "cat-253",
+ "title": "The oven timer",
+ "top": "2 minutes left",
+ "bottom": "watching like a hawk",
+ "mood": "snacks",
+ "tags": [
+ "dinner",
+ "hungry"
+ ],
+ "image": "/assets/memes/7rf5z.jpg",
+ "template": "Heavy Breathing Cat",
+ "source": "https://imgflip.com/meme/Heavy-Breathing-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Inhales deadlines. Exhales panic."
+ },
+ {
+ "id": "cat-254",
+ "title": "The plot twist episode",
+ "top": "don’t you dare end here",
+ "bottom": "TO BE CONTINUED",
+ "mood": "relatable",
+ "tags": [
+ "tv",
+ "drama"
+ ],
+ "image": "/assets/memes/7rf5z.jpg",
+ "template": "Heavy Breathing Cat",
+ "source": "https://imgflip.com/meme/Heavy-Breathing-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Inhales deadlines. Exhales panic."
+ },
+ {
+ "id": "cat-255",
+ "title": "The excuse",
+ "top": "“traffic was crazy”",
+ "bottom": "you work from home",
+ "mood": "judgment",
+ "tags": [
+ "work",
+ "lies"
+ ],
+ "image": "/assets/memes/7rf5z.jpg",
+ "template": "Heavy Breathing Cat",
+ "source": "https://imgflip.com/meme/Heavy-Breathing-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Inhales deadlines. Exhales panic."
+ },
+ {
+ "id": "cat-256",
+ "title": "The bedroom door",
+ "top": "you shall not nap",
+ "bottom": "without the tribute (treats)",
+ "mood": "judgment",
+ "tags": [
+ "door",
+ "treats"
+ ],
+ "image": "/assets/memes/zl7we.jpg",
+ "template": "Persian Cat Room Guardian",
+ "source": "https://imgflip.com/meme/Persian-Cat-Room-Guardian",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "You shall not pass. Without snacks."
+ },
+ {
+ "id": "cat-257",
+ "title": "The monday guardian",
+ "top": "none shall pass",
+ "bottom": "back to bed, all of you",
+ "mood": "relatable",
+ "tags": [
+ "monday",
+ "nap"
+ ],
+ "image": "/assets/memes/zl7we.jpg",
+ "template": "Persian Cat Room Guardian",
+ "source": "https://imgflip.com/meme/Persian-Cat-Room-Guardian",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "You shall not pass. Without snacks."
+ },
+ {
+ "id": "cat-258",
+ "title": "The wifi guardian",
+ "top": "password, mortal",
+ "bottom": "it’s on the fridge. as always.",
+ "mood": "chaos",
+ "tags": [
+ "tech",
+ "wifi"
+ ],
+ "image": "/assets/memes/zl7we.jpg",
+ "template": "Persian Cat Room Guardian",
+ "source": "https://imgflip.com/meme/Persian-Cat-Room-Guardian",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "You shall not pass. Without snacks."
+ },
+ {
+ "id": "cat-259",
+ "title": "The fridge guardian",
+ "top": "state your business",
+ "bottom": "it’s 3am. same business.",
+ "mood": "snacks",
+ "tags": [
+ "fridge",
+ "night"
+ ],
+ "image": "/assets/memes/zl7we.jpg",
+ "template": "Persian Cat Room Guardian",
+ "source": "https://imgflip.com/meme/Persian-Cat-Room-Guardian",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "You shall not pass. Without snacks."
+ },
+ {
+ "id": "cat-260",
+ "title": "The hallway guardian",
+ "top": "escorts you at night",
+ "bottom": "charges one head pat",
+ "mood": "wholesome",
+ "tags": [
+ "night",
+ "love"
+ ],
+ "image": "/assets/memes/zl7we.jpg",
+ "template": "Persian Cat Room Guardian",
+ "source": "https://imgflip.com/meme/Persian-Cat-Room-Guardian",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "You shall not pass. Without snacks."
+ },
+ {
+ "id": "cat-261",
+ "title": "The browser history",
+ "top": "ceiling cat sees",
+ "bottom": "all 47 tabs",
+ "mood": "judgment",
+ "tags": [
+ "tech",
+ "privacy"
+ ],
+ "image": "/assets/memes/22vtk.jpg",
+ "template": "Ceiling Cat",
+ "source": "https://imgflip.com/meme/Ceiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2006,
+ "status": "immortal",
+ "lore": "Watching you since 2006."
+ },
+ {
+ "id": "cat-262",
+ "title": "The diet witness",
+ "top": "saw the midnight snack",
+ "bottom": "tells no one. judges plenty.",
+ "mood": "relatable",
+ "tags": [
+ "diet",
+ "night"
+ ],
+ "image": "/assets/memes/22vtk.jpg",
+ "template": "Ceiling Cat",
+ "source": "https://imgflip.com/meme/Ceiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2006,
+ "status": "immortal",
+ "lore": "Watching you since 2006."
+ },
+ {
+ "id": "cat-263",
+ "title": "The ceiling fan",
+ "top": "staring contest",
+ "bottom": "loser blinks first. it’s you.",
+ "mood": "chaos",
+ "tags": [
+ "stare",
+ "duel"
+ ],
+ "image": "/assets/memes/22vtk.jpg",
+ "template": "Ceiling Cat",
+ "source": "https://imgflip.com/meme/Ceiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2006,
+ "status": "immortal",
+ "lore": "Watching you since 2006."
+ },
+ {
+ "id": "cat-264",
+ "title": "The 3am ceiling",
+ "top": "can’t sleep either, huh",
+ "bottom": "let’s both pretend",
+ "mood": "sleepy",
+ "tags": [
+ "night",
+ "insomnia"
+ ],
+ "image": "/assets/memes/22vtk.jpg",
+ "template": "Ceiling Cat",
+ "source": "https://imgflip.com/meme/Ceiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2006,
+ "status": "immortal",
+ "lore": "Watching you since 2006."
+ },
+ {
+ "id": "cat-265",
+ "title": "The guardian above",
+ "top": "watches over naps",
+ "bottom": "overtime, unpaid",
+ "mood": "wholesome",
+ "tags": [
+ "nap",
+ "care"
+ ],
+ "image": "/assets/memes/22vtk.jpg",
+ "template": "Ceiling Cat",
+ "source": "https://imgflip.com/meme/Ceiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2006,
+ "status": "immortal",
+ "lore": "Watching you since 2006."
+ },
+ {
+ "id": "cat-266",
+ "title": "The swamp era",
+ "top": "out of my swamp",
+ "bottom": "into my box",
+ "mood": "chaos",
+ "tags": [
+ "swamp",
+ "box"
+ ],
+ "image": "/assets/memes/3m5mp.jpg",
+ "template": "Shrek Cat",
+ "source": "https://imgflip.com/meme/Shrek-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Ogres have layers. Cats have everything."
+ },
+ {
+ "id": "cat-267",
+ "title": "The layers",
+ "top": "ogres have layers",
+ "bottom": "i have blankets",
+ "mood": "relatable",
+ "tags": [
+ "cozy",
+ "layers"
+ ],
+ "image": "/assets/memes/3m5mp.jpg",
+ "template": "Shrek Cat",
+ "source": "https://imgflip.com/meme/Shrek-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Ogres have layers. Cats have everything."
+ },
+ {
+ "id": "cat-268",
+ "title": "The onion verdict",
+ "top": "tastes like an onion",
+ "bottom": "crying anyway",
+ "mood": "snacks",
+ "tags": [
+ "onion",
+ "dinner"
+ ],
+ "image": "/assets/memes/3m5mp.jpg",
+ "template": "Shrek Cat",
+ "source": "https://imgflip.com/meme/Shrek-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Ogres have layers. Cats have everything."
+ },
+ {
+ "id": "cat-269",
+ "title": "The fairy tale",
+ "top": "“happily ever after”",
+ "bottom": "nap first. then ever after.",
+ "mood": "judgment",
+ "tags": [
+ "tales",
+ "nap"
+ ],
+ "image": "/assets/memes/3m5mp.jpg",
+ "template": "Shrek Cat",
+ "source": "https://imgflip.com/meme/Shrek-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Ogres have layers. Cats have everything."
+ },
+ {
+ "id": "cat-270",
+ "title": "The donkey energy",
+ "top": "annoying but loyal",
+ "bottom": "that’s friendship",
+ "mood": "wholesome",
+ "tags": [
+ "friends",
+ "love"
+ ],
+ "image": "/assets/memes/3m5mp.jpg",
+ "template": "Shrek Cat",
+ "source": "https://imgflip.com/meme/Shrek-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Ogres have layers. Cats have everything."
+ },
+ {
+ "id": "cat-271",
+ "title": "The founding father",
+ "top": "i can has",
+ "bottom": "a place in history",
+ "mood": "wholesome",
+ "tags": [
+ "history",
+ "classic"
+ ],
+ "image": "/assets/memes/jru7x.jpg",
+ "template": "I can has cheezburger cat",
+ "source": "https://imgflip.com/meme/33211581/I-can-has-cheezburger-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2007,
+ "status": "immortal",
+ "lore": "The one that started it all."
+ },
+ {
+ "id": "cat-272",
+ "title": "The ancient text",
+ "top": "2007. simpler times.",
+ "bottom": "cheezburger was enough",
+ "mood": "relatable",
+ "tags": [
+ "nostalgia",
+ "classic"
+ ],
+ "image": "/assets/memes/jru7x.jpg",
+ "template": "I can has cheezburger cat",
+ "source": "https://imgflip.com/meme/33211581/I-can-has-cheezburger-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2007,
+ "status": "immortal",
+ "lore": "The one that started it all."
+ },
+ {
+ "id": "cat-273",
+ "title": "The original order",
+ "top": "i can has",
+ "bottom": "cheezburger. still correct.",
+ "mood": "snacks",
+ "tags": [
+ "classic",
+ "dinner"
+ ],
+ "image": "/assets/memes/jru7x.jpg",
+ "template": "I can has cheezburger cat",
+ "source": "https://imgflip.com/meme/33211581/I-can-has-cheezburger-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2007,
+ "status": "immortal",
+ "lore": "The one that started it all."
+ },
+ {
+ "id": "cat-274",
+ "title": "The prophecy",
+ "top": "it began with burger",
+ "bottom": "it ends with burger",
+ "mood": "chaos",
+ "tags": [
+ "destiny",
+ "classic"
+ ],
+ "image": "/assets/memes/jru7x.jpg",
+ "template": "I can has cheezburger cat",
+ "source": "https://imgflip.com/meme/33211581/I-can-has-cheezburger-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2007,
+ "status": "immortal",
+ "lore": "The one that started it all."
+ },
+ {
+ "id": "cat-275",
+ "title": "The grammar",
+ "top": "“i can has”",
+ "bottom": "grammatically perfect, actually",
+ "mood": "judgment",
+ "tags": [
+ "grammar",
+ "attitude"
+ ],
+ "image": "/assets/memes/jru7x.jpg",
+ "template": "I can has cheezburger cat",
+ "source": "https://imgflip.com/meme/33211581/I-can-has-cheezburger-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2007,
+ "status": "immortal",
+ "lore": "The one that started it all."
+ },
+ {
+ "id": "cat-276",
+ "title": "The group chat trial",
+ "top": "“EXPLAIN YOURSELF”",
+ "bottom": "seen 9:41pm",
+ "mood": "relatable",
+ "tags": [
+ "texting",
+ "drama"
+ ],
+ "image": "/assets/memes/345v97.jpg",
+ "template": "Woman Yelling At Cat",
+ "source": "https://imgflip.com/meme/Woman-Yelling-At-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "immortal",
+ "lore": "Dinner theater in two panels."
+ },
+ {
+ "id": "cat-277",
+ "title": "The dinner trial",
+ "top": "“YOU ATE MY FRIES”",
+ "bottom": "no further questions",
+ "mood": "snacks",
+ "tags": [
+ "fries",
+ "drama"
+ ],
+ "image": "/assets/memes/345v97.jpg",
+ "template": "Woman Yelling At Cat",
+ "source": "https://imgflip.com/meme/Woman-Yelling-At-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "immortal",
+ "lore": "Dinner theater in two panels."
+ },
+ {
+ "id": "cat-278",
+ "title": "The thermostat trial",
+ "top": "“IT’S 74 IN HERE”",
+ "bottom": "wearing a sweater. unbothered.",
+ "mood": "judgment",
+ "tags": [
+ "home",
+ "thermostat"
+ ],
+ "image": "/assets/memes/345v97.jpg",
+ "template": "Woman Yelling At Cat",
+ "source": "https://imgflip.com/meme/Woman-Yelling-At-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "immortal",
+ "lore": "Dinner theater in two panels."
+ },
+ {
+ "id": "cat-279",
+ "title": "The 3am trial",
+ "top": "“WHY ARE YOU AWAKE”",
+ "bottom": "why are YOU awake",
+ "mood": "chaos",
+ "tags": [
+ "night",
+ "comeback"
+ ],
+ "image": "/assets/memes/345v97.jpg",
+ "template": "Woman Yelling At Cat",
+ "source": "https://imgflip.com/meme/Woman-Yelling-At-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "immortal",
+ "lore": "Dinner theater in two panels."
+ },
+ {
+ "id": "cat-280",
+ "title": "The verdict",
+ "top": "case dismissed",
+ "bottom": "cuddle sentence issued",
+ "mood": "wholesome",
+ "tags": [
+ "love",
+ "verdict"
+ ],
+ "image": "/assets/memes/345v97.jpg",
+ "template": "Woman Yelling At Cat",
+ "source": "https://imgflip.com/meme/Woman-Yelling-At-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "immortal",
+ "lore": "Dinner theater in two panels."
+ },
+ {
+ "id": "cat-281",
+ "title": "The yawn approaches",
+ "top": "inhales deeply",
+ "bottom": "asleep mid-breath",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "yawn"
+ ],
+ "image": "/assets/memes/7rf5z.jpg",
+ "template": "Heavy Breathing Cat",
+ "source": "https://imgflip.com/meme/Heavy-Breathing-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2013,
+ "status": "classic",
+ "lore": "Inhales deadlines. Exhales panic."
+ },
+ {
+ "id": "cat-282",
+ "title": "The dream guardian",
+ "top": "protects your naps",
+ "bottom": "snores on duty",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "duty"
+ ],
+ "image": "/assets/memes/zl7we.jpg",
+ "template": "Persian Cat Room Guardian",
+ "source": "https://imgflip.com/meme/Persian-Cat-Room-Guardian",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2019,
+ "status": "classic",
+ "lore": "You shall not pass. Without snacks."
+ },
+ {
+ "id": "cat-283",
+ "title": "The lullaby",
+ "top": "ceiling fan hums",
+ "bottom": "both asleep now",
+ "mood": "sleepy",
+ "tags": [
+ "night",
+ "sleep"
+ ],
+ "image": "/assets/memes/22vtk.jpg",
+ "template": "Ceiling Cat",
+ "source": "https://imgflip.com/meme/Ceiling-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2006,
+ "status": "immortal",
+ "lore": "Watching you since 2006."
+ },
+ {
+ "id": "cat-284",
+ "title": "The swamp nap",
+ "top": "do not disturb",
+ "bottom": "ogre sleeping",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "swamp"
+ ],
+ "image": "/assets/memes/3m5mp.jpg",
+ "template": "Shrek Cat",
+ "source": "https://imgflip.com/meme/Shrek-Cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2023,
+ "status": "alive",
+ "lore": "Ogres have layers. Cats have everything."
+ },
+ {
+ "id": "cat-285",
+ "title": "The food coma",
+ "top": "i can has nap",
+ "bottom": "after burger. obviously.",
+ "mood": "sleepy",
+ "tags": [
+ "nap",
+ "classic"
+ ],
+ "image": "/assets/memes/jru7x.jpg",
+ "template": "I can has cheezburger cat",
+ "source": "https://imgflip.com/meme/33211581/I-can-has-cheezburger-cat",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2007,
+ "status": "immortal",
+ "lore": "The one that started it all."
+ },
+ {
+ "id": "cat-286",
+ "title": "The group chat fire",
+ "top": "brought popcorn",
+ "bottom": "to your drama",
+ "mood": "chaos",
+ "tags": [
+ "drama",
+ "popcorn"
+ ],
+ "image": "/assets/memes/8tlh.jpg",
+ "template": "Burn Kitty",
+ "source": "https://imgflip.com/meme/Burn-Kitty",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Likes the fire. Is the fire."
+ },
+ {
+ "id": "cat-287",
+ "title": "The chaos enjoyer",
+ "top": "“this is fine”",
+ "bottom": "narrator: it was not fine",
+ "mood": "judgment",
+ "tags": [
+ "fire",
+ "denial"
+ ],
+ "image": "/assets/memes/8tlh.jpg",
+ "template": "Burn Kitty",
+ "source": "https://imgflip.com/meme/Burn-Kitty",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Likes the fire. Is the fire."
+ },
+ {
+ "id": "cat-288",
+ "title": "The monday fire",
+ "top": "everything’s burning",
+ "bottom": "anyway, coffee?",
+ "mood": "relatable",
+ "tags": [
+ "monday",
+ "coffee"
+ ],
+ "image": "/assets/memes/8tlh.jpg",
+ "template": "Burn Kitty",
+ "source": "https://imgflip.com/meme/Burn-Kitty",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Likes the fire. Is the fire."
+ },
+ {
+ "id": "cat-289",
+ "title": "The marshmallow plan",
+ "top": "everything’s on fire",
+ "bottom": "s’mores o’clock",
+ "mood": "snacks",
+ "tags": [
+ "fire",
+ "smores"
+ ],
+ "image": "/assets/memes/8tlh.jpg",
+ "template": "Burn Kitty",
+ "source": "https://imgflip.com/meme/Burn-Kitty",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Likes the fire. Is the fire."
+ },
+ {
+ "id": "cat-290",
+ "title": "The campfire",
+ "top": "stories and sparks",
+ "bottom": "stay for one more",
+ "mood": "wholesome",
+ "tags": [
+ "campfire",
+ "friends"
+ ],
+ "image": "/assets/memes/8tlh.jpg",
+ "template": "Burn Kitty",
+ "source": "https://imgflip.com/meme/Burn-Kitty",
+ "kind": "remix",
+ "added": "2026-09-14",
+ "origin": 2021,
+ "status": "alive",
+ "lore": "Likes the fire. Is the fire."
}
-]
\ No newline at end of file
+]
diff --git a/public/style.css b/public/style.css
index 23b7852..90a3ba9 100644
--- a/public/style.css
+++ b/public/style.css
@@ -1,7 +1,212 @@
-:root{--olive:#4a412a;--olive-dark:#332e20;--olive-soft:#797052;--cream:#f7f5ee;--paper:#fffef9;--line:#e4e0d4;--blue:#b9c5f3;--blue-dark:#424f82;--blue-pale:#e9edf9;--muted:#797665;--green:#e3ebd7;--pink:#f4e0db;--gold:#eee2b7;--radius:16px;font-family:'DM Sans',sans-serif;color:var(--olive);background:var(--cream);font-synthesis:none;scroll-behavior:smooth}
-*{box-sizing:border-box}body{margin:0}button,input,select,textarea{font:inherit}button,a,input,select,textarea{-webkit-tap-highlight-color:transparent}button{cursor:pointer}button{color:inherit}a{color:inherit}button:disabled{cursor:wait;opacity:.5}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid var(--blue-dark);outline-offset:4px}button{transition:background .16s,transform .16s,box-shadow .16s}button:active{transform:translateY(1px)}button svg,a svg{pointer-events:none}svg.icon{width:19px;height:19px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;display:block;flex-shrink:0}[data-icon]{display:inline-flex}button{border:0;background:none}h1,h2,h3,p{margin:0}[hidden]{display:none!important}.skip-link{position:fixed;top:-80px;left:20px;background:var(--olive);color:white;padding:12px;z-index:100}.skip-link:focus{top:10px}.sidebar{width:220px;position:fixed;inset:0 auto 0 0;padding:31px 22px 22px;background:#efede3;border-right:1px solid #e0ddcf;display:flex;flex-direction:column;z-index:20}.brand{display:flex;gap:9px;align-items:center;text-decoration:none;font-size:17px;font-weight:800;letter-spacing:-.8px;line-height:1.05}.brand img{width:54px;height:54px;image-rendering:pixelated}.brand-bottom{display:block;font-size:27px;letter-spacing:1.3px}.brand-dot{font-size:11px;vertical-align:top;letter-spacing:0;margin-left:3px}.side-tagline{font-size:12px;color:var(--muted);line-height:1.6;margin:18px 0 34px 8px}.nav-label{font-size:9px;font-weight:700;letter-spacing:1.4px;color:#8b8775;margin:0 8px 12px}.main-nav{display:grid;gap:5px}.nav-item{display:flex;gap:12px;align-items:center;width:100%;padding:12px 11px;border-radius:9px;text-align:left;font-size:12px;font-weight:600;min-height:43px}.nav-item:hover{background:#e5e2d4}.nav-item.active{background:var(--olive);color:#fffdf4}.nav-count{font-size:10px;margin-left:auto;opacity:.8;background:#dfdccd;padding:2px 6px;border-radius:5px;min-width:21px;text-align:center}.active .nav-count{background:#6b6046;color:#fff}.side-divider{height:1px;background:#dcd8c8;margin:26px 8px}.keycap{font-size:9px;border:1px solid #c9c4b2;border-radius:4px;padding:1px 4px;margin-left:auto}.side-bottom{margin-top:auto;padding-top:35px}.chimp-note{position:relative;border:1px solid #d8d3bf;border-radius:13px;padding:19px 12px 28px;background:#e8e4d3;overflow:hidden}.chimp-note p{position:relative;z-index:1;font-size:11px;line-height:1.65}.chimp-note em{font-weight:700;font-style:normal}.chimp-note img{position:absolute;width:66px;right:-7px;bottom:-6px;transform:rotate(-14deg);opacity:.72}.tiny-spark{font-size:20px;color:var(--olive-soft);display:block;margin-bottom:12px}.about-link{display:flex;gap:7px;align-items:center;font-size:9px;padding:16px 0;width:100%;justify-content:center}.swatch{background:var(--olive);width:10px;height:10px;border-radius:2px;display:inline-block}.shortcut-link{display:flex;align-items:center;gap:7px;font-size:10px;color:var(--muted);padding:10px 4px;width:100%;border-top:1px solid #ddd9cb}.shortcut-link svg{width:16px}.shortcut-link>span:last-child{margin-left:auto}.workspace{margin-left:220px}.topbar{height:83px;padding:0 38px;display:flex;align-items:center;justify-content:space-between;gap:20px;border-bottom:1px solid var(--line);background:var(--cream)}.breadcrumb{display:flex;align-items:center;gap:10px;font-size:9px;font-weight:700;letter-spacing:1.5px;white-space:nowrap}.status-dot{width:6px;height:6px;border-radius:100%;background:var(--olive-soft);box-shadow:0 0 0 4px #eae7da}.edition{color:#969181;padding-left:10px;border-left:1px solid #d7d1c2;letter-spacing:1px}.top-actions{display:flex;gap:13px;align-items:center}.search-wrap{display:flex;align-items:center;gap:9px;border:1px solid #dfdbcf;background:#eeece3;border-radius:8px;padding:9px 11px;width:253px}.search-wrap svg{width:16px;height:16px;color:#7b7664}.search-wrap input{width:100%;min-width:0;border:0;background:transparent;outline:0;font-size:11px;color:var(--olive)}.search-wrap:focus-within{border-color:var(--olive-soft);box-shadow:0 0 0 2px #e0dccb}.search-wrap kbd{font-size:10px;border:1px solid #d5d0c0;padding:0 4px;border-radius:3px}.create-button{display:flex;gap:7px;align-items:center;background:var(--olive);color:var(--cream);padding:11px 13px;border-radius:8px;font-size:11px;font-weight:600;white-space:nowrap}.create-button:hover,.primary-button:hover{background:#62573b}.create-button svg{width:16px;height:16px}.mobile-home{display:none}main{max-width:1490px;margin:auto;padding:30px 38px 0}.hero{min-height:315px;border-radius:18px;position:relative;background:var(--olive);color:var(--cream);overflow:hidden;display:flex;align-items:center;padding:35px 34px}.hero-copy{width:60%;position:relative;z-index:2}.eyebrow{font-size:9px;letter-spacing:1.6px;font-weight:700;display:flex;align-items:center;gap:7px}.hero .eyebrow{color:#d3cbb1;font-size:8px;letter-spacing:1.5px}.little-star{font-size:15px;color:var(--blue)}h1{font-family:Fraunces,Georgia,serif;font-size:clamp(38px,4.1vw,63px);letter-spacing:-2px;line-height:1.08;font-weight:600;margin-top:14px}h1 em{color:var(--blue);font-weight:600}.hero-copy p{font-size:11px;line-height:1.8;color:#d7d1c0;margin:15px 0 20px}.hero-button{display:inline-flex;align-items:center;gap:21px;padding:12px 15px;background:var(--blue);color:#303952;font-size:11px;font-weight:700;border-radius:7px}.hero-button:hover{background:#d5dcf7;transform:translateY(-2px)}.hero-button svg{width:16px;height:16px}.hero-footnote{font-size:9px;color:#b2aa92;margin-left:13px}.hero-art{position:absolute;right:21px;width:39%;top:0;height:100%}.hero-ring{position:absolute;width:269px;height:269px;border:1px solid #756a4c;border-radius:100%;top:34px;right:4px;transform:rotate(-20deg)}.hero-ring:after{content:'';position:absolute;inset:22px -30px;border:1px solid #756a4c;border-radius:100%;transform:rotate(60deg)}.hero-sticker{background:var(--paper);padding:9px 9px 7px;width:176px;border-radius:5px;position:absolute;box-shadow:0 10px 26px #211d2340;color:var(--olive)}.sticker-caption{font-size:12px;font-weight:700;line-height:1.35;padding:2px 2px 8px}.hero-sticker img{width:100%;display:block;object-fit:cover;border-radius:2px;max-height:139px}.back-sticker{right:4px;top:30px;transform:rotate(12deg);background:#dfe7ff}.back-sticker img{height:135px;object-fit:contain;background:#fff}.front-sticker{left:1px;top:72px;transform:rotate(-9deg);width:194px;animation:float-cat 7s ease-in-out infinite}.front-sticker img{height:131px;object-position:center}.sticker-footer{font-size:8px;display:flex;justify-content:space-between;align-items:center;padding-top:8px}.sticker-footer span{font-size:18px}.approval-sticker{width:82px;height:82px;position:absolute;background:var(--gold);color:var(--olive);right:4px;bottom:34px;border-radius:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;transform:rotate(13deg);font-family:Fraunces,serif;font-size:26px;box-shadow:0 0 0 4px var(--olive),0 0 0 5px #a69a75}.approval-sticker span{font-family:'DM Sans',sans-serif;font-size:7px;letter-spacing:1px;font-weight:700}.approval-sticker .mini-paw{font-size:17px}.art-star{position:absolute;font-size:40px;left:-21px;top:25px;color:var(--blue)}.hero-doodle{position:absolute;right:58px;bottom:14px;font-size:9px;color:#c8bea0;font-style:italic;transform:rotate(3deg)}.library{margin-top:34px;scroll-margin-top:20px}.library-heading{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-bottom:20px}.section-eyebrow{color:var(--muted);font-size:8px;letter-spacing:1.5px;margin-bottom:8px}h2{font-family:Fraunces,Georgia,serif;font-size:27px;font-weight:600;letter-spacing:-.7px}h2>span{color:#8f9dcf}.text-button{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:500;white-space:nowrap;padding:9px 0}.text-button:hover{color:var(--blue-dark)}.text-button svg{width:16px;height:16px}.mood-filters{display:flex;gap:7px;flex-wrap:wrap}.mood-filter{border:1px solid var(--line);display:flex;align-items:center;gap:7px;border-radius:30px;padding:9px 14px;font-size:11px;background:var(--paper);font-weight:500}.mood-filter span{font-size:12px}.mood-filter:hover{border-color:var(--olive-soft);background:#eeeadd}.mood-filter.active{background:var(--olive);border-color:var(--olive);color:var(--cream)}.filter-row{padding-bottom:18px;border-bottom:1px solid var(--line)}.results-bar{display:flex;justify-content:space-between;align-items:center;padding:17px 0;color:var(--muted);font-size:10px;gap:10px}.results-bar strong{color:var(--olive);font-weight:600}.result-controls{display:flex;gap:14px;align-items:center}.sort-label{display:flex;gap:5px;align-items:center;white-space:nowrap}.sort-label select{border:0;background:transparent;color:var(--olive);font-size:10px;font-weight:600;cursor:pointer;max-width:125px}.view-toggle{display:flex;gap:1px;background:#eae7dd;padding:3px;border-radius:6px}.view-toggle button{padding:4px;border-radius:4px;color:#999380}.view-toggle button.selected{background:var(--paper);color:var(--olive);box-shadow:0 1px 3px #42392012}.view-toggle svg{width:14px;height:14px}.meme-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:21px;align-items:start}.meme-grid.dense{grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}.meme-card{background:var(--paper);border:1px solid var(--line);border-radius:13px;overflow:hidden;transition:transform .2s,box-shadow .2s,border-color .2s;animation:card-in .35s both}.meme-card:hover{transform:translateY(-4px);box-shadow:0 8px 22px #4a412a10;border-color:#bfb8a1}.meme-open{display:block;width:100%;padding:0;text-align:left}.meme-visual{background:#fff;color:#24231e;display:flex;flex-direction:column;overflow:hidden}.meme-caption{font-size:clamp(13px,1.2vw,19px);font-weight:700;line-height:1.3;padding:15px 17px 11px;min-height:59px;display:flex;align-items:center;white-space:pre-wrap;overflow-wrap:anywhere}.meme-caption.bottom{font-size:clamp(12px,1.08vw,17px);padding:12px 17px 15px;min-height:54px}.meme-image{width:100%;height:215px;object-fit:contain;display:block;background:#f4f2ed}.dense .meme-image{height:168px}.dense .meme-caption{font-size:14px;padding:12px;min-height:56px}.dense .meme-caption.bottom{font-size:13px;min-height:58px}.card-meta{padding:13px 14px 12px;border-top:1px solid #efede5}.card-topline{display:flex;justify-content:space-between;gap:8px;align-items:center}.mood-label{font-size:8px;font-weight:600;padding:4px 7px;border-radius:4px;display:inline-flex;align-items:center;gap:4px;letter-spacing:.2px;background:var(--blue-pale);color:var(--blue-dark)}.mood-label[data-mood=chaos]{background:var(--gold);color:#6f5822}.mood-label[data-mood=wholesome]{background:var(--green);color:#4c653b}.mood-label[data-mood=judgment]{background:var(--pink);color:#82564b}.mood-label[data-mood=sleepy]{background:#eae3f2;color:#6a537f}.mood-label[data-mood=snacks]{background:#f4e7d7;color:#85602f}.card-source{font-size:8px;color:#a09b8d}.card-bottomline{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:8px}.card-title{font-size:11px;font-weight:600;line-height:1.4;margin:0;flex:1;overflow-wrap:anywhere}.save-button{color:#918876;padding:5px;border-radius:6px;position:relative}.save-button:hover{background:var(--pink);color:#9c6055}.save-button.saved{color:#9e5c53}.save-button.saved svg{fill:#eed2cc}.save-button svg{width:18px;height:18px}.load-more{display:flex;align-items:center;justify-content:center;gap:16px;padding:13px 24px;margin:30px auto 0;border:1px solid #ccc5b1;border-radius:7px;font-size:11px;font-weight:600;background:var(--paper)}.load-more:hover{background:#e9e4d5}.load-more svg{width:15px;height:15px}.end-note{display:flex;gap:14px;align-items:center;justify-content:center;padding:38px 0 8px;color:var(--muted);font-size:11px;text-align:left;line-height:1.8}.end-note>span{font-size:29px;color:var(--olive-soft)}.end-note button{padding:0;text-decoration:underline;font-size:10px}footer{padding:30px 0 20px;border-top:1px solid var(--line);margin-top:36px;display:flex;justify-content:space-between;align-items:center;gap:20px;font-size:10px;color:#8a8471}footer>span{display:flex;align-items:center;gap:8px}footer img{width:22px;opacity:.7}footer button{font-size:10px;padding:0}.page-description{font-size:12px;line-height:1.7;color:var(--muted);margin-top:9px}.empty-state{text-align:center;padding:65px 20px}.empty-face{font-size:66px;display:block;color:#958968;margin-bottom:18px}.empty-state h3{font-family:Fraunces,Georgia,serif;font-size:28px;margin-bottom:8px}.empty-state p{font-size:13px;color:var(--muted);margin-bottom:22px}.primary-button,.outline-button{border-radius:7px;padding:11px 14px;font-size:11px;font-weight:600;display:inline-flex;gap:7px;align-items:center;justify-content:center}.primary-button{background:var(--olive);color:var(--paper)}.outline-button{border:1px solid #d6d0bf;background:var(--paper)}.outline-button:hover{background:#eae6d9}.primary-button svg,.outline-button svg{width:16px;height:16px}.collection-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:25px 0}.collection-card{text-align:left;background:var(--blue-pale);border:1px solid #d5dced;border-radius:13px;padding:20px;min-height:155px;position:relative;overflow:hidden}.collection-card:nth-child(2){background:var(--gold);border-color:#e3d4a4}.collection-card:nth-child(3){background:var(--green);border-color:#d3dfc4}.collection-card:hover{transform:translateY(-3px);box-shadow:0 5px 0 #4a412a0d}.collection-card small{font-size:8px;letter-spacing:1px}.collection-card h3{font-family:Fraunces,serif;font-size:25px;max-width:70%;line-height:1.1;margin:14px 0 9px;font-weight:600}.collection-card p{font-size:10px;max-width:68%;line-height:1.5}.collection-card img{position:absolute;width:92px;height:90px;object-fit:cover;right:-8px;bottom:10px;border:5px solid #fff;transform:rotate(10deg);border-radius:4px;box-shadow:0 4px 10px #4a412a18}.collection-card.active{outline:2px solid var(--olive);outline-offset:3px}.mobile-nav{display:none}dialog{border:1px solid #cfc7b3;border-radius:17px;color:var(--olive);background:var(--cream);padding:0;box-shadow:0 25px 120px #18171150;max-height:92dvh;max-width:calc(100vw - 30px)}dialog::backdrop{background:#242117af;backdrop-filter:blur(5px)}body:has(dialog[open]){overflow:hidden}.viewer-dialog{width:890px}.viewer-top{display:flex;align-items:center;justify-content:space-between;padding:15px 22px;border-bottom:1px solid var(--line)}.viewer-top .eyebrow{font-size:9px}.viewer-progress{font-size:10px;color:var(--muted);margin-left:auto;margin-right:22px}.icon-button{padding:7px;border-radius:6px}.icon-button:hover{background:#e3dfd1}.viewer-body{position:relative;padding:22px 85px;min-height:240px;background:#e9e6db}.viewer-image-wrap{max-width:530px;margin:auto;box-shadow:0 5px 18px #4a412a15;border-radius:5px;overflow:hidden}.viewer-image-wrap .meme-image{height:auto;max-height:40dvh;object-fit:contain}.viewer-image-wrap .meme-caption{font-size:22px;padding:17px 22px;min-height:0}.viewer-arrow{position:absolute;top:45%;background:var(--paper);padding:12px;border:1px solid #d8d2c1;border-radius:50%;box-shadow:0 3px 7px #4a412a09}.viewer-arrow:hover{background:var(--blue)}.viewer-arrow.previous{left:20px}.viewer-arrow.next{right:20px}.viewer-details{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px 24px}.viewer-details h2{font-size:23px;line-height:1.2;margin-top:8px}.viewer-details p{font-size:10px;margin-top:7px;color:var(--muted)}.viewer-actions{display:flex;gap:7px;align-items:center;flex-shrink:0}.viewer-actions .saved{background:var(--pink);color:#854e43}.viewer-actions .saved svg{fill:var(--pink)}.viewer-bottom{padding:13px 24px;border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;gap:12px;color:var(--muted);font-size:9px}.viewer-bottom a{text-underline-offset:3px}.viewer-bottom button{display:flex;gap:6px;align-items:center;font-size:10px}.viewer-bottom svg{width:14px;height:14px}.viewer-bottom kbd{display:inline-block;background:#e8e4d8;border:1px solid #d6cfbc;font-size:9px;padding:2px 4px;border-radius:3px;margin:0 2px}.autoplay-track{height:3px;background:transparent}.autoplay-track i{display:block;height:3px;background:var(--blue-dark);width:0}.autoplay-track i.running{animation:progress 6s linear infinite}.editor-dialog{width:870px}.dialog-heading{display:flex;align-items:center;justify-content:space-between;padding:24px 26px;border-bottom:1px solid var(--line);gap:14px}.dialog-heading .eyebrow{margin-bottom:8px;color:var(--muted)}.editor-layout{display:grid;grid-template-columns:1fr 1fr;gap:24px;padding:24px}.editor-preview{align-self:start;border:1px solid var(--line);border-radius:7px;overflow:hidden;position:sticky;top:10px}.editor-preview .meme-image{height:270px}.editor-preview .meme-caption{font-size:19px;padding:18px;min-height:62px}#editor-form{display:grid;gap:13px}#editor-form label{display:grid;gap:7px;font-size:11px;font-weight:600}#editor-form select,#editor-form textarea,#editor-form input{width:100%;border:1px solid #d7d1c1;border-radius:6px;padding:10px;background:var(--paper);color:var(--olive);font-size:12px;resize:vertical}#editor-form .upload-button{display:flex;align-items:center;justify-content:center;gap:6px;padding:10px;border:1px dashed #bcb39a;border-radius:6px;cursor:pointer;font-size:11px}#editor-form .upload-button:hover{background:#e9e4d5}.upload-button svg{width:16px;height:16px}.editor-actions{display:flex;gap:8px;padding-top:3px}.local-note{font-size:10px;color:var(--muted);line-height:1.6}.info-dialog{width:600px}#info-content{padding:25px;line-height:1.8;font-size:13px;color:#736b55}#info-content p+p{margin-top:14px}#info-content strong{color:var(--olive)}#info-content .palette{display:flex;gap:10px;margin:22px 0}#info-content .palette>div{flex:1;height:75px;border-radius:8px;display:flex;align-items:end;justify-content:center;padding:8px;font-size:9px;border:1px solid #cfc8b5}.shortcut-table{width:100%;border-collapse:collapse}.shortcut-table td{border-bottom:1px solid var(--line);padding:10px 4px}.shortcut-table td:first-child{font-family:monospace;font-weight:700;color:var(--olive);width:130px}.toast{position:fixed;bottom:28px;left:calc(50% + 110px);transform:translate(-50%,15px);opacity:0;pointer-events:none;z-index:99;background:var(--olive);color:var(--paper);border:1px solid #7d7359;box-shadow:0 5px 22px #25200d30;border-radius:9px;padding:13px 22px;font-size:12px;transition:transform .2s,opacity .2s;max-width:85vw;text-align:center}.toast.visible{opacity:1;transform:translate(-50%,0)}@keyframes float-cat{0%,100%{transform:rotate(-9deg) translateY(0)}50%{transform:rotate(-7deg) translateY(-6px)}}@keyframes card-in{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:translateY(0)}}@keyframes progress{from{width:0}to{width:100%}}
+:root{--olive:#4a412a;--olive-dark:#332e20;--olive-soft:#797052;--cream:#f7f5ee;--paper:#fffef9;--line:#e4e0d4;--blue:#b9c5f3;--blue-dark:#424f82;--blue-pale:#e9edf9;--muted:#645e4c;--green:#e3ebd7;--pink:#f4e0db;--gold:#eee2b7;--radius:16px;font-family:'DM Sans',sans-serif;color:var(--olive);background:var(--cream);font-synthesis:none;scroll-behavior:smooth}
+*{box-sizing:border-box}body{margin:0}button,input,select,textarea{font:inherit}button,a,input,select,textarea{-webkit-tap-highlight-color:transparent}button{cursor:pointer}button{color:inherit}a{color:inherit}button:disabled{cursor:wait;opacity:.5}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid var(--blue-dark);outline-offset:4px}button{transition:background .16s,transform .16s,box-shadow .16s}button:active{transform:translateY(1px)}button svg,a svg{pointer-events:none}svg.icon{width:19px;height:19px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;display:block;flex-shrink:0}[data-icon]{display:inline-flex}button{border:0;background:none}h1,h2,h3,p{margin:0}[hidden]{display:none!important}.skip-link{position:fixed;top:-80px;left:20px;background:var(--olive);color:white;padding:12px;z-index:100}.skip-link:focus{top:10px}.sidebar{width:220px;position:fixed;inset:0 auto 0 0;padding:31px 22px 22px;background:#efede3;border-right:1px solid #e0ddcf;display:flex;flex-direction:column;z-index:20;overflow-y:auto;overscroll-behavior:contain}.brand{display:flex;gap:9px;align-items:center;text-decoration:none;font-size:17px;font-weight:800;letter-spacing:-.8px;line-height:1.05}.brand img{width:54px;height:54px;image-rendering:pixelated}.brand-bottom{display:block;font-size:27px;letter-spacing:1.3px}.brand-dot{font-size:11px;vertical-align:top;letter-spacing:0;margin-left:3px}.side-tagline{font-size:12px;color:var(--muted);line-height:1.6;margin:18px 0 34px 8px}.nav-label{font-size:9px;font-weight:700;letter-spacing:1.4px;color:#8b8775;margin:0 8px 12px}.main-nav{display:grid;gap:5px}.nav-item{display:flex;gap:12px;align-items:center;width:100%;padding:12px 11px;border-radius:9px;text-align:left;font-size:12px;font-weight:600;min-height:43px}.nav-item:hover{background:#e5e2d4}.nav-item.active{background:var(--olive);color:#fffdf4}.nav-count{font-size:10px;margin-left:auto;opacity:.8;background:#dfdccd;padding:2px 6px;border-radius:5px;min-width:21px;text-align:center}.active .nav-count{background:#6b6046;color:#fff}.side-divider{height:1px;background:#dcd8c8;margin:26px 8px}.keycap{font-size:9px;border:1px solid #c9c4b2;border-radius:4px;padding:1px 4px;margin-left:auto}.side-bottom{margin-top:auto;padding-top:35px}.chimp-note{position:relative;border:1px solid #d8d3bf;border-radius:13px;padding:19px 12px 28px;background:#e8e4d3;overflow:hidden}.chimp-note p{position:relative;z-index:1;font-size:11px;line-height:1.65}.chimp-note em{font-weight:700;font-style:normal}.chimp-note img{position:absolute;width:66px;right:-7px;bottom:-6px;transform:rotate(-14deg);opacity:.72}.tiny-spark{font-size:20px;color:var(--olive-soft);display:block;margin-bottom:12px}.about-link{display:flex;gap:7px;align-items:center;font-size:9px;padding:16px 0;width:100%;justify-content:center}.swatch{background:var(--olive);width:10px;height:10px;border-radius:2px;display:inline-block}.shortcut-link{display:flex;align-items:center;gap:7px;font-size:10px;color:var(--muted);padding:10px 4px;width:100%;border-top:1px solid #ddd9cb}.shortcut-link svg{width:16px}.shortcut-link>span:last-child{margin-left:auto}.workspace{margin-left:220px}.topbar{height:83px;padding:0 38px;display:flex;align-items:center;justify-content:space-between;gap:20px;border-bottom:1px solid var(--line);background:var(--cream)}.breadcrumb{display:flex;align-items:center;gap:10px;font-size:9px;font-weight:700;letter-spacing:1.5px;white-space:nowrap}.status-dot{width:6px;height:6px;border-radius:100%;background:var(--olive-soft);box-shadow:0 0 0 4px #eae7da}.edition{color:#969181;padding-left:10px;border-left:1px solid #d7d1c2;letter-spacing:1px}.top-actions{display:flex;gap:13px;align-items:center}.search-wrap{display:flex;align-items:center;gap:9px;border:1px solid #dfdbcf;background:#eeece3;border-radius:8px;padding:9px 11px;width:253px}.search-wrap svg{width:16px;height:16px;color:#7b7664}.search-wrap input{width:100%;min-width:0;border:0;background:transparent;outline:0;font-size:11px;color:var(--olive)}.search-wrap:focus-within{border-color:var(--olive-soft);box-shadow:0 0 0 2px #e0dccb}.search-wrap kbd{font-size:10px;border:1px solid #d5d0c0;padding:0 4px;border-radius:3px}.create-button{display:flex;gap:7px;align-items:center;background:var(--olive);color:var(--cream);padding:11px 13px;border-radius:8px;font-size:11px;font-weight:600;white-space:nowrap}.create-button:hover,.primary-button:hover{background:#62573b}.create-button svg{width:16px;height:16px}.mobile-home{display:none}main{max-width:1490px;margin:auto;padding:30px 38px 0}.hero{min-height:315px;border-radius:18px;position:relative;background:var(--olive);color:var(--cream);overflow:hidden;display:flex;align-items:center;padding:35px 34px}.hero-copy{width:60%;position:relative;z-index:2}.eyebrow{font-size:9px;letter-spacing:1.6px;font-weight:700;display:flex;align-items:center;gap:7px}.hero .eyebrow{color:#d3cbb1;font-size:8px;letter-spacing:1.5px}.little-star{font-size:15px;color:var(--blue)}h1{font-family:Fraunces,Georgia,serif;font-size:clamp(38px,4.1vw,63px);letter-spacing:-2px;line-height:1.08;font-weight:600;margin-top:14px}h1 em{color:var(--blue);font-weight:600}.hero-copy p{font-size:11px;line-height:1.8;color:#d7d1c0;margin:15px 0 20px}.hero-button{display:inline-flex;align-items:center;gap:21px;padding:12px 15px;background:var(--blue);color:#303952;font-size:11px;font-weight:700;border-radius:7px}.hero-button:hover{background:#d5dcf7;transform:translateY(-2px)}.hero-button svg{width:16px;height:16px}.hero-footnote{font-size:9px;color:#b2aa92;margin-left:13px}.hero-art{position:absolute;right:21px;width:39%;top:0;height:100%}.hero-ring{position:absolute;width:269px;height:269px;border:1px solid #756a4c;border-radius:100%;top:34px;right:4px;transform:rotate(-20deg)}.hero-ring:after{content:'';position:absolute;inset:22px -30px;border:1px solid #756a4c;border-radius:100%;transform:rotate(60deg)}.hero-sticker{background:var(--paper);padding:9px 9px 7px;width:176px;border-radius:5px;position:absolute;box-shadow:0 10px 26px #211d2340;color:var(--olive)}.sticker-caption{font-size:12px;font-weight:700;line-height:1.35;padding:2px 2px 8px}.hero-sticker img{width:100%;display:block;object-fit:cover;border-radius:2px;max-height:139px}.back-sticker{right:4px;top:30px;transform:rotate(12deg);background:#dfe7ff}.back-sticker img{height:135px;object-fit:contain;background:#fff}.front-sticker{left:1px;top:72px;transform:rotate(-9deg);width:194px;animation:float-cat 7s ease-in-out infinite}.front-sticker img{height:131px;object-position:center}.sticker-footer{font-size:8px;display:flex;justify-content:space-between;align-items:center;padding-top:8px}.sticker-footer span{font-size:18px}.approval-sticker{width:82px;height:82px;position:absolute;background:var(--gold);color:var(--olive);right:4px;bottom:34px;border-radius:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;transform:rotate(13deg);font-family:Fraunces,serif;font-size:26px;box-shadow:0 0 0 4px var(--olive),0 0 0 5px #a69a75}.approval-sticker span{font-family:'DM Sans',sans-serif;font-size:7px;letter-spacing:1px;font-weight:700}.approval-sticker .mini-paw{font-size:17px}.art-star{position:absolute;font-size:40px;left:-21px;top:25px;color:var(--blue)}.hero-doodle{position:absolute;right:58px;bottom:14px;font-size:9px;color:#c8bea0;font-style:italic;transform:rotate(3deg)}.library{margin-top:34px;scroll-margin-top:20px}.library-heading{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-bottom:20px}.section-eyebrow{color:var(--muted);font-size:8px;letter-spacing:1.5px;margin-bottom:8px}h2{font-family:Fraunces,Georgia,serif;font-size:27px;font-weight:600;letter-spacing:-.7px}h2>span{color:#8f9dcf}.text-button{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:500;white-space:nowrap;padding:9px 0}.text-button:hover{color:var(--blue-dark)}.text-button svg{width:16px;height:16px}.mood-filters{display:flex;gap:7px;flex-wrap:wrap}.mood-filter{border:1px solid var(--line);display:flex;align-items:center;gap:7px;border-radius:30px;padding:9px 14px;font-size:11px;background:var(--paper);font-weight:500}.mood-filter span{font-size:12px}.mood-filter:hover{border-color:var(--olive-soft);background:#eeeadd}.mood-filter.active{background:var(--olive);border-color:var(--olive);color:var(--cream)}.filter-row{padding-bottom:18px;border-bottom:1px solid var(--line)}.results-bar{display:flex;justify-content:space-between;align-items:center;padding:17px 0;color:var(--muted);font-size:10px;gap:10px}.results-bar strong{color:var(--olive);font-weight:600}.result-controls{display:flex;gap:14px;align-items:center}.sort-label{display:flex;gap:5px;align-items:center;white-space:nowrap}.sort-label select{border:0;background:transparent;color:var(--olive);font-size:10px;font-weight:600;cursor:pointer;max-width:125px}.view-toggle{display:flex;gap:1px;background:#eae7dd;padding:3px;border-radius:6px}.view-toggle button{padding:4px;border-radius:4px;color:#999380}.view-toggle button.selected{background:var(--paper);color:var(--olive);box-shadow:0 1px 3px #42392012}.view-toggle svg{width:14px;height:14px}.meme-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:21px;align-items:start}.meme-grid.dense{grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}.meme-card{background:var(--paper);border:1px solid var(--line);border-radius:13px;overflow:hidden;transition:transform .2s,box-shadow .2s,border-color .2s;animation:card-in .35s both}.meme-card:hover{transform:translateY(-4px);box-shadow:0 8px 22px #4a412a10;border-color:#bfb8a1}.meme-open{display:block;width:100%;padding:0;text-align:left}.meme-visual{background:#fff;color:#24231e;display:flex;flex-direction:column;overflow:hidden}.meme-caption{font-size:clamp(13px,1.2vw,19px);font-weight:700;line-height:1.3;padding:15px 17px 11px;min-height:59px;display:flex;align-items:center;white-space:pre-wrap;overflow-wrap:anywhere}.meme-caption.bottom{font-size:clamp(12px,1.08vw,17px);padding:12px 17px 15px;min-height:54px}.meme-image{width:100%;height:215px;object-fit:contain;display:block;background:#f4f2ed}.dense .meme-image{height:168px}.dense .meme-caption{font-size:14px;padding:12px;min-height:56px}.dense .meme-caption.bottom{font-size:13px;min-height:58px}.card-meta{padding:13px 14px 12px;border-top:1px solid #efede5}.card-topline{display:flex;justify-content:space-between;gap:8px;align-items:center}.mood-label{font-size:8px;font-weight:600;padding:4px 7px;border-radius:4px;display:inline-flex;align-items:center;gap:4px;letter-spacing:.2px;background:var(--blue-pale);color:var(--blue-dark)}.mood-label[data-mood=chaos]{background:var(--gold);color:#6f5822}.mood-label[data-mood=wholesome]{background:var(--green);color:#4c653b}.mood-label[data-mood=judgment]{background:var(--pink);color:#82564b}.mood-label[data-mood=sleepy]{background:#eae3f2;color:#6a537f}.mood-label[data-mood=snacks]{background:#f4e7d7;color:#85602f}.card-source{font-size:8px;color:#a09b8d}.card-bottomline{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:8px}.card-title{font-size:11px;font-weight:600;line-height:1.4;margin:0;flex:1;overflow-wrap:anywhere}.save-button{color:#918876;padding:5px;border-radius:6px;position:relative}.save-button:hover{background:var(--pink);color:#9c6055}.save-button.saved{color:#9e5c53}.save-button.saved svg{fill:#eed2cc}.save-button svg{width:18px;height:18px}.load-more{display:flex;align-items:center;justify-content:center;gap:16px;padding:13px 24px;margin:30px auto 0;border:1px solid #ccc5b1;border-radius:7px;font-size:11px;font-weight:600;background:var(--paper)}.load-more:hover{background:#e9e4d5}.load-more svg{width:15px;height:15px}.end-note{display:flex;gap:14px;align-items:center;justify-content:center;padding:38px 0 8px;color:var(--muted);font-size:11px;text-align:left;line-height:1.8}.end-note>span{font-size:29px;color:var(--olive-soft)}.end-note button{padding:0;text-decoration:underline;font-size:10px}footer{padding:30px 0 20px;border-top:1px solid var(--line);margin-top:36px;display:flex;justify-content:space-between;align-items:center;gap:20px;font-size:10px;color:#8a8471}footer>span{display:flex;align-items:center;gap:8px}footer img{width:22px;opacity:.7}footer button{font-size:10px;padding:0}.page-description{font-size:12px;line-height:1.7;color:var(--muted);margin-top:9px}.empty-state{text-align:center;padding:65px 20px}.empty-face{font-size:66px;display:block;color:#958968;margin-bottom:18px}.empty-state h3{font-family:Fraunces,Georgia,serif;font-size:28px;margin-bottom:8px}.empty-state p{font-size:13px;color:var(--muted);margin-bottom:22px}.primary-button,.outline-button{border-radius:7px;padding:11px 14px;font-size:11px;font-weight:600;display:inline-flex;gap:7px;align-items:center;justify-content:center}.primary-button{background:var(--olive);color:var(--paper)}.outline-button{border:1px solid #d6d0bf;background:var(--paper)}.outline-button:hover{background:#eae6d9}.primary-button svg,.outline-button svg{width:16px;height:16px}.collection-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:25px 0}.collection-card{text-align:left;background:var(--blue-pale);border:1px solid #d5dced;border-radius:13px;padding:20px;min-height:155px;position:relative;overflow:hidden}.collection-card:nth-child(2){background:var(--gold);border-color:#e3d4a4}.collection-card:nth-child(3){background:var(--green);border-color:#d3dfc4}.collection-card:hover{transform:translateY(-3px);box-shadow:0 5px 0 #4a412a0d}.collection-card small{font-size:8px;letter-spacing:1px}.collection-card h3{font-family:Fraunces,serif;font-size:25px;max-width:70%;line-height:1.1;margin:14px 0 9px;font-weight:600}.collection-card p{font-size:10px;max-width:68%;line-height:1.5}.collection-card img{position:absolute;width:92px;height:90px;object-fit:cover;right:-8px;bottom:10px;border:5px solid #fff;transform:rotate(10deg);border-radius:4px;box-shadow:0 4px 10px #4a412a18}.collection-card.active{outline:2px solid var(--olive);outline-offset:3px}.mobile-nav{display:none}dialog{border:1px solid #cfc7b3;border-radius:17px;color:var(--olive);background:var(--cream);padding:0;box-shadow:0 25px 120px #18171150;max-height:92dvh;max-width:calc(100vw - 30px)}dialog::backdrop{background:#242117af;backdrop-filter:blur(5px)}body:has(dialog[open]){overflow:hidden}.viewer-dialog{width:890px}.viewer-top{display:flex;align-items:center;justify-content:space-between;padding:15px 22px;border-bottom:1px solid var(--line)}.viewer-top .eyebrow{font-size:9px}.viewer-progress{font-size:10px;color:var(--muted);margin-left:auto;margin-right:22px}.icon-button{padding:7px;border-radius:6px}.icon-button:hover{background:#e3dfd1}.viewer-body{position:relative;padding:22px 85px;min-height:240px;background:#e9e6db}.viewer-image-wrap{max-width:530px;margin:auto;box-shadow:0 5px 18px #4a412a15;border-radius:5px;overflow:hidden}.viewer-image-wrap .meme-image{height:auto;max-height:40dvh;object-fit:contain}.viewer-image-wrap .meme-caption{font-size:22px;padding:17px 22px;min-height:0}.viewer-arrow{position:absolute;top:45%;background:var(--paper);padding:12px;border:1px solid #d8d2c1;border-radius:50%;box-shadow:0 3px 7px #4a412a09}.viewer-arrow:hover{background:var(--blue)}.viewer-arrow.previous{left:20px}.viewer-arrow.next{right:20px}.viewer-details{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px 24px}.viewer-details h2{font-size:23px;line-height:1.2;margin-top:8px}.viewer-details p{font-size:10px;margin-top:7px;color:var(--muted)}.viewer-actions{display:flex;gap:14px;align-items:center;flex-shrink:0;flex-wrap:wrap}.viewer-actions .saved{background:var(--pink);color:#854e43}.viewer-actions .saved svg{fill:var(--pink)}.viewer-bottom{padding:13px 24px;border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;gap:12px;color:var(--muted);font-size:9px}.viewer-bottom a{text-underline-offset:3px}.viewer-bottom button{display:flex;gap:6px;align-items:center;font-size:10px}.viewer-bottom svg{width:14px;height:14px}.viewer-bottom kbd{display:inline-block;background:#e8e4d8;border:1px solid #d6cfbc;font-size:9px;padding:2px 4px;border-radius:3px;margin:0 2px}.autoplay-track{height:3px;background:transparent}.autoplay-track i{display:block;height:3px;background:var(--blue-dark);width:0}.autoplay-track i.running{animation:progress 6s linear infinite}.editor-dialog{width:870px}.dialog-heading{display:flex;align-items:center;justify-content:space-between;padding:24px 26px;border-bottom:1px solid var(--line);gap:14px}.dialog-heading .eyebrow{margin-bottom:8px;color:var(--muted)}.editor-layout{display:grid;grid-template-columns:1fr 1fr;gap:24px;padding:24px}.editor-preview{align-self:start;border:1px solid var(--line);border-radius:7px;overflow:hidden;position:sticky;top:10px}.editor-preview .meme-image{height:270px}.editor-preview .meme-caption{font-size:19px;padding:18px;min-height:62px}#editor-form{display:grid;gap:13px}#editor-form label{display:grid;gap:7px;font-size:11px;font-weight:600}#editor-form select,#editor-form textarea,#editor-form input{width:100%;border:1px solid #d7d1c1;border-radius:6px;padding:10px;background:var(--paper);color:var(--olive);font-size:12px;resize:vertical}#editor-form .upload-button{display:flex;align-items:center;justify-content:center;gap:6px;padding:10px;border:1px dashed #bcb39a;border-radius:6px;cursor:pointer;font-size:11px}#editor-form .upload-button:hover{background:#e9e4d5}.upload-button svg{width:16px;height:16px}.editor-actions{display:flex;gap:12px;padding-top:3px;flex-wrap:wrap}.local-note{font-size:10px;color:var(--muted);line-height:1.6}.info-dialog{width:600px}#info-content{padding:25px;line-height:1.8;font-size:13px;color:#736b55}#info-content p+p{margin-top:14px}#info-content strong{color:var(--olive)}#info-content .palette{display:flex;gap:10px;margin:22px 0}#info-content .palette>div{flex:1;height:75px;border-radius:8px;display:flex;align-items:end;justify-content:center;padding:8px;font-size:9px;border:1px solid #cfc8b5}.shortcut-table{width:100%;border-collapse:collapse}.shortcut-table td{border-bottom:1px solid var(--line);padding:10px 4px}.shortcut-table td:first-child{font-family:monospace;font-weight:700;color:var(--olive);width:130px}.toast{position:fixed;bottom:28px;left:calc(50% + 110px);transform:translate(-50%,15px);opacity:0;pointer-events:none;z-index:99;background:var(--olive);color:var(--paper);border:1px solid #7d7359;box-shadow:0 5px 22px #25200d30;border-radius:9px;padding:13px 22px;font-size:12px;transition:transform .2s,opacity .2s;max-width:85vw;text-align:center}.toast.visible{opacity:1;transform:translate(-50%,0)}@keyframes float-cat{0%,100%{transform:rotate(-9deg) translateY(0)}50%{transform:rotate(-7deg) translateY(-6px)}}@keyframes card-in{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:translateY(0)}}@keyframes progress{from{width:0}to{width:100%}}
@media(min-width:1700px){.meme-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.meme-grid.dense{grid-template-columns:repeat(5,minmax(0,1fr))}.hero-art{width:330px;right:65px}.hero{padding-left:43px}.hero-copy{width:70%}.hero .eyebrow{font-size:10px}.hero-copy p{font-size:13px}.hero-button{font-size:12px}.meme-caption{font-size:17px}.meme-caption.bottom{font-size:16px}}
@media(max-width:1150px){.sidebar{width:190px;padding:25px 15px}.workspace{margin-left:190px}.brand{font-size:14px}.brand img{width:48px}.brand-bottom{font-size:24px}.topbar{padding:0 25px}.breadcrumb .edition{display:none}main{padding:25px 25px 0}.hero{padding:28px}.hero-copy{width:61%}.hero-art{width:34%;right:18px}.hero-sticker{width:150px}.front-sticker{width:162px;left:-19px}.hero-footnote{display:block;margin:11px 0 0}.hero-doodle{display:none}.hero-ring{width:210px;height:240px}.approval-sticker{right:-4px;bottom:37px;width:69px;height:69px;font-size:22px}.hero-copy p{font-size:10px}.meme-grid{gap:15px}.meme-image{height:180px}.meme-caption{font-size:14px;padding:12px}.meme-caption.bottom{font-size:13px;padding:12px}.meme-grid.dense{grid-template-columns:repeat(3,minmax(0,1fr))}.search-wrap{width:220px}.toast{left:calc(50% + 95px)}}
@media(max-width:900px){.sidebar{width:174px;padding:23px 11px}.workspace{margin-left:174px}.brand img{width:41px}.brand{gap:6px;font-size:13px}.brand-bottom{font-size:22px}.side-tagline{margin-bottom:27px}.nav-item{font-size:11px;gap:8px;padding:11px 9px}.nav-label{font-size:8px;letter-spacing:1px}.breadcrumb{display:none}.top-actions{width:100%;justify-content:space-between}.search-wrap{width:65%;max-width:310px}.hero{min-height:305px}.hero-copy{width:70%}.hero-art{width:28%;opacity:.9}.back-sticker{display:none}.front-sticker{width:135px;left:-15px;top:105px}.sticker-caption{font-size:10px}.sticker-footer{font-size:7px}.hero-sticker img{height:110px}.approval-sticker{bottom:23px;right:-5px;width:57px;height:57px;font-size:19px}.approval-sticker span{font-size:5px}.hero-ring{width:180px;right:-25px}.art-star{left:26px;top:46px}.hero-copy p{max-width:80%}.hero-copy p br{display:none}.hero .eyebrow{font-size:7px;letter-spacing:.9px}.hero-footnote{display:none}h1{font-size:42px}.library-heading h2{font-size:23px}.text-button{font-size:10px}.meme-grid,.meme-grid.dense{grid-template-columns:repeat(2,minmax(0,1fr))}.mood-filter{padding:8px 11px;font-size:10px}.hero-button{gap:10px}.collection-grid{grid-template-columns:1fr}.collection-card{min-height:120px}.collection-card h3{max-width:80%;margin:9px 0}.collection-card img{right:18px}.collection-card p{max-width:80%}.toast{left:calc(50% + 87px)}.viewer-details{align-items:start;flex-direction:column}.viewer-actions{width:100%;justify-content:end}.viewer-dialog{max-height:95dvh}.editor-layout{gap:18px;padding:20px}.viewer-image-wrap .meme-image{max-height:36dvh}}
@media(max-width:650px){.sidebar{display:none}.workspace{margin-left:0}.topbar{height:71px;padding:0 17px}.mobile-home{display:block;padding:0}.mobile-home img{width:37px;height:40px}.top-actions{gap:9px}.search-wrap{padding:9px;width:auto;flex:1}.search-wrap input{font-size:10px}.search-wrap kbd{display:none}.create-button{padding:10px}.create-button>span:last-child{display:none}.create-button svg{width:18px}.mobile-nav{display:flex;position:fixed;bottom:0;left:0;right:0;background:#efede3f5;backdrop-filter:blur(10px);padding:8px 10px max(9px,env(safe-area-inset-bottom));border-top:1px solid #d9d4c3;z-index:21;justify-content:space-around}.mobile-nav button{display:flex;flex-direction:column;align-items:center;gap:4px;font-size:9px;padding:5px 17px;border-radius:7px;color:#8a826b}.mobile-nav button.active{color:var(--olive);background:#e0dccb}.mobile-nav svg{width:19px;height:19px}main{padding:18px 17px 80px}.hero{padding:24px 21px;min-height:300px;border-radius:13px}.hero-copy{width:100%}.hero .eyebrow{font-size:7px;letter-spacing:.8px}.hero .eyebrow .little-star{font-size:11px}h1{font-size:39px;letter-spacing:-1.5px;margin-top:13px}.hero-copy p{font-size:10px;max-width:62%;margin-top:13px;margin-bottom:17px;line-height:1.6}.hero-button{font-size:10px;padding:11px 12px;gap:10px}.hero-art{right:0;width:32%;z-index:2;pointer-events:none}.front-sticker{width:111px;left:-5px;top:146px;padding:6px;transform:rotate(-8deg);animation:none}.front-sticker img{height:80px}.sticker-caption{font-size:9px;padding:0 2px 5px}.sticker-footer{font-size:6px;padding-top:4px}.sticker-footer span{font-size:14px}.hero-ring{width:145px;height:220px;top:90px;right:-50px;opacity:.6}.approval-sticker{width:49px;height:49px;right:13px;bottom:15px;font-size:16px}.approval-sticker span{font-size:5px}.approval-sticker .mini-paw{display:none}.art-star{font-size:28px;top:106px;left:40px}.library{margin-top:27px}.library-heading{gap:10px;margin-bottom:17px;align-items:end}.library-heading h2{font-size:23px;line-height:1.16;max-width:230px}.library-heading .text-button{font-size:0;padding:5px;background:#e9e5d9;border:1px solid #d9d2c2;border-radius:6px}.library-heading .text-button svg{width:17px;height:17px}.section-eyebrow{font-size:7px;margin-bottom:6px}.mood-filters{gap:6px;flex-wrap:nowrap;overflow:auto;padding:1px 1px 7px;scrollbar-width:none;margin-right:-17px;padding-right:17px}.mood-filter{white-space:nowrap;font-size:10px;padding:8px 12px}.filter-row{padding-bottom:8px}.results-bar{font-size:9px;padding:14px 0}.sort-label{font-size:0;gap:0}.sort-label select{font-size:9px;width:105px}.result-controls{gap:5px}.view-toggle button{padding:3px}.view-toggle svg{width:12px;height:12px}.meme-grid{gap:12px;grid-template-columns:repeat(2,minmax(0,1fr))}.meme-card{border-radius:9px}.meme-image{height:145px}.meme-caption{font-size:12px;padding:11px 10px;min-height:60px;line-height:1.3}.meme-caption.bottom{font-size:11px;padding:10px;min-height:54px}.card-meta{padding:10px 9px}.card-source{display:none}.card-title{font-size:10px;line-height:1.4}.card-bottomline{align-items:start}.save-button{padding:3px}.save-button svg{width:16px;height:16px}.mood-label{font-size:7px;padding:4px 5px}.meme-grid.dense{grid-template-columns:repeat(3,minmax(0,1fr));gap:7px}.dense .meme-image{height:100px}.dense .meme-caption,.dense .meme-caption.bottom{font-size:10px;padding:8px;min-height:57px}.dense .card-title{font-size:9px}.dense .card-meta{padding:8px 6px}.dense .card-bottomline{flex-wrap:wrap}.dense .mood-label{font-size:6px}.dense .save-button{padding:2px}.dense .save-button svg{width:14px}.load-more{margin-top:24px;font-size:11px}.end-note{font-size:10px}.end-note>span{font-size:22px}footer{font-size:8px;gap:10px;padding:22px 0 3px;flex-wrap:wrap}footer button{font-size:9px}.toast{left:50%;bottom:85px;width:max-content;max-width:90vw;font-size:11px}.viewer-dialog{max-width:calc(100vw - 16px);max-height:96dvh;border-radius:12px}.viewer-top{padding:10px 12px}.viewer-top .eyebrow{font-size:8px}.viewer-body{padding:12px 37px}.viewer-arrow{padding:6px}.viewer-arrow svg{width:16px;height:16px}.viewer-arrow.previous{left:4px}.viewer-arrow.next{right:4px}.viewer-image-wrap .meme-caption{font-size:16px;padding:14px}.viewer-image-wrap .meme-image{max-height:33dvh}.viewer-details{padding:15px;gap:13px}.viewer-details h2{font-size:21px}.viewer-details p{font-size:9px}.viewer-actions{justify-content:start;gap:6px;flex-wrap:wrap}.viewer-actions .primary-button,.viewer-actions .outline-button{padding:9px 11px;font-size:10px}.viewer-bottom{padding:11px 15px;font-size:9px;flex-wrap:wrap}.viewer-bottom>span{display:none}.editor-dialog{max-width:calc(100vw - 16px);max-height:95dvh}.dialog-heading{padding:18px}.dialog-heading h2{font-size:24px}.dialog-heading .eyebrow{font-size:8px}.editor-layout{grid-template-columns:1fr;padding:18px}.editor-preview{position:static;max-width:280px;margin:auto;width:100%}.editor-preview .meme-image{height:175px}.editor-preview .meme-caption{font-size:15px;padding:12px;min-height:40px}#editor-form{gap:14px}.info-dialog{max-width:calc(100vw - 24px)}#info-content{padding:20px;font-size:12px}.empty-state{padding:45px 10px}.empty-state h3{font-size:25px}.collection-card p{max-width:70%}.hero-copy .hero-button{max-width:70%}}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important;scroll-behavior:auto!important}.meme-card:hover{transform:none}.autoplay-track i.running{width:100%}}
+.arena-stats{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:18px}.stat-chip{background:var(--paper);border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:11px;font-weight:600;min-width:110px;box-shadow:0 2px 0 #4a412a10}.stat-chip strong{font-size:19px;display:block;font-family:Fraunces,Georgia,serif}.battle-grid{display:grid;grid-template-columns:1fr 64px 1fr;gap:14px;align-items:stretch;margin-bottom:14px}.battle-card{background:var(--paper);border:2px solid var(--line);border-radius:13px;overflow:hidden;text-align:left;padding:0;display:flex;flex-direction:column;animation:card-in .3s both;box-shadow:0 3px 0 #4a412a12}.battle-card:hover{transform:translateY(-4px);border-color:var(--olive);box-shadow:0 8px 22px #4a412a18}.battle-card:active{transform:translateY(1px)}.battle-card:focus-visible{outline:3px solid var(--blue-dark);outline-offset:3px}.battle-side{font-size:10px;font-weight:800;letter-spacing:1px;padding:9px 12px 0;color:var(--muted)}.battle-card .meme-visual{border-top:1px solid #f0ede2;margin-top:8px}.battle-meta{padding:12px 13px 14px;display:grid;gap:4px}.battle-meta .mood-label{justify-self:start}.battle-title{font-size:14px;font-weight:700;line-height:1.25}.battle-record{font-size:11px;font-weight:700;color:var(--olive)}.rate-bar{display:block;height:7px;background:#e9e4d4;border-radius:99px;overflow:hidden;margin-top:6px}.rate-bar i{display:block;height:100%;background:linear-gradient(90deg,var(--blue-dark),#7c8bd4);border-radius:99px}.rate-bar.small{width:70px;margin:0}.battle-cta{margin-top:8px;background:var(--olive);color:var(--paper);border-radius:7px;padding:9px 10px;font-size:11px;font-weight:700;text-align:center}.battle-card:hover .battle-cta{background:#62573b}.battle-vs{align-self:center;justify-self:center;font-family:Fraunces,Georgia,serif;font-weight:700;font-size:18px;background:var(--olive);color:var(--cream);width:54px;height:54px;border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 12px #4a412a30;animation:vs-pulse 2.2s ease-in-out infinite}@keyframes vs-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.1) rotate(-4deg)}}.arena-actions{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:20px}.arena-actions .text-button{font-size:11px;text-decoration:underline;color:var(--muted)}.arena-hint{font-size:11px;color:var(--muted)}.arena-hint kbd{background:var(--paper);border:1px solid var(--line);border-bottom-width:2px;border-radius:5px;padding:1px 6px;font-size:10px;font-family:inherit}.arena-intro{background:var(--olive);color:#f7f5ee;border-radius:13px;padding:18px 20px;margin-bottom:18px;box-shadow:0 6px 18px #4a412a22}.arena-intro h3{font-family:Fraunces,Georgia,serif;font-size:19px;margin-bottom:10px}.arena-intro ol{margin:0;padding-left:19px;display:grid;gap:7px;font-size:12px;line-height:1.55}.arena-intro kbd{background:#f7f5ee22;border:1px solid #f7f5ee55;border-radius:5px;padding:1px 7px;font-size:10px;font-family:inherit}.last-crowned{background:var(--gold);border:1px solid #e3d4a4;border-radius:10px;padding:10px 14px;font-size:12px;margin-bottom:14px}.last-crowned button{font-weight:800;text-decoration:underline}.leaderboard{background:var(--paper);border:1px solid var(--line);border-radius:13px;padding:16px 16px 8px}.leaderboard h3{font-family:Fraunces,Georgia,serif;font-size:21px;font-weight:600}.leader-sub{font-size:11px;color:var(--muted);margin:4px 0 8px}.leader-row{display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid var(--line);font-size:12px}.leader-row:hover{background:#faf8f1}.leader-row img{width:44px;height:44px;object-fit:cover;border-radius:8px;background:#f4f2ed}.leader-medal{width:28px;text-align:center;font-weight:800}.leader-name{font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.leader-score{font-size:11px;color:var(--muted);white-space:nowrap}.leader-open{font-size:11px;font-weight:700;text-decoration:underline}.leader-empty{font-size:12px;color:var(--muted);padding:10px 0 14px}
+.hall-title{margin-top:14px}
+.podium{display:grid;grid-template-columns:1fr 1.25fr 1fr;gap:10px;align-items:end;margin:12px 0 6px}
+.podium-card{background:var(--paper);border:2px solid var(--line);border-radius:12px;padding:12px 8px 10px;display:grid;gap:5px;justify-items:center;text-align:center}
+.podium-card:hover{transform:translateY(-3px);box-shadow:0 8px 20px #4a412a18}
+.podium-card img{width:100%;max-width:110px;aspect-ratio:1;object-fit:cover;border-radius:9px;background:#f4f2ed}
+.podium-card.place-1{border-color:#d9c57c;background:#fbf6e3;box-shadow:0 6px 20px #d9c57c44;padding-top:16px}
+[data-theme="dark"] .podium-card.place-1{background:#2e2a18;border-color:#6b5f30}
+.podium-card.place-1 img{max-width:140px}
+.podium-medal{font-size:26px}
+.podium-card.place-1 .podium-medal{font-size:34px}
+.podium-name{font-size:11px;font-weight:700;line-height:1.3}
+.podium-card .leader-score{font-size:10px}@media(max-width:650px){.battle-grid{grid-template-columns:1fr;gap:10px}.battle-vs{width:46px;height:46px;font-size:15px}.arena-stats{display:grid;grid-template-columns:repeat(3,1fr)}.stat-chip{min-width:0;padding:8px 10px}.mobile-nav button{padding:5px 10px;font-size:8px;flex:1}}@media(prefers-reduced-motion:reduce){.battle-card,.battle-card:hover{transform:none;animation:none}.battle-vs{animation:none}}
+[data-theme="dark"]{--cream:#171410;--paper:#221d15;--line:#3a3222;--olive:#ece2cb;--olive-dark:#f6efdd;--olive-soft:#8d8266;--muted:#a2967c;--blue:#9fadf0;--blue-dark:#b3bef5;--blue-pale:#252b45;--green:#2b3323;--pink:#3a2b25;--gold:#38311e}
+[data-theme="dark"] .sidebar{background:#1f1a13;border-color:#332c1f}
+[data-theme="dark"] .hero{background:#4A412A;color:#F7F5EE}
+[data-theme="dark"] .hero .eyebrow{color:#d3cbb1}
+[data-theme="dark"] .nav-item:hover{background:#2a2417}
+[data-theme="dark"] .nav-item.active{background:#ece2cb;color:#221d15}
+[data-theme="dark"] .nav-count{background:#38311f}
+[data-theme="dark"] .active .nav-count{background:#ece2cb;color:#221d15}
+[data-theme="dark"] .side-divider{background:#332c1f}
+[data-theme="dark"] .search-wrap{background:#221d15}
+[data-theme="dark"] dialog{border-color:#3a3222}
+[data-theme="dark"] .outline-button{border-color:#4a4230}
+[data-theme="dark"] .outline-button:hover{background:#2c2617}
+[data-theme="dark"] .collection-card{background:#282219!important;border-color:#3a3222!important}
+[data-theme="dark"] .collection-card:nth-child(2){background:#2e2a18!important;border-color:#453d22!important}
+[data-theme="dark"] .collection-card:nth-child(3){background:#222a1e!important;border-color:#33402c!important}
+[data-theme="dark"] .arena-intro{background:#2b2517;color:#f0e9d6;border:1px solid #3a3222}
+[data-theme="dark"] .mobile-nav{background:#1f1a13f5;border-color:#332c1f}
+[data-theme="dark"] .mobile-nav button{color:#8d8266}
+[data-theme="dark"] .mobile-nav button.active{color:#ece2cb;background:#2c2618}
+[data-theme="dark"] .skip-link{background:#ece2cb!important;color:#221d15!important}
+[data-theme="dark"] footer{color:var(--muted)}
+#theme-toggle,#sound-toggle{border:1px solid var(--line);background:var(--paper);border-radius:8px;padding:9px}
+#confetti{position:fixed;inset:0;pointer-events:none;z-index:200}
+.lab-intro{background:var(--blue-pale);border:1px solid #d5dced;border-radius:13px;padding:16px 18px;margin-bottom:16px}
+.lab-intro h3{font-family:Fraunces,Georgia,serif;font-size:20px;margin-bottom:6px}
+.lab-intro p{font-size:12px;color:var(--muted);line-height:1.6}
+.lab-layout{display:grid;grid-template-columns:1.1fr 1fr;gap:18px;align-items:start}
+.lab-preview{background:var(--paper);border:1px solid var(--line);border-radius:13px;overflow:hidden}
+.lab-controls{display:grid;gap:12px;align-content:start}
+.lab-row{display:flex;gap:8px;flex-wrap:wrap}
+.lab-provenance{font-size:12px;background:var(--paper);border:1px solid var(--line);border-radius:10px;padding:10px 12px;display:grid;gap:4px;line-height:1.5}
+.curse{font-weight:800;letter-spacing:.4px}
+[data-theme="dark"] .lab-intro{background:#252b45;border-color:#3a4370}
+@media(max-width:650px){.lab-layout{grid-template-columns:1fr}}
+.ticker{overflow:hidden;border-top:1px solid var(--line);border-bottom:1px solid var(--line);margin-top:22px;background:var(--paper)}
+.ticker-inner{display:inline-block;white-space:nowrap;padding:9px 0;font-size:11px;font-weight:700;letter-spacing:.5px;animation:tickmove 45s linear infinite}
+.ticker-inner span{padding-right:24px}
+@keyframes tickmove{0%{transform:translateX(0)}100%{transform:translateX(-50%)}}
+@media(prefers-reduced-motion:reduce){.ticker-inner{animation:none}}
+#library{position:relative}
+#library>*{position:relative;z-index:1}
+#library::before{content:'MEOW';position:absolute;top:-24px;right:-6px;z-index:0;font-family:Fraunces,Georgia,serif;font-size:clamp(90px,15vw,210px);font-weight:700;line-height:1;color:transparent;-webkit-text-stroke:1px var(--line);opacity:.7;pointer-events:none}
+body::after{content:'';position:fixed;inset:0;z-index:1;pointer-events:none;opacity:.05;background-image:url("data:image/svg+xml, ")}
+.pack-panel{display:grid;gap:12px;background:linear-gradient(135deg,var(--olive) 0%,#5c5136 100%);color:#f7f5ee;border-radius:13px;padding:22px;margin-bottom:16px;text-align:center}
+.pack-sealed{background:none;border:2px dashed #f7f5ee66;border-radius:12px;padding:26px;display:grid;gap:6px;justify-items:center;color:#f7f5ee;width:100%}
+.pack-sealed>span{font-size:64px}
+.pack-sealed strong{font-size:18px;font-family:Fraunces,Georgia,serif}
+.pack-sealed small{opacity:.75}
+.pack-sealed:hover{border-color:#f7f5ee;background:#f7f5ee11;transform:translateY(-2px)}
+.pack-sealed.opening>span{animation:packshake .5s ease-in-out infinite}
+@keyframes packshake{0%,100%{transform:rotate(-8deg) scale(1)}50%{transform:rotate(8deg) scale(1.12)}}
+.pack-wait{display:flex;gap:18px;align-items:center;justify-content:center;flex-wrap:wrap;text-align:left}
+.pack-wait .arena-hint{color:#d7d1c0}
+.pack-timer{font-family:Fraunces,Georgia,serif;font-size:30px}
+.pack-mini{display:flex;gap:10px;align-items:center;background:#f7f5ee14;border:1px solid #f7f5ee33;border-radius:10px;padding:8px;text-align:left;color:#f7f5ee;max-width:340px}
+.pack-mini img{width:64px;height:64px;object-fit:cover;border-radius:8px;flex-shrink:0}
+.pack-mini small{opacity:.75;display:block}
+.pack-mini.rare{border-color:#9fadf0;box-shadow:0 0 18px #9fadf066}
+.pack-mini.legendary{border-color:#ffd75e;box-shadow:0 0 26px #ffd75e88}
+.pack-mini.shiny{background:#ffd75e22}
+.pack-mini.fresh{animation:packpop .45s ease-out}
+@keyframes packpop{0%{transform:scale(.6);opacity:0}100%{transform:scale(1);opacity:1}}
+.pack-tabs{display:flex;gap:8px;margin-bottom:14px}
+.pack-tabs button{border:1px solid var(--line);background:var(--paper);border-radius:20px;padding:8px 14px;font-size:11px;font-weight:700}
+.pack-tabs button.active{background:var(--olive);border-color:var(--olive);color:var(--paper)}
+.meme-card{position:relative}
+.shiny-badge{position:absolute;top:8px;left:8px;z-index:2;background:#ffd75e;color:#3a2f10;font-size:10px;font-weight:800;border-radius:6px;padding:3px 8px}
+.meme-card.is-shiny{border-color:#d9c57c}
+.va-group{display:inline-flex;gap:5px;align-items:center}
+.dex-progress{background:var(--paper);border:1px solid var(--line);border-radius:13px;padding:16px 18px;margin-bottom:18px;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
+.dex-progress strong{font-family:Fraunces,Georgia,serif;font-size:24px;white-space:nowrap}
+.dex-progress .rate-bar{flex:1;min-width:140px}
+.dex-mood{font-family:Fraunces,Georgia,serif;font-size:20px;margin:20px 0 10px}
+.dex-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(108px,1fr));gap:10px}
+.dex-card{background:var(--paper);border:1px solid var(--line);border-radius:10px;padding:8px;display:grid;gap:5px;justify-items:center;text-align:center}
+button.dex-card:hover{transform:translateY(-3px);box-shadow:0 8px 20px #4a412a18}
+.dex-card img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:7px;background:#f4f2ed}
+.dex-num{font-family:ui-monospace,monospace;font-size:9px;color:var(--muted)}
+.dex-name{font-size:10px;font-weight:700;line-height:1.3}
+.dex-card.locked{background:#171410;color:#8f8468;border-style:dashed;cursor:help}
+.dex-card.locked .dex-num{color:#5c5545}
+.dex-q{font-family:Fraunces,Georgia,serif;font-size:34px;line-height:1.4;color:#d9c57c}
+.mobile-nav{overflow-x:auto;scrollbar-width:none}
+.mobile-nav button{flex:1 0 auto}
+@media(max-width:650px){.mobile-nav button{padding:5px 8px}}
+.meme-visual{position:relative}
+.font-fraunces .meme-caption{font-family:Fraunces,Georgia,serif}
+.font-mono .meme-caption{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.92em}
+.style-dark{background:#171410;color:#F7F5EE}
+.style-banana{background:#FFD75E;color:#3A2F10}
+.sticker{position:absolute;z-index:3;font-size:58px;line-height:1;filter:drop-shadow(0 3px 6px #00000055);pointer-events:none}
+.corner-tl{top:6px;left:10px}.corner-tr{top:6px;right:10px}.corner-bl{bottom:6px;left:10px}.corner-br{bottom:6px;right:10px}
+.editor-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
+.hot-badge{font-size:9px;font-weight:800;letter-spacing:.6px;background:#ffe3d6;border:1px solid #f0b49a;color:#a03d1c;border-radius:20px;padding:3px 8px;white-space:nowrap}
+/* contrast pass: microcopy on ecru cards was washing out */
+.card-source{color:#6f6a58}
+.save-button{color:#6f6a58}
+.nav-label{color:#6e6957}
+.nav-label.nav-gap{margin-top:20px}
+footer{color:#6e6957}
+.view-toggle button{color:#6e6957}
+.mobile-nav button{color:#6e6957}
+.search-wrap svg{color:#6e6957}
+.hero-footnote{color:#c6bfa9}
+#chimp-buddy{position:fixed;right:18px;bottom:18px;z-index:150;display:flex;flex-direction:column;align-items:end;gap:8px;perspective:320px;animation:buddybob 3s ease-in-out infinite}
+#buddy-btn{position:relative;overflow:hidden;background:var(--paper);border:2px solid var(--line);border-radius:50%;width:68px;height:68px;padding:0;box-shadow:0 10px 26px #4a412a40;transform-style:preserve-3d;will-change:transform}
+#buddy-btn img{width:100%;height:100%;object-fit:cover;display:block;border-radius:50%}
+#buddy-btn::after{content:'';position:absolute;inset:0;border-radius:50%;background:radial-gradient(circle at var(--gx,30%) var(--gy,25%),#ffffff66,transparent 55%);pointer-events:none}
+#chimp-buddy.muted #buddy-btn{opacity:.4;filter:grayscale(1)}
+.buddy-bubble{max-width:230px;background:var(--paper);border:1px solid var(--line);border-radius:12px 12px 4px 12px;padding:10px 12px;font-size:12px;line-height:1.5;box-shadow:0 8px 24px #4a412a25;animation:packpop .25s ease-out}
+@keyframes buddybob{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
+@media(max-width:650px){#chimp-buddy{bottom:76px;right:12px}#buddy-btn{width:54px;height:54px}}
+@media(prefers-reduced-motion:reduce){#buddy-btn{animation:none}}
+.reveal-actions{margin-top:10px;display:flex;justify-content:center}
+.forge{background:var(--paper);border:1px solid var(--line);border-radius:13px;padding:16px;margin-bottom:16px}
+.forge h3{font-family:Fraunces,Georgia,serif;font-size:20px}
+.forge-top{display:flex;gap:10px;align-items:center;justify-content:space-between;flex-wrap:wrap;margin:10px 0 4px}
+.scrap-count{font-size:15px;font-weight:800}
+.forge .primary-button:disabled{cursor:not-allowed;opacity:.45}
+[data-theme="dark"] .pack-panel{background:linear-gradient(135deg,#2b2517,#3a3120);color:#f0e9d6}
+#stash{display:grid;gap:16px;margin-top:18px}
+.sets-panel,.trophies{background:var(--paper);border:1px solid var(--line);border-radius:13px;padding:16px}
+.trophy-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;margin-top:10px}
+.trophy{border:1px solid var(--line);border-radius:10px;padding:12px 10px;text-align:center;display:grid;gap:4px;justify-items:center;opacity:.5}
+.trophy.won{opacity:1;border-color:#d9c57c;background:#fbf6e3}
+[data-theme="dark"] .trophy.won{background:#2e2a18;border-color:#6b5f30}
+.trophy span{font-size:26px}
+.trophy strong{font-size:11px}
+.trophy small{font-size:10px;color:var(--muted);line-height:1.4}
+.viewer-dialog.tv{width:1020px}
+.viewer-dialog.tv .viewer-body{position:relative}
+.viewer-dialog.tv .viewer-details,.viewer-dialog.tv .viewer-bottom{display:none}
+.viewer-dialog.tv .viewer-image-wrap .meme-image{max-height:68dvh}
+.viewer-dialog.tv .viewer-image-wrap{cursor:pointer}
+.tv-overlay{position:absolute;inset:auto 0 0 0;display:flex;flex-direction:column;gap:3px;background:linear-gradient(transparent,#181410ee);color:#f7f5ee;padding:40px 18px 14px;font-size:12px;border-radius:0 0 16px 16px}
+.tv-meta{display:flex;gap:10px;align-items:baseline;min-width:0}
+#tv-chan{font-weight:800;letter-spacing:.6px;white-space:nowrap;color:#B9C5F3}
+#tv-name{font-weight:800;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+.tv-sub{display:flex;gap:10px;opacity:.75;font-size:11px;min-width:0}
+.tv-sub.vj-row{min-height:1.4em}
+#tv-vj{color:#ffd75e;font-style:italic;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+#tv-next{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+.tv-controls{display:flex;gap:6px;margin-top:5px;flex-wrap:wrap}
+.tv-controls button{border:1px solid #f7f5ee55;border-radius:7px;padding:6px 10px;font-size:11px;font-weight:700;color:#f7f5ee;white-space:nowrap}
+.tv-controls button:hover{background:#f7f5ee22}
+.tv-up{border:1px solid #f7f5ee55;border-radius:7px;padding:6px 10px;font-size:11px;font-weight:700;color:#f7f5ee;cursor:pointer}
+.tv-up:hover{background:#f7f5ee22}
+.clip-player{width:100%;max-height:60dvh;background:#000;display:block}
+.viewer-dialog.tv .viewer-image-wrap{position:relative}
+.viewer-dialog.tv .viewer-image-wrap::after{content:'';position:absolute;inset:0;background:repeating-linear-gradient(0deg,#00000014 0 1px,transparent 1px 3px);box-shadow:inset 0 0 90px #00000055;pointer-events:none}
+#zap{position:absolute;inset:0;background:#fff;opacity:0;pointer-events:none;z-index:30}
+#zap.on{animation:zapflash .25s ease-out}
+@keyframes zapflash{0%{opacity:.5}100%{opacity:0}}
+#screen{position:absolute;inset:0;z-index:40;display:flex;align-items:center;justify-content:center;background:#14110c;color:#f7f5ee;opacity:0;pointer-events:none;text-align:center;padding:20px}
+#screen.show{animation:identshow 1.6s ease-out}
+@keyframes identshow{0%{opacity:0}12%{opacity:1}80%{opacity:1}100%{opacity:0}}
+.ident-big{font-family:Fraunces,Georgia,serif;font-size:clamp(28px,6vw,54px);font-weight:700}
+.ident-small{font-size:12px;letter-spacing:2px;opacity:.7;margin-top:8px}
+.testbars{height:70px;border-radius:8px;background:linear-gradient(to right,#c0c0c0 0%,#c0c0c0 14%,#c0c000 14%,#c0c000 28%,#00c0c0 28%,#00c0c0 42%,#00c000 42%,#00c000 57%,#c000c0 57%,#c000c0 71%,#c00000 71%,#c00000 85%,#0000c0 85%,#0000c0 100%)}
+.testcard{width:min(420px,90%)}
+.testcap{font-family:Fraunces,Georgia,serif;font-size:24px;margin-top:14px}
+.testcap small{display:block;font-family:'DM Sans',sans-serif;font-size:11px;letter-spacing:2px;opacity:.7;margin-top:6px}
+#tv-live{color:#ff6b5e;font-weight:800;white-space:nowrap}
+#tv-clock{opacity:.75;white-space:nowrap}
+.ad-break{background:#0d0d0f;color:#ffe900;display:flex;flex-direction:column;overflow:hidden;text-align:center}
+.ad-bug{align-self:start;background:#ffe900;color:#0d0d0f;font-size:10px;font-weight:800;letter-spacing:2px;padding:4px 10px;margin:12px 0 0 12px;border-radius:4px}
+.ad-brand{font-family:Fraunces,Georgia,serif;font-size:clamp(26px,5vw,44px);font-weight:700;padding:14px 16px 0}
+.ad-break img{width:100%;max-height:46dvh;object-fit:cover}
+.ad-line{font-size:15px;font-weight:600;padding:12px 16px 0}
+.ad-cta{display:inline-block;margin:12px auto 16px;border:2px solid #ffe900;border-radius:8px;padding:8px 16px;font-size:11px;font-weight:800;letter-spacing:1px}
+.sets-panel h3,.trophies h3{font-family:Fraunces,Georgia,serif;font-size:20px}
+#new-set-form{display:flex;gap:8px;margin:10px 0 6px}
+#new-set-form input{flex:1;border:1px solid var(--line);background:var(--paper);color:var(--olive);border-radius:7px;padding:10px 12px;font-size:12px;min-width:0}
+.set-row{display:flex;gap:8px;align-items:center;padding:8px 0;border-top:1px solid var(--line)}
+.set-open{flex:1;display:flex;justify-content:space-between;align-items:center;gap:10px;text-align:left;border-radius:8px;padding:8px 10px;font-size:12px}
+.set-open:hover{background:#eeeadd}
+[data-theme="dark"] .set-open:hover{background:#2c2618}
+.set-open.active{outline:2px solid var(--olive);outline-offset:2px}
+.set-open span{color:var(--muted);font-size:11px;white-space:nowrap}
+.set-delete{font-size:11px;color:var(--muted);text-decoration:underline;white-space:nowrap}
+.upload-label{display:inline-flex;align-items:center;cursor:pointer}
+.remove-from-set{margin:0 12px 12px;font-size:11px;font-weight:700;color:#9c6055;text-decoration:underline;align-self:start}
+.collect-body{padding:20px 22px;display:grid;gap:12px}
+#collect-list{display:grid;gap:6px}
+.collect-row{display:flex;justify-content:space-between;align-items:center;border:1px solid var(--line);border-radius:8px;padding:10px 12px;font-size:12px;font-weight:600}
+.collect-row.in{background:var(--olive);border-color:var(--olive);color:var(--paper)}
+#collect-new{display:flex;gap:8px}
+#collect-new input{flex:1;border:1px solid var(--line);background:var(--paper);color:var(--olive);border-radius:7px;padding:10px 12px;font-size:12px;min-width:0}
+.hero-paths{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:14px;font-size:10px;color:#d7d1c0}
+.hero-paths button{border:1px solid #f7f5ee44;border-radius:20px;padding:7px 12px;font-size:10px;font-weight:700;color:#f7f5ee}
+.hero-paths button:hover{background:#f7f5ee22}
diff --git a/public/sw.js b/public/sw.js
new file mode 100644
index 0000000..597e163
--- /dev/null
+++ b/public/sw.js
@@ -0,0 +1,31 @@
+const VERSION = 'catmemechimp-v1';
+const CORE = ['/', '/index.html', '/app.js', '/style.css', '/fonts.css', '/library.json', '/manifest.webmanifest', '/assets/chimp.svg'];
+self.addEventListener('install', (e) => {
+ e.waitUntil(caches.open(VERSION).then((c) => c.addAll(CORE)).then(() => self.skipWaiting()));
+});
+self.addEventListener('activate', (e) => {
+ e.waitUntil(
+ caches
+ .keys()
+ .then((keys) => Promise.all(keys.filter((k) => k !== VERSION).map((k) => caches.delete(k))))
+ .then(() => self.clients.claim())
+ );
+});
+self.addEventListener('fetch', (e) => {
+ const u = new URL(e.request.url);
+ if (e.request.method !== 'GET' || u.origin !== self.location.origin) return;
+ e.respondWith(
+ caches.match(e.request).then((hit) => {
+ const net = fetch(e.request)
+ .then((res) => {
+ if (res.ok) {
+ const copy = res.clone();
+ caches.open(VERSION).then((c) => c.put(e.request, copy));
+ }
+ return res;
+ })
+ .catch(() => hit);
+ return hit || net;
+ })
+ );
+});
diff --git a/scripts/smoke.mjs b/scripts/smoke.mjs
new file mode 100644
index 0000000..aaa976f
--- /dev/null
+++ b/scripts/smoke.mjs
@@ -0,0 +1,55 @@
+import { readFileSync, existsSync } from 'node:fs';
+import { join, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const root = join(dirname(fileURLToPath(import.meta.url)), '..');
+const pub = join(root, 'public');
+const failures = [];
+const fail = (msg) => failures.push(msg);
+
+const lib = JSON.parse(readFileSync(join(pub, 'library.json'), 'utf8'));
+if (!Array.isArray(lib) || !lib.length) fail('library.json must be a non-empty array');
+
+const moods = new Set(['relatable', 'chaos', 'wholesome', 'judgment', 'sleepy', 'snacks']);
+const seen = new Set();
+for (const [i, m] of lib.entries()) {
+ const where = `library[${i}] (${m.id || 'no-id'})`;
+ for (const f of ['id', 'title', 'mood', 'tags', 'image', 'template', 'kind', 'added']) {
+ if (m[f] === undefined || m[f] === null || m[f] === '') fail(`${where} missing ${f}`);
+ }
+ if (seen.has(m.id)) fail(`duplicate id ${m.id}`);
+ seen.add(m.id);
+ if (!moods.has(m.mood)) fail(`${where} bad mood ${m.mood}`);
+ if (!Array.isArray(m.tags) || !m.tags.length) fail(`${where} tags must be non-empty`);
+ if (typeof m.image !== 'string' || !m.image.startsWith('/assets/memes/')) fail(`${where} bad image path`);
+ else if (!existsSync(join(pub, m.image.slice(1)))) fail(`${where} missing file ${m.image}`);
+ if (typeof m.origin !== 'number') fail(`${where} missing origin year`);
+ if (!['immortal', 'classic', 'alive'].includes(m.status)) fail(`${where} bad status ${m.status}`);
+ if (typeof m.lore !== 'string' || !m.lore) fail(`${where} missing lore`);
+ if (m.source !== null && m.source !== undefined && !String(m.source).startsWith('https://')) fail(`${where} bad source`);
+}
+
+const html = readFileSync(join(pub, 'index.html'), 'utf8');
+const js = readFileSync(join(pub, 'app.js'), 'utf8');
+const defined = new Set([
+ ...[...html.matchAll(/id="([A-Za-z0-9-]+)"/g)].map((m) => m[1]),
+ ...[...js.matchAll(/id="([A-Za-z0-9-]+)"/g)].map((m) => m[1]),
+]);
+for (const m of js.matchAll(/\$\('#([A-Za-z0-9-]+)'\)/g)) {
+ if (!defined.has(m[1])) fail(`app.js uses #${m[1]} but no such id is rendered`);
+}
+const count = html.match(/id="all-count">(\d+)/);
+if (!count) fail('index.html missing #all-count');
+else if (Number(count[1]) !== lib.length) fail(`#all-count is ${count[1]} but library has ${lib.length}`);
+for (const id of ['arena', 'battle-grid', 'arena-stats', 'leaderboard', 'last-crowned', 'arena-skip', 'arena-reset', 'lab', 'lab-preview', 'lab-provenance', 'lab-top', 'lab-bottom', 'lab-cat', 'lab-chaos', 'lab-save', 'lab-download', 'theme-toggle', 'sound-toggle', 'confetti', 'stash', 'sets-panel', 'trophies', 'viewer-collect', 'collect-dialog', 'collect-list', 'collect-new', 'collect-new-name', 'tv-toggle', 'tv-overlay', 'tv-chan', 'tv-clock', 'tv-live', 'tv-count', 'tv-name', 'tv-next', 'tv-ch-up', 'tv-ch-down', 'tv-vol', 'tv-speed', 'tv-sleep', 'tv-exit', 'tv-vj', 'tv-clip-upload', 'screen', 'zap', 'packs', 'pack-panel', 'pack-stats', 'keep-tabs', 'keep-haul', 'keep-loved', 'daily-count', 'chimp-buddy', 'buddy-bubble', 'buddy-btn', 'ticker', 'ticker-inner', 'viewer-more', 'editor-font', 'editor-style', 'editor-sticker', 'editor-corner', 'editor-motion', 'lab-motion', 'lab-inspire', 'meme-grid', 'viewer', 'editor']) {
+ if (!html.includes(`id="${id}"`)) fail(`index.html missing #${id}`);
+}
+for (const f of ['sw.js', 'app.js', 'style.css', 'library.json', 'manifest.webmanifest', 'assets/clips/calico.mp4', 'assets/clips/tokyo.mp4', 'assets/clips/gatos.mp4']) {
+ if (!existsSync(join(pub, f))) fail(`public/${f} missing`);
+}
+
+if (failures.length) {
+ console.error(`smoke: ${failures.length} problem(s)\n- ${failures.join('\n- ')}`);
+ process.exit(1);
+}
+console.log(`smoke: OK — ${lib.length} memes, ${new Set(lib.map((m) => m.image)).size} templates, all assets present`);
diff --git a/server.mjs b/server.mjs
index 429145a..448ff80 100644
--- a/server.mjs
+++ b/server.mjs
@@ -3,7 +3,7 @@ import {readFile,stat} from 'node:fs/promises';
import {fileURLToPath} from 'node:url';
import {resolve,extname,sep} from 'node:path';
const root=fileURLToPath(new URL('./public/',import.meta.url));
-const types={'.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8','.js':'text/javascript; charset=utf-8','.json':'application/json; charset=utf-8','.webmanifest':'application/manifest+json','.svg':'image/svg+xml','.jpg':'image/jpeg','.png':'image/png','.ico':'image/x-icon','.woff2':'font/woff2'};
+const types={'.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8','.js':'text/javascript; charset=utf-8','.json':'application/json; charset=utf-8','.webmanifest':'application/manifest+json','.svg':'image/svg+xml','.jpg':'image/jpeg','.png':'image/png','.mp4':'video/mp4','.ico':'image/x-icon','.woff2':'font/woff2'};
const server=http.createServer(async(req,res)=>{
try{
if(!['GET','HEAD'].includes(req.method)){res.writeHead(405,{Allow:'GET, HEAD'});return res.end();}
@@ -13,7 +13,7 @@ const server=http.createServer(async(req,res)=>{
if(!file.startsWith(root.endsWith(sep)?root:root+sep)){res.writeHead(403);return res.end('Forbidden');}
if(!(await stat(file)).isFile())throw new Error('not-file');
const data=await readFile(file);
- res.writeHead(200,{'Content-Type':types[extname(file)]||'application/octet-stream','Content-Length':data.length,'X-Content-Type-Options':'nosniff','Referrer-Policy':'strict-origin-when-cross-origin','Cache-Control':p.startsWith('/assets/')?'public,max-age=86400':'no-cache','Content-Security-Policy':"default-src 'self'; img-src 'self' blob: data:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"});
+ res.writeHead(200,{'Content-Type':types[extname(file)]||'application/octet-stream','Content-Length':data.length,'X-Content-Type-Options':'nosniff','Referrer-Policy':'strict-origin-when-cross-origin','Cache-Control':p.startsWith('/assets/')?'public,max-age=86400':'no-cache','Content-Security-Policy':"default-src 'self'; img-src 'self' blob: data:; media-src 'self' blob:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"});
res.end(req.method==='HEAD'?undefined:data);
}catch{res.writeHead(404,{'Content-Type':'text/plain'});res.end('This cat wandered off. Page not found.');}
});