const catalogData = window.ACM_CATALOG;
const order = catalogData.order;
const labels = catalogData.labels;
const catalogs = catalogData.catalogs;
const defaults = catalogData.defaults;

const iconFile = {
  skin_tone: "skin.png",
  species: "species.png",
  eye_color: "eye.png",
  hair_color: "hair_color.png",
  hair_style: "hair_style.png",
  full_outfit: "outfit.png",
  top: "top.png",
  bottom: "bottom.png",
  socks: "socks.png",
  shoes: "shoes.png",
  gloves: "gloves.png",
  hat: "hat.png",
  accessory: "accessory.png",
  background: "background.png",
  character_theme: "style.png",
  character_theme_weight: "spark.png",
};

let gender = "female";
let quality = "lq";
let viewMode = "simple";
let whiteBackgroundLocked = true;
let state = { ...defaults[gender] };
let deferredInstallPrompt = null;
let renderCount = 0;

const controls = document.querySelector("#controls");
const canvas = document.querySelector("#previewCanvas");
const ctx = canvas.getContext("2d");
const statusEl = document.querySelector("#engineStatus");
const genderToggle = document.querySelector("#genderToggle");
const qualityToggle = document.querySelector("#qualityToggle");
const whiteBackground = document.querySelector("#whiteBackground");
const modeButtons = document.querySelectorAll(".mode-toggle button");

function optionsFor(key) {
  return catalogs[gender][key];
}

function indexFor(key) {
  return state[key] % optionsFor(key).length;
}

function currentName(key) {
  return optionsFor(key)[indexFor(key)];
}

function neighborName(key, amount) {
  const options = optionsFor(key);
  return options[(indexFor(key) + amount + options.length) % options.length];
}

function deviceName() {
  const ua = navigator.userAgent || "";
  if (/iPhone/i.test(ua)) return "iPhone";
  if (/iPad/i.test(ua)) return "iPad";
  if (/Android/i.test(ua)) return "Android";
  return "desktop browser";
}

function setStatus(message) {
  statusEl.textContent = message;
}

function syncCanvasResolution() {
  const scale = quality === "hq" ? 1.5 : 1;
  const width = Math.round(320 * scale);
  const height = Math.round(704 * scale);
  if (canvas.width !== width || canvas.height !== height) {
    canvas.width = width;
    canvas.height = height;
  }
}

function normalizeState() {
  for (const key of order) {
    const options = optionsFor(key);
    state[key] = (state[key] ?? 0) % options.length;
  }
  if (whiteBackgroundLocked) {
    state.background = 0;
  }
}

function move(key, amount) {
  const options = optionsFor(key);
  if (key === "background" && whiteBackgroundLocked) {
    flashStatus("Background is locked to white for stable previews.");
    return;
  }
  state[key] = (indexFor(key) + amount + options.length) % options.length;
  checkpoint(`${labels[key]}: ${currentName(key)}`);
}

function randomize(key) {
  if (key === "background" && whiteBackgroundLocked) {
    flashStatus("Background is locked to white for stable previews.");
    return;
  }
  state[key] = Math.floor(Math.random() * optionsFor(key).length);
  checkpoint(`Randomized ${labels[key]}`);
}

function randomizeAll() {
  for (const key of order) {
    if (key === "background" && whiteBackgroundLocked) {
      state[key] = 0;
    } else {
      state[key] = Math.floor(Math.random() * optionsFor(key).length);
    }
  }
  checkpoint("Randomized the whole character");
}

function resetSelector(key) {
  state[key] = defaults[gender][key] ?? 0;
  checkpoint(`Reset ${labels[key]}`);
}

function checkpoint(message) {
  normalizeState();
  updateControls();
  drawCharacter(message);
}

function flashStatus(message) {
  setStatus(message);
  window.clearTimeout(flashStatus.timer);
  flashStatus.timer = window.setTimeout(() => {
    drawCharacter("Preview unchanged");
  }, 900);
}

function updateControls() {
  document.body.classList.toggle("full-mode", viewMode === "full");
  modeButtons.forEach((button) => {
    const isActive = button.textContent.trim().toLowerCase() === viewMode;
    button.classList.toggle("active", isActive);
  });
  genderToggle.textContent = gender === "female" ? "Female" : "Male";
  qualityToggle.textContent = quality === "hq" ? "HQ" : "LQ";
  whiteBackground.textContent = whiteBackgroundLocked ? "White BG Locked" : "BG Unlocked";
  whiteBackground.classList.toggle("unlocked", !whiteBackgroundLocked);

  controls.innerHTML = "";
  for (const key of order) {
    const label = labels[key];
    const value = currentName(key);
    const locked = key === "background" && whiteBackgroundLocked;
    const row = document.createElement("div");
    row.className = `selector-row${locked ? " locked" : ""}`;
    row.innerHTML = `
      <img class="icon" src="../assets/selector_icons/${iconFile[key]}" alt="" title="${label}" />
      <button class="nav-button prev" type="button" title="Previous ${label}: ${neighborName(key, -1)}" aria-label="Previous ${label}"></button>
      <div class="value" title="${value}">
        <strong>${label}</strong>
        <span>${value}</span>
      </div>
      <button class="nav-button next" type="button" title="Next ${label}: ${neighborName(key, 1)}" aria-label="Next ${label}"></button>
      <button class="random" type="button" title="Random ${label}" aria-label="Random ${label}">*</button>
      <button class="reset" type="button" title="Reset ${label}" aria-label="Reset ${label}">1</button>
      <span class="count">${indexFor(key) + 1}/${optionsFor(key).length}</span>
    `;
    const buttons = row.querySelectorAll("button");
    buttons[0].addEventListener("click", () => move(key, -1));
    buttons[1].addEventListener("click", () => move(key, 1));
    buttons[2].addEventListener("click", () => randomize(key));
    buttons[3].addEventListener("click", () => resetSelector(key));
    controls.appendChild(row);
  }
}

function colorFromText(text, fallback) {
  const value = text.toLowerCase();
  const colors = [
    ["black", "#1d2028"], ["white", "#f8f7f2"], ["navy", "#1d2f5d"], ["blue", "#4d83de"],
    ["red", "#c8443e"], ["ruby", "#b7353f"], ["pink", "#ee8fbd"], ["purple", "#7d5ab7"],
    ["violet", "#8165c5"], ["lavender", "#b99be5"], ["green", "#3e985f"], ["emerald", "#348d62"],
    ["teal", "#2f9a9d"], ["mint", "#7fc8a0"], ["yellow", "#e1bd45"], ["gold", "#d6a238"],
    ["orange", "#df7d35"], ["brown", "#8c5836"], ["gray", "#9298a3"], ["grey", "#9298a3"],
    ["ebony", "#2b1712"], ["espresso", "#321b14"], ["deep cocoa", "#432315"], ["dark brown", "#5b2f1d"],
    ["rich brown", "#6f3d27"], ["caramel", "#b86f3f"], ["honey", "#c9874b"], ["tan", "#c47b46"],
    ["olive skin", "#a97854"], ["bronze", "#a56f43"], ["porcelain", "#f5d2bd"], ["fair", "#f5c9a9"],
    ["peach", "#efb082"], ["beige", "#e7bd92"], ["ivory", "#f2d0ad"], ["blue", "#accfff"],
    ["aqua", "#8bd7df"], ["teal", "#6dc7bd"], ["mint", "#a6dbb3"], ["green", "#9bc87b"],
    ["lavender", "#c5a2ef"], ["violet", "#9272cf"], ["purple", "#7d5ab7"], ["pink", "#ef98bd"],
    ["silver", "#cfd4dc"],
  ];
  const match = colors.find(([name]) => value.includes(name));
  return match ? match[1] : fallback;
}

function hairColor() {
  const value = currentName("hair_color").toLowerCase();
  const colors = [
    ["black", "#1d2028"], ["charcoal", "#2f3238"], ["white", "#f5f2e8"], ["silver", "#cdd2dc"],
    ["platinum", "#efe7b6"], ["blonde", "#e4c35e"], ["honey", "#d8a74c"], ["strawberry", "#df9a6e"],
    ["auburn", "#a54f2e"], ["copper", "#b75a32"], ["burgundy", "#7d2535"], ["ruby", "#b7353f"],
    ["red", "#b83f34"], ["pink", "#ee8fbd"], ["rose", "#c97996"], ["lavender", "#b99be5"],
    ["violet", "#8165c5"], ["indigo", "#384a9a"], ["navy", "#263c7b"], ["blue", "#4d83de"],
    ["teal", "#2f9a9d"], ["mint", "#7fc8a0"], ["emerald", "#348d62"], ["olive", "#758032"],
    ["caramel", "#b97842"], ["cinnamon", "#9f5d36"], ["brown", "#9d6741"],
  ];
  const match = colors.find(([name]) => value.includes(name));
  return match ? match[1] : "#a96f43";
}

function eyeColor() {
  return colorFromText(currentName("eye_color"), "#8a542f");
}

function outfitPalette() {
  const outfit = currentName("full_outfit").toLowerCase();
  const topName = outfit.startsWith("none") ? currentName("top").toLowerCase() : outfit;
  const bottomName = outfit.startsWith("none") ? currentName("bottom").toLowerCase() : outfit;
  const top = colorFromText(topName, "#ffffff");
  const bottom = colorFromText(bottomName, "#1d2f5d");
  const accent = topName.includes("school") || outfit.includes("school") ? "#c84141" : "#4d83de";

  if (outfit.includes("chef")) return { top: "#ffffff", bottom: "#f8f8f6", accent: "#cfcfd6", dress: true };
  if (outfit.includes("spacesuit")) return { top: "#182a56", bottom: "#182a56", accent: "#8ab8ff", bodysuit: true };
  if (outfit.includes("idol")) return { top: "#ff79a9", bottom: "#fff1f6", accent: "#7c4ed4", skirt: true };
  if (outfit.includes("kimono") || outfit.includes("yukata")) return { top: "#f7dde9", bottom: "#d65a88", accent: "#7c4ed4", dress: true };
  if (outfit.includes("dress") || bottomName.includes("skirt")) return { top, bottom, accent, skirt: true };
  return { top, bottom, accent };
}

function characterThemeAccent() {
  const theme = currentName("character_theme").toLowerCase();
  const colors = [
    ["kawaii", "#ff93c7"], ["candy", "#ff8fc7"], ["webcore", "#51d9ff"], ["vaporwave", "#b36cff"],
    ["cyber", "#00d6ff"], ["goth", "#3f3448"], ["emo", "#494052"], ["punk", "#ff3e64"],
    ["medieval", "#8a6238"], ["knight", "#9aa7b7"], ["peasant", "#9a7648"], ["rich", "#d6a238"],
    ["royal", "#7c4ed4"], ["sport", "#52a1ff"], ["volleyball", "#ffb13b"], ["basketball", "#e96f2d"],
    ["soccer", "#39a85b"], ["tennis", "#c7e85d"], ["musical", "#8b5cff"], ["singer", "#ff5ca8"],
    ["book", "#8b6843"], ["magician", "#6e51c5"], ["wizard", "#5d50bb"], ["evil", "#7c1f31"],
    ["hero", "#4d83de"], ["angel", "#ffd86c"], ["devil", "#bd2e49"], ["fire", "#ff6a2a"],
    ["ice", "#8bd7ff"], ["forest", "#4fa66b"], ["ocean", "#3aa6c8"], ["slime", "#72e0bc"],
  ];
  const match = colors.find(([name]) => theme.includes(name));
  return match ? match[1] : null;
}

function themePower() {
  const value = currentName("character_theme_weight").toLowerCase();
  if (value.includes("high")) return 1;
  if (value.includes("low")) return 0.35;
  return 0.62;
}

function backgroundFill() {
  if (whiteBackgroundLocked) return "#ffffff";
  const background = currentName("background").toLowerCase();
  if (background.includes("green")) return "#24d65c";
  if (background.includes("teal")) return "#18bca4";
  if (background.includes("blue")) return "#86bfff";
  if (background.includes("pink")) return "#ffd0e4";
  if (background.includes("purple")) return "#d9c3ff";
  if (background.includes("black")) return "#11141a";
  if (background.includes("gray") || background.includes("grey")) return "#d8dce2";
  if (background.includes("beach")) return "#f7df9d";
  if (background.includes("forest") || background.includes("park")) return "#bee4b8";
  if (background.includes("castle")) return "#e6e0d7";
  return "#ffffff";
}

function path(fill, stroke = "#33251f", width = 2.2) {
  ctx.fillStyle = fill;
  ctx.strokeStyle = stroke;
  ctx.lineWidth = width;
  ctx.fill();
  ctx.stroke();
}

function ellipse(x, y, rx, ry, fill, stroke = "#33251f", width = 2.2, rot = 0) {
  ctx.beginPath();
  ctx.ellipse(x, y, rx, ry, rot, 0, Math.PI * 2);
  path(fill, stroke, width);
}

function roundedRect(x, y, width, height, radius, fill, stroke = "#33251f", lineWidth = 2.2) {
  const r = Math.min(radius, width / 2, height / 2);
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.lineTo(x + width - r, y);
  ctx.quadraticCurveTo(x + width, y, x + width, y + r);
  ctx.lineTo(x + width, y + height - r);
  ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
  ctx.lineTo(x + r, y + height);
  ctx.quadraticCurveTo(x, y + height, x, y + height - r);
  ctx.lineTo(x, y + r);
  ctx.quadraticCurveTo(x, y, x + r, y);
  path(fill, stroke, lineWidth);
}

function limb(x1, y1, x2, y2, width, color) {
  ctx.strokeStyle = "#33251f";
  ctx.lineWidth = width + 4;
  ctx.lineCap = "round";
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y2);
  ctx.stroke();
  ctx.strokeStyle = color;
  ctx.lineWidth = width;
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y2);
  ctx.stroke();
  ctx.lineCap = "butt";
}

function triangle(points, fill) {
  ctx.beginPath();
  ctx.moveTo(points[0][0], points[0][1]);
  for (let i = 1; i < points.length; i += 1) ctx.lineTo(points[i][0], points[i][1]);
  ctx.closePath();
  path(fill);
}

function drawTail(cx, cy, hair, species) {
  if (species.includes("human") || species.includes("slime")) return;
  ctx.save();
  ctx.globalAlpha = 0.95;
  ctx.strokeStyle = "#33251f";
  ctx.lineWidth = species.includes("squirrel") ? 36 : 24;
  ctx.lineCap = "round";
  ctx.beginPath();
  ctx.moveTo(cx + 40, cy + 220);
  ctx.bezierCurveTo(cx + 118, cy + 196, cx + 100, cy + 92, cx + 52, cy + 126);
  ctx.stroke();
  ctx.strokeStyle = hair;
  ctx.lineWidth = species.includes("squirrel") ? 30 : 18;
  ctx.beginPath();
  ctx.moveTo(cx + 40, cy + 220);
  ctx.bezierCurveTo(cx + 118, cy + 196, cx + 100, cy + 92, cx + 52, cy + 126);
  ctx.stroke();
  if (species.includes("dragon") || species.includes("demon")) {
    triangle([[cx + 92, cy + 126], [cx + 124, cy + 110], [cx + 106, cy + 148]], hair);
  }
  ctx.restore();
}

function drawHair(cx, topY, hair, skin) {
  const style = currentName("hair_style").toLowerCase();
  if (style.includes("long")) {
    ellipse(cx, topY + 108, 60, 132, hair);
    ellipse(cx, topY + 118, 45, 98, skin, skin, 0);
  } else if (style.includes("twin")) {
    ellipse(cx - 58, topY + 110, 28, 86, hair, "#33251f", 2.2, -0.22);
    ellipse(cx + 58, topY + 110, 28, 86, hair, "#33251f", 2.2, 0.22);
  } else if (style.includes("bun")) {
    ellipse(cx - 48, topY + 42, 25, 25, hair);
    ellipse(cx + 48, topY + 42, 25, 25, hair);
  } else if (style.includes("ponytail")) {
    ellipse(cx + 54, topY + 92, 24, 82, hair, "#33251f", 2.2, 0.25);
  }

  ctx.beginPath();
  ctx.moveTo(cx - 58, topY + 84);
  ctx.bezierCurveTo(cx - 63, topY + 37, cx - 35, topY + 10, cx, topY + 9);
  ctx.bezierCurveTo(cx + 37, topY + 10, cx + 65, topY + 38, cx + 58, topY + 86);
  ctx.bezierCurveTo(cx + 45, topY + 68, cx + 34, topY + 52, cx + 10, topY + 52);
  ctx.bezierCurveTo(cx - 20, topY + 52, cx - 35, topY + 65, cx - 58, topY + 84);
  ctx.closePath();
  path(hair);

  ellipse(cx - 47, topY + 85, 16, 58, hair, "#33251f", 2.1, 0.12);
  ellipse(cx + 47, topY + 85, 16, 58, hair, "#33251f", 2.1, -0.12);

  const bangTips = [
    [cx - 35, topY + 54, cx - 18, topY + 57, cx - 27, topY + 102],
    [cx - 15, topY + 48, cx + 4, topY + 54, cx - 4, topY + 100],
    [cx + 10, topY + 50, cx + 28, topY + 58, cx + 17, topY + 99],
  ];
  for (const points of bangTips) {
    triangle([[points[0], points[1]], [points[2], points[3]], [points[4], points[5]]], hair);
  }
}

function drawSpeciesHead(cx, topY, hair, species) {
  if (species.includes("cat") || species.includes("kitsune") || species.includes("fox") || species.includes("wolf") || species.includes("dog")) {
    triangle([[cx - 38, topY + 42], [cx - 17, topY - 10], [cx + 0, topY + 54]], hair);
    triangle([[cx + 38, topY + 42], [cx + 17, topY - 10], [cx + 0, topY + 54]], hair);
  } else if (species.includes("bunny")) {
    roundedRect(cx - 35, topY - 30, 16, 72, 9, hair);
    roundedRect(cx + 19, topY - 30, 16, 72, 9, hair);
  } else if (species.includes("dragon") || species.includes("oni") || species.includes("demon") || species.includes("goat") || species.includes("deer")) {
    triangle([[cx - 34, topY + 34], [cx - 21, topY - 10], [cx - 8, topY + 44]], "#f2e0bd");
    triangle([[cx + 34, topY + 34], [cx + 21, topY - 10], [cx + 8, topY + 44]], "#f2e0bd");
  }
}

function drawFace(cx, headY, skin) {
  const eye = eyeColor();
  ctx.beginPath();
  ctx.moveTo(cx - 38, headY - 20);
  ctx.bezierCurveTo(cx - 41, headY - 58, cx + 41, headY - 58, cx + 38, headY - 20);
  ctx.bezierCurveTo(cx + 37, headY + 24, cx + 18, headY + 49, cx, headY + 52);
  ctx.bezierCurveTo(cx - 18, headY + 49, cx - 37, headY + 24, cx - 38, headY - 20);
  ctx.closePath();
  path(skin);

  ellipse(cx - 17, headY - 4, 9, 13, "#ffffff", "#33251f", 1.4);
  ellipse(cx + 17, headY - 4, 9, 13, "#ffffff", "#33251f", 1.4);
  ellipse(cx - 17, headY - 4, 5.2, 9, eye, eye, 0);
  ellipse(cx + 17, headY - 4, 5.2, 9, eye, eye, 0);
  ellipse(cx - 15, headY - 9, 2.1, 3.1, "#ffffff", "#ffffff", 0);
  ellipse(cx + 19, headY - 9, 2.1, 3.1, "#ffffff", "#ffffff", 0);
  ctx.strokeStyle = "#33251f";
  ctx.lineWidth = 1.7;
  ctx.beginPath();
  ctx.moveTo(cx - 28, headY - 19);
  ctx.quadraticCurveTo(cx - 17, headY - 24, cx - 6, headY - 19);
  ctx.moveTo(cx + 6, headY - 19);
  ctx.quadraticCurveTo(cx + 17, headY - 24, cx + 28, headY - 19);
  ctx.stroke();
  ctx.strokeStyle = "#8b5344";
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(cx, headY + 17, 14, 0.18, Math.PI - 0.18);
  ctx.stroke();
  ctx.strokeStyle = "rgba(210,95,103,.45)";
  ctx.beginPath();
  ctx.moveTo(cx - 31, headY + 13);
  ctx.lineTo(cx - 21, headY + 12);
  ctx.moveTo(cx + 21, headY + 12);
  ctx.lineTo(cx + 31, headY + 13);
  ctx.stroke();
}

function drawCharacter(reason = "Local preview rendered") {
  syncCanvasResolution();
  renderCount += 1;
  const scale = canvas.width / 320;
  ctx.setTransform(scale, 0, 0, scale, 0, 0);
  ctx.clearRect(0, 0, 320, 704);

  ctx.fillStyle = backgroundFill();
  ctx.fillRect(0, 0, 320, 704);
  if (!whiteBackgroundLocked) {
    const grad = ctx.createLinearGradient(0, 0, 0, 704);
    grad.addColorStop(0, "rgba(255,255,255,.45)");
    grad.addColorStop(1, "rgba(255,255,255,0)");
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, 320, 704);
  }

  const cx = 160;
  const skin = colorFromText(currentName("skin_tone"), "#f0b184");
  const hair = hairColor();
  const palette = outfitPalette();
  const themeAccent = characterThemeAccent();
  if (themeAccent && currentName("character_theme") !== "None") {
    palette.accent = themeAccent;
  }
  const species = currentName("species").toLowerCase();
  const male = gender === "male";
  const shoulder = male ? 62 : 50;
  const waist = male ? 43 : 34;
  const hip = male ? 46 : 44;
  const headY = 125;

  drawTail(cx, 78, hair, species);
  ellipse(cx, 636, 72, 13, "rgba(70,42,32,.12)", "rgba(70,42,32,.12)", 0);

  limb(cx - 24, 356, cx - 32, 572, 24, skin);
  limb(cx + 24, 356, cx + 32, 572, 24, skin);
  if (!palette.skirt && !palette.dress) {
    limb(cx - 24, 350, cx - 31, 586, 26, palette.bottom);
    limb(cx + 24, 350, cx + 31, 586, 26, palette.bottom);
  } else {
    triangle([[cx - 46, 320], [cx + 46, 320], [cx + 67, 454], [cx - 67, 454]], palette.bottom);
  }

  const sockName = currentName("socks").toLowerCase();
  const sockColor = colorFromText(sockName, sockName.includes("black") ? "#1f2229" : "#f6f5f1");
  limb(cx - 31, 552, cx - 35, 612, 20, sockColor);
  limb(cx + 31, 552, cx + 35, 612, 20, sockColor);
  const shoeName = currentName("shoes").toLowerCase();
  const shoeColor = colorFromText(shoeName, shoeName.includes("white") ? "#f0f0f0" : "#1d1d24");
  roundedRect(cx - 62, 604, 52, 23, 9, shoeColor);
  roundedRect(cx + 10, 604, 52, 23, 9, shoeColor);

  roundedRect(cx - 12, 171, 24, 27, 8, skin);
  ctx.beginPath();
  ctx.moveTo(cx - shoulder, 205);
  ctx.quadraticCurveTo(cx, 180, cx + shoulder, 205);
  ctx.lineTo(cx + waist, 330);
  ctx.quadraticCurveTo(cx, 346, cx - waist, 330);
  ctx.closePath();
  path(palette.top);

  ctx.strokeStyle = palette.accent;
  ctx.lineWidth = 4;
  ctx.beginPath();
  ctx.moveTo(cx - shoulder + 14, 218);
  ctx.lineTo(cx + shoulder - 14, 218);
  ctx.moveTo(cx, 198);
  ctx.lineTo(cx, 330);
  ctx.stroke();

  limb(cx - shoulder + 5, 218, cx - 77, 350, 17, skin);
  limb(cx + shoulder - 5, 218, cx + 77, 350, 17, skin);
  limb(cx - shoulder + 5, 218, cx - 77, 312, 22, palette.top);
  limb(cx + shoulder - 5, 218, cx + 77, 312, 22, palette.top);
  const gloveName = currentName("gloves").toLowerCase();
  const gloveColor = gloveName.includes("no gloves") ? skin : colorFromText(gloveName, gloveName.includes("white") ? "#f5f5f5" : "#20232b");
  ellipse(cx - 80, 355, 11, 16, gloveColor);
  ellipse(cx + 80, 355, 11, 16, gloveColor);

  if (species.includes("slime")) {
    ctx.save();
    ctx.globalAlpha = 0.28;
    ellipse(cx, 316, 68, 178, "#73e3c6", "#27a88b", 1.5);
    ctx.restore();
  }

  drawSpeciesHead(cx, 50, hair, species);
  drawHair(cx, 50, hair, skin);
  drawFace(cx, headY, skin);

  const hat = currentName("hat").toLowerCase();
  if (!hat.includes("no hat")) {
    if (hat.includes("crown")) {
      triangle([[cx - 29, 63], [cx - 13, 32], [cx, 62], [cx + 13, 32], [cx + 29, 63]], "#ffd45a");
    } else if (hat.includes("witch")) {
      triangle([[cx - 54, 70], [cx + 54, 70], [cx, 10]], "#343044");
      roundedRect(cx - 60, 68, 120, 12, 6, "#343044");
    } else {
      roundedRect(cx - 52, 60, 104, 22, 10, colorFromText(hat, "#3d4560"));
      ellipse(cx, 54, 42, 16, colorFromText(hat, "#3d4560"));
    }
  }

  const accessory = currentName("accessory").toLowerCase();
  if (!accessory.includes("no accessory")) {
    if (accessory.includes("glasses")) {
      ctx.strokeStyle = "#33251f";
      ctx.lineWidth = 2;
      ctx.strokeRect(cx - 29, headY - 13, 22, 16);
      ctx.strokeRect(cx + 7, headY - 13, 22, 16);
      ctx.beginPath();
      ctx.moveTo(cx - 7, headY - 5);
      ctx.lineTo(cx + 7, headY - 5);
      ctx.stroke();
    } else if (accessory.includes("ribbon")) {
      triangle([[cx - 13, 198], [cx - 42, 212], [cx - 13, 228]], palette.accent);
      triangle([[cx + 13, 198], [cx + 42, 212], [cx + 13, 228]], palette.accent);
    } else {
      ellipse(cx, 210, 7, 10, palette.accent, palette.accent, 0);
    }
  }

  const theme = currentName("character_theme").toLowerCase();
  if (themeAccent && theme !== "none") {
    ctx.save();
    ctx.globalAlpha = themePower();
    ctx.strokeStyle = themeAccent;
    ctx.fillStyle = themeAccent;
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.arc(cx - 54, 198, 9, 0, Math.PI * 2);
    ctx.arc(cx + 54, 198, 9, 0, Math.PI * 2);
    ctx.stroke();
    if (theme.includes("book") || theme.includes("librarian") || theme.includes("scholar")) {
      roundedRect(cx - 18, 346, 36, 24, 4, themeAccent, themeAccent, 0);
    } else if (theme.includes("musical") || theme.includes("singer") || theme.includes("dj")) {
      ctx.font = "bold 24px sans-serif";
      ctx.fillText("M", cx + 56, 158);
    } else if (theme.includes("sport") || theme.includes("volleyball") || theme.includes("basketball") || theme.includes("soccer")) {
      ellipse(cx + 65, 397, 13, 13, "#ffffff", themeAccent, 2);
    } else if (theme.includes("magician") || theme.includes("wizard") || theme.includes("sorcerer")) {
      ctx.font = "bold 22px sans-serif";
      ctx.fillText("*", cx - 72, 158);
    }
    ctx.restore();
  }

  const webGpuText = navigator.gpu ? "WebGPU visible" : "canvas preview";
  setStatus(`${reason}. ${renderCount} render(s), ${deviceName()}, ${webGpuText}.`);
}

function saveImage() {
  const link = document.createElement("a");
  link.download = `anime-character-maker-${gender}-${Date.now()}.png`;
  link.href = canvas.toDataURL("image/png");
  link.click();
  setStatus(`Saved preview image from this ${deviceName()}.`);
}

async function checkEngine() {
  const secure = location.protocol === "https:" || location.hostname === "localhost" || location.hostname === "127.0.0.1";
  const installText = secure ? "PWA install/offline cache is available." : "Use HTTPS for iPhone install/offline cache.";
  setStatus(`Ready on this ${deviceName()}. ${installText}`);
}

genderToggle.addEventListener("click", () => {
  gender = gender === "female" ? "male" : "female";
  state = { ...defaults[gender] };
  checkpoint(`Switched to ${gender}`);
});

qualityToggle.addEventListener("click", () => {
  quality = quality === "hq" ? "lq" : "hq";
  checkpoint(`${quality.toUpperCase()} local canvas`);
});

whiteBackground.addEventListener("click", () => {
  whiteBackgroundLocked = !whiteBackgroundLocked;
  if (whiteBackgroundLocked) state.background = 0;
  checkpoint(whiteBackgroundLocked ? "Background locked to white" : "Background unlocked");
});

modeButtons.forEach((button) => {
  button.addEventListener("click", () => {
    viewMode = button.textContent.trim().toLowerCase();
    checkpoint(`${button.textContent.trim()} selector mode`);
  });
});

window.addEventListener("beforeinstallprompt", (event) => {
  event.preventDefault();
  deferredInstallPrompt = event;
});

document.querySelector("#installBtn").addEventListener("click", async () => {
  if (!deferredInstallPrompt) {
    setStatus("On iPhone: Safari Share, then Add to Home Screen.");
    return;
  }
  deferredInstallPrompt.prompt();
});

document.querySelector("#randomizeAll").addEventListener("click", randomizeAll);
document.querySelector("#renderLocal").addEventListener("click", () => drawCharacter("Manual local preview"));
document.querySelector("#saveImage").addEventListener("click", saveImage);

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("sw.js").catch(() => {});
}

normalizeState();
updateControls();
drawCharacter("Initial local preview");
checkEngine();
