// ============================================================
// HUNGRY HOLE 3D — design tokens + game data. window.HH
// All tokens verbatim from the Master Brief §09–10.
// ============================================================

// ---- Brand color tokens ----
const C = {
  sky: '#22A6F2', grape: '#7C5CFC', sun: '#FFB627', coral: '#FF6B6B', mint: '#22C9A0',
  ink: '#1B2740', slate: '#6B7689', cloud: '#F6F9FE', card: '#EEF3FB', line: '#DCE5F2',
};

// ---- 10 goal types (icon drawn in UI kit by id) ----
const GOALS = {
  sweep:     { id: 'sweep',     label: 'Eat everything', color: C.sky,   verb: 'Sweep' },
  score:     { id: 'score',     label: 'Reach the target', color: C.sun,   verb: 'Score Rush' },
  treasure:  { id: 'treasure',  label: 'Eat only the gold', color: C.sun,   verb: 'Treasure' },
  combo:     { id: 'combo',     label: 'Keep the chain alive', color: C.coral, verb: 'Combo Chain' },
  bonus:     { id: 'bonus',     label: 'Clear the hidden shape', color: C.grape, verb: 'Bonus' },
  avoid:     { id: 'avoid',     label: 'Never eat the no-eats', color: C.coral, verb: 'Avoid' },
  rival:     { id: 'rival',     label: 'Out-eat the rival hole', color: C.grape, verb: 'Rival Race' },
  precision: { id: 'precision', label: 'Careful, exact eating', color: C.mint,  verb: 'Precision' },
  mega:      { id: 'mega',      label: 'Grow, then clear it', color: C.sky,   verb: 'Mega Clear' },
  boss:      { id: 'boss',      label: 'Eat the landmark in phases', color: C.coral, verb: 'World Boss' },
};

// ---- 6 worlds (palettes derived from brief §04/§09) ----
const WORLDS = [
  { id: 1, name: 'Sunny Town',    sub: 'cheery suburb',  pair: 'grass green · sky blue',
    sky: ['#8FE3FF', '#CFF3FF'], ground: '#7FD06A', groundDark: '#5FB94E', ring: '#F4C04B',
    accent: C.sky, props: ['cube','cone','bench','house','car','tree','mailbox','hydrant','bin','bush','fence','dog','bike','lamp','flowerbed'] },
  { id: 2, name: 'Candy Park',    sub: 'sweet carnival', pair: 'bubblegum pink · cream',
    sky: ['#FFD6EC', '#FFF0E6'], ground: '#FBA8D0', groundDark: '#F47FB6', ring: '#FFE08A',
    accent: C.coral, props: ['donut','lollipop','icecream','cupcake','candycane','gummy','cookie','gumball','popcorn','cottoncandy','balloon','cake'] },
  { id: 3, name: 'Beach Bay',     sub: 'sunny coast',    pair: 'turquoise · sand gold',
    sky: ['#A8F0E6', '#FFF4D6'], ground: '#F2D89B', groundDark: '#E6C277', ring: '#37C6D0',
    accent: C.mint, props: ['umbrella','beachball','bucket','flipflop','crab','starfish','surfboard','lifering','palm','boat','coconut','shell','sandcastle'] },
  { id: 4, name: 'Frost Village', sub: 'cozy winter',    pair: 'ice blue · snow white', weather: 'snow',
    sky: ['#D6ECFF', '#F4FAFF'], ground: '#E8F2FB', groundDark: '#CADDF0', ring: '#8FC7F2',
    accent: C.sky, props: ['snowman','gift','pine','igloo','snowball','mitten','bell','sled','lantern','penguin','icecube'] },
  { id: 5, name: 'Dino Valley',   sub: 'jungle ruins',   pair: 'jungle green · amber',
    sky: ['#BFE89A', '#FBE8C0'], ground: '#6FA84A', groundDark: '#558838', ring: '#D9952E',
    accent: C.mint, props: ['egg','bone','skull','fern','boulder','mushroom','ammonite','amber','dino','vine','statue'] },
  { id: 6, name: 'Neon Port',     sub: 'night arcade-city', pair: 'violet · cyan neon',
    sky: ['#2A1B5E', '#16203F'], ground: '#241452', groundDark: '#180D38', ring: '#21E6FF',
    accent: C.grape, props: ['robot','neon','rocket','drone','arcade','satellite','console','speaker','hologram','server','crystal','capsule'], night: true },
  // ---------- worlds 7–16 ----------
  { id: 7, name: 'Mega City', sub: 'downtown metropolis', pair: 'slate · teal', weather: 'rain',
    sky: ['#9FB6D6','#E6EEF8'], ground: '#8895AC', groundDark: '#6B7689', ring: '#37C6D0',
    accent: C.sky, props: ['car','bus','house','sign','lamp','hydrant','bench','statue','bin','mailbox'] },
  { id: 8, name: 'Toy Box', sub: 'the playroom', pair: 'primary brights',
    sky: ['#FFE6A8','#FFF6E0'], ground: '#FF9EBB', groundDark: '#E87BA0', ring: '#7C5CFC',
    accent: C.grape, props: ['cube','ball','balloon','gift','robot','drone','cone','sphere','crate'] },
  { id: 9, name: 'Farm Fields', sub: 'sunny countryside', pair: 'meadow · wheat',
    sky: ['#BFE8FF','#F0FAD6'], ground: '#9AD06A', groundDark: '#7BB24E', ring: '#E0B24A',
    accent: C.mint, props: ['barrel','fence','bush','tree','dog','crate','flowerbed','sign','hydrant'] },
  { id: 10, name: 'Space Station', sub: 'low orbit', pair: 'indigo · cyan',
    sky: ['#141A3A','#0A1024'], ground: '#1E2A52', groundDark: '#121838', ring: '#21E6FF',
    accent: C.sky, props: ['satellite','drone','console','server','crystal','rocket','capsule','hologram','speaker'], night: true },
  { id: 11, name: 'Haunted Hollow', sub: 'misty graveyard', pair: 'violet · sickly green', weather: 'rain',
    sky: ['#2B1F4A','#171130'], ground: '#3A2E52', groundDark: '#241B38', ring: '#7BE03A',
    accent: C.grape, props: ['pine','rock','skull','lantern','bone','statue','mushroom','vine','fence'], night: true },
  { id: 12, name: 'Coral Reef', sub: 'sunlit shallows', pair: 'aqua · coral',
    sky: ['#7FE0E0','#CFF6F0'], ground: '#3FB6C4', groundDark: '#2A93A0', ring: '#FF8FB0',
    accent: C.mint, props: ['shell','starfish','crab','coconut','lifering','boulder','fern','bucket','surfboard'] },
  { id: 13, name: 'Desert Dunes', sub: 'lost ruins', pair: 'amber · terracotta',
    sky: ['#FFD9A0','#FFF0CF'], ground: '#E0B26A','groundDark': '#C8944E', ring: '#D9742E',
    accent: C.sun, props: ['boulder','cone','statue','barrel','ammonite','crate','palm','rock','skull'] },
  { id: 14, name: 'Sky Gardens', sub: 'floating isles', pair: 'pastel · bloom',
    sky: ['#CFE6FF','#F6ECFF'], ground: '#A8E0A0', groundDark: '#86C47E', ring: '#FF9ECB',
    accent: C.grape, props: ['tree','flowerbed','bush','balloon','lamp','bench','sign','barrel','cone'] },
  { id: 15, name: 'Volcano Forge', sub: 'molten depths', pair: 'ember · charcoal',
    sky: ['#3A1810','#1A0C08'], ground: '#4A241A', groundDark: '#2E1510', ring: '#FF7A2C',
    accent: C.coral, props: ['boulder','rock','barrel','crystal','statue','crate','cube','amber'], night: true },
  { id: 16, name: 'Aurora Peak', sub: 'arctic night', pair: 'teal · violet glow', weather: 'snow',
    sky: ['#16314F','#0C1D31'], ground: '#1B3A52', groundDark: '#102638', ring: '#7BE0C4',
    accent: C.mint, props: ['pine','snowman','igloo','icecube','crystal','penguin','lantern','snowball','rock'], night: true },
  // ---------- worlds 17–19 · extra location biomes (Round-3 expansion) ----------
  { id: 17, name: 'Lake District', sub: 'calm highland lake', pair: 'still blue · pine green',
    sky: ['#BFE2F2','#E8F4F0'], ground: '#6FB38C', groundDark: '#508E6B', ring: '#3FA8C4',
    accent: C.mint, props: ['boat','reeds','tree','bench','fountain','rock','flowerbed','bush','lamp','buoy','dock','rowboat'] },
  { id: 18, name: 'Harbor', sub: 'working dockside', pair: 'dock grey · teal',
    sky: ['#A8C2D6','#E0EAF2'], ground: '#7E8B97', groundDark: '#5F6B78', ring: '#37B6C4',
    accent: C.sky, props: ['crate','barrel','boat','buoy','dock','rowboat','lamp','bench','sign','bollard','lifering','anchor'] },
  { id: 19, name: 'Fairground', sub: 'festive carnival', pair: 'festive brights',
    sky: ['#FFD6A8','#FFF0DE'], ground: '#E89ABF','groundDark': '#CE789F', ring: '#FFC24A',
    accent: C.coral, props: ['balloon','popcorn','cottoncandy','gift','lamp','tent','flag','bench','sign','cone','ball','prize'] },
];

// ---- 60 levels: [n, name, goalId, eatNote, star3] ----
const LEVELS = [
  // World 1 · Sunny Town
  [ [1,'First Bites','sweep','boxes & cones · teaches drag/eat','<35s'],
    [2,'Cone Rush','score','cones, signs, bins','55+'],
    [3,'Golden Crates','treasure','gold crates only, skip red','all gold'],
    [4,'Combo Lane','combo','benches & bins · keep a 6-chain','×10'],
    [5,'Heart Yard','bonus','devour the heart flowerbed','full'],
    [6,'Mind the People','avoid','eat props, never pedestrians','0 people'],
    [7,'Orange Rival','rival','out-eat the rival · cars, mailboxes','+20'],
    [8,'Tiny Lawn','precision',"small yard · don't clip the fence",'fence ok'],
    [9,'Bus Muncher','mega','grow, then eat the bus in 3 bites','<40s'],
    [10,'Town Hall','boss','clear the clock tower · 2 phases','<60s'] ],
  // World 2 · Candy Park
  [ [11,'Sugar Start','sweep','gummies & lollipops · 55s','<35s'],
    [12,'Sweet Tooth','score','donuts, candy canes','80+'],
    [13,'Golden Donuts','treasure','gold donuts, skip chili candies','all gold'],
    [14,'Sprinkle Chain','combo','cupcakes · hold an 8-chain','×12'],
    [15,'Star Lolly','bonus','eat the star candy mosaic','full'],
    [16,"Don't Pop Kids",'avoid','eat sweets, never balloon kids','0 kids'],
    [17,'Cotton Rival','rival','beat rival on cotton-candy stands','+25'],
    [18,'Conveyor','precision','sweets ride a moving belt · time it','≥90%'],
    [19,'Giant Cake','mega','devour the wedding cake tier by tier','<45s'],
    [20,'Gumball Boss','boss','crack & eat the gumball machine','<60s'] ],
  // World 3 · Beach Bay
  [ [21,'Low Tide','sweep','buckets & flip-flops · 55s','<35s'],
    [22,'Sandstorm Score','score','umbrellas, chairs','95+'],
    [23,'Golden Shells','treasure','gold shells only, skip crabs','all gold'],
    [24,'Wave Combo','combo','beach balls · ride a 10-chain','×14'],
    [25,'Anchor','bonus','eat the anchor pixel mosaic','full'],
    [26,'Save the Swimmers','avoid','eat gear, never swimmers','0 swimmers'],
    [27,'Jet-Ski Rival','rival','out-eat the rival across the marina','+25'],
    [28,'Pier Planks','precision',"narrow pier · don't drop supports",'supports ok'],
    [29,'Whale Float','mega','clear the giant inflatable whale','<45s'],
    [30,'Lighthouse','boss','topple & eat the lighthouse · phases','<65s'] ],
  // World 4 · Frost Village
  [ [31,'Fresh Snow','sweep','snowballs & mittens · 50s','<32s'],
    [32,'Gift Grab','score','presents, candy canes','115+'],
    [33,'Golden Bells','treasure','gold bells only, skip coal','all gold'],
    [34,'Frost Combo','combo','ornaments · keep a 12-chain','×16'],
    [35,'Snowflake','bonus','eat the snowflake mosaic','full'],
    [36,'Mind the Carolers','avoid','eat décor, never carolers','0 carolers'],
    [37,'Sleigh Rival','rival','out-eat the rival down the slope','+30'],
    [38,'Thin Ice','precision','cracking ice tiles · keep moving','no fall-in'],
    [39,'Snow Giant','mega','devour the giant snowman','<45s'],
    [40,'Ice Castle','boss','shatter & eat the ice castle · phases','<65s'] ],
  // World 5 · Dino Valley
  [ [41,'Hatchlings','sweep','eggs & ferns · 50s','<32s'],
    [42,'Bone Score','score','bones, rocks','135+'],
    [43,'Golden Amber','treasure','amber gems only, skip beetles','all amber'],
    [44,'Stampede','combo','small dinos · hold a 14-chain','×18'],
    [45,'T-Rex Skull','bonus','eat the skull mosaic','full'],
    [46,'Spare the Babies','avoid','eat props, never baby dinos','0 babies'],
    [47,'Raptor Rival','rival','out-eat the rival through ruins','+30'],
    [48,'Lava Cracks','precision','avoid the lava seams while eating','no lava'],
    [49,'Brontosaurus','mega','clear the giant statue in chunks','<50s'],
    [50,'Volcano Boss','boss','eat the erupting temple · phases','<70s'] ],
  // World 6 · Neon Port
  [ [51,'Boot Up','sweep','bots & cones · 50s','<32s'],
    [52,'Neon Score','score','signs, drones','160+'],
    [53,'Golden Chips','treasure','gold chips only, skip mines','all gold'],
    [54,'Circuit Combo','combo','arcades · hold a 16-chain','×20'],
    [55,'Rocket','bonus','eat the rocket mosaic','full'],
    [56,'Save the Bots','avoid','eat junk, never friendly bots','0 bots'],
    [57,'Hover Rival','rival','out-eat the rival across rooftops','+35'],
    [58,'Laser Grid','precision','time gaps in the moving laser grid','no laser hit'],
    [59,'Mega Mech','mega','devour the giant mech in chunks','<50s'],
    [60,'Skyscraper Boss','boss','clear the neon tower · grand finale','<75s'] ],
];

// ---- worlds 7–16 · 100 more levels (goal rhythm + themed names) ----
const GOAL_RHYTHM = ['sweep','score','treasure','combo','bonus','avoid','rival','precision','mega','boss'];
const NEW_NAMES = [
  // 7 Mega City
  ['Rush Hour','Gridlock','Golden Cabs','Crosswalk Combo','City Heart','Mind the Crowd','Taxi Rival','Alley Squeeze','Double-Decker','City Hall'],
  // 8 Toy Box
  ['Block Party','Score Stack','Golden Bricks','Domino Chain','Toy Star','Spare the Plushies','Wind-Up Rival','Tabletop','Mega Mech Toy','Boss Bear'],
  // 9 Farm Fields
  ['Morning Chores','Harvest Rush','Golden Hay','Orchard Chain','Wheat Heart','Mind the Chicks','Tractor Rival','Narrow Furrow','Prize Pumpkin','Big Red Barn'],
  // 10 Space Station
  ['Depressurize','Cargo Count','Golden Cells','Module Chain','Star Map','Avoid the Crew','Probe Rival','Airlock','Mega Satellite','Core Reactor'],
  // 11 Haunted Hollow
  ['Wake the Crypt','Spirit Score','Golden Skulls','Bone Chain','Ghost Sigil','Spare the Spirits','Phantom Rival','Cracked Tombs','Mega Mausoleum','The Reaper'],
  // 12 Coral Reef
  ['Tide Pool','Pearl Score','Golden Shells','Bubble Chain','Coral Heart','Save the Turtles','Dolphin Rival','Narrow Trench','Giant Clam','Kraken'],
  // 13 Desert Dunes
  ['Sand Sift','Scarab Score','Golden Idols','Caravan Chain','Sun Disc','Mind the Camels','Nomad Rival','Quicksand','Great Sphinx','Pyramid King'],
  // 14 Sky Gardens
  ['First Bloom','Petal Score','Golden Pots','Vine Chain','Cloud Heart','Spare the Doves','Glider Rival','Floating Stones','Giant Topiary','Cloud Castle'],
  // 15 Volcano Forge
  ['Ember Sweep','Magma Score','Golden Ore','Forge Chain','Fire Rune','Avoid the Lava','Salamander Rival','Cooling Cracks','Mega Geode','The Caldera'],
  // 16 Aurora Peak
  ['First Frost','Aurora Score','Golden Bells','Glacier Chain','Star Crystal','Save the Cubs','Husky Rival','Thin Glacier','Mega Iceberg','Frozen Throne'],
];
const STAR3 = { sweep:'<35s', score:'top tier', treasure:'all gold', combo:'long chain', bonus:'full shape', avoid:'0 misses', rival:'+lead', precision:'clean', mega:'fast', boss:'all phases' };
NEW_NAMES.forEach((names, wi) => {
  const wIdx = 6 + wi; // 0-based world index (7..16 → 6..15)
  const world = WORLDS[wIdx];
  const lvls = names.map((nm, i) => {
    const g = GOAL_RHYTHM[i];
    const n = 60 + wi * 10 + i + 1;
    const prop = world.props[i % world.props.length];
    return [n, nm, g, `${world.sub} · ${prop}s & more`, STAR3[g]];
  });
  LEVELS.push(lvls);
});

// player progress (stars per level, 0..3). Demo: W1 mostly done, into W2.
const STARS = {};
[3,3,2,3,1,3,2,3,3,2, 3,2,3,1,0,0,0,0,0,0].forEach((s, i) => { STARS[i + 1] = s; });

// ============================================================
//  AD-ONLY ECONOMY — no IAP, no money. Everything unlocks by
//  reaching a level gate, then watching N rewarded ads.
// ============================================================

// ---- difficulty curve (per level 1..160) ----
// Pacing model (Master Brief §2.6):
//   A) TARGET grows ~10× across the curve — gentle L1, steep, clear ramp.
//   B) TIME is DERIVED from how much there is to collect (more goals → more minutes).
//   C) HARDNESS ramps ~5× over a few levels via count/drift/rivals/danger.
// Anchors used below: TARGET1≈900 (L1), COUNT1=20 (L1) so time/target/count
// at level 1 are all referenced off the same gentle baseline.
const TARGET1 = 900;   // level-1 score goal (anchor for the ×10 ramp)
const COUNT1  = 20;    // level-1 object count (anchor for density ramp)

function DIFFICULTY(level, planetIndex) {
  const L = Math.max(1, level);
  // cross-planet ramp: each planet (and system loop) makes upcoming levels harder.
  const pm = (typeof planetForIndex === 'function' && planetIndex) ? planetForIndex(planetIndex).diffMul : 1;

  // A) GOAL TARGET — much harder ramp (user: "10× harder each level"). Literal 10×/level is
  //    unbeatable, so we use a steep super-linear curve (~2× the old targets by mid/late game)
  //    that still snowballs into reach. target = TARGET1 * (1 + 0.95*(L-1) + 0.10*(L-1)^1.5)
  const grow   = 1 + 0.95 * (L - 1) + 0.10 * Math.pow(L - 1, 1.5);
  const target = Math.round((TARGET1 * grow * pm) / 10) * 10;         // ×pm: outer planets need more score

  // C) HARDNESS — denser objects each level (count), faster drift, more rivals,
  //    more danger props. Clamps relax on outer planets (×pm) so it keeps ramping.
  const count   = Math.min(Math.round(140 * Math.min(1.7, pm)), Math.round(COUNT1 + (L - 1) * 1.6 + (pm - 1) * 22));
  const rivals  = Math.min(14, 1 + Math.floor((L - 1) / 7) + Math.floor((pm - 1) * 4)); // swarms on outer planets
  const speed   = Math.min(2.4 + (pm - 1) * 0.7, 1 + (L - 1) * 0.013 + (pm - 1) * 0.25); // faster drift outward
  const drift   = speed;                                              // alias the engine may read
  const bigBias = Math.min(0.78, 0.18 + L * 0.012 + (pm - 1) * 0.05); // more big-tier prey
  const hazard  = Math.min(0.4 + (pm - 1) * 0.16, (L >= 4 ? 0.05 + L * 0.006 : 0) + (pm - 1) * 0.1); // more danger outward
  const danger  = hazard;                                             // alias

  // B) TIME DERIVED FROM GOALS — base seconds + per-target & per-object budget.
  //    Early levels feel comfy; later levels longer but tense (target/count grow
  //    faster than the per-unit time we grant). Clamped to ≤ 1200s (20:00).
  //    seconds = BASE + 0.085*target + 1.2*count
  const time = Math.min(1200, Math.round(45 + 0.07 * target + 1.2 * count));

  return { time, count, target, rivals, speed, drift, bigBias, hazard, danger };
}

// ---- "continue after ad" bonus time (engine/UI may grant +N seconds on a fail) ----
const CONTINUE_BONUS_SEC = 30;                                   // seconds added per rewarded continue
function continuesUsed(level) {                                   // how many continues spent this level
  try { return parseInt(localStorage.getItem('hh_continue_' + level)) || 0; } catch (e) { return 0; }
}
function grantContinue(level) {                                   // mark a continue used; returns bonus secs
  try { localStorage.setItem('hh_continue_' + level, String(continuesUsed(level) + 1)); } catch (e) {}
  return CONTINUE_BONUS_SEC;
}
function resetContinues(level) {                                  // clear on level start / win
  try { localStorage.removeItem('hh_continue_' + level); } catch (e) {}
}

// ---- power-ups (unlock by level, then watch ads to keep stocked) ----
const POWERUPS = [
  // ---- the 9 already shipped ----
  { id:'magnet',  name:'Magnet',    icon:'magnet', color:'#22A6F2', desc:'Pulls nearby objects into the hole for 8s.',         unlock:1,  ads:0, charges:2 },
  { id:'time',    name:'+8 Sec',    icon:'time',   color:'#22C9A0', desc:'Adds 8 seconds to the clock instantly.',             unlock:2,  ads:0, charges:1 },
  { id:'slow',    name:'Slow-Mo',   icon:'slow',   color:'#7C5CFC', desc:'Slows the timer & drift for 6 seconds.',             unlock:4,  ads:0, charges:1 },
  { id:'frenzy',  name:'Frenzy ×2', icon:'frenzy', color:'#FFB627', desc:'Double score & faster grow for 8 seconds.',          unlock:6,  ads:0, charges:1 },
  { id:'bomb',    name:'Blast',     icon:'blast',  color:'#FF6B6B', desc:'Clears everything in a wide ring at once.',          unlock:9,  ads:0, charges:1 },
  { id:'grow',    name:'Mega Grow', icon:'grow',   color:'#FF8A3C', desc:'Instantly jumps the hole up a full size.',           unlock:12, ads:0, charges:1 },
  { id:'shield',  name:'Shield',    icon:'shield', color:'#37C6D0', desc:'Blocks one bad hit or rival contact.',               unlock:16, ads:0, charges:1 },
  { id:'tornado', name:'Tornado',   icon:'tornado',color:'#9B6BFF', desc:'A roaming vortex clears nearby targets for 6s.',      unlock:20, ads:0, charges:1 },
  { id:'titan',   name:'Titan',     icon:'titan',  color:'#FF5E7E', desc:'Swell to MAXIMUM size for 7s, then settle back.',    unlock:25, ads:0, charges:1 },
  // ---- 7 new (open-world rivals) ----
  { id:'vortex',   name:'Vortex',    icon:'vortex',   color:'#2AA8FF', desc:'Clears an entire block cluster at once.',            unlock:8,  ads:0, charges:1 },
  { id:'phase',    name:'Phase',     icon:'phase',    color:'#9B6BFF', desc:'Pass through rivals safely for 3s.',                 unlock:14, ads:0, charges:1 },
  { id:'aegis',    name:'Aegis',     icon:'aegis',    color:'#37C6D0', desc:'Blocks rival knockouts for 10s.',                    unlock:18, ads:0, charges:1 },
  { id:'dash',     name:'Dash',      icon:'dash',     color:'#22C9A0', desc:'Burst of speed to reach targets or escape.',         unlock:10, ads:0, charges:1 },
  { id:'blackhole',name:'Black Hole',icon:'blackhole',color:'#7C5CFC', desc:'3 seconds of unlimited clear radius.',               unlock:22, ads:0, charges:1 },
  { id:'goldrush', name:'Gold Rush', icon:'goldrush', color:'#FFD24A', desc:'Everything scores 3× for 8s.',                      unlock:24, ads:0, charges:1 },
  { id:'scout',    name:'Scout',     icon:'scout',    color:'#FF8FB0', desc:'Reveals bosses & gold for the round.',               unlock:28, ads:0, charges:1 },
  // ---- 6 new (city / weather themed) ----
  { id:'quake',    name:'Quake',     icon:'quake',   color:'#C2703A', desc:'Shockwave clears a whole block at once.',            unlock:30, ads:0, charges:1 },
  { id:'freeze',   name:'Freeze',    icon:'freeze',  color:'#8FD8FF', desc:'Stops traffic and rivals for 5s.',                  unlock:32, ads:0, charges:1 },
  { id:'storm',    name:'Storm Pull',icon:'storm',   color:'#6A8DFF', desc:'A cyclone clears a huge radius for 5s.',             unlock:35, ads:0, charges:1 },
  { id:'magnetx',  name:'Magnet+',   icon:'magnetx', color:'#22A6F2', desc:'Long-range magnet for 12s.',                         unlock:36, ads:0, charges:1 },
  { id:'rush',     name:'Coin Rush', icon:'coinrush',color:'#FFD24A', desc:'5× score for 6s.',                                  unlock:40, ads:0, charges:1 },
  { id:'colossus', name:'Colossus',  icon:'colossus',color:'#FF5E7E', desc:'Grow enormous for 10s and clear anything.',          unlock:45, ads:0, charges:1 },
];

// ---- cosmetics: ad-unlock model { unlock: levelGate, ads: rewardedAdsToOwn } ----
const SKINS = [
  // ---- CLASSIC (~12): round / ring / spinner / spiral / vortex colorways ----
  // The default stays the first classic round skin (owned + selected).
  { id:'classic', name:'Classic',     rim:'#1B2740', glow:'#22A6F2', owned:true,  sel:true,  category:'classic', shape:'round',   vfx:'classic',  img:'assets/ui-ai/hole-classic.webp' },
  { id:'lava',    name:'Ember',       rim:'#3a0d04', glow:'#FF6B2C', owned:false, unlock:3,  ads:3, category:'classic', shape:'round',   vfx:'lava',    img:'assets/ui-ai/hole-lava.webp'    },
  { id:'slime',   name:'Goo',         rim:'#16330f', glow:'#7BE03A', owned:false, unlock:5,  ads:3, category:'classic', shape:'round',   vfx:'slime',   img:'assets/ui-ai/hole-slime.webp'   },
  { id:'neon',    name:'Pulse',       rim:'#06121f', glow:'#21E6FF', owned:false, unlock:8,  ads:4, category:'classic', shape:'ring',    vfx:'neon',    img:'assets/ui-ai/hole-neon.webp'    },
  { id:'galaxy',  name:'Nebula',      rim:'#150a33', glow:'#7C5CFC', owned:false, unlock:11, ads:5, category:'classic', shape:'spiral',  vfx:'galaxy',  img:'assets/ui-ai/hole-galaxy.webp'  },
  { id:'gold',    name:'Bullion',     rim:'#6b4e0a', glow:'#FFD24A', owned:false, unlock:20, ads:6, category:'classic', shape:'ring',    vfx:'gold',    img:'assets/ui-ai/hole-gold.webp'    },
  { id:'toxic',   name:'Acid',        rim:'#1a330a', glow:'#9BE03A', owned:false, unlock:18, ads:5, category:'classic', shape:'vortex',  vfx:'slime',   img:'assets/ui-ai/hole-toxic.webp'   },
  { id:'ice',     name:'Glacier',     rim:'#0a2233', glow:'#8FD8FF', owned:false, unlock:22, ads:5, category:'classic', shape:'round',   vfx:'crystal', img:'assets/ui-ai/hole-ice.webp'     },
  { id:'magma',   name:'Magma Core',  rim:'#330d04', glow:'#FF5A2C', owned:false, unlock:26, ads:6, category:'classic', shape:'vortex',  vfx:'lava',    img:'assets/ui-ai/hole-magma.webp'   },
  { id:'sunset',  name:'Dusk',        rim:'#33180a', glow:'#FF8A3C', owned:false, unlock:40, ads:7, category:'classic', shape:'spiral',  vfx:'lava',    img:'assets/ui-ai/hole-sunset.webp'  },
  { id:'emerald', name:'Verdant',     rim:'#0a3322', glow:'#22C97A', owned:false, unlock:46, ads:8, category:'classic', shape:'ring',    vfx:'crystal', img:'assets/ui-ai/hole-emerald.webp' },
  { id:'cyclone', name:'Cyclone',     rim:'#0c2336', glow:'#3FB6FF', owned:false, unlock:16, ads:5, category:'classic', shape:'spinner', vfx:'cyclone', img:'assets/ui-ai/hole-cyclone.webp' },

  // ---- SPECIAL (~8): themed motif shapes ----
  { id:'lovecore',name:'Lovecore',    rim:'#3a0a1c', glow:'#FF5E8A', owned:false, unlock:24, ads:6, category:'special', shape:'heart',    vfx:'lovecore', img:'assets/ui-ai/hole-lovecore.webp' },
  { id:'bolt',    name:'Bolt',        rim:'#2a2406', glow:'#FFE23A', owned:false, unlock:28, ads:6, category:'special', shape:'lightning',vfx:'bolt',     img:'assets/ui-ai/hole-bolt.webp'     },
  { id:'twister', name:'Twister',     rim:'#1c2436', glow:'#9FCBFF', owned:false, unlock:32, ads:7, category:'special', shape:'tornado',  vfx:'cyclone',  img:'assets/ui-ai/hole-twister.webp'  },
  { id:'nova',    name:'Nova',        rim:'#2a1a06', glow:'#FFC24A', owned:false, unlock:30, ads:7, category:'special', shape:'star',     vfx:'nova',     img:'assets/ui-ai/hole-nova.webp'     },
  { id:'bloom',   name:'Bloom',       rim:'#2a0a26', glow:'#FF8FD0', owned:false, unlock:36, ads:7, category:'special', shape:'flower',   vfx:'bloom',    img:'assets/ui-ai/hole-bloom.webp'    },
  { id:'void',    name:'Void Walker', rim:'#140a28', glow:'#B06BFF', owned:false, unlock:34, ads:7, category:'special', shape:'vortex',   vfx:'galaxy',   img:'assets/ui-ai/hole-void.webp'     },
  { id:'aurora',  name:'Aurora',      rim:'#0a2a2a', glow:'#5FE8C4', owned:false, unlock:42, ads:8, category:'special', shape:'spiral',   vfx:'aurora',   img:'assets/ui-ai/hole-aurora.webp'   },
  { id:'googly',  name:'Googly',      rim:'#1B2740', glow:'#FFB627', owned:false, unlock:14, ads:4, googly:true, category:'special', shape:'round', vfx:'googly', img:'assets/ui-ai/hole-googly.webp' },
];
const TRAILS = [
  { id:'none', name:'None', owned:true, sel:true, img:'assets/ui-ai/trail-none.webp' },
  { id:'sparkle', name:'Sparkle', owned:false, unlock:2, ads:2, img:'assets/ui-ai/trail-sparkle.webp' },
  { id:'crumbs', name:'Crumb Trail', owned:false, unlock:6, ads:3, img:'assets/ui-ai/trail-crumbs.webp' },
  { id:'stars', name:'Star Trail', owned:false, unlock:10, ads:4, img:'assets/ui-ai/trail-stars.webp' },
  { id:'flame', name:'Flame', owned:false, unlock:15, ads:5, img:'assets/ui-ai/trail-flame.webp' },
  { id:'bubbles', name:'Bubbles', owned:false, unlock:22, ads:5, img:'assets/ui-ai/trail-bubbles.webp' },
  { id:'neonline', name:'Neon Line', owned:false, unlock:28, ads:6, img:'assets/ui-ai/trail-neonline.webp' },
  { id:'petals', name:'Petals', owned:false, unlock:34, ads:6, img:'assets/ui-ai/trail-petals.webp' },
  { id:'lightning', name:'Lightning', owned:false, unlock:42, ads:7, img:'assets/ui-ai/trail-lightning.webp' },
];
const EFFECTS = [
  { id:'confetti', name:'Confetti', owned:true, sel:true, img:'assets/ui-ai/effect-confetti.webp' },
  { id:'fireworks', name:'Fireworks', owned:false, unlock:7, ads:3, img:'assets/ui-ai/effect-fireworks.webp' },
  { id:'coins', name:'Coin Burst', owned:false, unlock:13, ads:4, img:'assets/ui-ai/effect-coins.webp' },
  { id:'rainbow', name:'Rainbow Pop', owned:false, unlock:25, ads:6, img:'assets/ui-ai/effect-rainbow.webp' },
  { id:'starburst', name:'Starburst', owned:false, unlock:32, ads:6, img:'assets/ui-ai/effect-starburst.webp' },
  { id:'embers', name:'Ember Storm', owned:false, unlock:38, ads:7, img:'assets/ui-ai/effect-embers.webp' },
  { id:'snowpop', name:'Snow Pop', owned:false, unlock:44, ads:7, img:'assets/ui-ai/effect-snowpop.webp' },
];

// ---- Daily reward (7-day) — ad-economy: coins + power-up charges + ad-tickets ----
const DAILY = [
  { day: 1, reward: '50', kind: 'coin', got: true }, { day: 2, reward: '80', kind: 'coin', got: true },
  { day: 3, reward: 'Magnet', kind: 'power', got: true }, { day: 4, reward: '150', kind: 'coin', today: true },
  { day: 5, reward: 'Trail', kind: 'skin', }, { day: 6, reward: '250', kind: 'coin' },
  { day: 7, reward: 'Skin', kind: 'chest', grand: true },
];

// ---- Missions (reward = coins or power-up charges) ----
const MISSIONS = [
  { title: 'Eat 500 objects', sub: 'Across any levels', prog: 0.74, reward: 200, kind: 'coin', done: false },
  { title: 'Use 5 power-ups', sub: 'In any levels', prog: 1, reward: 'Slow-Mo ×1', kind: 'power', done: true },
  { title: '3-star a boss', sub: 'Beat a world boss with full stars', prog: 0.33, reward: 'Frenzy ×1', kind: 'power', done: false },
  { title: 'Keep a ×10 combo', sub: 'In a single level', prog: 0.6, reward: 150, kind: 'coin', done: false },
];

// ---- Milestone chests (star-gated, ad-unlock) ----
const CHESTS = [
  { at: 15, reward: '300 coins', open: true }, { at: 30, reward: 'Galaxy skin', open: false, next: true },
  { at: 45, reward: 'Bomb ×3', open: false }, { at: 60, reward: 'Rainbow skin', open: false },
];

// ---- Onboarding ----
const ONBOARD = [
  { k: 'drag', title: 'Drag', line: 'Move the hungry hole with one finger.' },
  { k: 'eat', title: 'Eat', line: 'Anything that tips over the rim gets devoured.' },
  { k: 'grow', title: 'Grow', line: 'Clear targets to grow, then take bigger routes.' },
];

// ---- The juice / motion checklist (brief §03 Feel & §15.4 VFX) ----
const MOTION = [
  { group: 'On every eat', tint: C.sky, items: [
    { name: 'Object squash & tip', dur: '240ms', ease: 'cubic-bezier(.34,1.56,.64,1)', detail: 'Prop squashes, tips over the rim and drops in — gives it weight.' },
    { name: 'Gulp + crumble puff', dur: '300ms', ease: 'cubic-bezier(.16,1,.3,1)', detail: 'A dust/crumb puff bursts at the rim as the object is swallowed.' },
    { name: 'Score tick', dur: '200ms', ease: 'ease-out', detail: 'Score rolls up; a floating +value chip rises and fades.' },
    { name: 'Light haptic tap', dur: '—', ease: 'instant', detail: 'A short haptic on each bite; deeper thud on bigger objects.' },
  ]},
  { group: 'Combo chains', tint: C.coral, items: [
    { name: 'Combo callout', dur: '420ms', ease: 'cubic-bezier(.34,1.56,.64,1)', detail: "Screen-space 'GREAT! ×3' pops near the hole, tiered GREAT→AMAZING." },
    { name: 'Rising pitch chime', dur: 'per bite', ease: 'stepped', detail: 'Each chained bite raises the chime one step; meter stays lit.' },
    { name: 'Combo burst', dur: '500ms', ease: 'cubic-bezier(.16,1,.3,1)', detail: 'A bright restrained spark ring fires from the hole on chain.' },
  ]},
  { group: 'Growth', tint: C.mint, items: [
    { name: 'Growth ring-pulse', dur: '600ms', ease: 'cubic-bezier(.22,1,.36,1)', detail: 'Rim scales up smoothly; a ring pulse radiates outward.' },
    { name: 'Camera punch + ease-back', dur: '700ms', ease: 'cubic-bezier(.65,0,.35,1)', detail: 'A small camera punch, then the view eases back to reframe.' },
    { name: 'Medium haptic', dur: '—', ease: 'instant', detail: 'A firmer buzz marks the size threshold.' },
  ]},
  { group: 'Level complete', tint: C.sun, items: [
    { name: 'Star stamp', dur: '440ms', ease: 'cubic-bezier(.34,1.56,.64,1)', detail: 'Each earned star stamps in, scale 2→1 with a ding, staggered.' },
    { name: 'Coin shower', dur: '1200ms', ease: 'ease-out', detail: 'Coins arc up and rain down into the counter, tallying up.' },
    { name: 'Confetti (light)', dur: '1400ms', ease: 'linear', detail: 'Restrained confetti — celebratory, never cluttered.' },
    { name: 'Slow-mo on boss', dur: '→0.4× rate', ease: 'linear', detail: 'Final boss bite eases to slow motion before the flash.' },
  ]},
  { group: 'The hole', tint: C.grape, items: [
    { name: 'Rim glow pulse', dur: '2s loop', ease: 'ease-in-out', detail: 'The rim light breathes so the hole always feels alive and hungry.' },
    { name: 'Inner swirl', dur: '6s loop', ease: 'linear', detail: 'A slow conic wormhole rotates inside the void — depth without clutter.' },
    { name: 'Skin shimmer', dur: 'on equip', ease: 'ease-out', detail: 'Equipping a skin sweeps a specular highlight across the rim.' },
    { name: 'Eat lean', dur: '180ms', ease: 'ease-out', detail: 'The hole nudges toward whatever it just swallowed — gives it intent.' },
  ]},
  { group: 'World & atmosphere', tint: C.mint, items: [
    { name: 'Drifting clouds', dur: '30s loop', ease: 'linear', detail: 'Soft blurred clouds parallax across day skies.' },
    { name: 'Snowfall', dur: '5–10s', ease: 'linear', detail: 'Frost & Aurora worlds get gentle drifting snow.' },
    { name: 'Rising embers', dur: '3–6s', ease: 'ease-out', detail: 'Volcano Forge floats glowing embers up from the ground.' },
    { name: 'Bubble columns', dur: '4–8s', ease: 'ease-in', detail: 'Coral Reef & Space release slow rising bubbles.' },
    { name: 'God-ray sway', dur: '18s loop', ease: 'ease-in-out', detail: 'Volumetric light rays drift for a living sky.' },
    { name: 'Idle board tilt', dur: '12s loop', ease: 'ease-in-out', detail: 'The whole board breathes with a barely-there tilt sway.' },
  ]},
  { group: 'UI & feedback', tint: C.sky, items: [
    { name: 'Button press', dur: '80ms', ease: 'ease-out', detail: 'Chunky buttons depress on a 3D bottom-edge — tactile, springy.' },
    { name: 'Shimmer sweep', dur: '2.6s loop', ease: 'linear', detail: 'Claimable rewards & primary CTAs sweep a diagonal highlight.' },
    { name: 'Card rise-in', dur: '500ms', ease: 'cubic-bezier(.22,1,.36,1)', detail: 'Panels & list rows fade-and-rise with a small stagger.' },
    { name: 'Toggle knob spring', dur: '200ms', ease: 'cubic-bezier(.34,1.56,.64,1)', detail: 'Switches snap with an overshoot for satisfying feedback.' },
    { name: 'Number roll-up', dur: '600ms', ease: 'ease-out', detail: 'Scores & coin counts count up rather than snapping.' },
  ]},
  { group: 'Transitions & spectacle', tint: C.coral, items: [
    { name: 'World metamorphosis', dur: '900ms', ease: 'cubic-bezier(.65,0,.35,1)', detail: 'Sky, ground & palette cross-fade when you cross into a new world.' },
    { name: 'Growth shockwave', dur: '600ms', ease: 'cubic-bezier(.22,1,.36,1)', detail: 'A ring shockwave + camera ease-back punctuates each size-up.' },
    { name: 'Boss-finish flash', dur: '440ms', ease: 'ease-out', detail: 'A white screen flash + capped shake lands the final boss bite.' },
    { name: 'Fireworks', dur: '1.4s', ease: 'ease-out', detail: 'World Clear bursts restrained fireworks behind the chest.' },
    { name: 'Star sparkle', dur: '550ms', ease: 'ease-out', detail: 'Each eaten gold/special object pops a little sparkle.' },
  ]},
];

// ---- Rivals / leaderboard ----
const LEADERS = [
  { rank: 1, name: 'pixel.maw', score: 2840, you: false },
  { rank: 2, name: 'GULPzilla', score: 2610, you: false },
  { rank: 3, name: 'YOU', score: 2480, you: true },
  { rank: 4, name: 'noodle', score: 2210, you: false },
  { rank: 5, name: 'bigmouth88', score: 1990, you: false },
];
// ---- per-world leaderboards (local; bots seeded by worldId so each board is stable) ----
const LEADER_NAMES = ['pixel.maw', 'GULPzilla', 'noodle', 'bigmouth88', 'VortexVic', 'holeyMoley', 'munchkin', 'Abyssal', 'crater_cat', 'SinkHole', 'BlackBite', 'gobbler', 'ChompChamp', 'VoidViper', 'PitBoss', 'Maw_Master', 'deepdish', 'EventHorizon', 'nomnom', 'gulpgod'];
function worldLeaders(worldId) {
  const wid = Math.max(1, Math.min(WORLDS.length, worldId || 1));
  let seed = (wid * 2654435761) >>> 0;
  const rnd = () => { seed = (seed * 1664525 + 1013904471) >>> 0; return seed / 4294967296; };
  const scale = (DIFFICULTY(wid * 10).target) || 5000;            // the world's late-level target ≈ its score scale
  const pool = LEADER_NAMES.slice();
  for (let i = pool.length - 1; i > 0; i--) { const j = (rnd() * (i + 1)) | 0; const t = pool[i]; pool[i] = pool[j]; pool[j] = t; }
  const rows = [];
  for (let i = 0; i < 9; i++) rows.push({ name: pool[i], score: Math.round((scale * (1.45 - i * 0.1) + rnd() * scale * 0.12) / 10) * 10, you: false });
  const lvls = LEVELS[wid - 1] || [];
  const wStars = lvls.reduce((a, lv) => a + (STARS[lv[0]] || 0), 0), maxStars = Math.max(1, lvls.length * 3);
  const youScore = Math.round((scale * (0.45 + 0.95 * (wStars / maxStars))) / 10) * 10;   // earned: scales with your stars
  const me = (window.HH && window.HH.profile && window.HH.profile.name) || (function () { try { return localStorage.getItem('hh_name') || 'YOU'; } catch (e) { return 'YOU'; } })();
  rows.push({ name: me, score: youScore, you: true });
  rows.sort((a, b) => b.score - a.score);
  rows.forEach((p, i) => { p.rank = i + 1; });
  return rows;
}

// ============================================================
//  PER-LEVEL THEME — HH.themeFor(level) → { world, weather, season }
//  Every level gets its OWN identity: best location + signature weather +
//  season. The table below is HAND-CURATED so consecutive levels CONTRAST
//  (location, palette, day/night and weather all shift back-to-back) and so
//  each location is shown in its most flattering light:
//    Beach/Coral/Lake/Harbor → golden / clear (summer)
//    Frost / Aurora           → snow (winter)
//    Neon Port / Space        → clear or stormy NIGHT
//    Haunted Hollow           → overcast / rainy (autumn)
//    Farm Fields              → clear (summer/autumn)
//    Mega City / Toy Box      → cloudy / clear
//    Candy / Fairground       → clear / golden (spring)
//    Dino Valley / Desert     → golden (summer)
//    Volcano Forge            → stormy / overcast (ember sky)
//    Sky Gardens              → clear (spring)
//  WORLDS ids: 1 SunnyTown 2 Candy 3 Beach 4 Frost 5 Dino 6 NeonPort
//  7 MegaCity 8 ToyBox 9 Farm 10 Space 11 Haunted 12 Coral 13 Desert
//  14 SkyGardens 15 Volcano 16 Aurora 17 Lake 18 Harbor 19 Fairground
// ============================================================
const WEATHERS  = ['golden','clear','cloudy','overcast','rainy','stormy','snow'];
const SEASONS   = ['spring','summer','autumn','winter'];

// 160-slot curated table. Each entry = [worldId, weather, season].
// Designed as a deliberate march through ALL 19 biomes with no two adjacent
// levels sharing a look (location + weather both rotate), best-fit per place.
const THEME_TABLE = [
  // --- 1..10  Sunny Town (cheery suburb): bright + golden, day ---
  [1,'clear','spring'],   [3,'golden','summer'],  [7,'cloudy','autumn'],  [4,'snow','winter'],
  [6,'stormy','summer'],  [9,'clear','summer'],   [11,'overcast','autumn'],[2,'clear','spring'],
  [17,'clear','summer'],  [1,'golden','autumn'],
  // --- 11..20  Candy Park signature: clear/golden spring, contrasted ---
  [2,'clear','spring'],   [13,'golden','summer'], [16,'snow','winter'],   [8,'cloudy','spring'],
  [19,'golden','spring'], [10,'clear','summer'],  [12,'clear','summer'],  [11,'rainy','autumn'],
  [3,'golden','summer'],  [2,'clear','spring'],
  // --- 21..30  Beach Bay signature: golden/clear summer, contrasted ---
  [3,'golden','summer'],  [7,'cloudy','autumn'],  [4,'snow','winter'],    [6,'clear','summer'],
  [18,'clear','summer'],  [9,'clear','autumn'],   [15,'stormy','summer'], [14,'clear','spring'],
  [12,'golden','summer'], [3,'clear','summer'],
  // --- 31..40  Frost Village signature: snow winter, contrasted ---
  [4,'snow','winter'],    [1,'clear','spring'],   [13,'golden','summer'], [6,'stormy','winter'],
  [9,'overcast','autumn'],[19,'clear','spring'],  [11,'rainy','autumn'],  [17,'cloudy','autumn'],
  [16,'snow','winter'],   [4,'snow','winter'],
  // --- 41..50  Dino Valley signature: golden summer, contrasted ---
  [5,'golden','summer'],  [7,'rainy','autumn'],   [3,'clear','summer'],   [10,'clear','summer'],
  [12,'golden','summer'], [4,'snow','winter'],    [6,'stormy','summer'],  [18,'cloudy','autumn'],
  [14,'clear','spring'],  [5,'golden','summer'],
  // --- 51..60  Neon Port signature: clear/stormy NIGHT, contrasted ---
  [6,'clear','summer'],   [9,'clear','autumn'],   [16,'snow','winter'],   [2,'golden','spring'],
  [15,'stormy','summer'], [3,'golden','summer'],  [11,'overcast','autumn'],[17,'clear','summer'],
  [10,'stormy','summer'], [6,'stormy','summer'],
  // --- 61..70  Mega City signature: cloudy/clear, contrasted ---
  [7,'cloudy','autumn'],  [3,'golden','summer'],  [4,'snow','winter'],    [13,'golden','summer'],
  [19,'clear','spring'],  [12,'clear','summer'],  [6,'clear','summer'],   [9,'clear','autumn'],
  [11,'rainy','autumn'],  [7,'overcast','autumn'],
  // --- 71..80  Toy Box signature: clear/cloudy brights, contrasted ---
  [8,'clear','spring'],   [17,'clear','summer'],  [15,'stormy','summer'], [4,'snow','winter'],
  [2,'golden','spring'],  [10,'clear','summer'],  [18,'cloudy','autumn'], [5,'golden','summer'],
  [16,'snow','winter'],   [8,'cloudy','spring'],
  // --- 81..90  Farm Fields signature: clear summer/autumn, contrasted ---
  [9,'clear','summer'],   [6,'stormy','summer'],  [3,'golden','summer'],  [16,'snow','winter'],
  [13,'golden','summer'], [19,'golden','spring'], [11,'overcast','autumn'],[12,'clear','summer'],
  [7,'cloudy','autumn'],  [9,'clear','autumn'],
  // --- 91..100  Space Station signature: clear/stormy NIGHT, contrasted ---
  [10,'clear','summer'],  [2,'clear','spring'],   [4,'snow','winter'],    [5,'golden','summer'],
  [17,'clear','summer'],  [15,'stormy','summer'], [3,'golden','summer'],  [14,'clear','spring'],
  [6,'stormy','summer'],  [10,'stormy','summer'],
  // --- 101..110  Haunted Hollow signature: overcast/rainy autumn, contrasted ---
  [11,'overcast','autumn'],[3,'golden','summer'], [4,'snow','winter'],    [9,'clear','autumn'],
  [6,'clear','summer'],   [19,'clear','spring'],  [12,'clear','summer'],  [18,'cloudy','autumn'],
  [16,'snow','winter'],   [11,'rainy','autumn'],
  // --- 111..120  Coral Reef signature: golden/clear summer, contrasted ---
  [12,'golden','summer'], [7,'cloudy','autumn'],  [15,'stormy','summer'], [4,'snow','winter'],
  [2,'clear','spring'],   [10,'clear','summer'],  [5,'golden','summer'],  [17,'clear','summer'],
  [11,'overcast','autumn'],[12,'clear','summer'],
  // --- 121..130  Desert Dunes signature: golden summer, contrasted ---
  [13,'golden','summer'], [3,'clear','summer'],   [4,'snow','winter'],    [6,'stormy','summer'],
  [9,'clear','autumn'],   [19,'golden','spring'], [14,'clear','spring'],  [18,'cloudy','autumn'],
  [16,'snow','winter'],   [13,'golden','summer'],
  // --- 131..140  Sky Gardens signature: clear spring, contrasted ---
  [14,'clear','spring'],  [7,'rainy','autumn'],   [3,'golden','summer'],  [15,'overcast','summer'],
  [10,'clear','summer'],  [2,'golden','spring'],  [12,'clear','summer'],  [4,'snow','winter'],
  [17,'cloudy','autumn'], [14,'clear','spring'],
  // --- 141..150  Volcano Forge signature: stormy/overcast ember, contrasted ---
  [15,'stormy','summer'], [3,'golden','summer'],  [4,'snow','winter'],    [9,'clear','summer'],
  [6,'clear','summer'],   [19,'clear','spring'],  [11,'rainy','autumn'],  [5,'golden','summer'],
  [16,'snow','winter'],   [15,'overcast','summer'],
  // --- 151..160  Aurora Peak signature: snow winter NIGHT, contrasted finale ---
  [16,'snow','winter'],   [3,'golden','summer'],  [7,'cloudy','autumn'],  [13,'golden','summer'],
  [12,'clear','summer'],  [6,'stormy','summer'],  [11,'overcast','autumn'],[10,'clear','summer'],
  [4,'snow','winter'],    [16,'snow','winter'],
];

// HH.themeFor(level) — deterministic per-level identity. Levels inside the
// curated 1..160 table return its hand-tuned entry; any level beyond it
// derives a still-varied, contrasting rotating theme so EVERY level returns
// a valid { world, weather, season }.
function themeFor(level) {
  const L = Math.max(1, Math.floor(level || 1));
  const wmax = WORLDS.length;                 // 19 biomes
  let entry = THEME_TABLE[L - 1];
  if (!entry) {
    // Beyond the table: rotate through all worlds with co-prime strides so
    // the location, weather and season all keep shifting (no repeats back-to-back).
    const k = L - THEME_TABLE.length;          // 1,2,3,…
    const world = ((L * 7 + 3) % wmax) + 1;    // stride 7 over 19 → full cycle
    const weather = WEATHERS[(L * 3 + 1) % WEATHERS.length];
    const season = SEASONS[(L + 1) % SEASONS.length];
    entry = [world, weather, season];
    void k;
  }
  let [world, weather, season] = entry;
  // Clamp to a real WORLDS id (defensive — keeps engine lookups valid).
  if (!WORLDS.some(w => w.id === world)) world = ((world - 1) % wmax) + 1;
  if (WEATHERS.indexOf(weather) < 0) weather = 'clear';
  if (SEASONS.indexOf(season) < 0) season = 'summer';
  return { world, weather, season };
}

// ============================================================
// PER-LEVEL CHALLENGE MODIFIERS — so no two consecutive levels play the same.
// Each modifier tweaks real engine/timer knobs; HH.challengeFor(level) is read by
// BOTH the engine (count/gold/rivals/start-size/combo/score) and the React timer
// (timeMul), so they stay consistent. Deterministic → same level = same challenge.
//   mods keys: timeMul, countMul, scoreBase, goldMul, goldDense, rivalAdd,
//              startScale, bigBias, comboDecayMul, comboScore, frenzyStart(sec)
// ============================================================
const CHALLENGES = [
  { id:'classic',   name:'Classic',      color:C.sky,   desc:'Eat the city — clean and smooth.',          mods:{} },
  { id:'rush',      name:'Time Rush',     color:C.coral, desc:'Less time, DOUBLE score. Go fast!',         mods:{ timeMul:0.78, scoreBase:2 } },
  { id:'gold',      name:'Gold Rush',     color:C.sun,   desc:'Gold everywhere — and worth far more.',     mods:{ goldDense:5, goldMul:2.2 } },
  { id:'frenzy',    name:'Frenzy Start',  color:C.sun,   desc:'Begin in a ×2 score frenzy!',               mods:{ frenzyStart:6 } },
  { id:'swarm',     name:'Rival Swarm',   color:C.grape, desc:'More rival holes. Out-eat them all!',       mods:{ rivalAdd:2 } },
  { id:'giant',     name:'Giant Bites',   color:C.sky,   desc:'Start bigger — clear the heavy targets.',    mods:{ startScale:1.6, bigBias:0.32 } },
  { id:'chain',     name:'Combo Chain',   color:C.coral, desc:'Combo lasts longer — keep the chain alive!', mods:{ comboDecayMul:2.4, comboScore:1.6 } },
  { id:'precision', name:'Precision',     color:C.mint,  desc:'Fewer targets, each worth much more.',       mods:{ countMul:0.72, scoreBase:1.7 } },
];
function challengeFor(level) {
  const L = Math.max(1, Math.floor(level || 1));
  if (L === 1) return CHALLENGES[0];                 // gentle onboarding
  if (L % 10 === 0) return CHALLENGES[3];            // every boss level = feel-good Frenzy Start
  return CHALLENGES[(L * 3) % CHALLENGES.length];    // stride 3 over 8 → neighbours always differ
}

// ============================================================
// PLANETS — the infinite meta-layer above the 16 worlds.
// Beat all 16 worlds (levels 1..160) on a planet, take the PORTAL, and the whole
// 16-world run replays RE-TINTED as the next planet (Earth → Mars → Jupiter → …),
// looping forever with an escalating difficulty multiplier. The 3D engine reskins
// entirely from the world palette, so tinting the world object = new planet for free.
// ============================================================
const LEVELS_PER_PLANET = 160;                 // 16 worlds × 10 levels

// small hex mixer (self-contained; engine has its own but data scope needs one)
function _hx(h){ h=(h||'#000').replace('#',''); if(h.length===3)h=h.split('').map(c=>c+c).join(''); return [parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16)]; }
function _hex(r,g,b){ const c=v=>('0'+Math.max(0,Math.min(255,Math.round(v))).toString(16)).slice(-2); return '#'+c(r)+c(g)+c(b); }
function mixHex(a,b,t){ const A=_hx(a),B=_hx(b); return _hex(A[0]+(B[0]-A[0])*t, A[1]+(B[1]-A[1])*t, A[2]+(B[2]-A[2])*t); }

// tint = the color the whole biome palette leans toward on that planet.
// amt = how strongly. space:true forces a starfield/night sky preset in the engine.
// diffMul = cross-planet difficulty ramp (Earth 1.0 → harder outward).
const PLANETS = [
  { id:'earth',   name:'Earth',   icon:'🌍', tint:'#3FA9F5', amt:0.00, diffMul:1.00, space:false, tag:'Home world — where it all begins' },
  { id:'mars',    name:'Mars',    icon:'🔴', tint:'#C1440E', amt:0.52, diffMul:1.16, space:false, tag:'The red planet — dust & rust' },
  { id:'jupiter', name:'Jupiter', icon:'🟠', tint:'#D8934A', amt:0.48, diffMul:1.34, space:false, tag:'Gas giant — banded storms' },
  { id:'saturn',  name:'Saturn',  icon:'🪐', tint:'#E3C07A', amt:0.44, diffMul:1.54, space:false, tag:'Ringed world — pale gold' },
  { id:'venus',   name:'Venus',   icon:'🟡', tint:'#E8862A', amt:0.54, diffMul:1.76, space:false, tag:'Molten twin — toxic heat' },
  { id:'neptune', name:'Neptune', icon:'🔵', tint:'#2A6CF5', amt:0.52, diffMul:2.00, space:true,  tag:'Ice giant — supersonic winds' },
  { id:'mercury', name:'Mercury', icon:'⚪', tint:'#A08A6E', amt:0.46, diffMul:2.26, space:true,  tag:'Scorched rock — sun-blasted' },
  { id:'pluto',   name:'Pluto',   icon:'❄️', tint:'#8FB4D6', amt:0.48, diffMul:2.54, space:true,  tag:'Frozen edge — the cold frontier' },
];

// planet for a 0-based planet index, looping past the list with an intensifying
// "system loop" (Mars, then Mars II with a +50% ramp, etc.) → truly infinite.
function planetForIndex(p) {
  p = Math.max(0, Math.floor(p || 0));
  const base = PLANETS[p % PLANETS.length];
  const loop = Math.floor(p / PLANETS.length);         // 0,1,2… each full pass through the system
  const diffMul = base.diffMul * (1 + loop * 0.55);    // every system loop gets meaningfully harder
  const name = loop === 0 ? base.name : `${base.name} ${['','II','III','IV','V','VI','VII','VIII','IX','X'][loop] || ('×' + (loop + 1))}`;
  return Object.assign({}, base, { index: p, loop, diffMul, name, isNew: p > 0 });
}

// a WORLD object re-tinted for a planet (engine reskins the whole scene from this).
function worldView(worldIndex, planetIndex) {
  const w = WORLDS[Math.max(0, Math.min(15, worldIndex | 0))];
  const P = planetForIndex(planetIndex);
  if (P.amt <= 0 && !P.space) return w;                // Earth = untouched original biomes
  const t = P.amt, tc = P.tint;
  const v = Object.assign({}, w, {
    sky: [mixHex(w.sky[0], tc, t), mixHex(w.sky[1], tc, t * 0.82)],
    ground: mixHex(w.ground, tc, t * 0.92),
    groundDark: mixHex(w.groundDark, tc, t * 0.92),
    ring: mixHex(w.ring, tc, t * 0.5),
    accent: mixHex(w.accent, tc, t * 0.55),
    planet: P.id, planetName: P.name,
  });
  if (P.space) { v.night = true; v.sky = [mixHex('#0A1024', tc, 0.25), mixHex('#05060F', tc, 0.15)]; }
  return v;
}

// global (display) level number across every planet: planet 0 = 1..160, planet 1 = 161..320, …
function globalLevel(planetIndex, levelInPlanet) { return planetIndex * LEVELS_PER_PLANET + levelInPlanet; }

// wallet persists across sessions (local only — no servers, no login)
const _savedCoins = (() => { try { return parseInt(localStorage.getItem('hh_coins')); } catch (e) { return NaN; } })();
window.HH = { C, GOALS, WORLDS, LEVELS, STARS, SKINS, TRAILS, EFFECTS, DAILY, MISSIONS, CHESTS, ONBOARD, MOTION, LEADERS, worldLeaders,
  POWERUPS, DIFFICULTY, themeFor, CHALLENGES, challengeFor,
  PLANETS, LEVELS_PER_PLANET, planetForIndex, worldView, globalLevel, mixHex,
  CONTINUE_BONUS_SEC, continuesUsed, grantContinue, resetContinues,
  wallet: { coins: Number.isFinite(_savedCoins) ? _savedCoins : 3240,
            gems: (() => { try { const g = parseInt(localStorage.getItem('hh_gems')); return Number.isFinite(g) ? g : 60; } catch (e) { return 60; } })(),
            adsWatched: 0 },
  saveWallet() { try { localStorage.setItem('hh_coins', String(window.HH.wallet.coins)); localStorage.setItem('hh_gems', String(window.HH.wallet.gems)); } catch (e) {} },
  // spend from a currency ('coins'|'gems'); returns true if it went through
  spend(kind, amount) { const w = window.HH.wallet; if ((w[kind] || 0) < amount) return false; w[kind] -= amount; window.HH.saveWallet(); return true; },
  earn(kind, amount) { const w = window.HH.wallet; w[kind] = (w[kind] || 0) + amount; window.HH.saveWallet(); } };

// Restore the player's equipped cosmetics (hole skin / trail / effect) from a previous
// session so the chosen hole stays selected AND active in gameplay. A persisted pick is
// implicitly owned (you can't equip what you don't own).
(function restoreEquipped() {
  const map = [['hh_skin', SKINS], ['hh_trail', TRAILS], ['hh_effect', EFFECTS]];
  map.forEach(([key, family]) => {
    let id = null; try { id = localStorage.getItem(key); } catch (e) {}
    if (!id) return;
    const it = family.find(x => x.id === id);
    if (!it) return;
    family.forEach(x => { x.sel = false; });
    it.sel = true; it.owned = true;
  });
})();
