
const SECTION_IDS=['Viridia','Greenill','Skyly','Bluefull','Purplenum','Pinkal','Redria','Oran','Yellowboze','Whitill'];
const SID_LOGOS={Viridia:'LViridia.png',Greenill:'Greenill.png',Skyly:'LSkyly.png',Bluefull:'Bluefull.png',Purplenum:'LPurplenum.png',Pinkal:'LPinkal.png',Redria:'LRedria.png',Oran:'LOran.png',Yellowboze:'LYellowboze.png',Whitill:'LWhitill.png'};

const SECTION_IDS_UPPER=['VIRIDIA','GREENILL','SKYLY','BLUEFULL','PURPLENUM','PINKAL','REDRIA','ORAN','YELLOWBOZE','WHITILL'];
const SECTION_TITLE={VIRIDIA:'Viridia',GREENILL:'Greenill',SKYLY:'Skyly',BLUEFULL:'Bluefull',PURPLENUM:'Purplenum',PINKAL:'Pinkal',REDRIA:'Redria',ORAN:'Oran',YELLOWBOZE:'Yellowboze',WHITILL:'Whitill'};
const SECTION_CLASS_MODIFIERS=[['HUmar',0],['HUnewearl',1],['HUcast',2],['HUcaseal',9],['RAmar',3],['RAmarl',11],['RAcast',4],['RAcaseal',5],['FOmar',10],['FOmarl',6],['FOnewm',7],['FOnewearl',8]];
function calcSectionId(name,classModifier=3,isBlueBurst=false){const mod=isBlueBurst?Number(classModifier):5;if(name===''||mod===-1)return'';let flag=0,sum=mod;for(let i=0;i<name.length;i++){const added=name.charCodeAt(i);sum+=added;if(added>=0x0100&&added<0xff61){if(flag!==2){flag=2;sum+=45;}}else if(added<=0xff91){if(flag!==1){flag=1;sum+=45;}}}return SECTION_IDS_UPPER[sum%10]||'';}
function calcPair(name,classModifier=3){return{gc_xbox:calcSectionId(name,classModifier,false),blue_burst:calcSectionId(name,classModifier,true)}}
function titleSection(upper){return SECTION_TITLE[upper]||''}


const AREA_IMAGES={
  'Forest':'assets/location_assets/Forest/Forest1.png',
  'Cave':'assets/location_assets/Cave/Cave1.png',
  'Mine':'assets/location_assets/Mine/Mine1.png',
  'Ruins':'assets/location_assets/Ruins/Ruins1.png',
  'Falz':'assets/location_assets/Falz/Falz.png',
  'VR Temple':'assets/location_assets/VR_Temple/VRTemple1.png',
  'VR Spaceship':'assets/location_assets/VR_Spaceship/VRSpaceship1.png',
  'Central Control Area jungle seaside mountains':'assets/location_assets/Central_Control_Area_jungle_seaside_mountains/CentralControlArea.png',
  'Cliffs of Gal Da Val':'assets/location_assets/Cliffs_of_Gal_Da_Val/Cliffs_of_Gal_Da_Val.jpg',
  'Seabed':'assets/location_assets/Seabed/Seabed1.png',
  'Control Tower':'assets/location_assets/Control_Tower/ControlTower.png',
  'Test Subject Disposal Area':'assets/location_assets/Test_Subject_Disposal_Area/Test_Subject_Disposal_Area.jpg',
  'Under the Dome':'assets/location_assets/Under_the_Dome/UndertheDome.png',
  'Underground Channel':'assets/location_assets/Underground Channel/underground_channel.jpg',
  'Episode 4 / Crater':'assets/location_assets/Episode_4/Crater/Crater1.png',
  'Episode 4/Crater':'assets/location_assets/Episode_4/Crater/Crater1.png',
  'Episode 4 / Subterranean Desert':'assets/location_assets/Episode_4/Subterranean_Desert/SubterraneanDesert1.png',
  'Episode 4/Subterranean Desert':'assets/location_assets/Episode_4/Subterranean_Desert/SubterraneanDesert1.png',
  'Episode 4 / Meteor Impact Site':'assets/location_assets/Episode_4/Meteor_Impact_Site/Meteor_Impact_Site.jpg',
  'Episode 4/Meteor Impact Site':'assets/location_assets/Episode_4/Meteor_Impact_Site/Meteor_Impact_Site.jpg',
  'Monitor Room':'assets/location_assets/Monitor_Room/Monitor_Room.jpg',
  'Sil Dragon':'assets/location_assets/Sil_Dragon/Ultimate_Forest.png',
  'Ultimate Forest':'assets/location_assets/Sil_Dragon/Ultimate_Forest.png',
  'Ultimate_Forest':'assets/location_assets/Sil_Dragon/Ultimate_Forest.png'
};
const AREA_VARIANTS={
  'Forest':['assets/location_assets/Forest/Forest1.png','assets/location_assets/Forest/Forest2.png'],
  'Cave':['assets/location_assets/Cave/Cave1.png','assets/location_assets/Cave/Cave2.png','assets/location_assets/Cave/Cave3.png'],
  'Mine':['assets/location_assets/Mine/Mine1.png','assets/location_assets/Mine/Mine2.png'],
  'Ruins':['assets/location_assets/Ruins/Ruins1.png','assets/location_assets/Ruins/Ruins2.png','assets/location_assets/Ruins/Ruins3.png'],
  'VR Temple':['assets/location_assets/VR_Temple/VRTemple1.png','assets/location_assets/VR_Temple/VRTemple2.png'],
  'VR Spaceship':['assets/location_assets/VR_Spaceship/VRSpaceship1.png','assets/location_assets/VR_Spaceship/VRSpaceship2.png'],
  'Central Control Area jungle seaside mountains':['assets/location_assets/Central_Control_Area_jungle_seaside_mountains/CentralControlArea.png','assets/location_assets/Central_Control_Area_jungle_seaside_mountains/jungle1.png','assets/location_assets/Central_Control_Area_jungle_seaside_mountains/jungle2.png','assets/location_assets/Central_Control_Area_jungle_seaside_mountains/mountain.png','assets/location_assets/Central_Control_Area_jungle_seaside_mountains/seaside.png'],
  'Seabed':['assets/location_assets/Seabed/Seabed1.png','assets/location_assets/Seabed/Seabed2.png'],
  'Episode 4 / Crater':['assets/location_assets/Episode_4/Crater/Crater1.png','assets/location_assets/Episode_4/Crater/Crater2.png'],
  'Episode 4/Crater':['assets/location_assets/Episode_4/Crater/Crater1.png','assets/location_assets/Episode_4/Crater/Crater2.png'],
  'Episode 4 / Subterranean Desert':['assets/location_assets/Episode_4/Subterranean_Desert/SubterraneanDesert1.png'],
  'Episode 4/Subterranean Desert':['assets/location_assets/Episode_4/Subterranean_Desert/SubterraneanDesert1.png'],
  'Episode 4 / Meteor Impact Site':['assets/location_assets/Episode_4/Meteor_Impact_Site/Meteor_Impact_Site.jpg'],
  'Episode 4/Meteor Impact Site':['assets/location_assets/Episode_4/Meteor_Impact_Site/Meteor_Impact_Site.jpg'],
  'Sil Dragon':['assets/location_assets/Sil_Dragon/Ultimate_Forest.png'],
  'Ultimate Forest':['assets/location_assets/Sil_Dragon/Ultimate_Forest.png'],
  'Ultimate_Forest':['assets/location_assets/Sil_Dragon/Ultimate_Forest.png']
};
function areaImage(a){return AREA_IMAGES[a]||''}
function areaVariantImage(a,seed=''){const arr=AREA_VARIANTS[a]; if(!arr||!arr.length) return areaImage(a); let h=0; const key=String(seed||a); for(let i=0;i<key.length;i++) h=(h*31+key.charCodeAt(i))>>>0; return arr[h%arr.length];}

const HEADER_WORLD_IMAGES=[
  'assets/location_assets/Forest/Forest1.png','assets/location_assets/Forest/Forest2.png','assets/location_assets/Sil_Dragon/Ultimate_Forest.png',
  'assets/location_assets/Cave/Cave1.png','assets/location_assets/Cave/Cave2.png','assets/location_assets/Cave/Cave3.png',
  'assets/location_assets/Mine/Mine1.png','assets/location_assets/Mine/Mine2.png',
  'assets/location_assets/Ruins/Ruins1.png','assets/location_assets/Ruins/Ruins2.png','assets/location_assets/Ruins/Ruins3.png',
  'assets/location_assets/Seabed/Seabed1.png','assets/location_assets/Seabed/Seabed2.png',
  'assets/location_assets/Control_Tower/ControlTower.png','assets/location_assets/VR_Temple/VRTemple1.png','assets/location_assets/VR_Spaceship/VRSpaceship1.png',
  'assets/location_assets/Episode_4/Crater/Crater1.png','assets/location_assets/Episode_4/Subterranean_Desert/SubterraneanDesert1.png'
];
const HEADER_SEASIDE_IMAGES=[
  'assets/location_assets/Central_Control_Area_jungle_seaside_mountains/seaside.png',
  'assets/location_assets/Central_Control_Area_jungle_seaside_mountains/CentralControlArea.png',
  'assets/location_assets/Seabed/Seabed1.png','assets/location_assets/Seabed/Seabed2.png',
  'assets/location_assets/Cliffs_of_Gal_Da_Val/Cliffs_of_Gal_Da_Val.jpg'
];
let headerAmbienceMode=localStorage.getItem('pso_header_ambience')||'seaside';
let headerAmbienceIndex=0;
function setHeaderAmbience(mode){
  headerAmbienceMode=(mode==='world')?'world':'seaside';
  localStorage.setItem('pso_header_ambience',headerAmbienceMode);
  const wb=$('headerWorldBtn'), sb=$('headerSeasideBtn'), hint=$('headerAmbienceHint');
  if(wb) wb.classList.toggle('active',headerAmbienceMode==='world');
  if(sb) sb.classList.toggle('active',headerAmbienceMode==='seaside');
  if(hint) hint.textContent='Header: '+(headerAmbienceMode==='world'?'World Rotate':'Seaside');
  headerAmbienceIndex=0;
  rotateHeaderAmbience(true);
}
function rotateHeaderAmbience(force=false){
  const top=document.querySelector('.top'); if(!top) return;
  const pool=headerAmbienceMode==='world'?HEADER_WORLD_IMAGES:HEADER_SEASIDE_IMAGES;
  if(!pool.length) return;
  if(!force) headerAmbienceIndex=(headerAmbienceIndex+1)%pool.length;
  const bg=pool[headerAmbienceIndex%pool.length];
  top.style.setProperty('--header-bg',`url('${bg}')`);
  document.documentElement.style.setProperty('--page-bg',`url('${bg}')`);
}
function initHeaderAmbience(){
  const wb=$('headerWorldBtn'), sb=$('headerSeasideBtn');
  if(wb) wb.onclick=()=>setHeaderAmbience('world');
  if(sb) sb.onclick=()=>setHeaderAmbience('seaside');
  setHeaderAmbience(headerAmbienceMode);
  setInterval(()=>rotateHeaderAmbience(false),9000);
}
const AREA_DISPLAY={
  '':'All Locations',
  'Central Control Area jungle seaside mountains':'CCA',
  'Control Tower':'Tower',
  'Episode 4 / Crater':'EP4 · Crater',
  'Episode 4/Crater':'EP4 · Crater',
  'Episode 4 / Subterranean Desert':'EP4 · Desert',
  'Episode 4/Subterranean Desert':'EP4 · Desert',
  'Episode 4 / Meteor Impact Site':'EP4 · Meteor Impact Site',
  'Episode 4/Meteor Impact Site':'EP4 · Meteor Impact Site',
  'VR Temple':'Temple',
  'VR Spaceship':'Spaceship',
  'Test Subject Disposal Area':'Test Subject Disposal Area',
  'Cliffs of Gal Da Val':'Cliffs of Gal Da Val',
  'Sil Dragon':'Sil Dragon · Ultimate Forest',
  'Ultimate Forest':'Ultimate Forest',
  'Ultimate_Forest':'Ultimate Forest'
};
const AREA_ORDER=[
  'Forest','Cave','Mine','Ruins','Falz','Monitor Room','Sil Dragon',
  'VR Temple','VR Spaceship','Central Control Area jungle seaside mountains','Cliffs of Gal Da Val','Control Tower','Seabed','Test Subject Disposal Area',
  'Episode 4 / Crater','Episode 4/Crater','Episode 4 / Meteor Impact Site','Episode 4/Meteor Impact Site','Episode 4 / Subterranean Desert','Episode 4/Subterranean Desert',
  'Episode 1','Episode 2'
];
function areaLabel(a){return AREA_DISPLAY[a]||String(a||'').replace(/_/g,' ')}
function areaEpisode(a){
  if(!a) return 'All Locations';
  if(String(a).startsWith('Episode 4')) return 'Episode 4';
  if(['Forest','Cave','Mine','Ruins','Falz','Monitor Room','Sil Dragon','Episode 1'].includes(a)) return 'Episode 1';
  return 'Episode 2';
}
function orderedAreas(list){
  const seen=new Set(); const out=[];
  AREA_ORDER.forEach(a=>{ if(list.includes(a) && !seen.has(a)){out.push(a); seen.add(a);} });
  list.slice().sort().forEach(a=>{ if(!seen.has(a)){out.push(a); seen.add(a);} });
  return out.filter(a=>a!=='Episode 1' && a!=='Episode 2');
}
function lockedArea(a){return currentMode==='gc' && areaEpisode(a)==='Episode 4'}
const DIFFS=['Normal','Hard','Very Hard','Ultimate'];
const CLASS_BUTTONS=['Humar','Hunewearl','Hucast','Hucaseal','Ramar','Ramarl','Racast','Racaseal','Fomar','Fomarl','Fonewm','Fonewearl'];
const CLASS_LABEL={Humar:'HUmar',Hunewearl:'HUnewearl',Hucast:'HUcast',Hucaseal:'HUcaseal',Ramar:'RAmar',Ramarl:'RAmarl',Racast:'RAcast',Racaseal:'RAcaseal',Fomar:'FOmar',Fomarl:'FOmarl',Fonewm:'FOnewm',Fonewearl:'FOnewearl'};
const ITEM_KIND_FILTERS=['All','Weapon','Frame','Barrier','Unit','Other'];
const WEAPON_TYPES=['All','Sabers','Swords','Partisans','Daggers','DoubleSabers','TwinSwords','Claws','Fists','Katanas','Slicers','Handguns','Rifles','Shots','Mechguns','Launchers','Needles','Canes','Rods','Wands','Cards'];
// v1.1 hard equip filtering based on HU / RA / FO weapon-type availability.
// User-provided mapping: green = class family can equip, red = cannot equip.
const CLASS_FAMILY={
  Humar:'HU', Hunewearl:'HU', Hucast:'HU', Hucaseal:'HU',
  Ramar:'RA', Ramarl:'RA', Racast:'RA', Racaseal:'RA',
  Fomar:'FO', Fomarl:'FO', Fonewm:'FO', Fonewearl:'FO'
};
const WEAPON_EQUIP={
  Sabers:['HU','RA','FO'],
  Swords:['HU'],
  Daggers:['HU'],
  Partisans:['HU','RA'],
  Slicers:['HU','RA','FO'],
  DoubleSabers:['HU','RA','FO'],
  Claws:['HU','RA','FO'],
  Katanas:['HU'],
  TwinSwords:['HU'],
  Fists:['HU','RA','FO'],
  Handguns:['HU','RA','FO'],
  Rifles:['RA'],
  Mechguns:['HU','RA','FO'],
  Shots:['RA'],
  Launchers:['RA'],
  Needles:['RA'],
  Canes:['FO'],
  Rods:['FO'],
  Wands:['FO'],
  Cards:['FO'],
  Other:['HU','RA','FO']
};
function selectedFamily(){return selectedClass ? CLASS_FAMILY[selectedClass] : ''}
function canEquipType(type){const fam=selectedFamily(); if(!fam) return true; return (WEAPON_EQUIP[type]||WEAPON_EQUIP.Other).includes(fam)}
function allowedTypesForSelectedClass(){const fam=selectedFamily(); if(!fam) return WEAPON_TYPES.filter(t=>t!=='All'); return WEAPON_TYPES.filter(t=>t!=='All' && canEquipType(t));}

let DATA=[], GC_DATA=null, BB_DATA=null, AREAS=[], ITEMS=[], MONSTERS=[], tab='trail', rareOnly=false, hideCommon=true, selectedClass='', selectedType='All', selectedKind='All', currentMode='gc';
const $=id=>document.getElementById(id);
const BANNER_IMAGES=["assets/rotating_banner/extra_1047.png", "assets/rotating_banner/extra_105.png", "assets/rotating_banner/extra_1187.png", "assets/rotating_banner/extra_1291.png", "assets/rotating_banner/extra_1336.png", "assets/rotating_banner/extra_1372.png", "assets/rotating_banner/extra_1394.png", "assets/rotating_banner/extra_1405.png", "assets/rotating_banner/extra_155.png", "assets/rotating_banner/extra_163.png", "assets/rotating_banner/extra_165-1.png", "assets/rotating_banner/extra_1795-1.png", "assets/rotating_banner/extra_1847.png", "assets/rotating_banner/extra_1923.png", "assets/rotating_banner/extra_208.png", "assets/rotating_banner/extra_2155.png", "assets/rotating_banner/extra_2184.png", "assets/rotating_banner/extra_2194.png", "assets/rotating_banner/extra_2201-1.png", "assets/rotating_banner/extra_2201-2.png", "assets/rotating_banner/extra_2201-5.png", "assets/rotating_banner/extra_2201-6.png", "assets/rotating_banner/extra_2288-3.png", "assets/rotating_banner/extra_2765.png", "assets/rotating_banner/extra_2773.png", "assets/rotating_banner/extra_2778.png", "assets/rotating_banner/extra_2787.png", "assets/rotating_banner/extra_2797-1.png", "assets/rotating_banner/extra_2805.png", "assets/rotating_banner/extra_294.png", "assets/rotating_banner/extra_3265-3.png", "assets/rotating_banner/extra_337.png", "assets/rotating_banner/extra_3482.png", "assets/rotating_banner/extra_353.png", "assets/rotating_banner/extra_3759-4.png", "assets/rotating_banner/extra_432-1.png", "assets/rotating_banner/extra_4439.png", "assets/rotating_banner/extra_4936.png", "assets/rotating_banner/extra_4998.png", "assets/rotating_banner/extra_50-1.png", "assets/rotating_banner/extra_5166.png", "assets/rotating_banner/extra_5206.png", "assets/rotating_banner/extra_5232.png", "assets/rotating_banner/extra_5238.png", "assets/rotating_banner/extra_558.png", "assets/rotating_banner/extra_5904.png", "assets/rotating_banner/extra_650.png", "assets/rotating_banner/extra_6621.png", "assets/rotating_banner/extra_68.png", "assets/rotating_banner/extra_6807.png", "assets/rotating_banner/extra_6836.png", "assets/rotating_banner/extra_695-1.png", "assets/rotating_banner/extra_695-2.png", "assets/rotating_banner/extra_740.png", "assets/rotating_banner/extra_76.png", "assets/rotating_banner/extra_78.png", "assets/rotating_banner/extra_887.png", "assets/rotating_banner/extra_925.png", "assets/rotating_banner/extra_96.png", "assets/rotating_banner/1325.png", "assets/rotating_banner/1720.png", "assets/rotating_banner/2150.png", "assets/rotating_banner/2154.png", "assets/rotating_banner/2199.png", "assets/rotating_banner/2304.png", "assets/rotating_banner/2620.png", "assets/rotating_banner/2636.png", "assets/rotating_banner/2662.png", "assets/rotating_banner/2898.png", "assets/rotating_banner/2970.png", "assets/rotating_banner/2976.png", "assets/rotating_banner/3439.png", "assets/rotating_banner/3447.png", "assets/rotating_banner/3607.png", "assets/rotating_banner/3643.png", "assets/rotating_banner/3704.png", "assets/rotating_banner/3816.png", "assets/rotating_banner/3905.png"];
let bannerIndex=Math.floor(Math.random()*Math.max(1,BANNER_IMAGES.length));
function showBanner(nextIndex){
  if(!BANNER_IMAGES.length) return;
  const img=$('memoryBannerImg');
  bannerIndex=(nextIndex+BANNER_IMAGES.length)%BANNER_IMAGES.length;
  if(!img) return;
  img.classList.add('fadeOut');
  setTimeout(()=>{img.src=BANNER_IMAGES[bannerIndex]; img.classList.remove('fadeOut');},180);
  const chip=$('bannerCountChip');
  if(chip) chip.textContent=`${bannerIndex+1} / ${BANNER_IMAGES.length} screenshots`;
}
function initBanner(){
  showBanner(bannerIndex);
  const prev=$('bannerPrev'), next=$('bannerNext');
  if(prev) prev.onclick=()=>showBanner(bannerIndex-1);
  if(next) next.onclick=()=>showBanner(bannerIndex+1);
  setInterval(()=>showBanner(bannerIndex+1),6500);
}

function opt(sel,value,text){const o=document.createElement('option');o.value=value;o.textContent=text;sel.appendChild(o)}
function sidLogo(s){return s&&SID_LOGOS[s]?`assets/secids/${SID_LOGOS[s]}`:''}
function sidIcon(s){return sidLogo(s)}
function sidBadge(s){const src=sidLogo(s);return src?`<span class="sidBadge"><img src="${src}" alt="${s}">${s}</span>`:`<span class="sidBadge">${s||'All IDs'}</span>`}
function weaponIcon(t){return `assets/ui/weapon_buttons/${t}.png`}
function classIcon(c,pressed=false){return `assets/ui/class_buttons/${c}_${pressed?'pressed':'unpressed'}.png`}
function classPortrait(c){return `assets/ui/class_portraits/${c}_pressed.png`}
function weaponBadge(t){return `<span class="chip typeBadge"><img src="${weaponIcon(t)}" onerror="this.style.display='none'" alt="${t}">${t}</span>`}
function norm(s){return (s||'').toLowerCase().replace(/[^a-z0-9]/g,'')}
const typeRules=[
 ['Needles',[/needle/]],['Rifles',[/rifle/,/wals/,/visk/,/justy/,/frozen.?shooter/,/snow.?queen/,/bringers?rifle/,/antiandroidrifle/,/angelharp/,/eggrappy/,/ano.?rifle/]],['Shots',[/shot/,/crushbullet/,/meteorsmash/,/finalimpact/,/spreadneedle/]],['Mechguns',[/mechgun/,/m\&a60vise/,/h\&s25justice/,/l\&k14combat/,/yasmin(kov)?9000/,/guld/,/milla/]],['Launchers',[/launcher/,/bazooka/,/cannon/,/flamevisit/,/panzer/,/ironfaust/,/nug2000/,/gi.?gue.?bazooka/]],
 ['Swords',[/sword/,/sawd/,/zanba/,/calibur/,/chainsawd/,/red.?sword/,/dragon.?slayer/,/last.?survivor/,/flowen/]],['Partisans',[/partisan/,/brionac/,/vjaya/,/gae.?bolg/,/soul.?eater/,/soul.?banish/,/berdysh/,/red.?partisan/,/imperial.?pick/]],['Sabers',[/saber/,/brand/,/pallasch/,/gladius/,/lavis.?cannon/,/red.?saber/,/ancient.?saber/,/db'?s.?saber/,/kaladbolg/,/durandal/]],['Daggers',[/dagger/,/knife/,/blade/,/bloody.?art/,/cross.?scar/,/red.?dagger/,/sange.?yasha/]],['DoubleSabers',[/double.?saber/,/stag.?cutlery/,/twin.?brand/,/meteor.?cudgel/,/monkey.?king.?bar/,/black.?king.?bar/,/double.?cannon/,/demolition.?comet/]],['TwinSwords',[/yamato/,/musashi/,/asuka/,/sange.*yasha/,/g.?assassin'?s.?sabers/]],['Claws',[/claw/,/silence.?claw/,/photon.?claw/,/booma/,/gobooma/,/gigobooma/,/rappy'?s.?fan/]],['Fists',[/fist/,/knuckle/,/god.?hand/,/sonic.?knuckle/,/brave.?knuckle/,/angry.?fist/]],['Katanas',[/agito/,/orotiagito/,/sange/,/yasha/,/kamui/]],['Slicers',[/rappy'?s.?wing/,/slicer/,/diska/,/rainbow.?baton/,/flight.?cutter/,/red.?slicer/,/slicer.?of.?assassin/]],
 ['Canes',[/cane/,/club/,/mace/,/summit.?moon/,/twinkle.?star/,/striker.?of.?chao/]],['Rods',[/rod/,/staff/,/psycho.?wand/,/caduceus/,/prophets.?of.?motav/,/plantain.?fan/]],['Wands',[/wand/,/scepter/,/magical.?piece/,/rabbit.?wand/,/evil.?curst/]],['Cards',[/card/,/hitogata/,/talis/,/mahu/,/guard(i)?anna/]]
];
const ITEM_TYPE_OVERRIDES={
  // v2.10.10 audit: explicit rare/conversion weapons that regex names missed.
  'varista':'Handguns','customrayver00':'Handguns','bravace':'Handguns','suppressedgun':'Handguns','redhandgun':'Handguns','rubybullet':'Handguns','heavenpunisher':'Handguns','yasminkov2000h':'Handguns','partsofeggblaster':'Handguns',
  'guiltylight':'Launchers','redscorpio':'Launchers','maserbeam':'Launchers','partsofbaranz':'Launchers','belrasrightarm':'Launchers','giguesbody':'Launchers',
  'yasminkov3000r':'Rifles','yasminkov7000v':'Rifles','holyray':'Rifles','bringersrightarm':'Rifles',
  'twinpsychogun':'Mechguns',
  'elysion':'Sabers',
  'victoraxe':'Swords',
  'yamigarasu':'Katanas',
  'morningglory':'Claws','heartofpoumn':'Claws',
  'twinchakram':'Daggers','sbeatsarms':'Daggers','parmsarms':'Daggers',
  'grassassassinsarms':'TwinSwords',
  'sinowberillsarms':'Fists',
  'twinblaze':'DoubleSabers',
  'flightfan':'Slicers',
  'rappyswing':'Slicers','rappysfan':'Slicers',
  'madamsumbrella':'Partisans','madamsparasol':'Partisans',
  'stingtip':'Canes','windmill':'Canes','hildebearshead':'Canes','hildeblueshead':'Canes','branchofpakupaku':'Canes',
  'battleverge':'Rods','bravehammer':'Rods','aliveaqhu':'Rods','demonicfork':'Rods','sorcerersrightarm':'Rods','fatsia':'Rods','plantainleaf':'Rods',
  'technicalcrozier':'Wands','thesighofagod':'Wands',
  'galgryphonswing':'Cards'
};
function itemType(item){const n=norm(item); if(!item||item==='No Item') return 'Other'; if(ITEM_TYPE_OVERRIDES[n]) return ITEM_TYPE_OVERRIDES[n]; for(const [type,rules] of typeRules){if(rules.some(re=>re.test(n))) return type;} return 'Other'}
function clearDynamicControls(){['section','difficulty','area','suggestions','sidQuick','guideSids','guideDiffs','guideAreas','weaponGrid','guideWeaponMatrix','classGrid','advancedClassSelect','guideClassSelect'].forEach(id=>{const el=$(id); if(el) el.innerHTML='';});}
function initSectionCalculator(){
  const nameEl=$('calcHunterName'), classEl=$('calcHunterClass');
  if(!nameEl||!classEl) return;
  classEl.innerHTML=SECTION_CLASS_MODIFIERS.map(([label,val])=>`<option value="${val}">${label}</option>`).join('');
  nameEl.value=localStorage.getItem('pso_section_name')||'';
  classEl.value=localStorage.getItem('pso_section_class')||'3';
  function draw(){
    const result=calcPair(nameEl.value,Number(classEl.value));
    const gc=titleSection(result.gc_xbox), bb=titleSection(result.blue_burst);
    $('calcGcResult').textContent=gc||'—'; $('calcBbResult').textContent=bb||'—';
    $('calcGcImg').src=gc?sidLogo(gc):'assets/secids/blank.png'; $('calcBbImg').src=bb?sidLogo(bb):'assets/secids/blank.png';
    localStorage.setItem('pso_section_name',nameEl.value); localStorage.setItem('pso_section_class',classEl.value);
  }
  function use(which){
    const result=calcPair(nameEl.value,Number(classEl.value));
    const sid=titleSection(which==='bb'?result.blue_burst:result.gc_xbox); if(!sid) return;
    $('section').value=sid;
    const modeTarget=which==='bb'?'bb':'gc';
    if(currentMode!==modeTarget){ setMode(modeTarget); setTimeout(()=>{$('section').value=sid; render();},0); } else render();
  }
  nameEl.addEventListener('input',draw); classEl.addEventListener('change',draw);
  $('calcUseGc').onclick=()=>use('gc'); $('calcUseBb').onclick=()=>use('bb');
  draw();
}
function initSelects(){
  opt($('section'),'','All Section IDs'); SECTION_IDS.forEach(s=>opt($('section'),s,s)); const q=$('sidQuick'); SECTION_IDS.forEach(sid=>{const b=document.createElement('button'); b.type='button'; b.title=sid; b.innerHTML=`<img src="${sidLogo(sid)}" alt="${sid}">`; b.onclick=()=>{$('section').value=sid; render();}; q.appendChild(b);});
  opt($('difficulty'),'','All difficulties'); (DIFFS).forEach(d=>opt($('difficulty'),d,d));
  opt($('area'),'','All Locations'); AREAS.forEach(a=>opt($('area'),a,areaLabel(a)));
  [...ITEMS,...MONSTERS].forEach(x=>{const o=document.createElement('option'); o.value=x; $('suggestions').appendChild(o);});
  $('difficulty').value=(currentMode==='bb'?'Ultimate':'Normal'); $('area').value=''; initClassButtons(); initWeaponButtons(); initGuide();
}
function initClassButtons(){const g=$('classGrid'); if(g) g.innerHTML=''; CLASS_BUTTONS.forEach(c=>{const b=document.createElement('button'); b.className='classBtn'; b.title=CLASS_LABEL[c]; b.innerHTML=`<img src="${classIcon(c,false)}" alt="${CLASS_LABEL[c]}">`; b.onclick=()=>{selectedClass=selectedClass===c?'':c; render();}; if(g) g.appendChild(b);}); const adv=$('advancedClassSelect'); if(adv){adv.innerHTML=''; opt(adv,'','Any class'); CLASS_BUTTONS.forEach(c=>opt(adv,c,CLASS_LABEL[c])); adv.onchange=()=>{selectedClass=adv.value; render();};}}
function initWeaponButtons(){
  const g=$('weaponGrid');
  WEAPON_TYPES.forEach(t=>{
    const b=document.createElement('button'); b.className='weaponBtn'+(t==='All'?' all':''); b.title=t;
    b.innerHTML=t==='All'?'ALL WEAPONS':`<img src="${weaponIcon(t)}" alt="${t}">`;
    b.onclick=()=>{selectedType=t; selectedKind='Weapon'; if(t==='All') selectedKind='All'; render();};
    g.appendChild(b);
  });
  ['Weapon','Frame','Barrier','Unit','Other'].forEach(k=>{
    const b=document.createElement('button'); b.className='weaponBtn all kindBtn'; b.title=k;
    b.textContent=k==='Other'?'Items / Other':k;
    b.onclick=()=>{selectedKind=selectedKind===k?'All':k; selectedType='All'; render();};
    g.appendChild(b);
  });
}

function initGuide(){
  const gs=$('guideSids'), gd=$('guideDiffs'), ga=$('guideAreas');
  const classSel=$('guideClassSelect'); if(classSel){classSel.innerHTML=''; opt(classSel,'','Any class'); CLASS_BUTTONS.forEach(c=>opt(classSel,c,CLASS_LABEL[c])); classSel.onchange=()=>{selectedClass=classSel.value; render();};} initWeaponMatrix();
  SECTION_IDS.forEach(sid=>{const b=document.createElement('button'); b.type='button'; b.className='guideBtn sidPick'; b.title=sid; b.innerHTML=`<img src="${sidLogo(sid)}" alt="${sid}">`; b.onclick=()=>{$('section').value=$('section').value===sid?'':sid; render();}; gs.appendChild(b);});
  (DIFFS).forEach(d=>{const b=document.createElement('button'); b.type='button'; b.className='guideBtn'; b.textContent=d; b.onclick=()=>{$('difficulty').value=d; render();}; gd.appendChild(b);});
  const allBtn=document.createElement('button');
  allBtn.type='button'; allBtn.className='guideBtn areaPick allLocations'; allBtn.dataset.area='';
  allBtn.innerHTML='<span>All Locations</span>'; allBtn.onclick=()=>{$('area').value=''; render();}; ga.appendChild(allBtn);
  const ordered = currentMode==='gc'
    ? orderedAreas([...new Set([...AREAS,'Episode 4 / Crater','Episode 4 / Subterranean Desert'])])
    : orderedAreas(AREAS);
  ['Episode 1','Episode 2','Episode 4'].forEach(group=>{
    const groupAreas=ordered.filter(a=>areaEpisode(a)===group);
    if(!groupAreas.length) return;
    const h=document.createElement('div'); h.className='guideAreaHeader'; h.textContent=group; ga.appendChild(h);
    groupAreas.forEach(a=>{
      const b=document.createElement('button'); b.type='button'; b.className='guideBtn areaPick'; b.dataset.area=a;
      const img=areaVariantImage(a,a); if(img){b.classList.add('hasAreaImage'); b.style.setProperty('--area-bg',`url("${img}")`);}
      if(lockedArea(a)) b.classList.add('locked');
      b.innerHTML=`<span>${areaLabel(a)}</span>`;
      b.onclick=()=>{ if(lockedArea(a)) return; $('area').value=$('area').value===a?'':a; render(); };
      ga.appendChild(b);
    });
  });
  $('guideGo').onclick=()=>{tab='cards';document.querySelectorAll('.tabs button').forEach(b=>b.classList.remove('active'));$('tabCards').classList.add('active');render();$('output').scrollIntoView({behavior:'smooth',block:'start'});};
  $('guideReset').onclick=()=>{$('section').value='';$('difficulty').value=(currentMode==='bb'?'Ultimate':'Normal');$('area').value='';$('search').value='';selectedClass='';selectedType='All';selectedKind='All';rareOnly=false;hideCommon=true;$('rareOnly').classList.remove('primary');$('hideCommon').classList.add('primary');render();};
}

function initWeaponMatrix(){
  const g=$('guideWeaponMatrix'); if(!g) return; g.innerHTML='';
  WEAPON_TYPES.forEach(t=>{
    const b=document.createElement('button');
    b.type='button';
    b.className='weaponTile'+(t==='All'?' all':'');
    b.title=t;
    b.innerHTML=t==='All'?'ALL':`<img src="${weaponIcon(t)}" alt="${t}"><span class="lockMark">×</span>`;
    b.onclick=()=>{ if(t!=='All' && !canEquipType(t)) return; selectedType=t; render(); };
    g.appendChild(b);
  });
}
function updateWeaponMatrix(){
  const available=availableWeaponTypes();
  document.querySelectorAll('#guideWeaponMatrix .weaponTile').forEach((b,i)=>{
    const t=WEAPON_TYPES[i];
    const equip=t==='All' || canEquipType(t);
    const hasDrop=t==='All' || available.has(t);
    const usable=equip && hasDrop;
    b.classList.toggle('allowed',usable && t!=='All');
    b.classList.toggle('available',usable && t!=='All');
    b.classList.toggle('locked',!usable);
    b.classList.toggle('active',t===selectedType);
    b.disabled=t!=='All' && !usable;
    b.title=t==='All'?'All weapon types':(!equip?`${t}: not equipable by selected class`:(!hasDrop?`${t}: no drops in current route`:`${t}: ${Array.from(baseRowsForCurrentRoute()).filter(r=>(r.itemType||'Other')===t && classAllows(t)).length} visible routes`));
    const mark=b.querySelector('.lockMark'); if(mark) mark.textContent=usable?'✓':'–';
  });
  if(selectedType!=='All' && !available.has(selectedType)) selectedType='All';
}
function updateClassPreview(){
  const p=$('guideClassPreview'); if(!p) return;
  if(!selectedClass){p.innerHTML='Choose a class to gray out unusable weapons.'; return;}
  p.innerHTML=`<img src="${classIcon(selectedClass,true)}" alt="${CLASS_LABEL[selectedClass]}"><span>${CLASS_LABEL[selectedClass]} · ${CLASS_FAMILY[selectedClass]} weapon rules active</span>`;
}

function updateGuide(){
  const sid=$('section').value,diff=$('difficulty').value,area=$('area').value;
  if($('guideClassSelect')) $('guideClassSelect').value=selectedClass||'';
  if($('advancedClassSelect')) $('advancedClassSelect').value=selectedClass||'';
  updateClassPreview();
  updateWeaponMatrix();
  document.querySelectorAll('#guideSids .guideBtn').forEach(b=>b.classList.toggle('active',b.title===sid));
  document.querySelectorAll('#guideDiffs .guideBtn').forEach(b=>b.classList.toggle('active',b.textContent===diff));
  document.querySelectorAll('#guideAreas .guideBtn').forEach(b=>b.classList.toggle('active',(b.dataset.area||'')===area));
  const who=[selectedClass?CLASS_LABEL[selectedClass]:'Any class',sid||'Any ID'].join(' · ');
  const where=[diff||'Any difficulty',area?areaLabel(area):'All Locations'].join(' · ');
  $('guideStatus').innerHTML=`${who}<small>${where}</small>`;
}


const SID_THEME={
  Viridia:['#36d17c','#123b26','#eafff2'], Greenill:['#59e36f','#133b1f','#effff1'], Skyly:['#5fb8ff','#102b52','#eef8ff'], Bluefull:['#477cff','#121d55','#eef2ff'], Purplenum:['#b16dff','#2a1552','#fbf1ff'], Pinkal:['#ff7fb8','#4b1732','#fff2f8'], Redria:['#ff625f','#4b1515','#fff1f1'], Oran:['#ff9a3d','#4b2a12','#fff6ec'], Yellowboze:['#ffd94d','#4b3b0f','#fff9df'], Whitill:['#f5f7ff','#273142','#ffffff']
};
function applySectionTheme(){
  const sid=$('section')?.value||''; const c=SID_THEME[sid]||['#75d8ff','#204c80','#eef6ff'];
  document.documentElement.style.setProperty('--sid',c[0]);
  document.documentElement.style.setProperty('--sid2',c[1]);
  document.documentElement.style.setProperty('--sidText',c[2]);
  document.documentElement.style.setProperty('--sidSoft',c[0]+'33');
  document.body.dataset.sid=sid||'';
  const title=document.querySelector('.memoryBannerKicker'); if(title) title.textContent=sid?`${sid} Seaside Trail`:'Pioneer 2 Memory Layer';
  let rib=$('activeSidRibbon');
  const top=document.querySelector('.top');
  if(top && !rib){ rib=document.createElement('div'); rib.id='activeSidRibbon'; rib.className='activeSidRibbon'; top.insertAdjacentElement('afterend',rib); }
  if(rib){
    const icon=sidIcon(sid);
    rib.innerHTML=sid?`${icon?`<img src="${esc(icon)}" alt="${esc(sid)}">`:''}<span>${esc(sid)} active Section ID</span><small>site color locked</small>`:`<span>No Section ID selected</span><small>choose one to color the trail</small>`;
  }
}
function trailEnemyKey(r){return `${r.area}||${r.monster}`}
function bestRows(rows){
  const m=new Map();
  rows.forEach(r=>{ if(!isRealDrop(r)) return; const k=trailEnemyKey(r); const old=m.get(k); if(!old || (r.expectedKills||1e12)<(old.expectedKills||1e12)) m.set(k,r); });
  return [...m.values()].sort((a,b)=>AREA_ORDER.indexOf(a.area)-AREA_ORDER.indexOf(b.area)||(a.expectedKills||1e12)-(b.expectedKills||1e12));
}
function topEndpoints(rows){
  const m=new Map(); rows.forEach(r=>{ if(!isRealDrop(r)) return; const old=m.get(r.item); if(!old || (r.expectedKills||1e12)<(old.expectedKills||1e12)) m.set(r.item,r); });
  return [...m.values()].sort((a,b)=>(a.expectedKills||1e12)-(b.expectedKills||1e12)).slice(0,12);
}
function renderTrail(rows){
  const sid=$('section').value, diff=$('difficulty').value, area=$('area').value;
  const sample=bestRows(rows); const endpoints=topEndpoints(rows);
  const path=[currentMode==='bb'?'Blue Burst':'GC/Xbox',sid||'Choose ID',diff||'Difficulty',area?areaLabel(area):'Area','Enemy','Drop'];
  const byArea={}; sample.forEach(r=>{(byArea[r.area]??=[]).push(r)});
  const endpointHtml=endpoints.map(r=>`<button class="endpointBtn" onclick="document.getElementById('search').value='${String(r.item).replace(/'/g,"\\'")}';render();">${r.item}</button>`).join('');
  const areas=orderedAreas(Object.keys(byArea)).slice(0, area?1:8);
  const hero=(LAST_OPEN_ROUTE&&LAST_OPEN_ROUTE.route)?provenanceHero(LAST_OPEN_ROUTE.route,LAST_OPEN_ROUTE.item):'';
  return `<div class="provHeader"><div class="provTitle">${sid?sid+' ':''}Provenance Trail</div><div class="provText">A mobile hunt route from identity to evidence: Section ID sets the color, difficulty narrows the world, area reveals enemies, and each enemy opens its strongest visible drop route.</div></div><div class="provPath">${path.map((x,i)=>`<span class="provNode ${i===1&&sid?'hot':''}">${x}</span>${i<path.length-1?'<span class="provArrow">→</span>':''}`).join('')}</div>${hero}${endpointHtml?`<div class="endpointBar">${endpointHtml}</div>`:''}<div class="routeGrid">${areas.map(a=>{const bg=areaVariantImage(a,a); const list=byArea[a].slice(0,18); return `<section class="routeArea"><div class="routeAreaHead" style="${bg?`background-image:linear-gradient(180deg,rgba(7,18,32,.15),rgba(7,18,32,.86)),url('${bg}')`:''}"><div><div class="routeAreaTitle">${areaLabel(a)}</div><div class="routeAreaMeta">${areaEpisode(a)} · ${list.length} visible enemy nodes</div></div></div><div class="enemyRail">${list.map((r,idx)=>`<button class="enemyChip ${idx===0?'active':''}" onclick="this.closest('.routeArea').querySelector('.routeDetails').innerHTML=decodeURIComponent('${encodeURIComponent(routeDropHtml(r)).replace(/'/g,'%27')}');this.parentElement.querySelectorAll('.enemyChip').forEach(b=>b.classList.remove('active'));this.classList.add('active')">${r.monsterImage?`<img src="${r.monsterImage}" alt="${r.monster}">`:'<div class="missing">?</div>'}<div class="enemyChipName">${r.monster}</div><div class="enemyChipItem">${r.item}</div></button>`).join('')}</div><div class="routeDetails">${list[0]?routeDropHtml(list[0]):''}</div></section>`}).join('')}</div>`;
}
function routeDropHtml(r){
  const prob=r.probabilityAfter||{};
  const chance=chanceText(prob);
  const kills=fmtInt(r.expectedKills);
  const type=r.itemType||'Other';
  const rarity=rarityForKills(r.expectedKills);
  return `<div class="routeDrop expanded">${r.monsterImage?`<img src="${r.monsterImage}" alt="${r.monster}">`:''}<div class="routeDropBody"><b>${r.item}</b><small>${sidBadge(r.sectionId)} · ${r.difficulty} · ${areaLabel(r.area)} · ${r.monster}</small><div class="routeDropMeta"><span>${weaponBadge(type)}</span><span class="chip gold">Rate ${r.rate}</span>${kills?`<span class="chip">≈ ${kills} kills expected</span>`:''}<span class="chip">Drop ${r.percent}%</span>${chance?`<span class="chip">${chance}</span>`:''}${r.episode?`<span class="chip">${r.episode}</span>`:''}</div>${r.submaps&&r.submaps.length?`<div class="routeDropSub">Submaps: ${r.submaps.join(' / ')}</div>`:''}${r.rareEnemy?`<div class="routeDropSub">Rare enemy route.</div>`:''}</div></div>`
}


function escapeAttr(s){return String(s??'').replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/'/g,'&#39;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
function cleanItemRows(rows){return rows.filter(r=>isRealDrop(r) && !isCommon(r));}
function bestRoutesForItem(rows,item){return rows.filter(r=>r.item===item).slice().sort((a,b)=>(a.expectedKills||1e12)-(b.expectedKills||1e12));}
function sectionProfile(allRows,sid){
  const rows=cleanItemRows(allRows).filter(r=>r.sectionId===sid);
  const allClean=cleanItemRows(allRows);
  const itemSections={}; allClean.forEach(r=>{(itemSections[r.item]??=new Set()).add(r.sectionId)});
  const bestByItem={}; rows.forEach(r=>{const old=bestByItem[r.item]; if(!old || (r.expectedKills||1e12)<(old.expectedKills||1e12)) bestByItem[r.item]=r;});
  const unique=Object.values(bestByItem).filter(r=>(itemSections[r.item]||new Set()).size===1).sort((a,b)=>(a.expectedKills||1e12)-(b.expectedKills||1e12));
  const signature=Object.values(bestByItem).sort((a,b)=>{
    const au=(itemSections[a.item]||new Set()).size, bu=(itemSections[b.item]||new Set()).size;
    return au-bu || (a.expectedKills||1e12)-(b.expectedKills||1e12);
  });
  const types={}; rows.forEach(r=>{const t=r.itemType||'Other'; types[t]=(types[t]||0)+1;});
  const topTypes=Object.entries(types).sort((a,b)=>b[1]-a[1]).slice(0,4);
  return {sid,rows,unique,signature,topTypes,totalItems:Object.keys(bestByItem).length};
}
function endpointButton(r,label='Hunt'){
  const item=escapeAttr(r.item), sid=escapeAttr(r.sectionId), diff=escapeAttr(r.difficulty), area=escapeAttr(r.area);
  return `<button onclick="document.getElementById('section').value='${sid}';document.getElementById('difficulty').value='${diff}';document.getElementById('area').value='${area}';document.getElementById('search').value='${item}';tab='trail';document.querySelectorAll('.tabs button').forEach(b=>b.classList.remove('active'));document.getElementById('tabTrail').classList.add('active');render();document.getElementById('output').scrollIntoView({behavior:'smooth',block:'start'});">${label}</button>`;
}
function endpointHtml(r){
  const kills=fmtInt(r.expectedKills); const t=r.itemType||'Other';
  return `<div class="endpoint"><div><b>${r.item}</b><small>${sidBadge(r.sectionId)} ${r.difficulty} · ${areaLabel(r.area)} · ${r.monster}${kills?` · ${kills} kills exp.`:''}</small><small>${weaponBadge(t)} <span class="chip gold">${r.rate}</span></small></div>${endpointButton(r)}</div>`;
}
function renderStrategy(allRows){
  const sid=$('section').value;
  const base=cleanItemRows(DATA);
  const profiles=SECTION_IDS.map(s=>sectionProfile(DATA,s));
  const active=sid?sectionProfile(DATA,sid):profiles.slice().sort((a,b)=>b.unique.length-a.unique.length)[0];
  const topUnique=active.unique.slice(0,8);
  const topSignature=active.signature.slice(0,8);
  const compare=profiles.map(p=>({p,score:p.unique.length*3+p.totalItems+Math.min(40,p.rows.length/4)})).sort((a,b)=>b.score-a.score);
  const roleNames={Viridia:'balanced spear + shot routes',Greenill:'rifle-leaning ranger routes',Skyly:'hunter sword fantasy',Bluefull:'partisan / polearm routes',Purplenum:'mechgun utility routes',Pinkal:'force technique/wand fantasy',Redria:'armor, units, account support',Oran:'dagger / close-combat routes',Yellowboze:'broad money + mixed utility',Whitill:'prestige late-game routes'};
  const chosenUnique=topUnique.length?topUnique:topSignature.slice(0,5);
  const complement=profiles.filter(p=>p.sid!==active.sid).map(p=>{
    const missing=p.unique.filter(r=>!new Set(active.signature.map(x=>x.item)).has(r.item)).slice(0,4);
    return {p,missing,score:missing.length*4+p.unique.length};
  }).sort((a,b)=>b.score-a.score).slice(0,3);
  const roster=[active,...complement.map(x=>x.p)].slice(0,4);
  return `<div class="strategyWrap"><section class="strategyHero"><div class="strategyTitle">${active.sid} Section ID Strategy</div><div class="strategyText"><b>Account question:</b> what does this Section ID unlock, what are its strongest endpoints, and which extra characters cover what it misses? This layer is generated from the loaded drop database, so it changes with GC/Xbox or Blue Burst mode.</div></section><div class="strategyGrid"><section class="strategyCard hot"><div class="strategyCardHead"><img src="${sidLogo(active.sid)}" alt="${active.sid}"><div><b>${active.sid}</b><small>${roleNames[active.sid]||'section identity'}</small></div></div><div class="strategySub">Strongest unique / signature endpoints</div><div class="endpointList">${(topUnique.length?topUnique:topSignature).slice(0,6).map(endpointHtml).join('')||'<div class="emptyStrategy">No unique endpoints in this filter.</div>'}</div></section><section class="strategyCard"><div class="strategySub">Why choose it?</div><div class="endpointList">${active.topTypes.map(([t,n])=>`<div class="endpoint"><div><b>${t}</b><small>${n} visible routes in this mode</small></div><div class="miniBars">${Array.from({length:Math.min(8,Math.ceil(n/3))}).map((_,i)=>`<i style="height:${8+i*2}px"></i>`).join('')}</div></div>`).join('')}<div class="endpoint"><div><b>${active.unique.length} unique item endpoints</b><small>${active.totalItems} distinct rare items visible for ${active.sid}</small></div></div></div></section></div><section class="strategyCard"><div class="strategySub">Suggested account roster</div><div class="rosterGrid">${roster.map((p,i)=>`<div class="rosterSlot"><span class="slotNo">${i+1}</span><b>${p.sid}</b><p>${i===0?'Main identity: start here and hunt its signature endpoints.':'Complement: covers additional unique endpoints your main does not naturally own.'}</p><p>${p.unique.slice(0,3).map(r=>r.item).join(' · ')||'mixed utility routes'}</p></div>`).join('')}</div></section><section class="strategyCard"><div class="strategySub">Complement IDs</div><div class="strategyGrid">${complement.map(c=>`<div class="strategyCard"><div class="strategyCardHead"><img src="${sidLogo(c.p.sid)}" alt="${c.p.sid}"><div><b>${c.p.sid}</b><small>adds ${c.missing.length} clear missing endpoints</small></div></div><div class="endpointList">${(c.missing.length?c.missing:c.p.unique.slice(0,3)).map(endpointHtml).join('')}</div></div>`).join('')}</div></section><section class="compareTable"><table><thead><tr><th>Section ID</th><th>Unique endpoints</th><th>Distinct items</th><th>Top bias</th><th>Open strategy</th></tr></thead><tbody>${compare.map(({p})=>`<tr><td>${sidBadge(p.sid)}</td><td>${p.unique.length}</td><td>${p.totalItems}</td><td>${p.topTypes.map(x=>x[0]).join(', ')}</td><td><button onclick="document.getElementById('section').value='${p.sid}';render();">View</button></td></tr>`).join('')}</tbody></table></section></div>`;
}

function isRealDrop(r){
  const item=String(r.item||'').trim();
  const rate=String(r.rate||'').trim();
  return !!item && item!=='-' && item!=='—' && item!=='No Item' && rate!=='-' && rate!=='—' && Number(r.percent||0)>0;
}
function isCommon(r){return !isRealDrop(r)|| r.rate==='0/1'|| ['Monomate','Dimate','Trimate','Monofluid','Difluid','Trifluid','Sol Atomizer','Moon Atomizer','Star Atomizer','Antidote','Antiparalysis','Telepipe'].includes(r.item)}
function classAllows(type){return canEquipType(type||'Other')}
function rowKind(r){
  const t=r.itemType||'Other';
  if(t && t!=='Other') return 'Weapon';
  return itemKind(r.item);
}

/* v4.2.1 Final tiny polish: PSO search aliases */
const SEARCH_ALIAS_V421 = {
  'sjs':['sealed j-sword','sealed j sword','seald j sword'],
  'sealedjsword':['sealed j-sword','sealed j sword'],
  'pwand':['psycho wand'],
  'psy wand':['psycho wand'],
  'psycho':['psycho wand'],
  'needle':['spread needle'],
  'spread':['spread needle'],
  'rr':['red ring'],
  'redring':['red ring'],
  'fs':['frozen shooter'],
  'frozenshooter':['frozen shooter'],
  'hs':['heaven striker'],
  'heavenstriker':['heaven striker'],
  'hp':['heaven punisher'],
  'heavenpunisher':['heaven punisher'],
  'lavis':['lavis cannon'],
  'lc':['lavis cannon'],
  'battle':['god/battle','god battle'],
  'godbattle':['god/battle','god battle'],
  'godbattlearmor':['god/battle','god battle','god/battle armor'],
  'arm':['god/arm','god arm'],
  'godarm':['god/arm','god arm'],
  'v101':['v101'],
  'v502':['v502'],
  'v801':['v801'],
  'smartlink':['smartlink'],
  'limitter':['limiter','limitter'],
  'limiter':['limiter','limitter'],
  'dob':['diska of braveman'],
  'braveman':['diska of braveman'],
  'red sword':['red sword'],
  'redsword':['red sword'],
  'chainsawd':['chain sawd'],
  'chain saw':['chain sawd'],
  'gm':['handgun: guld','handgun: milla','guld','milla'],
  'guldmilla':['handgun: guld','handgun: milla','guld','milla'],
  'guld milla':['handgun: guld','handgun: milla','guld','milla'],
  'flowen3084':["flowen's sword 3084",'flowens sword 3084','flowen’s sword 3084'],
  'yunchang':['yunchang'],
  'pgf':['pgf','poison gas field']
};
function normalizeSearchV421(x){return String(x||'').toLowerCase().replace(/[’]/g,"'").replace(/[^a-z0-9/+' -]+/g,' ').replace(/\s+/g,' ').trim();}
function compactSearchV421(x){return normalizeSearchV421(x).replace(/[^a-z0-9]+/g,'');}
function expandedSearchTermsV421(q){
  const raw=normalizeSearchV421(q);
  if(!raw) return [];
  const compact=compactSearchV421(raw);
  const terms=new Set([raw, compact]);
  (SEARCH_ALIAS_V421[raw]||[]).forEach(x=>terms.add(normalizeSearchV421(x)));
  (SEARCH_ALIAS_V421[compact]||[]).forEach(x=>terms.add(normalizeSearchV421(x)));
  raw.split(' ').filter(Boolean).forEach(part=>{(SEARCH_ALIAS_V421[part]||[]).forEach(x=>terms.add(normalizeSearchV421(x)));});
  return [...terms].filter(Boolean);
}
function rowMatchesSearchV421(r,q){
  const terms=expandedSearchTermsV421(q);
  if(!terms.length) return true;
  const hay=normalizeSearchV421([r.item,r.monster,r.area,r.sectionId,r.difficulty,r.itemType].join(' '));
  const hayCompact=compactSearchV421(hay);
  return terms.some(t=>hay.includes(t)||hayCompact.includes(compactSearchV421(t)));
}

function baseRowsForCurrentRoute(){
  const sid=$('section').value,diff=$('difficulty').value,areaRaw=$('area').value,q=$('search').value.trim().toLowerCase();
  const area=areaRaw==='Caves'?'Cave':areaRaw;
  return DATA.filter(r=>{
    return isRealDrop(r)
      &&(!sid||r.sectionId===sid)
      &&(!diff||r.difficulty===diff)
      &&(!area||r.area===area)
      &&(!rareOnly||r.rareEnemy)
      &&(!hideCommon||!isCommon(r))
      && rowMatchesSearchV421(r,q);
  });
}
function availableWeaponTypes(){
  const set=new Set();
  baseRowsForCurrentRoute().forEach(r=>{
    const t=r.itemType||'Other';
    if(t && t!=='Other' && rowKind(r)==='Weapon' && classAllows(t)) set.add(t);
  });
  return set;
}
function filtered(){return baseRowsForCurrentRoute().filter(r=>{const t=r.itemType||'Other', k=rowKind(r);return (selectedType==='All'||t===selectedType)&&(selectedKind==='All'||k===selectedKind)&&(selectedType==='All'||classAllows(t));});}
/* v2.10.14: class select no longer hides the raw database in All mode; it only gates explicit weapon-type filtering. */
function updateSelectedSid(){const sid=$('section').value,el=$('selectedSid');if(sid){el.className='selectedSid show sidBadge';el.innerHTML=`<img src="${sidLogo(sid)}" alt="${sid}">${sid}`;}else{el.className='selectedSid';el.innerHTML='';}document.querySelectorAll('#sidQuick button').forEach(b=>b.classList.toggle('primary',b.title===sid));}
function updateFilterButtons(){
  document.querySelectorAll('.classBtn').forEach((b,i)=>{const c=CLASS_BUTTONS[i],on=c===selectedClass;b.classList.toggle('active',on);b.innerHTML=`<img src="${classIcon(c,on)}" alt="${CLASS_LABEL[c]}">`;});
  const adv=$('advancedClassSelect'); if(adv) adv.value=selectedClass;
  const available=availableWeaponTypes();
  document.querySelectorAll('.weaponBtn').forEach((b,i)=>{
    if(i<WEAPON_TYPES.length){
      const t=WEAPON_TYPES[i];
      const equip=t==='All'||canEquipType(t);
      const hasDrop=t==='All'||available.has(t);
      const disabled=t!=='All' && (!equip || !hasDrop);
      b.classList.toggle('active',t===selectedType && selectedKind!=='Frame' && selectedKind!=='Barrier' && selectedKind!=='Unit' && selectedKind!=='Other');
      b.classList.toggle('disabled',disabled);
      b.classList.toggle('available',!disabled && t!=='All');
      b.disabled=disabled;
      b.title=t==='All'?'All weapon types':(!equip?`${t}: not equipable by selected class`:(!hasDrop?`${t}: no drops in current route`:`${t}: available in current route`));
      if(disabled && selectedType===t) selectedType='All';
    }else{
      const k=['Weapon','Frame','Barrier','Unit','Other'][i-WEAPON_TYPES.length];
      b.classList.toggle('active',selectedKind===k && selectedType==='All');
      b.classList.remove('disabled'); b.disabled=false;
    }
  });
}
function updateActiveFilters(rows=[]){
  const parts=[];
  if(selectedClass){
    const allowed=allowedTypesForSelectedClass();
    const available=[...availableWeaponTypes()];
    parts.push(`<span class="chip green">${CLASS_LABEL[selectedClass]} equip + drop availability active</span>`);
    parts.push(`<span class="chip">Available weapons: ${available.slice(0,6).join(', ')||'none'}${available.length>6?'…':''}</span>`);
  }
  if(selectedType!=='All') parts.push(weaponBadge(selectedType));
  if(selectedKind!=='All') parts.push(`<span class="chip gold">${selectedKind==='Other'?'Items / Other':selectedKind} filter</span>`);
  if(rows&&rows.length){
    const counts={Weapon:0,Frame:0,Barrier:0,Unit:0,Other:0};
    rows.forEach(r=>{const k=rowKind(r); counts[k]=(counts[k]||0)+1;});
    parts.push(`<span class="chip">Weapons ${counts.Weapon||0}</span>`);
    parts.push(`<span class="chip">Frames ${counts.Frame||0}</span>`);
    parts.push(`<span class="chip">Barriers ${counts.Barrier||0}</span>`);
    parts.push(`<span class="chip">Units ${counts.Unit||0}</span>`);
    parts.push(`<span class="chip">Items ${counts.Other||0}</span>`);
  }
  $('activeFilters').innerHTML=parts.join('');
}
function updateStats(rows){applySectionTheme();updateSelectedSid();updateFilterButtons();updateActiveFilters(rows);updateGuide();$('matchCount').textContent=rows.length;$('enemyCount').textContent=new Set(rows.map(r=>r.monster)).size;$('itemCount').textContent=new Set(rows.map(r=>r.item).filter(x=>x&&x!=='-'&&x!=='No Item')).size;$('typeCount').textContent=new Set(rows.map(r=>rowKind(r)||'Other')).size;$('imageCount').textContent=rows.filter(r=>r.monsterImage).length;}
function fmtInt(n){const v=Number(n); if(!isFinite(v)||v<=0) return ''; return Math.round(v).toLocaleString('en-US');}
function rarityForKills(k){k=Number(k)||0; if(k<=50) return ['common','common']; if(k<=500) return ['rare','rare']; if(k<=5000) return ['very rare','very']; if(k<=25000) return ['ultra rare','ultra']; return ['mythic hunt','mythic'];}
function chanceText(prob){const p1000=prob['1000']; if(p1000!==undefined) return `Chance after 1,000 kills: ${p1000}%`; const p500=prob['500']; if(p500!==undefined) return `Chance after 500 kills: ${p500}%`; return '';}


const AD_BANNERS=[
  'assets/ad_banners/pso_jungle_banner_01.webp',
  'assets/ad_banners/pso_jungle_banner_02.webp',
  'assets/ad_banners/pso_jungle_banner_03.webp',
  'assets/ad_banners/pso_jungle_banner_04.webp',
  'assets/ad_banners/pso_jungle_banner_05.webp',
  'assets/ad_banners/pso_jungle_banner_06.webp',
  'assets/ad_banners/pso_jungle_banner_07.webp',
  'assets/ad_banners/pso_jungle_banner_08.webp',
  'assets/ad_banners/pso_jungle_banner_09.webp',
  'assets/ad_banners/pso_jungle_banner_10.webp'
];
function adBanner(i,context='drops'){
  const src=AD_BANNERS[i%AD_BANNERS.length];
  const lines=context==='planner'
    ? ['Account route ambience','']
    : ['Ragol field transmission',''];
  return `<aside class="adBanner"><img src="${src}" alt="PSO ambient field banner" loading="lazy"><div class="adBannerText"><span>${lines[0]}</span>${lines[1]?`<b>${lines[1]}</b>`:''}</div></aside>`;
}
function withAdBanners(htmlItems,context='drops',every=8){
  if(!Array.isArray(htmlItems)||htmlItems.length<=every) return htmlItems.join('');
  const out=[];
  htmlItems.forEach((html,i)=>{
    out.push(html);
    if((i+1)%every===0 && i<htmlItems.length-1) out.push(adBanner(Math.floor(i/every),context));
  });
  return out.join('');
}

function card(r){const prob=r.probabilityAfter||{},t=r.itemType||'Other';const kills=fmtInt(r.expectedKills);const [rarityLabel,rarityClass]=rarityForKills(r.expectedKills);const chance=chanceText(prob);const bg=areaVariantImage(r.area,r.monster||r.area);const imgStyle=bg?` style="background-image:linear-gradient(180deg,rgba(7,18,32,.18),rgba(7,18,32,.78)),url('${bg}')"`:'';return `<div class="card ${bg?'hasAreaBg':''}"><div class="img"${imgStyle}>${r.monsterImage?`<img src="${r.monsterImage}" alt="${r.monster}">`:`<div class="missing">NO<br>ASSET</div>`}</div><div class="body ${r.monsterImage?'enemyWatermark':''}"${r.monsterImage?` style="--enemy-bg:url('${r.monsterImage}')"`:''}><div class="enemy">${r.monster}</div><div class="item">${r.item}</div><div class="huntTop"><div class="huntMeta">${sidBadge(r.sectionId)}<br>${r.difficulty} · ${areaLabel(r.area)}<br>${r.episode} · ${r.platform||''}${r.submaps?'<br><span class="chip">'+r.submaps.join(' / ')+'</span>':''}</div><div class="typeMini"><img src="${weaponIcon(t)}" onerror="this.parentElement.style.display='none'" alt="${t}"></div></div><div class="rarity ${rarityClass}">&#9733; ${rarityLabel}</div><div class="wow"><div class="rateBig">${r.rate}</div>${kills?`<div class="killsBig">&asymp; ${kills} kills expected</div>`:''}${chance?`<div class="chanceLine">${chance}</div>`:''}</div><details class="details"><summary>Details</summary><div class="detailBox">Drop chance: ${r.percent}%<br>Chance after 100 kills: ${prob['100']??0}%<br>Chance after 500 kills: ${prob['500']??0}%<br>Chance after 1,000 kills: ${prob['1000']??0}%${r.rareEnemy?'<br>Rare enemy route.':''}</div></details></div></div>`;}
function render(){const rows=filtered().sort((a,b)=>(a.expectedKills||1e12)-(b.expectedKills||1e12)||(a.area+a.monster+a.item).localeCompare(b.area+b.monster+b.item));updateStats(rows);const out=$('output');if(!rows.length){out.innerHTML='<div class="empty">No drops match this filter. Try another area, All weapon type, or disable common-item filter.</div>';return;}if(tab==='trail'){out.innerHTML=renderTrail(rows);return;}if(tab==='strategy'){out.innerHTML=renderStrategy(rows);return;}if(tab==='cards'){const sid=$('section').value, diff=$('difficulty').value, area=$('area').value, q=$('search').value.trim();const focused=(sid&&diff)||q;const narrowed=sid&&diff&&area;const limit=focused?rows.length:(narrowed?120:24);const hint=(!focused&&rows.length>limit)?`<div class="narrowHint"><b>${rows.length} drops found.</b> Choose Section ID + difficulty, or search an item, to show the full route pool. Showing first ${limit}.</div>`:'';out.innerHTML=hint+'<div class="grid">'+withAdBanners(rows.slice(0,limit).map(card),'drops',8)+'</div>'+(rows.length>limit?`<div class="empty">Showing ${limit} of ${rows.length}. Narrow the route for the full local list.</div>`:'');return;}if(tab==='table'){out.innerHTML=`<div style="overflow:auto;padding:18px"><table style="width:100%;border-collapse:collapse"><thead><tr><th>Section</th><th>Type</th><th>Difficulty</th><th>Area</th><th>Enemy</th><th>Item</th><th>Rate</th><th>%</th></tr></thead><tbody>${rows.slice(0,900).map(r=>`<tr><td>${sidBadge(r.sectionId)}</td><td>${weaponBadge(r.itemType||'Other')}</td><td>${r.difficulty}</td><td>${r.area}</td><td>${r.monster}</td><td style="color:var(--gold);font-weight:800">${r.item}</td><td>${r.rate}</td><td>${r.percent}</td></tr>`).join('')}</tbody></table></div><style>td,th{border-bottom:1px solid #294d76;padding:9px;text-align:left;white-space:nowrap}th{color:var(--gold)}

/* v0.9 Compact Companion Cards — scan first, detail on demand */
.grid{
  grid-template-columns:repeat(auto-fill,minmax(230px,1fr));
  gap:12px;
  padding:14px;
}
.card{
  border-radius:20px;
  min-height:0;
  overflow:hidden;
}
.card .img{
  height:118px;
  padding:8px;
  background:radial-gradient(circle at center,#315f94 0,#09182a 66%);
}
.card .img img{
  max-height:108px;
  max-width:100%;
}
.card .body{
  padding:12px 13px 13px;
}
.enemy{
  font-size:20px;
  line-height:1.05;
}
.item{
  font-size:21px;
  line-height:1.08;
  margin-top:5px;
}
.huntTop{
  margin-top:9px;
  display:block;
}
.huntMeta{
  font-size:13px;
  line-height:1.35;
}
.sidBadge{
  padding:3px 7px;
  font-size:12px;
}
.sidBadge img{
  width:20px;
  height:20px;
}
.typeMini{
  display:none;
}
.rarity{
  margin-top:9px;
  padding:6px 9px;
  font-size:11px;
  letter-spacing:.08em;
}
.wow{
  margin-top:10px;
  padding:11px 10px;
  border-radius:17px;
}
.wow .rateBig{
  font-size:35px;
  letter-spacing:-.045em;
}
.wow .killsBig{
  font-size:19px;
  margin-top:0;
}
.chanceLine{
  font-size:13px;
  margin-top:8px;
}
.details{
  margin-top:9px;
}
.details summary{
  font-size:13px;
}
.details .detailBox{
  font-size:13px;
  line-height:1.45;
}
.tabs{
  position:sticky;
  top:0;
  z-index:5;
  backdrop-filter:blur(16px);
  background:#0b1a2dee;
}
@media(min-width:760px){
  .grid{grid-template-columns:repeat(auto-fill,minmax(260px,1fr));}
  .card .img{height:132px}.card .img img{max-height:122px}
}
@media(max-width:560px){
  .wrap{padding-left:10px;padding-right:10px}
  .grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:10px}
  .card{border-radius:18px}
  .card .img{height:96px}
  .card .img img{max-height:88px}
  .card .body{padding:10px}
  .enemy{font-size:17px}.item{font-size:18px}
  .huntMeta{font-size:12px}
  .wow .rateBig{font-size:28px}.wow .killsBig{font-size:16px}
  .chanceLine{display:none}
  .rarity{font-size:10px;padding:5px 8px}
}


/* v1.1 Class Equip Filters + area-backed hunt cards */
.guideBtn.areaPick{
  position:relative;
  overflow:hidden;
  min-width:128px;
  height:68px;
  justify-content:flex-start;
  align-items:flex-end;
  text-align:left;
  border-radius:18px;
  padding:10px;
  background:#08182a;
}
.guideBtn.areaPick.hasAreaImage::before{
  content:"";
  position:absolute;
  inset:0;
  background-image:linear-gradient(180deg,rgba(4,9,16,.08),rgba(4,9,16,.82)),var(--area-bg);
  background-size:cover;
  background-position:center;
  transform:scale(1.02);
}
.guideBtn.areaPick span{position:relative;z-index:1;text-shadow:0 2px 8px #000;font-size:12px;line-height:1.12;font-weight:1000;}
.guideBtn.areaPick.active::after{content:"";position:absolute;inset:0;border:2px solid var(--gold);border-radius:18px;box-shadow:inset 0 0 0 2px #0008,0 0 16px #ffe99155;}
.card .img{background-size:cover;background-position:center;position:relative;}
.card .img::after{content:"";position:absolute;inset:0;background:radial-gradient(circle at center,rgba(49,95,148,.32),rgba(7,18,32,.60));pointer-events:none;}
.card .img img,.card .img .missing{position:relative;z-index:1;}
@media(max-width:560px){.guideBtn.areaPick{min-width:112px;height:62px}.guideOptions.scroll{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));overflow:visible}.guideBtn.areaPick{width:100%}}



/* v1.3 Class dropdown + readable weapon equip matrix */
.classSelect{width:100%;border:1px solid #426b9a;background:#09182acc;color:var(--text);border-radius:15px;padding:13px 14px;font-size:16px;outline:none}
.classPreview{margin-top:10px;min-height:42px;display:flex;align-items:center;gap:10px;color:var(--muted);font-weight:800}
.classPreview img{max-width:96px;max-height:44px;object-fit:contain;filter:drop-shadow(0 6px 10px #0008)}
.weaponMatrixWrap{margin-top:14px;border:1px solid #294d76;background:#071524aa;border-radius:22px;padding:14px}
.weaponMatrix{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:9px}
.weaponTile{border:1px solid #31547c;background:#08182acc;border-radius:16px;padding:8px 7px;min-height:64px;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;transition:.15s ease}
.weaponTile img{max-width:92px;max-height:42px;object-fit:contain;image-rendering:auto;transition:.15s ease}
.weaponTile.allowed{border-color:#5aa675;background:linear-gradient(180deg,#103520cc,#08182acc);box-shadow:inset 0 0 0 1px #87e7ad33}
.weaponTile.locked{opacity:.38;filter:grayscale(1);background:#07111d}
.weaponTile.locked img{opacity:.42}
.weaponTile.active{border-color:var(--gold);box-shadow:0 0 0 2px #ffe99133,0 0 18px #ffe99122;background:#342a66}
.weaponTile .lockMark{position:absolute;right:7px;bottom:5px;font-size:13px;opacity:.85}
.weaponTile.all{font-weight:1000;color:var(--gold);letter-spacing:.08em}
@media(max-width:560px){.weaponMatrix{grid-template-columns:repeat(3,minmax(0,1fr));gap:7px}.weaponTile{min-height:54px;padding:6px}.weaponTile img{max-width:82px;max-height:34px}.classPreview img{max-width:86px}}

.modeSwitch{margin:0 0 18px;display:grid;grid-template-columns:1fr 1fr;gap:12px}
.modeBtn{position:relative;min-height:96px;border-radius:22px;border:1px solid #31547c;overflow:hidden;background:#071524;color:#fff;cursor:pointer;padding:0;text-align:left;box-shadow:0 12px 28px #0006}
.modeBtn img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:.72;filter:saturate(1.08)}
.modeBtn:after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(3,8,16,.78),rgba(3,8,16,.18))}
.modeBtn .modeText{position:relative;z-index:1;display:flex;flex-direction:column;gap:4px;padding:18px;font-weight:1000;text-shadow:0 2px 10px #000}
.modeBtn .modeText small{font-size:12px;color:#d7e8ff;font-weight:800;letter-spacing:.04em}
.modeBtn.active{border-color:#ffe991;box-shadow:0 0 0 2px rgba(255,233,145,.18),0 16px 38px #0008}
.modeBtn.active .modeText{color:#ffe991}
body.bbMode{background:radial-gradient(circle at 50% 0,#1b6f9e 0,#071e35 44%,#03080f 100%)}
body.bbMode .brand:after{content:" BLUE BURST";color:#75d8ff;text-shadow:0 0 14px #2edcff;margin-left:8px}
@media(max-width:700px){.modeSwitch{grid-template-columns:1fr}.modeBtn{min-height:84px}}


/* v2.0 Clear identity banner + equal platform glow */
.memoryBanner{
  min-height:0 !important;
  padding:10px !important;
  border-radius:28px !important;
  background:linear-gradient(180deg,#102946ee,#071524ee) !important;
}
.memoryBanner::after{display:none !important;}
.memoryBanner img{
  position:relative !important;
  inset:auto !important;
  display:block !important;
  width:100% !important;
  height:auto !important;
  max-height:360px !important;
  object-fit:contain !important;
  object-position:center !important;
  background:#02070d !important;
  border-radius:22px !important;
  opacity:1 !important;
  transform:none !important;
  box-shadow:inset 0 0 0 1px #ffffff18,0 12px 34px #0008;
}
.memoryBanner img.fadeOut{opacity:.18 !important;transform:none !important;}
.memoryBannerText{
  position:relative !important;
  z-index:1 !important;
  padding:10px 8px 2px !important;
  max-width:none !important;
  display:flex !important;
  align-items:center !important;
  justify-content:space-between !important;
  gap:10px !important;
  flex-wrap:wrap !important;
}
.memoryBannerTitle,.memoryBannerCopy{display:none !important;}
.memoryBannerKicker{font-size:11px !important;color:#8fdcff !important;text-shadow:0 0 10px #2edcff55;}
.memoryBannerMeta{margin-top:0 !important;}
.bannerControls{right:20px !important;top:20px !important;bottom:auto !important;}
.bannerControls button{width:42px;height:42px;padding:0 !important;font-size:24px;line-height:1;border-color:#7ccfff;background:#061527d9;color:#dff7ff;box-shadow:0 0 18px #2edcff33;}
body.gcMode .brand:after{content:" GAMECUBE / XBOX";color:#75d8ff;text-shadow:0 0 14px #2edcff;margin-left:8px;}
body.bbMode .brand:after{content:" BLUE BURST";color:#75d8ff;text-shadow:0 0 14px #2edcff;margin-left:8px;}
.modeBtn.active .modeText{color:#75d8ff !important;text-shadow:0 0 14px #2edcff,0 2px 10px #000;}
.modeBtn.active{border-color:#75d8ff !important;box-shadow:0 0 0 2px rgba(117,216,255,.18),0 0 28px rgba(46,220,255,.20),0 16px 38px #0008 !important;}
.modeBtn.active .modeText small{color:#dff7ff !important;}
@media(max-width:700px){
  .memoryBanner{padding:8px !important;border-radius:22px !important;}
  .memoryBanner img{max-height:230px !important;border-radius:18px !important;}
  .memoryBannerText{padding:9px 4px 0 !important;}
  .bannerControls{right:14px !important;top:14px !important;}
  .bannerControls button{width:36px;height:36px;font-size:21px;}
}


/* v2.1 Provenance Trail + Section ID chroma theme */
:root{--sid:#75d8ff;--sid2:#7d54df;--sidText:#eef6ff;--sidSoft:rgba(117,216,255,.16)}
body{background:radial-gradient(circle at 50% 0,var(--sid2) 0,#071220 42%,#040910 100%) !important;transition:background .35s ease}
body::before{background-size:30px 30px;opacity:.28}
.brand{color:var(--sidText);text-shadow:0 0 18px var(--sidSoft),0 4px 28px #000}
.panel,.memoryBanner{border-color:color-mix(in srgb,var(--sid) 56%,#345d8d) !important;box-shadow:0 20px 70px #0008, inset 0 1px 0 #ffffff18,0 0 34px var(--sidSoft) !important}
.guideTitle,.memoryBannerKicker,.tabs button.active,.item,.stat b{color:var(--sid) !important;text-shadow:0 0 12px var(--sidSoft)}
button.primary,.guideBtn.active,.weaponTile.active{border-color:var(--sid) !important;background:linear-gradient(135deg,var(--sid2),#12345a) !important;box-shadow:0 0 0 2px var(--sidSoft),0 0 18px var(--sidSoft) !important;color:#fff !important}
.sidBadge{border-color:var(--sid) !important;background:linear-gradient(180deg,rgba(255,255,255,.06),rgba(0,0,0,.22));box-shadow:0 0 14px var(--sidSoft)}
.tabs{gap:8px;overflow:auto}.tabs button{white-space:nowrap}
.provHeader{padding:18px;border-bottom:1px solid #294d76;background:linear-gradient(180deg,rgba(255,255,255,.04),rgba(0,0,0,.08))}
.provTitle{font-weight:1000;font-size:clamp(22px,5vw,38px);color:var(--sid);line-height:1.02}.provText{color:#cfe1f5;line-height:1.45;margin-top:7px;max-width:760px}.provPath{display:flex;gap:8px;overflow:auto;padding:14px 18px;border-bottom:1px solid #294d76;background:#06111dcc}.provNode{flex:0 0 auto;border:1px solid #31547c;background:#08182acc;border-radius:999px;padding:9px 12px;font-weight:900;color:#eaf4ff}.provNode.hot{border-color:var(--sid);color:var(--sid);box-shadow:0 0 16px var(--sidSoft)}.provArrow{align-self:center;color:#89a8c7;font-weight:1000}.routeGrid{display:grid;grid-template-columns:1fr;gap:14px;padding:14px}.routeArea{border:1px solid #31547c;background:#071524cc;border-radius:24px;overflow:hidden}.routeAreaHead{min-height:92px;display:flex;align-items:flex-end;padding:14px;background-size:cover;background-position:center;position:relative}.routeAreaHead:before{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(4,9,16,.1),rgba(4,9,16,.86))}.routeAreaHead>*{position:relative}.routeAreaTitle{font-size:24px;font-weight:1000;color:#fff;text-shadow:0 2px 12px #000}.routeAreaMeta{color:#d7e8ff;font-size:12px;font-weight:800}.enemyRail{display:flex;gap:10px;overflow:auto;padding:12px}.enemyChip{flex:0 0 86px;border:1px solid #294d76;background:#08182a;border-radius:18px;padding:8px;text-align:center;cursor:pointer;min-height:110px}.enemyChip img{height:52px;max-width:70px;object-fit:contain;filter:drop-shadow(0 8px 10px #000)}.enemyChipName{font-size:11px;line-height:1.1;margin-top:6px;font-weight:900;color:#eaf4ff}.enemyChipItem{font-size:10px;color:var(--sid);margin-top:4px;line-height:1.1}.enemyChip.active{border-color:var(--sid);box-shadow:0 0 0 2px var(--sidSoft)}.routeDetails{padding:0 12px 14px}.routeDrop{border:1px solid #294d76;background:#09182a;border-radius:18px;margin-top:10px;padding:12px;display:grid;grid-template-columns:54px 1fr;gap:10px;align-items:center}.routeDrop img{width:54px;height:54px;object-fit:contain}.routeDrop b{color:var(--sid);font-size:16px}.routeDrop small{color:#9fb3c9;display:block;line-height:1.35;margin-top:3px}.endpointBar{display:flex;gap:8px;overflow:auto;padding:12px 18px;background:#06111dcc;border-bottom:1px solid #294d76}.endpointBtn{flex:0 0 auto;border-radius:999px}.endpointBtn.on{border-color:var(--sid);color:var(--sid);box-shadow:0 0 12px var(--sidSoft)}
@media(max-width:560px){.stats{grid-template-columns:repeat(2,1fr)}.stat{padding:11px}.stat b{font-size:24px}.provPath{padding:12px}.routeGrid{padding:10px}.enemyRail{padding:10px}.enemyChip{flex-basis:78px}.routeDrop{grid-template-columns:44px 1fr}.routeDrop img{width:44px;height:44px}}



/* v2.2 Seaside Compact Patch — mojibake-safe + lighter mobile provenance */
:root{--seaTop:#4bbfe0;--seaMid:#1f6fa8;--seaDeep:#061425;}
body{
  background:
    radial-gradient(circle at 50% -120px,rgba(174,239,255,.92) 0,rgba(75,191,224,.62) 22%,rgba(31,111,168,.45) 42%,rgba(6,20,37,1) 78%,#030810 100%);
}
body:before{opacity:.24;background-size:36px 36px;}
.top,.guideHero{position:relative;}
.panel{background:linear-gradient(180deg,rgba(27,81,123,.76),rgba(7,23,42,.88));border-color:rgba(119,197,232,.38)}
.tabs{background:rgba(8,31,55,.88)}

/* route / provenance cards: denser without losing the trail */
.provHeader{padding:14px 14px 10px!important;}
.provTitle{font-size:clamp(22px,6vw,34px)!important;line-height:1.05!important;}
.provText{font-size:13px!important;line-height:1.35!important;max-width:820px;}
.provPath{padding:8px 12px 12px!important;gap:6px!important;overflow:auto;white-space:nowrap;}
.provNode{padding:7px 10px!important;font-size:12px!important;}
.provArrow{opacity:.7;font-size:13px!important;}
.endpointBar{padding:8px 10px!important;gap:6px!important;}
.endpointBtn{padding:7px 10px!important;font-size:12px!important;}
.routeGrid{display:grid!important;grid-template-columns:repeat(auto-fill,minmax(300px,1fr))!important;gap:12px!important;padding:12px!important;}
.routeArea{border-radius:20px!important;overflow:hidden;background:linear-gradient(180deg,rgba(9,32,57,.88),rgba(4,13,25,.94))!important;}
.routeAreaHead{min-height:74px!important;padding:12px!important;background-size:cover!important;background-position:center!important;}
.routeAreaTitle{font-size:20px!important;line-height:1.05!important;}
.routeAreaMeta{font-size:12px!important;margin-top:3px!important;}
.enemyRail{display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr))!important;gap:7px!important;padding:9px!important;overflow:visible!important;}
.enemyChip{min-width:0!important;width:100%!important;border-radius:15px!important;padding:6px 5px!important;min-height:82px!important;}
.enemyChip img{width:42px!important;height:38px!important;object-fit:contain!important;margin:0 auto 3px!important;}
.enemyChipName{font-size:11px!important;line-height:1.05!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.enemyChipItem{font-size:10px!important;line-height:1.05!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.86;}
.routeDetails{padding:0 9px 10px!important;}
.routeDrop{grid-template-columns:44px 1fr!important;gap:8px!important;padding:9px!important;border-radius:15px!important;margin-top:0!important;}
.routeDrop img{width:44px!important;height:44px!important;}
.routeDrop b{font-size:14px!important;line-height:1.15!important;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.routeDrop small{font-size:11px!important;line-height:1.28!important;}

/* hunt cards: 2-column phone scan, lower vertical cost */
.card{background:linear-gradient(180deg,rgba(16,54,88,.94),rgba(5,16,30,.98));}
.wow{padding:8px 8px!important;margin-top:8px!important;}
.wow .rateBig{font-size:clamp(24px,7vw,34px)!important;}
.wow .killsBig{font-size:clamp(13px,4vw,17px)!important;}
.huntMeta{font-size:12px!important;line-height:1.28!important;}
.details{display:none;}
@media(max-width:560px){
  .wrap{padding:12px 8px 44px!important;}
  .top{margin-bottom:12px!important;}
  .brand{font-size:34px!important;}
  .tag{font-size:13px!important;line-height:1.35!important;}
  .pill{font-size:12px;padding:7px 9px!important;}
  .controls,.buttons,.stats,.tabs{padding-left:10px!important;padding-right:10px!important;}
  .stats{gap:8px!important;}
  .stat{padding:9px!important;border-radius:14px!important;}
  .stat b{font-size:22px!important;}
  .stat span{font-size:11px!important;}
  .guide{padding:12px!important;}
  .guideText{font-size:13px!important;}
  .guideStep{padding:10px!important;border-radius:18px!important;}
  .routeGrid{grid-template-columns:1fr!important;gap:10px!important;padding:10px!important;}
  .routeAreaHead{min-height:64px!important;}
  .enemyRail{grid-template-columns:repeat(4,minmax(0,1fr))!important;gap:6px!important;padding:8px!important;}
  .enemyChip{min-height:72px!important;border-radius:13px!important;}
  .enemyChip img{width:34px!important;height:32px!important;}
  .enemyChipName{font-size:10px!important;}
  .enemyChipItem{display:none!important;}
  .routeDrop{grid-template-columns:38px 1fr!important;padding:8px!important;}
  .routeDrop img{width:38px!important;height:38px!important;}
  .routeDrop b{font-size:13px!important;}
  .routeDrop small{font-size:10.5px!important;}
  .grid{grid-template-columns:repeat(2,minmax(0,1fr))!important;gap:8px!important;padding:8px!important;}
  .card .img{height:82px!important;}
  .card .img img{max-height:76px!important;}
  .enemy{font-size:15px!important;}
  .item{font-size:15px!important;}
  .sidBadge img{width:16px!important;height:16px!important;}
  .sidBadge{font-size:10px!important;padding:2px 5px!important;}
  .rarity{display:none!important;}
  .wow .rateBig{font-size:22px!important;}
  .wow .killsBig{font-size:12px!important;}
}



/* v2.8 Location-backed planner cards + stronger Section ID header */
.top{
  position:relative;
  overflow:hidden;
  border:1px solid color-mix(in srgb,var(--sid) 58%,rgba(117,184,255,.35));
  border-radius:28px;
  padding:18px 18px 16px;
  background:
    linear-gradient(135deg,var(--sidSoft),rgba(5,18,32,.72) 34%,rgba(3,9,18,.86)),
    radial-gradient(circle at 82% 18%,var(--sidSoft),transparent 34%);
  box-shadow:0 20px 70px rgba(0,0,0,.36), inset 0 1px 0 rgba(255,255,255,.12), 0 0 40px var(--sidSoft);
}
.top::before{
  content:"";
  position:absolute;
  left:0;right:0;top:0;height:5px;
  background:linear-gradient(90deg,transparent,var(--sid),#fff,var(--sid),transparent);
  opacity:.92;
}
.activeSidRibbon{
  display:flex;
  align-items:center;
  gap:9px;
  width:max-content;
  max-width:100%;
  margin:0 0 14px;
  padding:8px 12px;
  border:1px solid var(--sid);
  border-radius:999px;
  background:linear-gradient(135deg,var(--sidSoft),rgba(5,18,32,.80));
  color:var(--sidText);
  font-weight:1000;
  letter-spacing:.12em;
  text-transform:uppercase;
  box-shadow:0 0 24px var(--sidSoft);
}
.activeSidRibbon img{width:24px;height:24px;object-fit:contain;filter:drop-shadow(0 0 10px var(--sidSoft));}
.activeSidRibbon small{letter-spacing:.05em;text-transform:none;color:#d7e8ff;font-weight:800;}

.plannerAreaMap{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(145px,1fr));
  gap:9px;
  margin-bottom:12px;
}
.areaCountCard{
  position:relative;
  min-height:86px;
  border:1px solid rgba(117,184,255,.28);
  border-radius:18px;
  overflow:hidden;
  padding:10px;
  background:#06111d;
  display:flex;
  flex-direction:column;
  justify-content:flex-end;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08);
}
.areaCountCard::before{
  content:"";
  position:absolute;
  inset:0;
  background-image:var(--area-bg);
  background-size:cover;
  background-position:center;
  opacity:.32;
  filter:saturate(1.15) contrast(1.08);
  transform:scale(1.02);
}
.areaCountCard::after{
  content:"";
  position:absolute;
  inset:0;
  background:linear-gradient(180deg,rgba(3,10,20,.10),rgba(3,10,20,.88));
}
.areaCountCard>*{position:relative;z-index:1}
.areaCountCard b{font-size:17px;color:#fff;text-shadow:0 2px 14px #000;}
.areaCountCard span{font-size:12px;color:var(--gold);font-weight:900;margin-top:3px;}

.missingCard{
  position:relative;
  overflow:hidden;
  background:linear-gradient(180deg,rgba(3,13,24,.22),rgba(3,13,24,.88))!important;
  min-height:260px;
}
.missingCard::before{
  content:"";
  position:absolute;
  inset:0;
  background-image:var(--area-bg);
  background-size:cover;
  background-position:center;
  opacity:.24;
  filter:blur(.7px) saturate(1.15) contrast(1.08);
  transform:scale(1.035);
}
.missingCard::after{
  content:"";
  position:absolute;
  inset:0;
  background:
    linear-gradient(180deg,rgba(2,9,18,.26),rgba(2,9,18,.93) 74%),
    radial-gradient(circle at 78% 14%,var(--sidSoft),transparent 34%);
}
.missingCard>*{position:relative;z-index:1}
.missingCard b{font-size:18px!important;text-shadow:0 2px 14px #000;}
.missingCard small{font-size:13px!important;color:#c7d9ee!important;text-shadow:0 1px 8px #000;}
.enemyMini{
  border-top-color:rgba(255,255,255,.18)!important;
  background:rgba(2,9,18,.26);
  border-radius:16px;
  padding:9px!important;
}
.enemyMini img,.enemyMini .missing{
  background:rgba(2,9,18,.34);
  border-radius:16px;
}
.routeBtn{
  border-color:color-mix(in srgb,var(--sid) 50%,#75b8ff)!important;
  background:linear-gradient(135deg,rgba(49,95,148,.70),var(--sidSoft))!important;
}
.provHeroCard{
  position:relative;
  overflow:hidden;
  border:1px solid var(--sid);
  border-radius:22px;
  min-height:150px;
  margin:12px;
  padding:16px;
  background:#06111d;
  display:flex;
  flex-direction:column;
  justify-content:flex-end;
  box-shadow:0 18px 42px rgba(0,0,0,.32),0 0 28px var(--sidSoft);
}
.provHeroCard::before{
  content:"";
  position:absolute;inset:0;
  background-image:var(--area-bg);
  background-size:cover;background-position:center;
  opacity:.36;filter:saturate(1.16) contrast(1.05);
}
.provHeroCard::after{
  content:"";
  position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(3,10,20,.08),rgba(3,10,20,.92));
}
.provHeroCard>*{position:relative;z-index:1}
.provHeroCard h3{margin:0;color:var(--gold);font-size:24px;line-height:1.05;text-shadow:0 2px 12px #000;}
.provHeroCard p{margin:7px 0 0;color:#d7e8ff;font-weight:800;}

@media(max-width:560px){
  .top{border-radius:22px;padding:14px 12px!important;}
  .activeSidRibbon{font-size:12px;margin-bottom:10px;padding:7px 10px;}
  .plannerAreaMap{grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;}
  .areaCountCard{min-height:72px;border-radius:15px;}
  .missingCard{min-height:238px;border-radius:19px!important;padding:12px!important;}
  .missingGrid{gap:12px!important;}
}

/* v2.10.15 action SFX patch */
.sfxToggle{border:1px solid #426b9a;background:#08182acc;color:#dff2ff;border-radius:999px;padding:8px 10px;font-weight:900;font-size:12px;white-space:nowrap;box-shadow:0 0 14px rgba(124,207,255,.12)}
.sfxToggle.on{border-color:var(--sid);color:var(--sid);box-shadow:0 0 0 2px var(--sidSoft),0 0 18px var(--sidSoft)}
@media(max-width:720px){.sfxToggle{position:absolute;right:18px;top:106px;padding:7px 9px;font-size:11px}}



/* v2.10.20 final site footer — outside cards/panels */
.siteFooter{
  max-width:1220px;
  margin:34px auto 0;
  padding:28px 18px 46px;
  color:#9fb3c9;
  font-size:13px;
  line-height:1.55;
  text-align:center;
  border-top:1px solid rgba(117,184,255,.24);
}
.siteFooter b{color:#eef6ff;letter-spacing:.08em;text-transform:uppercase}
.siteFooter a{color:#ffe991;text-decoration:none;font-weight:900}
.siteFooter a:hover{text-decoration:underline}
.siteFooter .footerCredit{font-size:15px;margin-bottom:10px;color:#dcecff}
.siteFooter .footerDisclaimer{max-width:860px;margin:0 auto;color:#8fa8c3}
.siteFooter .footerSmall{margin-top:12px;color:#6f879f;font-size:12px}
@media(max-width:560px){.siteFooter{padding:24px 16px 40px;font-size:12px}}

</style>`;return;}const grouped={};rows.forEach(r=>{if(!isRealDrop(r))return;(grouped[r.item]??=[]).push(r)});out.innerHTML='<div class="grid">'+Object.keys(grouped).sort().slice(0,300).map(it=>{let rs=grouped[it];let best=rs.slice().sort((a,b)=>(a.expectedKills||1e9)-(b.expectedKills||1e9))[0];return `<div class="card"><div class="body"><div class="item">${it}</div><div class="meta">${rs.length} known routes in current filter</div><div class="rate">${weaponBadge(best.itemType||'Other')}<span class="chip gold">best ${best.rate}</span>${sidBadge(best.sectionId)}<span class="chip">${best.monster}</span></div><div class="meta">${best.difficulty} · ${best.area}</div></div></div>`}).join('')+'</div>';}
['section','difficulty','area','search'].forEach(id=>$(id).addEventListener('input',render));$('apply').onclick=render;$('clear').onclick=()=>{$('section').value='';$('difficulty').value=(currentMode==='bb'?'Ultimate':'Normal');$('area').value='';$('search').value='';rareOnly=false;hideCommon=true;selectedClass='';selectedType='All';selectedKind='All';render();};$('rareOnly').onclick=()=>{rareOnly=!rareOnly;$('rareOnly').classList.toggle('primary',rareOnly);render();};$('hideCommon').onclick=()=>{hideCommon=!hideCommon;$('hideCommon').classList.toggle('primary',hideCommon);render();};$('hideCommon').classList.add('primary');$('tabTrail').onclick=()=>{tab='trail';document.querySelectorAll('.tabs button').forEach(b=>b.classList.remove('active'));$('tabTrail').classList.add('active');render()};$('tabStrategy').onclick=()=>{tab='strategy';document.querySelectorAll('.tabs button').forEach(b=>b.classList.remove('active'));$('tabStrategy').classList.add('active');render()};$('tabCards').onclick=()=>{tab='cards';document.querySelectorAll('.tabs button').forEach(b=>b.classList.remove('active'));$('tabCards').classList.add('active');render()};$('tabTable').onclick=()=>{tab='table';document.querySelectorAll('.tabs button').forEach(b=>b.classList.remove('active'));$('tabTable').classList.add('active');render()};$('tabItems').onclick=()=>{tab='items';document.querySelectorAll('.tabs button').forEach(b=>b.classList.remove('active'));$('tabItems').classList.add('active');render()};
initHeaderAmbience();
initBanner();
initSectionCalculator();

function uniqueSorted(arr){return [...new Set(arr.filter(Boolean))].sort((a,b)=>String(a).localeCompare(String(b)));}
function makeBlueBurstHybridData(){
  if(!GC_DATA||!BB_DATA) return BB_DATA;
  const gcBase=(GC_DATA.records||[]).filter(r=>r.difficulty!=='Ultimate').map(r=>({...r,platform:'Blue Burst / base table'}));
  const bbUltimate=(BB_DATA.records||[]).map(r=>({...r,platform:r.platform||'Blue Burst'}));
  const records=[...gcBase,...bbUltimate];
  return {
    source:'Blue Burst hybrid: GC/Xbox Normal-Hard-Very Hard + Blue Burst Ultimate/Ep4',
    platform:'Blue Burst',
    records,
    sectionIds:uniqueSorted([...(GC_DATA.sectionIds||[]),...(BB_DATA.sectionIds||[])]),
    difficulties:['Normal','Hard','Very Hard','Ultimate'],
    areas:orderedAreas(uniqueSorted([...(GC_DATA.areas||[]),...(BB_DATA.areas||[])])),
    items:uniqueSorted([...(GC_DATA.items||[]),...(BB_DATA.items||[])]),
    monsters:uniqueSorted([...(GC_DATA.monsters||[]),...(BB_DATA.monsters||[])])
  };
}

function loadDataset(j){
  DATA=j.records.map(r=>({...r,itemType:itemType(r.item)}));
  AREAS=orderedAreas(j.areas||[]); ITEMS=j.items; MONSTERS=j.monsters;
  clearDynamicControls(); initSelects(); render();
}
function setMode(mode){
  currentMode=mode;
  document.body.classList.toggle('bbMode',mode==='bb');
  document.body.classList.toggle('gcMode',mode==='gc');
  $('modeGc')?.classList.toggle('active',mode==='gc');
  $('modeBb')?.classList.toggle('active',mode==='bb');
  const source=$('modeSourceText'); if(source) source.textContent=mode==='bb'?'Source: Blue Burst hybrid · Normal/Hard/VH base + Ultimate/Ep4 Blue Burst.':'Source: PSOGC Drop Charts · GameCube/Xbox.';
  const pill=$('versionPill'); if(pill) pill.textContent=mode==='bb'?'v2.10.20 · Blue Burst':'v2.10.20 · GC/Xbox';
  const bchip=$('bannerModeChip'); if(bchip) bchip.textContent=mode==='bb'?'Blue Burst archive':'GC/Xbox archive';
  loadDataset(mode==='bb'?makeBlueBurstHybridData():GC_DATA);
}


/* v2.7 Account Planner / data-driven strategy layer */
let STRATEGY_JSON=null, OVERLAP_JSON=null, ROSTER_JSON=null, ITEM_INDEX_JSON=null;
const PLANNER={activeCount:1,chars:[{cls:'Hucast',sid:'Skyly'},{cls:'Ramarl',sid:'Redria'},{cls:'Fonewearl',sid:'Whitill'}],loadout:{Weapon:'Red Sword',Frame:'Aura Field',Barrier:'Kasami Bracer',Unit1:'God/Battle',Unit2:'',Unit3:'',Unit4:''},dream:''};
const HTML_ESCAPE={'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'};
function esc(s){return String(s??'').replace(/[&<>"']/g,c=>HTML_ESCAPE[c]);}
function safeId(s){return String(s??'').replace(/[^a-zA-Z0-9_-]/g,'_');}
function itemIndex(){return ITEM_INDEX_JSON&&ITEM_INDEX_JSON.items?ITEM_INDEX_JSON.items:{};}
function activePlannerChars(){return PLANNER.chars.slice(0,Math.max(1,Math.min(3,PLANNER.activeCount||1)));}
function activePlannerSids(){return activePlannerChars().map(c=>c.sid).filter(Boolean);}
function setPlannerActiveCount(n){PLANNER.activeCount=Math.max(1,Math.min(3,Number(n)||1)); render();}
function allPlannerItems(){return Object.keys(itemIndex()).filter(x=>x&&x!=='-'&&x!=='No Item').sort((a,b)=>a.localeCompare(b));}
function dataTypesForItem(item){const set=new Set(); DATA.forEach(r=>{if(r.item===item && r.itemType) set.add(r.itemType)}); return [...set];}
function itemKind(item){
  const low=String(item).toLowerCase(); const idx=itemIndex()[item]||{}; const cat=String(idx.category||'').toLowerCase(); const types=dataTypesForItem(item).map(x=>String(x).toLowerCase());
  if(cat.includes('unit')||low.includes('/')||/^(cure|god|devil|heavenly|centurion|v101|v502|v801|resist|master|general|angel|dragon|metal|hp|tp)\b/.test(low)) return 'Unit';
  if(cat.includes('armor')||/(frame|armor|field|coat|uniform|cloak|plate|garment|nimaidou|suit|costume|mantle|dress)/i.test(item)) return 'Frame';
  if(cat.includes('shield')||cat.includes('barrier')||/(shield|barrier|guard|bracer|wall|relief|ring|gear|merge)/i.test(item)) return 'Barrier';
  if(cat.includes('weapon')||types.some(t=>!['other','armor','unit','shield','barrier','frame'].includes(t))) return 'Weapon';
  return 'Other';
}
function itemsForKind(kind){return allPlannerItems().filter(x=>itemKind(x)===kind);}
function routeForItem(item, preferSids=[]){
  const idx=itemIndex()[item]; let routes=[];
  if(idx&&Array.isArray(idx.bestRoutes)) routes=idx.bestRoutes.filter(r=>r&&r.item&&r.item!=='-'&&r.rate&&r.rate!=='-');
  if(!routes.length) routes=DATA.filter(r=>r.item===item && r.rate && r.rate!=='-');
  routes=routes.slice().sort((a,b)=>{
    const ap=preferSids.includes(a.sectionId)?-1:0, bp=preferSids.includes(b.sectionId)?-1:0;
    return ap-bp || ((a.expectedKills||1e15)-(b.expectedKills||1e15));
  });
  return routes[0]||null;
}

const MONSTER_IMAGE_FALLBACKS={
  'Del Rappy Desert':'assets/monster_assets/Episode_4/Subterranean_Desert/Del_Rappy_Desert.jpg',
  'Del Rappy Crater':'assets/monster_assets/Episode_4/Crater/Del_Rappy_Crater.jpg',
  'Sinow Blue':'assets/monster_assets/Mine/Sinow_Blue.png',
  'Pouilly Slime':'assets/monster_assets/Cave/Pouilly_Slime.png',
  'Pouilly_Slime':'assets/monster_assets/Cave/Pouilly_Slime.png',
  'Pofuilly Slime':'assets/monster_assets/Cave/Pofuilly_Slime.png',
  'Pofuilly_Slime':'assets/monster_assets/Cave/Pofuilly_Slime.png',
  'Garanz':'assets/monster_assets/Mine/Garanz.png',
  'Geranz':'assets/monster_assets/Mine/Garanz.png',
  'Baranz':'assets/monster_assets/Mine/Baranz.png',
  'Del Lily':'assets/monster_assets/Control_Tower/Del_Lily.png',
  'Del_Lily':'assets/monster_assets/Control_Tower/Del_Lily.png',
  'Ill Gill':'assets/monster_assets/Control_Tower/Ill_Gill.png',
  'Ill_Gill':'assets/monster_assets/Control_Tower/Ill_Gill.png',
  'Zol Gibbon':'assets/monster_assets/Central_Control_Area_jungle_seaside_mountains/Zol_Gibbon.png',
  'Zol_Gibbon':'assets/monster_assets/Central_Control_Area_jungle_seaside_mountains/Zol_Gibbon.png'
};
function sourceRecords(){return [...(DATA||[]),...((GC_DATA&&GC_DATA.records)||[]),...((BB_DATA&&BB_DATA.records)||[])];}
function inferredMonsterImage(route){
  if(!route) return '';
  if(route.monsterImage) return route.monsterImage;
  const monster=route.monster||'';
  const area=route.area||'';
  const direct=MONSTER_IMAGE_FALLBACKS[monster];
  if(direct) return direct;
  const exact=sourceRecords().find(r=>r.monster===monster&&r.area===area&&r.monsterImage);
  if(exact) return exact.monsterImage;
  const loose=sourceRecords().find(r=>r.monster===monster&&r.monsterImage);
  if(loose) return loose.monsterImage;
  const file=String(monster).replace(/[^A-Za-z0-9]+/g,'_').replace(/^_+|_+$/g,'');
  if(!file) return '';
  if(area.includes('Tower')) return `assets/monster_assets/Control_Tower/${file}.png`;
  if(area.includes('CCA')||area.includes('Central Control')) return `assets/monster_assets/Central_Control_Area_jungle_seaside_mountains/${file}.png`;
  if(area.includes('Subterranean')) return `assets/monster_assets/Episode_4/Subterranean_Desert/${file}.png`;
  if(area.includes('Crater')) return `assets/monster_assets/Episode_4/Crater/${file}.png`;
  if(area.includes('Temple')) return `assets/monster_assets/VR_Temple/${file}.png`;
  if(area.includes('Spaceship')) return `assets/monster_assets/VR_Spaceship/${file}.png`;
  if(area.includes('Mine')) return `assets/monster_assets/Mine/${file}.png`;
  if(area.includes('Cave')) return `assets/monster_assets/Cave/${file}.png`;
  if(area.includes('Forest')) return `assets/monster_assets/Forest/${file}.png`;
  if(area.includes('Ruins')) return `assets/monster_assets/Ruins/${file}.png`;
  return '';
}
function routeToDataRow(route){
  if(!route) return null;
  const row=sourceRecords().find(r=>r.item===route.item&&r.sectionId===route.sectionId&&r.monster===route.monster&&r.area===route.area&&r.difficulty===route.difficulty)
    || sourceRecords().find(r=>r.monster===route.monster&&r.area===route.area&&r.monsterImage)
    || route;
  return {...route,...row,monsterImage:(row&&row.monsterImage)||inferredMonsterImage(route)};
}

function routeSummary(route){if(!route) return 'No route found in current data.'; return `${esc(route.sectionId)} / ${esc(route.difficulty)} / ${esc(areaLabel(route.area||''))} / ${esc(route.monster)} / ${esc(route.rate)}`;}
let LAST_OPEN_ROUTE=null; function openRoute(item,sid){const r=routeForItem(item,sid?[sid]:activePlannerSids()); if(!r) return; LAST_OPEN_ROUTE={route:r,item:item}; $('section').value=r.sectionId||sid||''; $('difficulty').value=r.difficulty||'Ultimate'; $('area').value=r.area||''; $('search').value=item; tab='trail'; document.querySelectorAll('.tabs button').forEach(b=>b.classList.remove('active')); $('tabTrail').classList.add('active'); render(); $('output').scrollIntoView({behavior:'smooth',block:'start'});}
function itemCoveredByRoster(item, sids){const idx=itemIndex()[item]; if(idx&&Array.isArray(idx.sectionIds)) return idx.sectionIds.some(s=>sids.includes(s)); return DATA.some(r=>r.item===item && sids.includes(r.sectionId));}
function selectedLoadoutItems(){return Object.values(PLANNER.loadout).filter(Boolean);}
function desiredItems(){const typed=PLANNER.dream.split(/[,\n]+/).map(x=>x.trim()).filter(Boolean); return [...new Set([...selectedLoadoutItems(),...typed])];}
function coverageForSids(sids){const items=allPlannerItems(); if(!items.length) return {total:0,covered:0,pct:0}; const covered=items.filter(it=>itemCoveredByRoster(it,sids)).length; return {total:items.length,covered,pct:Math.round(covered/items.length*100)};}
function roleFit(sid,role){return (ROSTER_JSON&&ROSTER_JSON.roleFitBySectionId&&ROSTER_JSON.roleFitBySectionId[sid]&&ROSTER_JSON.roleFitBySectionId[sid][role])||0;}
function coverageLines(sids){const base=coverageForSids(sids); const maxRole=30; const roles=['Hunter','Ranger','Force']; let out=`<div class="coverageLine"><b>Account</b><div class="barTrack"><div class="barFill" style="width:${base.pct}%"></div></div><span>${base.pct}%</span></div>`; roles.forEach(role=>{const raw=sids.reduce((sum,s)=>sum+roleFit(s,role),0); const pct=Math.min(100,Math.round(raw/maxRole*100)); out+=`<div class="coverageLine"><b>${role}</b><div class="barTrack"><div class="barFill" style="width:${pct}%"></div></div><span>${pct}%</span></div>`}); return out;}
function selectOptions(kind, current){const items=itemsForKind(kind); const top=['','Red Sword','Spread Needle','Aura Field','Kasami Bracer','God/Battle','Red Ring','Psycho Wand','Yasminkov 9000M','Frozen Shooter'].filter(x=>!x||items.includes(x)); const rest=items.filter(x=>!top.includes(x)); return [...top,...rest].map(x=>`<option value="${esc(x)}" ${x===current?'selected':''}>${x?esc(x):'None'}</option>`).join('');}
function classOptions(current){return CLASS_BUTTONS.map(c=>`<option value="${esc(c)}" ${c===current?'selected':''}>${esc(CLASS_LABEL[c]||c)}</option>`).join('');}
function sidOptions(current){return SECTION_IDS.map(sid=>`<option value="${sid}" ${sid===current?'selected':''}>${sid}</option>`).join('');}
function setPlannerChar(i,k,v){PLANNER.chars[i][k]=v; const sid=PLANNER.chars[0].sid; if(sid) $('section').value=sid; render();}
function setPlannerSlot(k,v){PLANNER.loadout[k]=v; render();}
function setPlannerDream(v){PLANNER.dream=v; render();}

function areaBgForRoute(route,item=''){
  if(!route) return '';
  const a=route.area||route.location||'';
  return areaVariantImage(a,`${item}|${route.monster||''}|${route.sectionId||''}`)||areaImage(a)||'';
}
function cssBgUrl(url){return url?` style="--area-bg:url('${String(url).replace(/'/g,"%27")}')"`:'';}
function plannerAreaMap(){
  const sids=activePlannerSids();
  const items=desiredItems();
  const counts=new Map();
  items.forEach(item=>{
    const route=routeForItem(item,sids);
    if(!route) return;
    const key=route.area||'Unknown area';
    const old=counts.get(key)||{area:key,count:0,img:areaBgForRoute(route,item)};
    old.count++;
    if(!old.img) old.img=areaBgForRoute(route,item);
    counts.set(key,old);
  });
  const arr=[...counts.values()].sort((a,b)=>b.count-a.count||String(a.area).localeCompare(String(b.area))).slice(0,8);
  if(!arr.length) return '';
  return `<div class="plannerAreaMap">${arr.map(x=>`<div class="areaCountCard"${cssBgUrl(x.img)}><b>${esc(areaLabel(x.area))}</b><span>${x.count} required ${x.count===1?'item':'items'}</span></div>`).join('')}</div>`;
}
function provenanceHero(route,item){
  const img=areaBgForRoute(route,item);
  if(!route||!img) return '';
  return `<div class="provHeroCard"${cssBgUrl(img)}><h3>${esc(item||route.item||'Hunt route')}</h3><p>${esc(route.sectionId||'Any ID')} -> ${esc(route.difficulty||'Any difficulty')} -> ${esc(areaLabel(route.area||''))} -> ${esc(route.monster||'Unknown enemy')} -> ${esc(route.rate||'')}</p></div>`;
}

function plannerMissingCards(){
  const sids=activePlannerSids();
  const items=desiredItems();
  if(!items.length) return '<div class="emptyStrategy">Choose loadout items or type a dream item list.</div>';
  const cards=items.map(item=>{
    const covered=itemCoveredByRoster(item,sids);
    const route=routeForItem(item,sids);
    const row=routeToDataRow(route);
    const bg=areaBgForRoute(route,item);
    const monsterImg=(row&&row.monsterImage)||inferredMonsterImage(route);
    const img=monsterImg?`<img src="${esc(monsterImg)}" alt="${esc((row&&row.monster)||route&&route.monster||'enemy')}" onerror="this.outerHTML='<div class=&quot;missing&quot;>?</div>'">`:'<div class="missing">?</div>';
    const routeAttr=route?` onclick="openRoute('${esc(item).replace(/&#39;/g,"\\'")}','${esc(route.sectionId||'')}')"`:'';
    return `<div class="missingCard ${monsterImg?'enemyWatermark':''}"${monsterImg?` style="--enemy-bg:url('${esc(monsterImg)}')"`:cssBgUrl(bg)}><b>${esc(item)}</b><div class="pillList"><span class="miniPill ${covered?'good':'miss'}">${covered?'covered by roster':'missing from roster'}</span>${route?`<span class="miniPill">best: ${esc(route.sectionId)}</span>`:''}</div><small>${routeSummary(route)}</small><div class="enemyMini"${routeAttr}>${img}<div><b>${esc(route&&route.monster||'Unknown enemy')}</b><small>${esc(route&&route.area?areaLabel(route.area):'')} ${route&&route.episode?' / '+esc(route.episode):''}</small></div></div>${route?`<button class="routeBtn"${routeAttr}>Open Provenance Trail</button>`:''}</div>`;
  }).join('');
  return plannerAreaMap()+cards;
}
function renderWhyChoose(sid){if(!STRATEGY_JSON||!STRATEGY_JSON.sectionIds) return '<div class="emptyStrategy">Loading strategy JSON...</div>'; const d=STRATEGY_JSON.sectionIds[sid]||{}; const sig=(d.signatureItems||[]).slice(0,8); const excl=(d.exclusiveItems||[]).slice(0,6); const weapons=(d.topWeapons||[]).slice(0,5).map(r=>r.item); const armor=(d.topArmorUnits||[]).slice(0,5).map(r=>r.item); const util=(d.topUtilityItems||[]).slice(0,5).map(r=>r.item); const prestige=(d.prestigeHunts||[]).slice(0,5).map(r=>`${r.item} (${r.rate})`); return `<div class="signatureBands"><div class="sigBand"><h4>Why choose ${esc(sid)}?</h4><p>${sig.map(esc).join('<br>')||'No signature list.'}</p></div><div class="sigBand"><h4>Exclusive endpoints</h4><p>${excl.map(esc).join('<br>')||'No exclusive endpoints in this layer.'}</p></div><div class="sigBand"><h4>Top weapons</h4><p>${[...new Set(weapons)].map(esc).join('<br>')}</p></div><div class="sigBand"><h4>Armor / units</h4><p>${[...new Set(armor)].map(esc).join('<br>')}</p></div><div class="sigBand"><h4>Utility</h4><p>${[...new Set(util)].map(esc).join('<br>')}</p></div><div class="sigBand"><h4>Prestige hunts</h4><p>${[...new Set(prestige)].map(esc).join('<br>')}</p></div></div>`;}
function recommendedRoster(){if(!ROSTER_JSON) return ''; const selected=activePlannerSids(); const two=(ROSTER_JSON.best2CharacterCombinations||[]).find(c=>selected.includes(c.sectionIds[0])||selected.includes(c.sectionIds[1])) || (ROSTER_JSON.best2CharacterCombinations||[])[0]; const three=(ROSTER_JSON.best3CharacterCombinations||[]).find(c=>selected.some(s=>c.sectionIds.includes(s))) || (ROSTER_JSON.best3CharacterCombinations||[])[0]; return `<div class="plannerCard"><div class="plannerSub">Recommended Account Routes</div><div class="rosterGrid">${two?`<div class="rosterSlot"><span class="slotNo">2</span><b>${two.sectionIds.join(' + ')}</b><p>${two.coverageItems} coverage items · score ${two.score}</p><p>${two.topItems.slice(0,6).map(esc).join(' · ')}</p></div>`:''}${three?`<div class="rosterSlot"><span class="slotNo">3</span><b>${three.sectionIds.join(' + ')}</b><p>${three.coverageItems} coverage items · score ${three.score}</p><p>${three.topItems.slice(0,6).map(esc).join(' · ')}</p></div>`:''}</div></div>`;}
function renderPlanner(){
  const sids=activePlannerSids(); const main=PLANNER.chars[0]; const activeSid=$('section').value||main.sid||'Redria'; const cov=coverageForSids(sids); const selected=desiredItems(); const charIcon=main.cls?classPortrait(main.cls):'';
  return `<div class="plannerWrap"><section class="plannerHero"><div class="plannerTitle">Build Your PSO Account</div><div class="plannerText">Start with the character fantasy, then let the data route backward: loadout -> missing items -> Section IDs -> enemies -> drop rates -> Provenance Trail. This is the player question layer above the database.</div></section><div class="plannerGrid"><section class="plannerCard hot"><div class="plannerSub">1. Account roster</div><div class="charCountToggle">${[1,2,3].map(n=>`<button type="button" class="${PLANNER.activeCount===n?'active':''}" onclick="setPlannerActiveCount(${n})">${n} character${n>1?'s':''}</button>`).join('')}</div><div class="slotGrid">${PLANNER.chars.map((c,i)=>`<div class="charSlot ${i>=PLANNER.activeCount?'inactive':''}"><span class="charNum">${i+1}</span><select onchange="setPlannerChar(${i},'cls',this.value)">${classOptions(c.cls)}</select><select onchange="setPlannerChar(${i},'sid',this.value)">${sidOptions(c.sid)}</select></div>`).join('')}</div><div class="plannerNote">Choose 1, 2, or 3 active characters. Grey slots are planned-but-inactive; adding them shows which missing pool becomes reachable.</div></section><section class="plannerCard"><div class="plannerSub">Account coverage</div><div class="coverageBox">${coverageLines(sids)}</div><div class="plannerNote">${cov.covered} of ${cov.total} indexed items covered by this roster.</div></section></div><div class="plannerGrid"><section class="plannerCard"><div class="plannerSub">2. Loadout planner</div><div class="loadoutShell"><div class="charSilhouette">${charIcon?`<img src="${charIcon}" alt="${esc(main.cls)}">`:''}<b>${esc(CLASS_LABEL[main.cls]||main.cls)}</b><small>${esc(main.sid)} / LV 200 fantasy</small></div><div class="loadoutGrid"><div class="loadoutRow"><label>Weapon</label><select onchange="setPlannerSlot('Weapon',this.value)">${selectOptions('Weapon',PLANNER.loadout.Weapon)}</select></div><div class="loadoutRow"><label>Frame</label><select onchange="setPlannerSlot('Frame',this.value)">${selectOptions('Frame',PLANNER.loadout.Frame)}</select></div><div class="loadoutRow"><label>Barrier</label><select onchange="setPlannerSlot('Barrier',this.value)">${selectOptions('Barrier',PLANNER.loadout.Barrier)}</select></div>${['Unit1','Unit2','Unit3','Unit4'].map(u=>`<div class="loadoutRow"><label>${u.replace('Unit','Unit ')}</label><select onchange="setPlannerSlot('${u}',this.value)">${selectOptions('Unit',PLANNER.loadout[u])}</select></div>`).join('')}</div></div></section><section class="plannerCard"><div class="plannerSub">3. Design my dream character</div><div class="dreamBox"><textarea class="dreamInput" placeholder="Type extra dream items, comma separated: Spread Needle, Red Ring, Psycho Wand" oninput="setPlannerDream(this.value)">${esc(PLANNER.dream)}</textarea><div class="pillList">${selected.map(it=>`<span class="miniPill ${itemCoveredByRoster(it,sids)?'good':'miss'}">${esc(it)}</span>`).join('')}</div></div></section></div><section class="plannerCard hot"><div class="plannerSub">4. Missing pieces -> enemy cards</div><div class="missingGrid">${plannerMissingCards()}</div></section><section class="plannerCard"><div class="plannerSub">5. Why choose this Section ID?</div>${renderWhyChoose(activeSid)}</section>${recommendedRoster()}</div>`;
}
function plannerInitHandlers(){const b=$('tabPlanner'); if(!b) return; b.onclick=()=>{tab='planner';document.querySelectorAll('.tabs button').forEach(x=>x.classList.remove('active'));b.classList.add('active');render();};}
const render_v26=render;
render=function(){ if(tab==='planner'){const rows=filtered().sort((a,b)=>(a.expectedKills||1e12)-(b.expectedKills||1e12)); updateStats(rows); $('output').innerHTML=renderPlanner(); return;} return render_v26(); };
plannerInitHandlers();
Promise.allSettled([
  fetch('data/strategy/section-id-strategy.json').then(r=>r.json()),
  fetch('data/strategy/section-id-overlap.json').then(r=>r.json()),
  fetch('data/strategy/account-roster-planner.json').then(r=>r.json()),
  fetch('data/strategy/item-endpoint-index.json').then(r=>r.json())
]).then(res=>{[STRATEGY_JSON,OVERLAP_JSON,ROSTER_JSON,ITEM_INDEX_JSON]=res.map(x=>x.status==='fulfilled'?x.value:null); if(tab==='planner') render();});


/* v2.10.8 quiet SFX patch
   Default: silent. No scroll/tap/select/card sounds.
   The old global pointerdown/change listeners were removed because they made normal browsing noisy,
   especially on mobile and when switching GameCube/Xbox / Blue Burst modes. */
const SFX={
  enabled:false,
  files:{
    confirm:'assets/sfx/03levelup.ogg',
    jungle:'assets/sfx/jungle_a_lush_load.mp3'
  },
  pool:{},
  last:0
};
function sfxAudio(name){
  if(!SFX.pool[name]){
    const a=new Audio(SFX.files[name]||SFX.files.confirm);
    a.preload='none';
    if(name==='jungle'){
      a.loop=true;
      a.volume=0.18;
    }else{
      a.volume=0.22;
    }
    SFX.pool[name]=a;
  }
  return SFX.pool[name];
}
function playSfx(name='confirm'){
  if(!SFX.enabled) return;
  try{
    const a=sfxAudio(name);
    if(name==='jungle'){
      if(a.paused){
        const p=a.play();
        if(p&&p.catch) p.catch(()=>{});
      }
      return;
    }
    const now=Date.now();
    if(now-SFX.last<700) return;
    SFX.last=now;
    a.pause();
    a.currentTime=0;
    const p=a.play();
    if(p&&p.catch) p.catch(()=>{});
  }catch(e){}
}
function stopSfx(name='jungle'){
  try{
    const a=sfxAudio(name);
    a.pause();
  }catch(e){}
}
function initSfx(){
  const toggle=document.getElementById('sfxToggle');
  if(toggle){
    toggle.classList.toggle('on',false);
    toggle.textContent='SFX OFF';
    toggle.setAttribute('aria-label','Sound effects are off');
    toggle.onclick=(ev)=>{
      ev.stopPropagation();
      SFX.enabled=!SFX.enabled;
      toggle.classList.toggle('on',SFX.enabled);
      toggle.textContent=SFX.enabled?'SFX ON':'SFX OFF';
      toggle.setAttribute('aria-label',SFX.enabled?'Sound effects are on':'Sound effects are off');
      if(SFX.enabled){
        playSfx('jungle');
      }else{
        stopSfx('jungle');
      }
    };
  }
}
initSfx();

Promise.all([
  fetch('data/pso-drops-full.json').then(r=>r.json()),
  fetch('data/pso-blueburst-drops-ultimate.json').then(r=>r.json())
]).then(([gc,bb])=>{
  GC_DATA=gc; BB_DATA=bb;
  $('modeGc').onclick=()=>setMode('gc');
  $('modeBb').onclick=()=>setMode('bb');
  setMode('bb');
});



/* PSODrops v4.1 Community Strategy Edition — additive override */
const COMMUNITY_V41={"Redria": {"communityTier": "S+", "identity": "The Account Stabilizer", "signatureHunts": ["Spread Needle", "Lavis Cannon", "Red Ring"], "utility": ["God/Battle", "God/Arm", "Cannon Rouge", "V502"], "recommendedFor": ["First Account", "Hunter", "Ranger"], "popularity": 100, "accountValue": 100, "communityScore": 96, "itemScores": {"Spread Needle": 100, "Lavis Cannon": 94, "Red Ring": 95, "God/Battle": 96, "God/Arm": 82, "Cannon Rouge": 86, "V502": 88}}, "Skyly": {"communityTier": "S", "identity": "The Hunter Dream", "signatureHunts": ["Sealed J-Sword", "Red Sword", "Chain Sawd"], "utility": ["Smartlink", "Twin Blaze", "Guren", "Shouren"], "recommendedFor": ["HUcast", "HUnewearl", "Melee Accounts"], "popularity": 96, "accountValue": 92, "communityScore": 89, "itemScores": {"Sealed J-Sword": 98, "Red Sword": 93, "Chain Sawd": 90, "Smartlink": 84, "Twin Blaze": 83, "Guren": 70, "Shouren": 70}}, "Purplenum": {"communityTier": "S", "identity": "The Ranger Kingdom", "signatureHunts": ["Spread Needle", "Psycho Wand", "Heaven Striker"], "utility": ["Frozen Shooter", "V502", "V801", "Heaven Punisher"], "recommendedFor": ["Ranger", "Force", "Second Account"], "popularity": 98, "accountValue": 96, "communityScore": 94, "itemScores": {"Spread Needle": 100, "Psycho Wand": 98, "Heaven Striker": 94, "Frozen Shooter": 95, "V502": 88, "V801": 87, "Heaven Punisher": 74}}, "Pinkal": {"communityTier": "S", "identity": "The Force Library", "signatureHunts": ["Psycho Wand", "Clio", "Lame d'Argent", "Slicer of Fanatic"], "utility": ["V801", "Rambling May", "Diska of Braveman"], "recommendedFor": ["Force", "FOnewearl", "FOmar"], "popularity": 93, "accountValue": 90, "communityScore": 87, "itemScores": {"Psycho Wand": 98, "Clio": 73, "Lame d'Argent": 86, "Slicer of Fanatic": 79, "V801": 87, "Rambling May": 70, "Diska of Braveman": 78}}, "Viridia": {"communityTier": "S", "identity": "The Utility Generalist", "signatureHunts": ["Red Ring", "Frozen Shooter", "Lame d'Argent", "V101"], "utility": ["Limiter", "V101", "High Hit Yunchang", "PGF"], "recommendedFor": ["First Account", "Utility Account", "Ranger"], "popularity": 95, "accountValue": 95, "communityScore": 89, "itemScores": {"Red Ring": 95, "Frozen Shooter": 95, "Lame d'Argent": 86, "V101": 82, "Limiter": 80, "High Hit Yunchang": 70, "PGF": 70}}, "Whitill": {"communityTier": "A", "identity": "The Completionist", "signatureHunts": ["Handgun: Guld", "Handgun: Milla", "Red Ring", "Frozen Shooter"], "utility": ["V502", "S-Parts ver2.01", "Limiter"], "recommendedFor": ["Completionist", "Hunter", "Force"], "popularity": 86, "accountValue": 85, "communityScore": 84, "itemScores": {"Handgun: Guld": 78, "Handgun: Milla": 78, "Red Ring": 95, "Frozen Shooter": 95, "V502": 88, "S-Parts ver2.01": 70, "Limiter": 80}}, "Oran": {"communityTier": "A", "identity": "The Balanced Farmer", "signatureHunts": ["Slicer of Fanatic", "Syncesta", "Prophets of Motav", "Psycho Wand"], "utility": ["Red Sword", "V101", "S-Parts ver2.01"], "recommendedFor": ["Balanced Accounts", "Force Support", "Third Account"], "popularity": 84, "accountValue": 86, "communityScore": 84, "itemScores": {"Slicer of Fanatic": 79, "Syncesta": 77, "Prophets of Motav": 76, "Psycho Wand": 98, "Red Sword": 93, "V101": 82, "S-Parts ver2.01": 70}}, "Yellowboze": {"communityTier": "A", "identity": "The High-Hit Hunter", "signatureHunts": ["Zanba", "Flowen's Sword 3084", "Cannon Rouge", "Galatine"], "utility": ["Frozen Shooter", "Spread Needle", "V101"], "recommendedFor": ["High-Hit Hunters", "Second Account", "Treasure Farmers"], "popularity": 80, "accountValue": 82, "communityScore": 84, "itemScores": {"Zanba": 75, "Flowen's Sword 3084": 75, "Cannon Rouge": 86, "Galatine": 70, "Frozen Shooter": 95, "Spread Needle": 100, "V101": 82}}, "Bluefull": {"communityTier": "A", "identity": "The Heaven/Psycho Specialist", "signatureHunts": ["Psycho Wand", "Heaven Striker", "Heaven Punisher"], "utility": ["Limiter", "V502", "V801", "Vjaya"], "recommendedFor": ["Force", "Ranger", "Specialist Account"], "popularity": 82, "accountValue": 84, "communityScore": 83, "itemScores": {"Psycho Wand": 98, "Heaven Striker": 94, "Heaven Punisher": 74, "Limiter": 80, "V502": 88, "V801": 87, "Vjaya": 70}}, "Greenill": {"communityTier": "D", "identity": "The Rifle Specialist", "signatureHunts": ["Spread Needle", "Lame d'Argent", "Holy Ray", "Rianov 303SNR-5"], "utility": ["Red Ring", "V101", "V502"], "recommendedFor": ["Rifle Collectors", "Challenge Accounts", "Niche Ranger"], "popularity": 42, "accountValue": 40, "communityScore": 59, "itemScores": {"Spread Needle": 100, "Lame d'Argent": 86, "Holy Ray": 62, "Rianov 303SNR-5": 60, "Red Ring": 95, "V101": 82, "V502": 88}}};
const COMMUNITY_ITEM_SCORE={"Spread Needle": 100, "Psycho Wand": 98, "Sealed J-Sword": 98, "Sealed J-Sword (SJS)": 98, "God/Battle": 96, "Frozen Shooter": 95, "Red Ring": 95, "Lavis Cannon": 94, "Heaven Striker": 94, "Red Sword": 93, "Chain Sawd": 90, "V502": 88, "V801": 87, "Cannon Rouge": 86, "Lame d'Argent": 86, "Smartlink": 84, "Twin Blaze": 83, "God/Arm": 82, "V101": 82, "Limiter": 80, "Slicer of Fanatic": 79, "Diska of Braveman": 78, "Handgun: Guld": 78, "Handgun: Milla": 78, "Syncesta": 77, "Prophets of Motav": 76, "Zanba": 75, "Flowen's Sword 3084": 75, "Heaven Punisher": 74, "Clio": 73, "Holy Ray": 62, "Rianov 303SNR-5": 60};
let STRATEGY_MODE='community';
function setStrategyMode(mode){STRATEGY_MODE=mode==='database'?'database':'community'; render();}
function strategyToggleHtml(){return `<div class="strategyModeToggle"><button class="${STRATEGY_MODE==='database'?'active':''}" onclick="setStrategyMode('database')">Database</button><button class="${STRATEGY_MODE==='community'?'active':''}" onclick="setStrategyMode('community')">Community</button></div>`;}
function communityDataForSid(sid){return COMMUNITY_V41[sid]||{communityTier:'?',identity:'Section Identity',signatureHunts:[],utility:[],recommendedFor:[],communityScore:0,itemScores:{}};}
function communityItemScore(item){return COMMUNITY_ITEM_SCORE[item]||COMMUNITY_ITEM_SCORE[String(item).replace(' (SJS)','')]||70;}
function communityItemTag(item,cls='gold'){return `<span class="identityTag ${cls}">${esc(item)} <small>${communityItemScore(item)}</small></span>`;}
function identityCardHtml(sid){const d=communityDataForSid(sid);return `<section class="identityCard"><div class="identityTop"><div class="identityName"><img src="${sidLogo(sid)}" alt="${esc(sid)}"><div><b>${esc(sid)}</b><div class="identityRole">${esc(d.identity)}</div></div></div><div style="display:flex;gap:8px;flex-wrap:wrap"><span class="tierPill">Community Tier: ${esc(d.communityTier)}</span><span class="scorePill">Community Score: ${d.communityScore}</span></div></div><div class="identityColumns"><div class="identityBlock signature"><h4>Signature Hunts</h4><div class="identityList">${(d.signatureHunts||[]).map(x=>communityItemTag(x,'gold')).join('')}</div></div><div class="identityBlock utility"><h4>Utility</h4><div class="identityList">${(d.utility||[]).map(x=>communityItemTag(x,'blue')).join('')}</div></div><div class="identityBlock recommend"><h4>Recommended For</h4><div class="identityList">${(d.recommendedFor||[]).map(x=>`<span class="identityTag green">${esc(x)}</span>`).join('')}</div></div></div></section>`;}
function routeForCommunityItem(item,sid){return routeForItem(item,[sid])||routeForItem(item,[]);}
function communityScoreCards(sid){const d=communityDataForSid(sid); const rows=[...(d.signatureHunts||[]),...(d.utility||[])].map(item=>{const route=routeForCommunityItem(item,sid);return {item,score:communityItemScore(item),route};}).sort((a,b)=>b.score-a.score);return `<div class="communityScoreGrid">${rows.map(x=>`<div class="scoreCard"><b>${esc(x.item)}</b><small>${x.route?routeSummary(x.route):'Community priority item; no route found in this filtered database.'}</small><div class="scoreMeter"><i style="width:${Math.max(4,Math.min(100,x.score))}%"></i></div>${x.route?`<button class="routeBtn" onclick="openRoute('${esc(x.item).replace(/&#39;/g,"\\'")}','${esc(x.route.sectionId||sid)}')">Open route</button>`:''}</div>`).join('')}</div>`;}
function communityCoverageFor(sids){const keyItems=[...new Set(Object.values(COMMUNITY_V41).flatMap(d=>[...(d.signatureHunts||[]),...(d.utility||[])]))]; const covered=keyItems.filter(it=>itemCoveredByRoster(it,sids)); return {total:keyItems.length,covered:covered.length,pct:Math.round(covered.length/keyItems.length*100),coveredItems:covered,missing:keyItems.filter(x=>!covered.includes(x))};}
function bestCommunityComplements(baseSid,count){const chosen=[baseSid]; while(chosen.length<count){let best=null; SECTION_IDS.filter(s=>!chosen.includes(s)).forEach(s=>{const cov=communityCoverageFor([...chosen,s]); const score=cov.pct+(communityDataForSid(s).communityScore||0)/4; if(!best||score>best.score) best={sid:s,cov,score};}); if(!best) break; chosen.push(best.sid);} return chosen;}
function accountRouteCards(baseSid){const one=[baseSid]; const two=bestCommunityComplements(baseSid,2); const three=bestCommunityComplements(baseSid,3); const make=(label,sids)=>{const cov=communityCoverageFor(sids); const unlock=cov.coveredItems.slice(0,7); const missing=cov.missing.slice(0,5); return `<div class="accountRouteCard"><b>${esc(sids.join(' + '))}</b><div class="bigPct">${cov.pct}%</div><small>${label} · community key coverage</small><div class="splitList">${unlock.map(x=>`<span class="unlockText">Unlocks: ${esc(x)}</span>`).join('')}${missing.length?`<span class="missingText">Missing: ${missing.map(esc).join(' · ')}</span>`:''}</div></div>`;}; return `<div class="accountRouteGrid">${make('1 Character',one)}${make('2 Characters',two)}${make('3 Characters',three)}</div>`;}
const renderStrategy_database_v41=renderStrategy;
renderStrategy=function(allRows){ if(STRATEGY_MODE==='database') return `<div class="strategyWrap"><section class="strategyHero"><div class="strategyTitle">Database Strategy Layer</div><div class="strategyText"><b>Factual drop mode.</b> Shows raw routes, exclusive endpoints, rarity, and database-derived complements.</div>${strategyToggleHtml()}</section>${renderStrategy_database_v41(allRows).replace('<div class="strategyWrap">','').replace('</div>','')}</div>`; const sid=$('section').value||'Redria'; return `<div class="strategyWrap"><section class="strategyHero"><div class="strategyTitle">WHY PLAYERS CREATE THIS ID</div><div class="strategyText"><b>Community Strategy v4.1.</b> This layer answers the player question first: identity, account role, signature hunts, utility, and roster value. Rarity still matters, but it no longer dominates the strategy.</div>${strategyToggleHtml()}</section>${identityCardHtml(sid)}<section class="strategyCard hot"><div class="strategySub">Community Score</div><div class="strategyText">Weighted as 40% popularity, 30% utility, 20% account value, and 10% rarity. This favors useful IDs players actually build accounts around.</div>${communityScoreCards(sid)}</section><section class="strategyCard"><div class="strategySub">Account Planner V4</div>${accountRouteCards(sid)}</section><section class="strategyCard"><div class="strategySub">Database support still available</div><div class="strategyText">Switch to Database mode anytime for factual route evidence, drop rates, exclusive endpoints, and raw item coverage. v4.1 adds community wisdom without removing the underlying drop engine.</div></section></div>`; };
const renderPlanner_database_v41=renderPlanner;
renderPlanner=function(){ if(STRATEGY_MODE==='database') return renderPlanner_database_v41(); const sids=activePlannerSids(); const main=PLANNER.chars[0]; const activeSid=sids[0]||$('section').value||main.sid||'Redria'; const cov=communityCoverageFor(sids.length?sids:[activeSid]); return `<div class="plannerWrap"><section class="plannerHero"><div class="plannerTitle">Account Planner V4</div><div class="plannerText">Plan by community role first, then route into the database when you want exact enemies and rates. Choose 1, 2, or 3 characters and the planner shows what the account identity unlocks.</div>${strategyToggleHtml()}</section><div class="plannerCommunityIntro">${identityCardHtml(activeSid)}</div><div class="plannerGrid"><section class="plannerCard hot"><div class="plannerSub">1. Account roster</div><div class="charCountToggle">${[1,2,3].map(n=>`<button type="button" class="${PLANNER.activeCount===n?'active':''}" onclick="setPlannerActiveCount(${n})">${n} character${n>1?'s':''}</button>`).join('')}</div><div class="slotGrid">${PLANNER.chars.map((c,i)=>`<div class="charSlot ${i>=PLANNER.activeCount?'inactive':''}"><span class="charNum">${i+1}</span><select onchange="setPlannerChar(${i},'cls',this.value)">${classOptions(c.cls)}</select><select onchange="setPlannerChar(${i},'sid',this.value)">${sidOptions(c.sid)}</select></div>`).join('')}</div></section><section class="plannerCard"><div class="plannerSub">Community coverage</div><div class="coverageBox"><div class="coverageLine"><b>Key Items</b><div class="barTrack"><div class="barFill" style="width:${cov.pct}%"></div></div><span>${cov.pct}%</span></div>${coverageLines(sids)}</div><div class="plannerNote">Community key coverage uses signature + utility items across all Section IDs.</div></section></div><section class="plannerCard hot"><div class="plannerSub">1 / 2 / 3 Character Account Routes</div>${accountRouteCards(activeSid)}</section><section class="plannerCard"><div class="plannerSub">Recommended missing targets</div><div class="pillList">${cov.missing.slice(0,14).map(x=>`<span class="miniPill miss">${esc(x)}</span>`).join('')||'<span class="miniPill good">No major community misses.</span>'}</div></section><section class="plannerCard"><div class="plannerSub">Exact route engine</div>${renderPlanner_database_v41().match(/<section class="plannerCard hot"><div class="plannerSub">4\. Missing pieces -> enemy cards<\/div>[\s\S]*?<\/section>/)?.[0]||'<div class="emptyStrategy">Use Database mode for full enemy-card routing.</div>'}</section></div>`; };


/* PSODrops v4.2 Community Planner Edition — robust additive layer */
const COMMUNITY_DATA_URL_V42='data/strategy/community-section-id-v4.1.json';
let COMMUNITY_EXTERNAL_V42=null;
let PLANNER_MODE='community';
const itemAliasMap={
  'sjs':['Sealed J-Sword','Sealed J-Sword (SJS)'],
  'sealed j-sword':['Sealed J-Sword','Sealed J-Sword (SJS)','SJS'],
  'sealed j-sword (sjs)':['Sealed J-Sword','Sealed J-Sword (SJS)','SJS'],
  'limitter':['Limiter'],
  'limiter':['Limiter','Limitter'],
  'god/battle armor':['God/Battle','God/Battle Armor'],
  'god/battle':['God/Battle','God/Battle Armor'],
  'god arm / battle armor':['God/Arm','God/Battle','God/Battle Armor'],
  'handgun guld/milla':['Handgun: Guld','Handgun: Milla','Handgun Guld/Milla'],
  'handgun: guld/milla':['Handgun: Guld','Handgun: Milla','Handgun Guld/Milla'],
  'flowen’s sword 3084':["Flowen's Sword 3084","Flowen’s Sword 3084","Flowen's Sword (3084)"],
  "flowen's sword 3084":["Flowen's Sword 3084","Flowen’s Sword 3084","Flowen's Sword (3084)"],
  'v802/vjaya':['Vjaya','V802','V801','V502'],
  'twin blaze / guren / shouren':['Twin Blaze','Guren','Shouren'],
  'pgf (poison gas field)':['PGF','Poison Gas Field'],
  'high hit yunchang':['Yunchang','High Hit Yunchang'],
  'handgun: guld':['Handgun: Guld','Guld'],
  'handgun: milla':['Handgun: Milla','Milla'],
  's-parts ver2.01':['S-Parts ver2.01','S-Parts ver2.01']
};
function communityRootV42(){return (COMMUNITY_EXTERNAL_V42&&COMMUNITY_EXTERNAL_V42.sectionIds)?COMMUNITY_EXTERNAL_V42.sectionIds:COMMUNITY_V41;}
function normalizeItemNameV42(item){return String(item||'').trim().replace(/[’]/g,"'").replace(/\s+/g,' ');}
function aliasesForItem(item){const raw=String(item||'').trim(); const key=normalizeItemNameV42(raw).toLowerCase(); const arr=itemAliasMap[key]||[]; return [...new Set([raw,normalizeItemNameV42(raw),...arr].filter(Boolean))];}
function routeForCommunityItem(item,sid){for(const alias of aliasesForItem(item)){const route=routeForItem(alias,[sid])||routeForItem(alias,[]); if(route) return route;} return null;}
function itemCoveredByRoster(item,sids){for(const alias of aliasesForItem(item)){const idx=itemIndex()[alias]; if(idx&&Array.isArray(idx.sectionIds)&&idx.sectionIds.some(s=>sids.includes(s))) return true; if(DATA.some(r=>r.item===alias && sids.includes(r.sectionId))) return true;} return false;}
function communityDataForSid(sid){const root=communityRootV42();return root[sid]||{communityTier:'?',identity:'Section Identity',signatureHunts:[],utility:[],recommendedFor:[],communityScore:0,itemScores:{},bestFor:[],starterValue:0,secondAccountValue:0};}
function allCommunityItemsV42(){return [...new Set(Object.values(communityRootV42()).flatMap(d=>[...(d.signatureHunts||[]),...(d.utility||[])]))];}
function communityItemScore(item){const root=communityRootV42(); for(const sid of Object.keys(root)){const scores=root[sid].itemScores||{}; for(const alias of aliasesForItem(item)){if(scores[alias]!=null) return scores[alias];}} for(const alias of aliasesForItem(item)){if(COMMUNITY_ITEM_SCORE[alias]!=null) return COMMUNITY_ITEM_SCORE[alias];} return 70;}
function scoreBreakdownV42(sid,item){const d=communityDataForSid(sid); const popularity=Number((d.itemScores&&d.itemScores[item])||communityItemScore(item)||d.popularityScore||d.popularity||70); const utility=Number(d.utilityScore||70); const accountValue=Number(d.accountValueScore||d.accountValue||70); const route=routeForCommunityItem(item,sid); const rarity=route&&route.expectedKills?Math.max(55,Math.min(100,Math.round(100-Math.log10(route.expectedKills)*8))):Number(d.rarityScore||70); const score=Math.round(popularity*.4+utility*.3+accountValue*.2+rarity*.1); return {popularity,utility,accountValue,rarity,score};}
function scoreBreakdownHtmlV42(b){return `<div class="scoreBreakdown"><span>Popularity <b>${b.popularity}</b></span><span>Utility <b>${b.utility}</b></span><span>Account <b>${b.accountValue}</b></span><span>Rarity <b>${b.rarity}</b></span></div>`;}
function strategyModeToggleHtml(){return `<div class="strategyModeToggle"><button class="${STRATEGY_MODE==='database'?'active':''}" onclick="setStrategyMode('database')">Database</button><button class="${STRATEGY_MODE==='community'?'active':''}" onclick="setStrategyMode('community')">Community</button></div>`;}
function plannerModeToggleHtml(){return `<div class="strategyModeToggle"><button class="${PLANNER_MODE==='database'?'active':''}" onclick="setPlannerMode('database')">Database</button><button class="${PLANNER_MODE==='community'?'active':''}" onclick="setPlannerMode('community')">Community</button></div>`;}
function setStrategyMode(mode){STRATEGY_MODE=mode==='database'?'database':'community'; updatePlannerUrlStateV42(); render();}
function setPlannerMode(mode){PLANNER_MODE=mode==='database'?'database':'community'; updatePlannerUrlStateV42(); render();}
function identityCardHtml(sid){const d=communityDataForSid(sid);const score=Number(d.communityScoreCalculated||d.communityScore||0);return `<section class="identityCard"><div class="identityTop"><div class="identityName"><img src="${sidLogo(sid)}" alt="${esc(sid)}"><div><b>${esc(sid)}</b><div class="identityRole">${esc(d.identity)}</div></div></div><div style="display:flex;gap:8px;flex-wrap:wrap"><span class="tierPill">Community Tier: ${esc(d.communityTier)}</span><span class="scorePill">Community Score: ${score}</span></div></div><div class="identityColumns"><div class="identityBlock signature"><h4>Signature Hunts</h4><div class="identityList">${(d.signatureHunts||[]).map(x=>communityItemTag(x,'gold')).join('')}</div></div><div class="identityBlock utility"><h4>Utility</h4><div class="identityList">${(d.utility||[]).map(x=>communityItemTag(x,'blue')).join('')}</div></div><div class="identityBlock recommend"><h4>Recommended For</h4><div class="identityList">${(d.recommendedFor||d.bestFor||[]).map(x=>`<span class="identityTag green">${esc(x)}</span>`).join('')}</div></div></div></section>`;}
function communityScoreCards(sid){const d=communityDataForSid(sid); const rows=[...(d.signatureHunts||[]),...(d.utility||[])].map(item=>{const route=routeForCommunityItem(item,sid); const bd=scoreBreakdownV42(sid,item); return {item,score:bd.score,bd,route};}).sort((a,b)=>b.score-a.score);return `<div class="communityScoreGrid">${rows.map(x=>`<div class="scoreCard"><b>${esc(x.item)}</b><small>${x.route?routeSummary(x.route):'Community priority item; no exact route found in this database slice.'}</small><div class="scoreMeter"><i style="width:${Math.max(4,Math.min(100,x.score))}%"></i></div>${scoreBreakdownHtmlV42(x.bd)}${x.route?`<button class="routeBtn" onclick="openRoute('${esc(x.item).replace(/&#39;/g,"\\'")}','${esc(x.route.sectionId||sid)}')">Open route</button>`:''}</div>`).join('')}</div>`;}
function communityCoverageFor(sids){const keyItems=allCommunityItemsV42(); const covered=keyItems.filter(it=>itemCoveredByRoster(it,sids)); const total=Math.max(1,keyItems.length); return {total:keyItems.length,covered:covered.length,pct:Math.round(covered.length/total*100),coveredItems:covered,missing:keyItems.filter(x=>!covered.includes(x))};}
function comboWhyV42(sids){const text=sids.map(s=>`${s}: ${communityDataForSid(s).identity}`).join(' + '); if(sids.length===1) return `Builds around ${text}.`; if(sids.length===2) return `Adds a second identity layer: ${text}.`; return `Completes the account triangle: ${text}.`;}
function classFantasyV42(sids){const tags=[...new Set(sids.flatMap(s=>(communityDataForSid(s).recommendedFor||communityDataForSid(s).bestFor||[])))]; return tags.slice(0,5).join(' · ')||'Flexible PSO roster';}
function bestCommunityComplements(baseSid,count){const suggested=(communityDataForSid(baseSid).suggestedCombos||[]).find(x=>x.length===count); if(suggested&&suggested.every(s=>SECTION_IDS.includes(s))) return suggested; const chosen=[baseSid]; while(chosen.length<count){let best=null; SECTION_IDS.filter(s=>!chosen.includes(s)).forEach(s=>{const cov=communityCoverageFor([...chosen,s]); const score=cov.pct+(communityDataForSid(s).communityScore||0)/4+(communityDataForSid(s).secondAccountValue||0)/8; if(!best||score>best.score) best={sid:s,cov,score};}); if(!best) break; chosen.push(best.sid);} return chosen;}
function accountRouteCards(baseSid){const routes=[bestCommunityComplements(baseSid,1),bestCommunityComplements(baseSid,2),bestCommunityComplements(baseSid,3)]; const labels=['1 Character','2 Characters','3 Characters']; const make=(label,sids)=>{const cov=communityCoverageFor(sids); const unlock=cov.coveredItems.slice(0,8); const missing=cov.missing.slice(0,6); return `<div class="accountRouteCard"><b>${esc(sids.join(' + '))}</b><div class="bigPct">${cov.pct}%</div><small>${label} · community key coverage</small><div class="routeWhy"><b>Why:</b> ${esc(comboWhyV42(sids))}</div><div class="routeWhy"><b>Fantasy:</b> ${esc(classFantasyV42(sids))}</div><div class="splitList">${unlock.map(x=>`<span class="unlockText">Unlocks: ${esc(x)}</span>`).join('')}${missing.length?`<span class="missingText">Missing: ${missing.map(esc).join(' · ')}</span>`:''}</div></div>`;}; return `<div class="accountRouteGrid">${routes.map((r,i)=>make(labels[i],r)).join('')}</div>`;}
function communityComparisonTableV42(currentSid){const rows=SECTION_IDS.map(s=>{const d=communityDataForSid(s);return `<tr><td>${sidBadge(s)}</td><td><span class="tierPill">${esc(d.communityTier)}</span></td><td>${esc(d.identity)}</td><td>${(d.bestFor||d.recommendedFor||[]).slice(0,3).map(x=>`<span class="identityTag green">${esc(x)}</span>`).join(' ')}</td><td>${(d.signatureHunts||[]).slice(0,4).map(x=>`<span class="identityTag gold">${esc(x)}</span>`).join(' ')}</td><td>${(d.utility||[]).slice(0,4).map(x=>`<span class="identityTag blue">${esc(x)}</span>`).join(' ')}</td><td><b style="color:var(--gold)">${esc(d.communityScoreCalculated||d.communityScore||0)}</b></td><td>${esc(d.starterValue||0)}</td><td>${esc(d.secondAccountValue||0)}</td><td><button onclick="setCommunityMainV42('${s}')">Set as Main</button> <button onclick="setCommunityMainV42('${s}');setStrategyMode('community')">Compare</button> <button onclick="addPlannerSidV42('${s}')">Add to Slot</button></td></tr>`;}).join(''); return `<div class="compareTable"><table><thead><tr><th>Section ID</th><th>Tier</th><th>Identity</th><th>Best For</th><th>Signature Hunts</th><th>Utility</th><th>Score</th><th>Starter</th><th>Second</th><th>Actions</th></tr></thead><tbody>${rows}</tbody></table></div>`;}
function setCommunityMainV42(sid){const el=$('section'); if(el) el.value=sid; if(PLANNER&&PLANNER.chars&&PLANNER.chars[0]) PLANNER.chars[0].sid=sid; updatePlannerUrlStateV42(); render();}
function addPlannerSidV42(sid){let idx=PLANNER.chars.findIndex((c,i)=>i<PLANNER.activeCount&&!c.sid); if(idx<0){idx=Math.min(PLANNER.activeCount,2); PLANNER.activeCount=Math.max(1,Math.min(3,idx+1));} PLANNER.chars[idx].sid=sid; updatePlannerUrlStateV42(); render();}
function renderStrategyDatabase(allRows){return renderStrategy_database_v41(allRows);}
function renderStrategyCommunity(allRows){const sid=$('section').value||'Redria';return `<div class="strategyWrap"><section class="strategyHero"><div class="strategyTitle">WHY PLAYERS CREATE THIS ID</div><div class="strategyText"><b>Community Strategy v4.2.</b> Identity, account role, signature hunts, utility, and planner value now sit above raw rarity. Exact database routes remain one tap away.</div>${strategyModeToggleHtml()}</section>${identityCardHtml(sid)}<section class="strategyCard hot"><div class="strategySub">Community Score Formula</div><div class="strategyText">Score = 40% popularity + 30% utility + 20% account value + 10% rarity.</div>${communityScoreCards(sid)}</section><section class="strategyCard"><div class="strategySub">Account Planner V4.2</div>${accountRouteCards(sid)}</section><section class="strategyCard"><div class="strategySub">Community Comparison</div>${communityComparisonTableV42(sid)}</section></div>`;}
function renderStrategy(allRows){return STRATEGY_MODE==='database'?`<div class="strategyWrap"><section class="strategyHero"><div class="strategyTitle">Database Strategy Layer</div><div class="strategyText"><b>Factual drop mode.</b> Raw routes, exclusive endpoints, rarity, and database-derived complements.</div>${strategyModeToggleHtml()}</section>${renderStrategyDatabase(allRows)}</div>`:renderStrategyCommunity(allRows);}
function renderPlannerDatabase(){return renderPlanner_database_v41();}
function renderPlannerCommunity(){const sids=activePlannerSids(); const activeSid=sids[0]||$('section').value||'Redria'; const cov=communityCoverageFor(sids.length?sids:[activeSid]); const archetypes=['First Account Recommendation','Hunter Main Account','Ranger Main Account','Force Main Account','Second Character Recommendation','Third Character Completion Route']; return `<div class="plannerWrap"><section class="plannerHero"><div class="plannerTitle">Account Planner V4.2</div><div class="plannerText">Plan by account identity first, then open exact enemy/drop routes when needed. Strategy and Planner now have separate Database/Community modes.</div>${plannerModeToggleHtml()}</section><div class="plannerCommunityIntro">${identityCardHtml(activeSid)}</div><div class="plannerGrid"><section class="plannerCard hot"><div class="plannerSub">1. Account roster</div><div class="charCountToggle">${[1,2,3].map(n=>`<button type="button" class="${PLANNER.activeCount===n?'active':''}" onclick="setPlannerActiveCount(${n})">${n} character${n>1?'s':''}</button>`).join('')}</div><div class="slotGrid">${PLANNER.chars.map((c,i)=>`<div class="charSlot ${i>=PLANNER.activeCount?'inactive':''}"><span class="charNum">${i+1}</span><select onchange="setPlannerChar(${i},'cls',this.value)">${classOptions(c.cls)}</select><select onchange="setPlannerChar(${i},'sid',this.value)">${sidOptions(c.sid)}</select></div>`).join('')}</div></section><section class="plannerCard"><div class="plannerSub">Community coverage</div><div class="coverageBox"><div class="coverageLine"><b>Key Items</b><div class="barTrack"><div class="barFill" style="width:${cov.pct}%"></div></div><span>${cov.pct}%</span></div>${coverageLines(sids)}</div><div class="plannerNote">Coverage uses alias-normalized signature + utility items across all community IDs.</div></section></div><section class="plannerCard"><div class="plannerSub">Account Archetypes</div><div class="pillList">${archetypes.map(a=>`<span class="miniPill good">${esc(a)}</span>`).join('')}</div></section><section class="plannerCard hot"><div class="plannerSub">1 / 2 / 3 Character Account Routes</div>${accountRouteCards(activeSid)}</section><section class="plannerCard"><div class="plannerSub">Recommended missing targets</div><div class="pillList">${cov.missing.slice(0,16).map(x=>`<span class="miniPill miss">${esc(x)}</span>`).join('')||'<span class="miniPill good">No major community misses.</span>'}</div></section><section class="plannerCard"><div class="plannerSub">Community Comparison</div>${communityComparisonTableV42(activeSid)}</section></div>`;}
function renderPlanner(){return PLANNER_MODE==='database'?renderPlannerDatabase():renderPlannerCommunity();}
const setPlannerActiveCount_v42=setPlannerActiveCount;
setPlannerActiveCount=function(n){PLANNER.activeCount=Math.max(1,Math.min(3,Number(n)||1)); updatePlannerUrlStateV42(); render();};
const setPlannerChar_v42=setPlannerChar;
setPlannerChar=function(i,k,v){PLANNER.chars[i][k]=v; const sid=PLANNER.chars[0].sid; if(sid&&$('section')) $('section').value=sid; updatePlannerUrlStateV42(); render();};
function updatePlannerUrlStateV42(){try{const url=new URL(location.href); const sids=activePlannerSids(); url.searchParams.set('planner',sids.join(',')); url.searchParams.set('count',String(PLANNER.activeCount||1)); url.searchParams.set('strategyMode',STRATEGY_MODE); url.searchParams.set('plannerMode',PLANNER_MODE); history.replaceState(null,'',url.toString());}catch(e){}}
function applyPlannerUrlStateV42(){try{const p=new URLSearchParams(location.search); const planner=(p.get('planner')||'').split(',').map(x=>x.trim()).filter(Boolean); if(planner.length){PLANNER.activeCount=Math.max(1,Math.min(3,Number(p.get('count'))||planner.length)); planner.slice(0,3).forEach((sid,i)=>{if(SECTION_IDS.includes(sid)) PLANNER.chars[i].sid=sid;}); if(PLANNER.chars[0]&&$('section')) $('section').value=PLANNER.chars[0].sid;} const sm=p.get('strategyMode')||p.get('mode'); const pm=p.get('plannerMode')||p.get('mode'); if(sm) STRATEGY_MODE=sm==='database'?'database':'community'; if(pm) PLANNER_MODE=pm==='database'?'database':'community';}catch(e){}}
function loadCommunityDataV42(){return fetch(COMMUNITY_DATA_URL_V42).then(r=>{if(!r.ok) throw new Error('HTTP '+r.status); return r.json();}).then(j=>{if(j&&j.sectionIds){COMMUNITY_EXTERNAL_V42=j; console.info('[PSODrops v4.2] community data loaded',COMMUNITY_DATA_URL_V42); if(typeof render==='function'&&DATA&&DATA.length) render();}}).catch(err=>{console.warn('[PSODrops v4.2] community data fallback active:',err&&err.message?err.message:err);});}


/* v4.3 Search Rescue + Provenance Compare Mode
   - Search no longer dies behind active filters.
   - Autocomplete becomes alias + item + enemy aware.
   - Provenance enemy portraits show other Section ID colors for the same enemy node.
   - Route details include a compact compare panel. */
const PSO_V43_STYLE=document.createElement('style');
PSO_V43_STYLE.textContent=`
.searchAssist{margin-top:8px;display:none;gap:6px;flex-wrap:wrap}
.searchAssist.show{display:flex}
.searchAssist button{border:1px solid rgba(255,255,255,.18);background:rgba(10,25,42,.86);color:#eaf6ff;border-radius:999px;padding:7px 10px;font-size:12px;font-weight:800;box-shadow:0 0 12px rgba(0,0,0,.22)}
.searchAssist button b{color:var(--gold)}
.searchRescue{margin:14px;padding:16px;border:1px solid rgba(255,217,77,.42);border-radius:20px;background:linear-gradient(180deg,rgba(255,217,77,.12),rgba(9,20,35,.92));box-shadow:0 0 24px rgba(255,154,61,.12)}
.searchRescue h3{margin:0 0 6px;color:var(--gold);font-size:18px}.searchRescue p{margin:0 0 12px;color:#d9edff;line-height:1.45}.searchRescue .rescueActions{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px}.searchRescue button{border:1px solid rgba(255,255,255,.18);background:#102844;color:#fff;border-radius:13px;padding:9px 11px;font-weight:900}.searchRescue .primaryRescue{background:linear-gradient(180deg,var(--sid),var(--sid2));color:#fff}
.enemyChip{position:relative;overflow:hidden}.enemyChip .sidColorField{position:absolute;inset:6px 6px auto 6px;height:38px;border-radius:18px;opacity:.36;filter:blur(.2px);background:linear-gradient(90deg,var(--sidA,#75d8ff),var(--sidB,#ff9a3d));z-index:0}.enemyChip img,.enemyChip .missing,.enemyChipName,.enemyChipItem{position:relative;z-index:1}.sidDotRail{position:relative;z-index:2;display:flex;justify-content:center;gap:3px;margin:3px 0 1px;min-height:8px}.sidDot{width:7px;height:7px;border-radius:99px;background:var(--c);box-shadow:0 0 7px var(--c);border:1px solid rgba(255,255,255,.5)}
.routeCompare{margin-top:12px;border-top:1px solid rgba(255,255,255,.13);padding-top:10px}.routeCompareTitle{font-size:12px;text-transform:uppercase;letter-spacing:.09em;color:var(--gold);font-weight:900;margin-bottom:8px}.compareSidGrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px}.compareSidCard{border:1px solid rgba(255,255,255,.16);border-left:5px solid var(--c);border-radius:14px;padding:9px;background:linear-gradient(90deg,color-mix(in srgb,var(--c) 22%,transparent),rgba(7,18,32,.75))}.compareSidCard b{display:block;color:#fff}.compareSidCard small{display:block;color:#cfe7ff;line-height:1.35}.compareSidCard .rateMini{color:var(--gold);font-weight:900}.routeCompareToggle{margin-top:10px}.routeCompareToggle button{border-radius:999px;padding:8px 11px;border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.07);color:#fff;font-weight:900}
@media(max-width:640px){.compareSidGrid{grid-template-columns:1fr}.searchRescue{margin:10px;padding:13px}.enemyChip .sidColorField{height:32px}}
`;
document.head.appendChild(PSO_V43_STYLE);
function sidThemeColorV43(sid){return (SID_THEME&&SID_THEME[sid]&&SID_THEME[sid][0])||'#75d8ff';}
function initSearchAssistV43(){
  const input=$('search'); if(!input) return;
  let panel=$('searchAssistV43');
  if(!panel){panel=document.createElement('div'); panel.id='searchAssistV43'; panel.className='searchAssist'; input.insertAdjacentElement('afterend',panel);}
  const dl=$('suggestions');
  if(dl && !dl.dataset.v43){
    const aliasValues=new Set();
    Object.entries(SEARCH_ALIAS_V421||{}).forEach(([k,v])=>{aliasValues.add(k); (v||[]).forEach(x=>aliasValues.add(x));});
    [...aliasValues].sort().forEach(x=>{const o=document.createElement('option'); o.value=x; dl.appendChild(o);});
    dl.dataset.v43='1';
  }
  input.addEventListener('input',()=>updateSearchAssistV43());
  input.addEventListener('focus',()=>updateSearchAssistV43());
  document.addEventListener('click',ev=>{if(!panel.contains(ev.target)&&ev.target!==input) panel.classList.remove('show');});
}
function searchUniverseRowsV43(q){
  q=String(q||'').trim(); if(!q) return [];
  return DATA.filter(r=>isRealDrop(r)&&rowMatchesSearchV421(r,q)).sort((a,b)=>{
    const aq=normalizeSearchV421(q), ai=normalizeSearchV421(a.item), bi=normalizeSearchV421(b.item);
    const as=ai===aq?0:ai.includes(aq)?1:2, bs=bi===aq?0:bi.includes(aq)?1:2;
    return as-bs||(a.expectedKills||1e12)-(b.expectedKills||1e12)||String(a.item).localeCompare(String(b.item));
  });
}
function updateSearchAssistV43(){
  const input=$('search'), panel=$('searchAssistV43'); if(!input||!panel) return;
  const q=input.value.trim(); if(!q){panel.classList.remove('show'); panel.innerHTML=''; return;}
  const rows=searchUniverseRowsV43(q); const items=[...new Set(rows.map(r=>r.item).filter(Boolean))].slice(0,7);
  const monsters=[...new Set(rows.map(r=>r.monster).filter(Boolean))].slice(0,4);
  const picks=[...items.map(x=>['item',x]),...monsters.map(x=>['enemy',x])].slice(0,10);
  panel.innerHTML=picks.map(([kind,x])=>`<button type="button" onclick="document.getElementById('search').value='${String(x).replace(/'/g,"\\'")}';document.getElementById('searchAssistV43').classList.remove('show');render();"><b>${kind}</b> ${esc(x)}</button>`).join('');
  panel.classList.toggle('show',!!picks.length);
}
function clearRouteFiltersForSearchV43(){
  const q=$('search').value;
  $('section').value=''; $('difficulty').value=''; $('area').value=''; selectedType='All'; selectedKind='All'; selectedClass=''; rareOnly=false; hideCommon=false;
  $('rareOnly')?.classList.remove('primary'); $('hideCommon')?.classList.remove('primary'); $('search').value=q; render();
}
function rescueSearchHtmlV43(q){
  const rows=searchUniverseRowsV43(q).filter(r=>!isCommon(r));
  if(!rows.length) return `<div class="empty">No database matches for “${esc(q)}”. Try a shorter item name, enemy name, or alias like SJS / RR / PWand.</div>`;
  const itemCount=new Set(rows.map(r=>r.item)).size, enemyCount=new Set(rows.map(r=>r.monster)).size;
  const cards=rows.slice(0,48).map(card);
  return `<div class="searchRescue"><h3>Search found ${rows.length} global routes</h3><p>Your active filters hide them, so PSODrops is showing global matches for <b>${esc(q)}</b> instead of a dead zero-view state. Clear the route filters to make these the live view.</p><div class="rescueActions"><button class="primaryRescue" onclick="clearRouteFiltersForSearchV43()">Clear filters + show all matches</button><button onclick="$('difficulty').value='Ultimate';render();">Try Ultimate</button><button onclick="$('section').value='';render();">Any Section ID</button></div><div class="pillList"><span class="miniPill good">${itemCount} items</span><span class="miniPill good">${enemyCount} enemies</span><span class="miniPill good">alias-aware search</span></div></div><div class="grid">${withAdBanners(cards,'drops',8)}</div>`;
}
function nodeRowsForEnemyV43(r){
  if(!r) return [];
  return DATA.filter(x=>isRealDrop(x)&&!isCommon(x)&&x.monster===r.monster&&x.area===r.area&&x.difficulty===r.difficulty)
    .sort((a,b)=>SECTION_IDS.indexOf(a.sectionId)-SECTION_IDS.indexOf(b.sectionId)||(a.expectedKills||1e12)-(b.expectedKills||1e12));
}
function sidDotsForNodeV43(r){
  const active=$('section')?.value||'';
  const sids=[...new Set(nodeRowsForEnemyV43(r).map(x=>x.sectionId).filter(Boolean))].filter(s=>s!==active).slice(0,8);
  if(!sids.length) return '';
  return `<div class="sidDotRail" title="Other Section IDs with drops on this enemy node">${sids.map(s=>`<i class="sidDot" style="--c:${sidThemeColorV43(s)}" title="${esc(s)}"></i>`).join('')}</div>`;
}
function sidFieldStyleV43(r){
  const active=$('section')?.value||'';
  const sids=[...new Set(nodeRowsForEnemyV43(r).map(x=>x.sectionId).filter(Boolean))].filter(s=>s!==active);
  if(!sids.length) return '';
  const a=sidThemeColorV43(sids[0]), b=sidThemeColorV43(sids[1]||sids[0]);
  return `<span class="sidColorField" style="--sidA:${a};--sidB:${b}"></span>`;
}
function comparePanelForRouteV43(r){
  const rows=nodeRowsForEnemyV43(r);
  if(!rows.length) return '';
  const bestBySid=new Map();
  rows.forEach(x=>{const old=bestBySid.get(x.sectionId); if(!old||(x.expectedKills||1e12)<(old.expectedKills||1e12)) bestBySid.set(x.sectionId,x);});
  const cards=[...bestBySid.values()].map(x=>`<div class="compareSidCard" style="--c:${sidThemeColorV43(x.sectionId)}"><b>${sidBadge(x.sectionId)}</b><small>${esc(x.item)}<br><span class="rateMini">${esc(x.rate)}</span> · ${esc(areaLabel(x.area))}</small><button class="routeBtn" onclick="openRoute('${esc(x.item).replace(/&#39;/g,"\\'")}','${esc(x.sectionId)}')">Open</button></div>`).join('');
  return `<div class="routeCompare"><div class="routeCompareTitle">Compare this enemy node across Section IDs</div><div class="compareSidGrid">${cards}</div></div>`;
}
function renderTrail(rows){
  const sid=$('section').value,diff=$('difficulty').value,area=$('area').value;
  const path=['Identity',sid||'Any Section ID',diff||'Any Difficulty',area?areaLabel(area):'All Locations','Enemy Evidence'];
  const areas={}; bestRows(rows).forEach(r=>{(areas[r.area]??=[]).push(r)});
  const endpoints=topEndpoints(rows).slice(0,10);
  const endpointHtml=endpoints.map(r=>`<button class="endpointBtn" onclick="document.getElementById('search').value='${String(r.item).replace(/'/g,"\\'")}';render();">${esc(r.item)}</button>`).join('');
  const hero=(LAST_OPEN_ROUTE&&LAST_OPEN_ROUTE.route)?provenanceHero(LAST_OPEN_ROUTE.route,LAST_OPEN_ROUTE.item):'';
  return `<div class="provHeader"><div class="provTitle">${sid?sid+' ':''}Provenance Trail</div><div class="provText">A mobile hunt route from identity to evidence. Enemy portraits now glow with other Section ID colors when the same enemy node has useful alternate drops.</div></div><div class="provPath">${path.map((x,i)=>`<span class="provNode ${i===1&&sid?'hot':''}">${esc(x)}</span>${i<path.length-1?'<span class="provArrow">→</span>':''}`).join('')}</div>${hero}${endpointHtml?`<div class="endpointBar">${endpointHtml}</div>`:''}<div class="routeGrid">${Object.keys(areas).map(a=>{const bg=areaVariantImage(a,a); const list=areas[a].slice(0,18); return `<section class="routeArea"><div class="routeAreaHead" style="${bg?`background-image:linear-gradient(180deg,rgba(7,18,32,.15),rgba(7,18,32,.86)),url('${bg}')`:''}"><div><div class="routeAreaTitle">${esc(areaLabel(a))}</div><div class="routeAreaMeta">${esc(areaEpisode(a))} · ${list.length} visible enemy nodes</div></div></div><div class="enemyRail">${list.map((r,idx)=>`<button class="enemyChip ${idx===0?'active':''}" onclick="this.closest('.routeArea').querySelector('.routeDetails').innerHTML=decodeURIComponent('${encodeURIComponent(routeDropHtml(r)).replace(/'/g,'%27')}');this.parentElement.querySelectorAll('.enemyChip').forEach(b=>b.classList.remove('active'));this.classList.add('active')">${sidFieldStyleV43(r)}${r.monsterImage?`<img src="${esc(r.monsterImage)}" alt="${esc(r.monster)}">`:'<div class="missing">?</div>'}${sidDotsForNodeV43(r)}<div class="enemyChipName">${esc(r.monster)}</div><div class="enemyChipItem">${esc(r.item)}</div></button>`).join('')}</div><div class="routeDetails">${list[0]?routeDropHtml(list[0]):''}</div></section>`}).join('')}</div>`;
}
function routeDropHtml(r){
  const prob=r.probabilityAfter||{};
  const chance=chanceText(prob);
  const kills=fmtInt(r.expectedKills);
  const type=r.itemType||'Other';
  return `<div class="routeDrop expanded">${r.monsterImage?`<img src="${esc(r.monsterImage)}" alt="${esc(r.monster)}">`:''}<div class="routeDropBody"><b>${esc(r.item)}</b><small>${sidBadge(r.sectionId)} · ${esc(r.difficulty)} · ${esc(areaLabel(r.area))} · ${esc(r.monster)}</small><div class="routeDropMeta"><span>${weaponBadge(type)}</span><span class="chip gold">Rate ${esc(r.rate)}</span>${kills?`<span class="chip">≈ ${kills} kills expected</span>`:''}<span class="chip">Drop ${esc(r.percent)}%</span>${chance?`<span class="chip">${esc(chance)}</span>`:''}${r.episode?`<span class="chip">${esc(r.episode)}</span>`:''}</div>${r.submaps&&r.submaps.length?`<div class="routeDropSub">Submaps: ${r.submaps.map(esc).join(' / ')}</div>`:''}${r.rareEnemy?`<div class="routeDropSub">Rare enemy route.</div>`:''}${comparePanelForRouteV43(r)}</div></div>`;
}
function render(){
  const rows=filtered().sort((a,b)=>(a.expectedKills||1e12)-(b.expectedKills||1e12)||(a.area+a.monster+a.item).localeCompare(b.area+b.monster+b.item));
  updateStats(rows); updateSearchAssistV43();
  const out=$('output'); const q=$('search').value.trim();
  if(!rows.length){out.innerHTML=q?rescueSearchHtmlV43(q):'<div class="empty">No drops match this filter. Try another area, All weapon type, or disable common-item filter.</div>';return;}
  if(tab==='trail'){out.innerHTML=renderTrail(rows);return;}
  if(tab==='strategy'){out.innerHTML=renderStrategy(rows);return;}
  if(tab==='planner'){out.innerHTML=renderPlanner();return;}
  if(tab==='cards'){const sid=$('section').value, diff=$('difficulty').value, area=$('area').value, q=$('search').value.trim();const focused=(sid&&diff)||q;const narrowed=sid&&diff&&area;const limit=focused?rows.length:(narrowed?120:24);const hint=(!focused&&rows.length>limit)?`<div class="narrowHint"><b>${rows.length} drops found.</b> Choose Section ID + difficulty, or search an item, to show the full route pool. Showing first ${limit}.</div>`:'';out.innerHTML=hint+'<div class="grid">'+withAdBanners(rows.slice(0,limit).map(card),'drops',8)+'</div>'+(rows.length>limit?`<div class="empty">Showing ${limit} of ${rows.length}. Narrow the route for the full local list.</div>`:'');return;}
  if(tab==='table'){out.innerHTML=`<div style="overflow:auto;padding:18px"><table style="width:100%;border-collapse:collapse"><thead><tr><th>Section</th><th>Type</th><th>Difficulty</th><th>Area</th><th>Enemy</th><th>Item</th><th>Rate</th><th>%</th></tr></thead><tbody>${rows.slice(0,900).map(r=>`<tr><td>${sidBadge(r.sectionId)}</td><td>${weaponBadge(r.itemType||'Other')}</td><td>${esc(r.difficulty)}</td><td>${esc(r.area)}</td><td>${esc(r.monster)}</td><td style="color:var(--gold);font-weight:800">${esc(r.item)}</td><td>${esc(r.rate)}</td><td>${esc(r.percent)}</td></tr>`).join('')}</tbody></table></div>`;return;}
  if(tab==='items'){out.innerHTML=renderItems(rows);return;}
}
setTimeout(initSearchAssistV43,0);

applyPlannerUrlStateV42();
loadCommunityDataV42();

