/* global React, ReactDOM, PWLC_AJAX, PWLC_DATA */
// Pixel Calculator – LED Calculator v1.24.1 (admin-managed DB + RFQ history + cookies)

const FALLBACK_CABS = [
  { id: "500x500", label: "500 × 500 mm", width_mm: 500, height_mm: 500 },
  { id: "500x1000", label: "500 × 1000 mm", width_mm: 500, height_mm: 1000 },
  { id: "1000x500", label: "1000 × 500 mm", width_mm: 1000, height_mm: 500 },
  { id: "600x337.5", label: "600 × 337.5 mm (16:9)", width_mm: 600, height_mm: 337.5 },
  { id: "640x480", label: "640 × 480 mm (4:3)", width_mm: 640, height_mm: 480 },
  { id: "960x540", label: "960 × 540 mm (16:9)", width_mm: 960, height_mm: 540 },
  { id: "768x432", label: "768 × 432 mm (16:9)", width_mm: 768, height_mm: 432 },
  { id: "960x960", label: "960 × 960 mm", width_mm: 960, height_mm: 960 },
  { id: "640x640", label: "640 × 640 mm", width_mm: 640, height_mm: 640 },
  { id: "640x360", label: "640 × 360 mm (16:9)", width_mm: 640, height_mm: 360 },
  { id: "960x480", label: "960 × 480 mm (2:1)", width_mm: 960, height_mm: 480 },
];
const SERVER_CABS = (typeof PWLC_DATA !== "undefined" && Array.isArray(PWLC_DATA.cabinets)) ? PWLC_DATA.cabinets : FALLBACK_CABS;
const DEFAULT_COUNTER = 5188;
const INITIAL_COUNTER = (() => {
  if (typeof PWLC_DATA !== "undefined" && PWLC_DATA && typeof PWLC_DATA.design_counter !== "undefined") {
    const parsed = Number(PWLC_DATA.design_counter);
    if (Number.isFinite(parsed) && parsed >= 0) {
      return parsed;
    }
  }
  return DEFAULT_COUNTER;
})();

const SPEC_INDEX = {};
const CABINETS = SERVER_CABS.map(c => {
  const base = { id: c.id, label: c.label, w: c.width_mm, h: c.height_mm };
  SPEC_INDEX[c.id] = {
    id: c.id, label: c.label, width_mm: c.width_mm, height_mm: c.height_mm,
    common_pixel_pitches_mm: Array.isArray(c.common_pixel_pitches_mm) ? c.common_pixel_pitches_mm : [],
    resolutions_by_pitch: c.resolutions_by_pitch || {}
  };
  return base;
});

const DEFAULT_CABINET_IDS = {
  A: CABINETS.find((cab) => cab.id === "600x337.5")?.id || CABINETS[0]?.id,
  B: CABINETS.find((cab) => cab.id === "640x480")?.id || CABINETS[0]?.id,
  C: CABINETS.find((cab) => cab.id === "500x500")?.id || CABINETS[0]?.id,
};

const PITCHES_DEFAULT = [0.468, 0.625, 0.78125, 0.9375, 1.0, 1.25, 1.56, 1.8, 2.0, 2.5, 2.6, 2.9, 3.9, 4.8, 6.6, 7.8];

const CABINET_LOGO_MAX_SIZE_MM = 100;
const CABINET_LOGO_RELATIVE_PATH = "../public/images/pixel-wall-logo-color.png";
const SAMPLE_SCREEN_CONTENT_IMAGES = [
  { id: "sample-1-woman", label: "Women", path: "../public/images/sample-1-woman.jpeg" },
  { id: "sample-2-cat", label: "Cat", path: "../public/images/sample-2-cat.jpeg" },
  { id: "sample-3-nature", label: "Nature", path: "../public/images/sample-3-nature.jpg" },
  { id: "sample-4-chip", label: "Chip", path: "../public/images/sample-4-chip.jpg" },
  { id: "sample-5-car", label: "Car", path: "../public/images/sample-5-car.jpg" },
];

const RESOLUTION_STANDARDS = (() => {
  if (typeof PWLC_DATA === "undefined" || !PWLC_DATA || !Array.isArray(PWLC_DATA.resolution_standards)) {
    return [];
  }
  return PWLC_DATA.resolution_standards
    .map((entry) => {
      if (!entry) {
        return null;
      }
      const name = typeof entry.name === "string" ? entry.name.trim() : "";
      const width = Math.round(Number(entry.width));
      const height = Math.round(Number(entry.height));
      let total = Number(entry.total_pixels);
      if (!Number.isFinite(total) || total <= 0) {
        total = width * height;
      }
      if (!name || !Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0 || !Number.isFinite(total) || total <= 0) {
        return null;
      }
      const id = (typeof entry.id === "string" && entry.id) ? entry.id : `${name}-${width}x${height}`;
      return {
        id,
        name,
        width,
        height,
        total,
      };
    })
    .filter((item) => item)
    .sort((a, b) => {
      if (a.total === b.total) {
        return a.name.localeCompare(b.name);
      }
      return a.total - b.total;
    });
})();

function normalizeProcessorPort(port){
  if(!port || typeof port !== "object"){
    return null;
  }
  const count = Number(port.count);
  const capacity = Number(port.max_pixels_per_port);
  if(!Number.isFinite(count) || count <= 0 || !Number.isFinite(capacity) || capacity <= 0){
    return null;
  }
  return {
    count: Math.max(0, Math.floor(count)),
    maxPixelsPerPort: capacity,
  };
}

function normalizeProcessorStringList(value){
  if(Array.isArray(value)){
    return value
      .map((item)=> (typeof item === "string" ? item.trim() : ""))
      .filter((item)=>item);
  }
  if(typeof value === "string"){
    return value
      .split(/[\n,]+/)
      .map((item)=>item.trim())
      .filter((item)=>item);
  }
  return [];
}

function normalizeProcessorEntry(entry){
  if(!entry || typeof entry !== "object"){
    return null;
  }
  const brand = typeof entry.brand === "string" ? entry.brand.trim() : (typeof entry.manufacturer === "string" ? entry.manufacturer.trim() : "");
  const model = typeof entry.model === "string" ? entry.model.trim() : "";
  const maxPixelsMP = Number(entry.max_pixels);
  const maxWidth = Number(entry.max_width);
  const maxHeight = Number(entry.max_height);
  if(!brand || !model){
    return null;
  }
  if(!Number.isFinite(maxPixelsMP) || maxPixelsMP <= 0){
    return null;
  }
  const ethernet = normalizeProcessorPort(entry.ethernet_ports);
  const fiber = normalizeProcessorPort(entry.fiber_ports);
  return {
    brand,
    manufacturer: brand,
    model,
    maxPixelsMP,
    maxWidth: Number.isFinite(maxWidth) && maxWidth > 0 ? Math.round(maxWidth) : null,
    maxHeight: Number.isFinite(maxHeight) && maxHeight > 0 ? Math.round(maxHeight) : null,
    ethernet,
    fiber,
    inputPorts: normalizeProcessorStringList(entry.input_ports),
    features: normalizeProcessorStringList(entry.features),
  };
}

const PROCESSOR_CATALOG = (() => {
  if (typeof PWLC_DATA === "undefined" || !PWLC_DATA || !Array.isArray(PWLC_DATA.processors)) {
    return [];
  }
  return PWLC_DATA.processors
    .map((entry) => normalizeProcessorEntry(entry))
    .filter((item) => item)
    .sort((a, b) => {
      const brandCompare = a.brand.localeCompare(b.brand);
      if (brandCompare !== 0) {
        return brandCompare;
      }
      return a.model.localeCompare(b.model);
    });
})();

function computeProcessorRecommendation(processors, totalPixels, resX, resY){
  if(!processors || processors.length === 0){
    return null;
  }
  const totalPixelsMP = Number(totalPixels) / 1_000_000;
  if(!Number.isFinite(totalPixelsMP) || totalPixelsMP <= 0){
    return null;
  }
  const matches = [];
  let maxEthernet = null;
  let maxFiber = null;
  processors.forEach((proc)=>{
    if(!proc){
      return;
    }
    if(Number.isFinite(proc.maxPixelsMP) && proc.maxPixelsMP > 0 && proc.maxPixelsMP + 1e-6 < totalPixelsMP){
      return;
    }
    if(Number.isFinite(proc.maxWidth) && proc.maxWidth && Number.isFinite(resX) && resX > proc.maxWidth){
      return;
    }
    if(Number.isFinite(proc.maxHeight) && proc.maxHeight && Number.isFinite(resY) && resY > proc.maxHeight){
      return;
    }
    if(!proc.ethernet){
      return;
    }
    const perPort = proc.ethernet.maxPixelsPerPort;
    const count = proc.ethernet.count;
    if(!Number.isFinite(perPort) || perPort <= 0 || !Number.isFinite(count) || count <= 0){
      return;
    }
    const ethernetNeeded = Math.ceil(totalPixelsMP / perPort);
    if(!Number.isFinite(ethernetNeeded) || ethernetNeeded > count){
      return;
    }
    const match = {
      brand: proc.brand,
      manufacturer: proc.manufacturer,
      model: proc.model,
      ethernetPortsNeeded: ethernetNeeded,
    };
    if(proc.fiber && Number.isFinite(proc.fiber.maxPixelsPerPort) && proc.fiber.maxPixelsPerPort > 0 && Number.isFinite(proc.fiber.count) && proc.fiber.count > 0){
      const fiberNeeded = Math.ceil(totalPixelsMP / proc.fiber.maxPixelsPerPort);
      match.fiberPortsNeeded = fiberNeeded;
      if(fiberNeeded <= proc.fiber.count){
        if(maxFiber === null || fiberNeeded > maxFiber){
          maxFiber = fiberNeeded;
        }
      }
    }
    matches.push(match);
    if(maxEthernet === null || ethernetNeeded > maxEthernet){
      maxEthernet = ethernetNeeded;
    }
  });
  const brandGroups = new Map();
  matches.forEach((item)=>{
    const brand = item.brand || item.manufacturer || "";
    const brandKey = brand || "Unknown Brand";
    const model = item.model || "";
    if(!brandGroups.has(brandKey)){
      brandGroups.set(brandKey, new Set());
    }
    if(model){
      brandGroups.get(brandKey).add(model);
    }
  });
  const recommendedNames = matches.map((item)=>`${item.brand || item.manufacturer || ""} ${item.model}`.trim());
  const groupedNames = Array.from(brandGroups.entries())
    .sort((a, b)=>a[0].localeCompare(b[0]))
    .map(([brand, modelsSet])=>{
      const models = Array.from(modelsSet);
      models.sort((a, b)=>a.localeCompare(b));
      return `${brand} (${models.join(', ')})`;
    });
  const portSegments = [];
  if(maxEthernet !== null){
    portSegments.push(`${maxEthernet} Ethernet port${maxEthernet === 1 ? '' : 's'} needed`);
  }
  if(maxFiber !== null){
    portSegments.push(`${maxFiber} Fiber port${maxFiber === 1 ? '' : 's'} needed`);
  }
  const requirementText = portSegments.length > 0 ? portSegments.join(' | ') : `${fmt(totalPixelsMP, 2)} MP load`;
  const recommendedText = groupedNames.length > 0 ? `Recommended: ${groupedNames.join(', ')}` : 'Recommended: None available';
  return {
    totalPixelsMP,
    matches,
    requirementText,
    recommendedText,
    summary: `${requirementText} | ${recommendedText}`,
    recommendedNames,
    recommendedGroups: groupedNames,
    maxEthernetPorts: maxEthernet,
    maxFiberPorts: maxFiber,
  };
}

function getSpecForCab(cabId){ return SPEC_INDEX[cabId]; }
function getSupportedPitches(cabId){
  const spec = getSpecForCab(cabId);
  return spec && spec.common_pixel_pitches_mm && spec.common_pixel_pitches_mm.length ? spec.common_pixel_pitches_mm.slice() : PITCHES_DEFAULT;
}
function getCabinetTileResolution(cabId, pitch){
  const spec = getSpecForCab(cabId);
  if (!spec) return null;
  const key = String(pitch);
  const val = (spec.resolutions_by_pitch || {})[key];
  if (!val) return null;
  const [xs, ys] = val.split("x");
  const x = parseInt(xs, 10); const y = parseInt(ys, 10);
  if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
  return { x, y };
}

const FIT_MODES = [
  { id: "nearest", label: "Nearest (minimize area error)" },
  { id: "fit-within", label: "Fit Within (never exceed target)" },
  { id: "meet-or-exceed", label: "Meet or Exceed (never under target)" },
];

const SCREEN_CONTENT_FIT_OPTIONS = [
  { value: "stretch", label: "Stretch" },
  { value: "fill", label: "Fill" },
  { value: "crop", label: "Crop" },
];

const INSTALL_ENVIRONMENT_OPTIONS = [
  { value: "", label: "Select installation environment" },
  { value: "indoor", label: "Indoor" },
  { value: "outdoor", label: "Outdoor" },
  { value: "mixed", label: "Indoor & Outdoor" },
];

const SERVICE_SCOPE_OPTIONS = [
  { value: "", label: "Select service scope" },
  { value: "equipment-only", label: "Equipment only (no installation services)" },
  { value: "full-service", label: "Full service installation" },
];

const COUNTRY_OPTIONS = [
  { value: "", label: "Select country" },
  { value: "AF", label: "Afghanistan" },
  { value: "AL", label: "Albania" },
  { value: "DZ", label: "Algeria" },
  { value: "AS", label: "American Samoa" },
  { value: "AD", label: "Andorra" },
  { value: "AO", label: "Angola" },
  { value: "AI", label: "Anguilla" },
  { value: "AQ", label: "Antarctica" },
  { value: "AG", label: "Antigua and Barbuda" },
  { value: "AR", label: "Argentina" },
  { value: "AM", label: "Armenia" },
  { value: "AW", label: "Aruba" },
  { value: "AU", label: "Australia" },
  { value: "AT", label: "Austria" },
  { value: "AZ", label: "Azerbaijan" },
  { value: "BS", label: "Bahamas" },
  { value: "BH", label: "Bahrain" },
  { value: "BD", label: "Bangladesh" },
  { value: "BB", label: "Barbados" },
  { value: "BY", label: "Belarus" },
  { value: "BE", label: "Belgium" },
  { value: "BZ", label: "Belize" },
  { value: "BJ", label: "Benin" },
  { value: "BM", label: "Bermuda" },
  { value: "BT", label: "Bhutan" },
  { value: "BO", label: "Bolivia" },
  { value: "BQ", label: "Bonaire, Sint Eustatius and Saba" },
  { value: "BA", label: "Bosnia and Herzegovina" },
  { value: "BW", label: "Botswana" },
  { value: "BR", label: "Brazil" },
  { value: "IO", label: "British Indian Ocean Territory" },
  { value: "BN", label: "Brunei" },
  { value: "BG", label: "Bulgaria" },
  { value: "BF", label: "Burkina Faso" },
  { value: "BI", label: "Burundi" },
  { value: "KH", label: "Cambodia" },
  { value: "CM", label: "Cameroon" },
  { value: "CA", label: "Canada" },
  { value: "CV", label: "Cape Verde" },
  { value: "KY", label: "Cayman Islands" },
  { value: "CF", label: "Central African Republic" },
  { value: "TD", label: "Chad" },
  { value: "CL", label: "Chile" },
  { value: "CN", label: "China" },
  { value: "CX", label: "Christmas Island" },
  { value: "CC", label: "Cocos (Keeling) Islands" },
  { value: "CO", label: "Colombia" },
  { value: "KM", label: "Comoros" },
  { value: "CG", label: "Congo" },
  { value: "CD", label: "Congo (Democratic Republic)" },
  { value: "CK", label: "Cook Islands" },
  { value: "CR", label: "Costa Rica" },
  { value: "CI", label: "Côte d’Ivoire" },
  { value: "HR", label: "Croatia" },
  { value: "CU", label: "Cuba" },
  { value: "CW", label: "Curaçao" },
  { value: "CY", label: "Cyprus" },
  { value: "CZ", label: "Czechia" },
  { value: "DK", label: "Denmark" },
  { value: "DJ", label: "Djibouti" },
  { value: "DM", label: "Dominica" },
  { value: "DO", label: "Dominican Republic" },
  { value: "EC", label: "Ecuador" },
  { value: "EG", label: "Egypt" },
  { value: "SV", label: "El Salvador" },
  { value: "GQ", label: "Equatorial Guinea" },
  { value: "ER", label: "Eritrea" },
  { value: "EE", label: "Estonia" },
  { value: "SZ", label: "Eswatini" },
  { value: "ET", label: "Ethiopia" },
  { value: "FK", label: "Falkland Islands" },
  { value: "FO", label: "Faroe Islands" },
  { value: "FJ", label: "Fiji" },
  { value: "FI", label: "Finland" },
  { value: "FR", label: "France" },
  { value: "GF", label: "French Guiana" },
  { value: "PF", label: "French Polynesia" },
  { value: "GA", label: "Gabon" },
  { value: "GM", label: "Gambia" },
  { value: "GE", label: "Georgia" },
  { value: "DE", label: "Germany" },
  { value: "GH", label: "Ghana" },
  { value: "GI", label: "Gibraltar" },
  { value: "GR", label: "Greece" },
  { value: "GL", label: "Greenland" },
  { value: "GD", label: "Grenada" },
  { value: "GP", label: "Guadeloupe" },
  { value: "GU", label: "Guam" },
  { value: "GT", label: "Guatemala" },
  { value: "GG", label: "Guernsey" },
  { value: "GN", label: "Guinea" },
  { value: "GW", label: "Guinea-Bissau" },
  { value: "GY", label: "Guyana" },
  { value: "HT", label: "Haiti" },
  { value: "VA", label: "Holy See" },
  { value: "HN", label: "Honduras" },
  { value: "HK", label: "Hong Kong" },
  { value: "HU", label: "Hungary" },
  { value: "IS", label: "Iceland" },
  { value: "IN", label: "India" },
  { value: "ID", label: "Indonesia" },
  { value: "IR", label: "Iran" },
  { value: "IQ", label: "Iraq" },
  { value: "IE", label: "Ireland" },
  { value: "IM", label: "Isle of Man" },
  { value: "IL", label: "Israel" },
  { value: "IT", label: "Italy" },
  { value: "JM", label: "Jamaica" },
  { value: "JP", label: "Japan" },
  { value: "JE", label: "Jersey" },
  { value: "JO", label: "Jordan" },
  { value: "KZ", label: "Kazakhstan" },
  { value: "KE", label: "Kenya" },
  { value: "KI", label: "Kiribati" },
  { value: "KP", label: "Korea (North)" },
  { value: "KR", label: "Korea (South)" },
  { value: "KW", label: "Kuwait" },
  { value: "KG", label: "Kyrgyzstan" },
  { value: "LA", label: "Laos" },
  { value: "LV", label: "Latvia" },
  { value: "LB", label: "Lebanon" },
  { value: "LS", label: "Lesotho" },
  { value: "LR", label: "Liberia" },
  { value: "LY", label: "Libya" },
  { value: "LI", label: "Liechtenstein" },
  { value: "LT", label: "Lithuania" },
  { value: "LU", label: "Luxembourg" },
  { value: "MO", label: "Macao" },
  { value: "MG", label: "Madagascar" },
  { value: "MW", label: "Malawi" },
  { value: "MY", label: "Malaysia" },
  { value: "MV", label: "Maldives" },
  { value: "ML", label: "Mali" },
  { value: "MT", label: "Malta" },
  { value: "MH", label: "Marshall Islands" },
  { value: "MQ", label: "Martinique" },
  { value: "MR", label: "Mauritania" },
  { value: "MU", label: "Mauritius" },
  { value: "YT", label: "Mayotte" },
  { value: "MX", label: "Mexico" },
  { value: "FM", label: "Micronesia" },
  { value: "MD", label: "Moldova" },
  { value: "MC", label: "Monaco" },
  { value: "MN", label: "Mongolia" },
  { value: "ME", label: "Montenegro" },
  { value: "MS", label: "Montserrat" },
  { value: "MA", label: "Morocco" },
  { value: "MZ", label: "Mozambique" },
  { value: "MM", label: "Myanmar" },
  { value: "NA", label: "Namibia" },
  { value: "NR", label: "Nauru" },
  { value: "NP", label: "Nepal" },
  { value: "NL", label: "Netherlands" },
  { value: "NC", label: "New Caledonia" },
  { value: "NZ", label: "New Zealand" },
  { value: "NI", label: "Nicaragua" },
  { value: "NE", label: "Niger" },
  { value: "NG", label: "Nigeria" },
  { value: "NU", label: "Niue" },
  { value: "NF", label: "Norfolk Island" },
  { value: "MK", label: "North Macedonia" },
  { value: "MP", label: "Northern Mariana Islands" },
  { value: "NO", label: "Norway" },
  { value: "OM", label: "Oman" },
  { value: "PK", label: "Pakistan" },
  { value: "PW", label: "Palau" },
  { value: "PS", label: "Palestine State" },
  { value: "PA", label: "Panama" },
  { value: "PG", label: "Papua New Guinea" },
  { value: "PY", label: "Paraguay" },
  { value: "PE", label: "Peru" },
  { value: "PH", label: "Philippines" },
  { value: "PN", label: "Pitcairn" },
  { value: "PL", label: "Poland" },
  { value: "PT", label: "Portugal" },
  { value: "PR", label: "Puerto Rico" },
  { value: "QA", label: "Qatar" },
  { value: "RE", label: "Réunion" },
  { value: "RO", label: "Romania" },
  { value: "RU", label: "Russia" },
  { value: "RW", label: "Rwanda" },
  { value: "BL", label: "Saint Barthélemy" },
  { value: "SH", label: "Saint Helena" },
  { value: "KN", label: "Saint Kitts and Nevis" },
  { value: "LC", label: "Saint Lucia" },
  { value: "MF", label: "Saint Martin" },
  { value: "PM", label: "Saint Pierre and Miquelon" },
  { value: "VC", label: "Saint Vincent and the Grenadines" },
  { value: "WS", label: "Samoa" },
  { value: "SM", label: "San Marino" },
  { value: "ST", label: "Sao Tome and Principe" },
  { value: "SA", label: "Saudi Arabia" },
  { value: "SN", label: "Senegal" },
  { value: "RS", label: "Serbia" },
  { value: "SC", label: "Seychelles" },
  { value: "SL", label: "Sierra Leone" },
  { value: "SG", label: "Singapore" },
  { value: "SX", label: "Sint Maarten" },
  { value: "SK", label: "Slovakia" },
  { value: "SI", label: "Slovenia" },
  { value: "SB", label: "Solomon Islands" },
  { value: "SO", label: "Somalia" },
  { value: "ZA", label: "South Africa" },
  { value: "GS", label: "South Georgia and the South Sandwich Islands" },
  { value: "SS", label: "South Sudan" },
  { value: "ES", label: "Spain" },
  { value: "LK", label: "Sri Lanka" },
  { value: "SD", label: "Sudan" },
  { value: "SR", label: "Suriname" },
  { value: "SJ", label: "Svalbard and Jan Mayen" },
  { value: "SE", label: "Sweden" },
  { value: "CH", label: "Switzerland" },
  { value: "SY", label: "Syria" },
  { value: "TW", label: "Taiwan" },
  { value: "TJ", label: "Tajikistan" },
  { value: "TZ", label: "Tanzania" },
  { value: "TH", label: "Thailand" },
  { value: "TL", label: "Timor-Leste" },
  { value: "TG", label: "Togo" },
  { value: "TK", label: "Tokelau" },
  { value: "TO", label: "Tonga" },
  { value: "TT", label: "Trinidad and Tobago" },
  { value: "TN", label: "Tunisia" },
  { value: "TR", label: "Turkey" },
  { value: "TM", label: "Turkmenistan" },
  { value: "TC", label: "Turks and Caicos Islands" },
  { value: "TV", label: "Tuvalu" },
  { value: "UG", label: "Uganda" },
  { value: "UA", label: "Ukraine" },
  { value: "AE", label: "United Arab Emirates" },
  { value: "GB", label: "United Kingdom" },
  { value: "US", label: "United States" },
  { value: "UM", label: "United States Minor Outlying Islands" },
  { value: "UY", label: "Uruguay" },
  { value: "UZ", label: "Uzbekistan" },
  { value: "VU", label: "Vanuatu" },
  { value: "VE", label: "Venezuela" },
  { value: "VN", label: "Vietnam" },
  { value: "VG", label: "Virgin Islands (British)" },
  { value: "VI", label: "Virgin Islands (U.S.)" },
  { value: "WF", label: "Wallis and Futuna" },
  { value: "EH", label: "Western Sahara" },
  { value: "YE", label: "Yemen" },
  { value: "ZM", label: "Zambia" },
  { value: "ZW", label: "Zimbabwe" },
];

const RES_CHOICES = [
  { id: "any", label: "Any", target: null },
  { id: "720p", label: "720P HD: 1280 × 720", target: { x: 1280, y: 720 } },
  { id: "1080p", label: "1080P Full HD: 1920 × 1080", target: { x: 1920, y: 1080 } },
  { id: "2k", label: "2K: 2048 × 1080", target: { x: 2048, y: 1080 } },
  { id: "1440p", label: "1440P QHD: 2560 × 1440", target: { x: 2560, y: 1440 } },
  { id: "2160p", label: "2160P UHD: 3840 × 2160", target: { x: 3840, y: 2160 } },
  { id: "4k", label: "4K: 4096 × 2160", target: { x: 4096, y: 2160 } },
  { id: "5k", label: "5K: 5120 × 2880", target: { x: 5120, y: 2880 } },
  { id: "8k", label: "8K: 7680 × 4320", target: { x: 7680, y: 4320 } },
];

const MM_PER_M = 1000, MM_PER_IN = 25.4, MM_PER_FT = 304.8;
const FT_PER_M = MM_PER_M / MM_PER_FT;
const LENGTH_UNIT_OPTIONS = [
  { value: "mm", label: "Millimeters (mm)" },
  { value: "m", label: "Meters (m)" },
  { value: "in", label: "Inches (in)" },
  { value: "ft", label: "Feet (ft)" },
];
const TOOL_VERSION = "1.24.1";

function toMM(value, unit){ if (!Number.isFinite(value)) return 0; switch(unit){ case "m": return value*MM_PER_M; case "ft": return value*MM_PER_FT; case "in": return value*MM_PER_IN; default: return value; } }
function fromMM(mm, unit){ switch(unit){ case "m": return mm/MM_PER_M; case "ft": return mm/MM_PER_FT; case "in": return mm/MM_PER_IN; default: return mm; } }
function formatUnitInput(mmValue, unit){
  const converted = fromMM(mmValue, unit);
  if(!Number.isFinite(converted)){
    return "0";
  }
  let decimals = 0;
  if(unit === "m"){
    decimals = 3;
  } else if(unit === "in" || unit === "ft"){
    decimals = 3;
  }
  const factor = Math.pow(10, decimals);
  const rounded = decimals > 0 ? Math.round(converted * factor) / factor : Math.round(converted);
  return String(rounded);
}
function diagonalInches(w,h){ return Math.sqrt(w*w+h*h)/MM_PER_IN; }
function formatAspect(resX,resY){
  if(!resX||!resY) return "—";
  const r=resX/resY;
  const COMMON=[
    {x:16,y:9,label:"16:9"},{x:4,y:3,label:"4:3"},{x:21,y:9,label:"21:9"},{x:32,y:9,label:"32:9"},
    {x:2,y:1,label:"2:1"},{x:3,y:1,label:"3:1"},{x:3,y:4,label:"3:4"},{x:9,y:16,label:"9:16"},{x:1,y:1,label:"1:1"},
  ];
  let best=null;
  for(const cr of COMMON){ const err=Math.abs(r-cr.x/cr.y); if(!best||err<best.err){ best={label:cr.label,err}; } }
  if(best && best.err <= (best.label==="1:1" ? 0.02 : 0.01)) return best.label;
  return r.toFixed(2);
}
function computeGrid(dw,dh,cw,ch,mode){
  if(cw<=0||ch<=0) return {cols:0,rows:0};
  const rc=dw/cw, rr=dh/ch;
  let cols, rows;
  switch(mode){
    case "fit-within": cols=Math.max(1,Math.floor(rc)); rows=Math.max(1,Math.floor(rr)); break;
    case "meet-or-exceed": cols=Math.max(1,Math.ceil(rc)); rows=Math.max(1,Math.ceil(rr)); break;
    default: {
      const cands=[
        {c:Math.max(1,Math.floor(rc)),r:Math.max(1,Math.floor(rr))},
        {c:Math.max(1,Math.ceil(rc)),r:Math.max(1,Math.floor(rr))},
        {c:Math.max(1,Math.floor(rc)),r:Math.max(1,Math.ceil(rr))},
        {c:Math.max(1,Math.ceil(rc)),r:Math.max(1,Math.ceil(rr))},
      ];
      const target=dw*dh; let best=cands[0]; let bestErr=Infinity;
      for(const cand of cands){ const area=cand.c*cw*(cand.r*ch); const err=Math.abs(area-target); if(err<bestErr){ best=cand; bestErr=err; } }
      cols=best.c; rows=best.r; break;
    }
  }
  return {cols,rows};
}
function fmt(n,d=2){ if(!Number.isFinite(n)) return "—"; return n.toLocaleString(undefined,{maximumFractionDigits:d,minimumFractionDigits:0}); }
function fmtPitch(n){ if(!Number.isFinite(n)) return "—"; return `${n.toFixed(5).replace(/\.0+$/, "").replace(/(\.[0-9]*?)0+$/, "$1")} mm`; }
function ceilToDecimals(value,decimals=0){
  if(!Number.isFinite(value)) return null;
  const factor = 10 ** decimals;
  const fudge = 1e-9;
  return Math.ceil((value * factor) - fudge) / factor;
}
function fmtFixed(value,decimals){
  if(!Number.isFinite(value)) return "—";
  return value.toLocaleString(undefined,{minimumFractionDigits:decimals,maximumFractionDigits:decimals});
}
function humanPixels(n){ if(!Number.isFinite(n)) return "—"; if(n>=1_000_000) return `${(n/1_000_000).toFixed(2)} MPix`; if(n>=1_000) return `${(n/1_000).toFixed(2)} KPix`; return `${n.toLocaleString()} px`; }
function findResolutionStandard(totalPixels){
  if(!Number.isFinite(totalPixels) || totalPixels <= 0 || RESOLUTION_STANDARDS.length === 0){
    return null;
  }
  let match = null;
  for(const standard of RESOLUTION_STANDARDS){
    if(totalPixels >= standard.total){
      if(!match || standard.total > match.total){
        match = standard;
      }
    }
  }
  return match;
}
function formatPixelsWithStandard(totalPixels){
  const base = humanPixels(totalPixels);
  if(!Number.isFinite(totalPixels) || totalPixels <= 0){
    return base;
  }
  const standard = findResolutionStandard(totalPixels);
  if(standard){
    return `${base} (${standard.name})`;
  }
  return base;
}
function resScore(resX,resY,target){ if(!target) return Infinity; const dx=Math.abs(resX-target.x)/target.x; const dy=Math.abs(resY-target.y)/target.y; return dx+dy; }
function ensurePitchSupported(current,allowed){ if(allowed.length===0) return current; if(allowed.includes(current)) return current; let best=allowed[0]; let bestErr=Math.abs(current-best); for(const p of allowed){ const e=Math.abs(current-p); if(e<bestErr){ best=p; bestErr=e; } } return best; }

// Cookie helpers
function setCookie(name, value, days){
  const d=new Date(); d.setTime(d.getTime()+days*24*60*60*1000);
  document.cookie = name + "=" + encodeURIComponent(value) + ";expires=" + d.toUTCString() + ";path=/";
}
function getCookie(name){
  const n = name + "=";
  const ca = document.cookie.split(';');
  for(let i=0;i<ca.length;i++){ let c=ca[i]; while(c.charAt(0)==' ') c=c.substring(1); if(c.indexOf(n)==0) return decodeURIComponent(c.substring(n.length,c.length)); }
  return "";
}

const MATERIAL_SYMBOLS_STYLE_ID = "pwlc-material-symbols-style";
const MATERIAL_SYMBOLS_LINK_ID = "pwlc-material-symbols-outlined";
const MATERIAL_SYMBOLS_LINK_HREF = "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap";
function ensureMaterialSymbolFont(){
  if (typeof document === "undefined") {
    return;
  }
  if (!document.getElementById(MATERIAL_SYMBOLS_LINK_ID)) {
    const link = document.createElement("link");
    link.id = MATERIAL_SYMBOLS_LINK_ID;
    link.rel = "stylesheet";
    link.href = MATERIAL_SYMBOLS_LINK_HREF;
    document.head.appendChild(link);
  }
  if (!document.getElementById(MATERIAL_SYMBOLS_STYLE_ID)) {
    const style = document.createElement("style");
    style.id = MATERIAL_SYMBOLS_STYLE_ID;
    style.textContent = [
      ".material-symbols-outlined{",
      "font-family:'Material Symbols Outlined';",
      "font-weight:normal;",
      "font-style:normal;",
      "font-size:24px;",
      "line-height:1;",
      "letter-spacing:normal;",
      "text-transform:none;",
      "display:inline-block;",
      "white-space:nowrap;",
      "word-wrap:normal;",
      "direction:ltr;",
      "font-feature-settings:'liga';",
      "-webkit-font-feature-settings:'liga';",
      "-webkit-font-smoothing:antialiased;",
      "font-variation-settings:'FILL' 0,'wght' 400,'GRAD' 0,'opsz' 24;",
      "}"
    ].join("");
    document.head.appendChild(style);
  }
}


function vec3Sub(a,b){
  return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
}

function vec3Dot(a,b){
  return a.x * b.x + a.y * b.y + a.z * b.z;
}

function vec3Cross(a,b){
  return {
    x: a.y * b.z - a.z * b.y,
    y: a.z * b.x - a.x * b.z,
    z: a.x * b.y - a.y * b.x,
  };
}

function vec3Length(a){
  return Math.sqrt(Math.max(0, vec3Dot(a, a)));
}

function vec3Normalize(a){
  const len = vec3Length(a);
  if(len <= 1e-6){
    return { x: 0, y: 0, z: 0 };
  }
  return { x: a.x / len, y: a.y / len, z: a.z / len };
}

function createCameraBasis(position, target, up){
  const forward = vec3Normalize(vec3Sub(target, position));
  let right = vec3Cross(up, forward);
  if(vec3Length(right) <= 1e-6){
    right = { x: 1, y: 0, z: 0 };
  } else {
    right = vec3Normalize(right);
  }
  let trueUp = vec3Cross(forward, right);
  if(vec3Length(trueUp) <= 1e-6){
    trueUp = { x: 0, y: 0, z: 1 };
  } else {
    trueUp = vec3Normalize(trueUp);
  }
  return { position, forward, right, up: trueUp };
}

function project3DPoint(point, camera, viewport, fovRad){
  const relative = vec3Sub(point, camera.position);
  const cx = vec3Dot(relative, camera.right);
  const cy = vec3Dot(relative, camera.up);
  const cz = vec3Dot(relative, camera.forward);
  if(cz <= 1e-3){
    return null;
  }
  const focal = (viewport.height / 2) / Math.tan(fovRad / 2);
  const screenX = viewport.width / 2 + (cx * focal) / cz;
  const screenY = viewport.height / 2 - (cy * focal) / cz;
  return { x: screenX, y: screenY, depth: cz };
}

function computeHomographyMatrix(srcPoints, dstPoints){
  if(!Array.isArray(srcPoints) || !Array.isArray(dstPoints) || srcPoints.length !== 4 || dstPoints.length !== 4){
    return null;
  }
  const matrix = new Array(8);
  for(let i = 0; i < 4; i += 1){
    const sx = srcPoints[i].x;
    const sy = srcPoints[i].y;
    const dx = dstPoints[i].x;
    const dy = dstPoints[i].y;
    matrix[i * 2] = [sx, sy, 1, 0, 0, 0, -dx * sx, -dx * sy, dx];
    matrix[i * 2 + 1] = [0, 0, 0, sx, sy, 1, -dy * sx, -dy * sy, dy];
  }
  const dimension = 8;
  for(let col = 0; col < dimension; col += 1){
    let pivotRow = col;
    let pivotValue = Math.abs(matrix[pivotRow][col]);
    for(let row = col + 1; row < dimension; row += 1){
      const candidate = Math.abs(matrix[row][col]);
      if(candidate > pivotValue){
        pivotRow = row;
        pivotValue = candidate;
      }
    }
    if(pivotValue <= 1e-12){
      return null;
    }
    if(pivotRow !== col){
      const temp = matrix[col];
      matrix[col] = matrix[pivotRow];
      matrix[pivotRow] = temp;
    }
    const pivot = matrix[col][col];
    for(let c = col; c < 9; c += 1){
      matrix[col][c] /= pivot;
    }
    for(let row = 0; row < dimension; row += 1){
      if(row === col){
        continue;
      }
      const factor = matrix[row][col];
      if(Math.abs(factor) <= 1e-12){
        continue;
      }
      for(let c = col; c < 9; c += 1){
        matrix[row][c] -= factor * matrix[col][c];
      }
    }
  }
  const homography = new Array(9);
  for(let i = 0; i < dimension; i += 1){
    homography[i] = matrix[i][8];
  }
  homography[8] = 1;
  return homography;
}

function invertHomographyMatrix(matrix){
  if(!Array.isArray(matrix) || matrix.length !== 9){
    return null;
  }
  const a = matrix[0];
  const b = matrix[1];
  const c = matrix[2];
  const d = matrix[3];
  const e = matrix[4];
  const f = matrix[5];
  const g = matrix[6];
  const h = matrix[7];
  const i = matrix[8];
  const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
  if(Math.abs(det) <= 1e-18){
    return null;
  }
  const invDet = 1 / det;
  return [
    (e * i - f * h) * invDet,
    (c * h - b * i) * invDet,
    (b * f - c * e) * invDet,
    (f * g - d * i) * invDet,
    (a * i - c * g) * invDet,
    (c * d - a * f) * invDet,
    (d * h - e * g) * invDet,
    (b * g - a * h) * invDet,
    (a * e - b * d) * invDet,
  ];
}

function isPointInConvexPolygon(point, polygon){
  if(!polygon || polygon.length < 3){
    return false;
  }
  let hasPositive = false;
  let hasNegative = false;
  const tolerance = 1e-6;
  for(let i = 0; i < polygon.length; i += 1){
    const current = polygon[i];
    const next = polygon[(i + 1) % polygon.length];
    const edgeX = next.x - current.x;
    const edgeY = next.y - current.y;
    const pointX = point.x - current.x;
    const pointY = point.y - current.y;
    const cross = edgeX * pointY - edgeY * pointX;
    if(Math.abs(cross) <= tolerance){
      continue;
    }
    if(cross > 0){
      hasPositive = true;
    } else {
      hasNegative = true;
    }
    if(hasPositive && hasNegative){
      return false;
    }
  }
  return true;
}

function bilinearSample(imageData, width, height, x, y){
  const clampedX = Math.max(0, Math.min(width - 1, x));
  const clampedY = Math.max(0, Math.min(height - 1, y));
  const x0 = Math.floor(clampedX);
  const y0 = Math.floor(clampedY);
  const x1 = Math.min(width - 1, x0 + 1);
  const y1 = Math.min(height - 1, y0 + 1);
  const tx = clampedX - x0;
  const ty = clampedY - y0;
  const idx00 = (y0 * width + x0) * 4;
  const idx10 = (y0 * width + x1) * 4;
  const idx01 = (y1 * width + x0) * 4;
  const idx11 = (y1 * width + x1) * 4;
  const w00 = (1 - tx) * (1 - ty);
  const w10 = tx * (1 - ty);
  const w01 = (1 - tx) * ty;
  const w11 = tx * ty;
  return [
    imageData[idx00] * w00 + imageData[idx10] * w10 + imageData[idx01] * w01 + imageData[idx11] * w11,
    imageData[idx00 + 1] * w00 + imageData[idx10 + 1] * w10 + imageData[idx01 + 1] * w01 + imageData[idx11 + 1] * w11,
    imageData[idx00 + 2] * w00 + imageData[idx10 + 2] * w10 + imageData[idx01 + 2] * w01 + imageData[idx11 + 2] * w11,
    imageData[idx00 + 3] * w00 + imageData[idx10 + 3] * w10 + imageData[idx01 + 3] * w01 + imageData[idx11 + 3] * w11,
  ];
}

const APP_ASSET_BASE_URL = (()=>{
  if(typeof document === "undefined"){
    return "";
  }
  const scripts = document.getElementsByTagName("script");
  for(let i=scripts.length - 1;i>=0;i-=1){
    const srcAttr = scripts[i] && scripts[i].getAttribute ? scripts[i].getAttribute("src") : null;
    if(srcAttr && srcAttr.indexOf("/assets/app.jsx") !== -1){
      const withoutQuery = srcAttr.split("?")[0] || "";
      return withoutQuery.replace(/app\.jsx$/, "");
    }
  }
  return "";
})();

function resolveAssetUrl(relativePath){
  if(!relativePath){
    return "";
  }
  if(/^https?:/i.test(relativePath)){
    return relativePath;
  }
  if(APP_ASSET_BASE_URL){
    return APP_ASSET_BASE_URL + relativePath.replace(/^\//, "");
  }
  return relativePath;
}

const HTML2CANVAS_CDN = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
const JSPDF_CDN = "https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js";
// Cache script loading promises so we only download each library once per session.
const externalScriptPromises = {};

function loadExternalScriptOnce(src){
  if(externalScriptPromises[src]){
    return externalScriptPromises[src];
  }
  externalScriptPromises[src] = new Promise((resolve, reject)=>{
    if(typeof document === "undefined"){
      reject(new Error("Document is unavailable"));
      return;
    }
    const existing = Array.prototype.slice.call(document.getElementsByTagName("script"))
      .find((node)=>node && node.getAttribute && node.getAttribute("src") === src);
    if(existing){
      if(existing.getAttribute("data-loaded") === "true"){
        resolve();
        return;
      }
      existing.addEventListener("load",()=>resolve());
      existing.addEventListener("error",(event)=>{
        delete externalScriptPromises[src];
        reject(event);
      });
      return;
    }
    const script = document.createElement("script");
    script.src = src;
    script.async = true;
    script.crossOrigin = "anonymous";
    script.referrerPolicy = "no-referrer";
    script.onload = ()=>{
      script.setAttribute("data-loaded", "true");
      resolve();
    };
    script.onerror = (event)=>{
      delete externalScriptPromises[src];
      reject(new Error(`Failed to load script: ${src}`));
    };
    document.head.appendChild(script);
  });
  return externalScriptPromises[src];
}

async function ensureHtml2Canvas(){
  if(typeof window !== "undefined" && typeof window.html2canvas === "function"){
    return window.html2canvas;
  }
  await loadExternalScriptOnce(HTML2CANVAS_CDN);
  if(typeof window === "undefined" || typeof window.html2canvas !== "function"){
    throw new Error("html2canvas is unavailable after loading script");
  }
  return window.html2canvas;
}

async function ensureJsPDF(){
  if(typeof window !== "undefined" && window.jspdf && typeof window.jspdf.jsPDF === "function"){
    return window.jspdf.jsPDF;
  }
  await loadExternalScriptOnce(JSPDF_CDN);
  if(typeof window === "undefined" || !window.jspdf || typeof window.jspdf.jsPDF !== "function"){
    throw new Error("jsPDF is unavailable after loading script");
  }
  return window.jspdf.jsPDF;
}

function isElementVisible(el){
  if(!el){
    return false;
  }
  if(typeof el.getClientRects === "function" && el.getClientRects().length === 0){
    return false;
  }
  const style = window.getComputedStyle ? window.getComputedStyle(el) : null;
  if(style && (style.visibility === "hidden" || style.display === "none")){
    return false;
  }
  if(typeof el.offsetParent === "undefined"){
    return true;
  }
  return el.offsetParent !== null || style?.position === "fixed";
}

function mat4Multiply(a,b){
  const out = new Array(16);
  for(let column=0;column<4;column+=1){
    const colOffset = column * 4;
    const b0 = b[colOffset + 0];
    const b1 = b[colOffset + 1];
    const b2 = b[colOffset + 2];
    const b3 = b[colOffset + 3];
    out[colOffset + 0] = a[0] * b0 + a[4] * b1 + a[8] * b2 + a[12] * b3;
    out[colOffset + 1] = a[1] * b0 + a[5] * b1 + a[9] * b2 + a[13] * b3;
    out[colOffset + 2] = a[2] * b0 + a[6] * b1 + a[10] * b2 + a[14] * b3;
    out[colOffset + 3] = a[3] * b0 + a[7] * b1 + a[11] * b2 + a[15] * b3;
  }
  return out;
}

function mat4FromTRS(translation = [0,0,0], rotation = [0,0,0,1], scale = [1,1,1]){
  const [tx,ty,tz] = translation;
  const [rx,ry,rz,rw] = rotation;
  const [sx,sy,sz] = scale;
  const x2 = rx + rx;
  const y2 = ry + ry;
  const z2 = rz + rz;
  const xx = rx * x2;
  const xy = rx * y2;
  const xz = rx * z2;
  const yy = ry * y2;
  const yz = ry * z2;
  const zz = rz * z2;
  const wx = rw * x2;
  const wy = rw * y2;
  const wz = rw * z2;
  return [
    (1 - (yy + zz)) * sx,
    (xy + wz) * sx,
    (xz - wy) * sx,
    0,
    (xy - wz) * sy,
    (1 - (xx + zz)) * sy,
    (yz + wx) * sy,
    0,
    (xz + wy) * sz,
    (yz - wx) * sz,
    (1 - (xx + yy)) * sz,
    0,
    tx,
    ty,
    tz,
    1,
  ];
}

function mat4TransformPoint(matrix, point){
  const x = point[0];
  const y = point[1];
  const z = point[2];
  const nx = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12];
  const ny = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13];
  const nz = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14];
  const nw = matrix[3] * x + matrix[7] * y + matrix[11] * z + matrix[15];
  if(nw && Math.abs(nw) > 1e-6 && Math.abs(nw - 1) > 1e-6){
    return [nx / nw, ny / nw, nz / nw];
  }
  return [nx, ny, nz];
}

function parseBinaryGLTF(arrayBuffer){
  if(!(arrayBuffer instanceof ArrayBuffer)){
    throw new Error("Expected ArrayBuffer for GLB parsing");
  }
  const view = new DataView(arrayBuffer);
  const magic = view.getUint32(0, true);
  if(magic !== 0x46546c67){ // 'glTF'
    throw new Error("Invalid GLB header");
  }
  const version = view.getUint32(4, true);
  if(version !== 2){
    throw new Error(`Unsupported GLB version: ${version}`);
  }
  const length = view.getUint32(8, true);
  let offset = 12;
  let json = null;
  let binary = null;
  const decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-8") : null;
  while(offset < length){
    const chunkLength = view.getUint32(offset, true);
    offset += 4;
    const chunkType = view.getUint32(offset, true);
    offset += 4;
    const chunkStart = offset;
    const typeStr = String.fromCharCode(
      chunkType & 0xFF,
      (chunkType >> 8) & 0xFF,
      (chunkType >> 16) & 0xFF,
      (chunkType >> 24) & 0xFF
    );
    if(typeStr === "JSON"){
      if(!decoder){
        throw new Error("TextDecoder is not available");
      }
      const chunkView = new Uint8Array(arrayBuffer, chunkStart, chunkLength);
      const jsonText = decoder.decode(chunkView);
      json = JSON.parse(jsonText);
    } else if(typeStr === "BIN\0"){
      binary = new Uint8Array(arrayBuffer, chunkStart, chunkLength);
    }
    offset += chunkLength;
  }
  return { json, binary };
}

function getComponentSize(componentType){
  switch(componentType){
    case 5120: // BYTE
    case 5121: // UNSIGNED_BYTE
      return 1;
    case 5122: // SHORT
    case 5123: // UNSIGNED_SHORT
      return 2;
    case 5125: // UNSIGNED_INT
    case 5126: // FLOAT
      return 4;
    default:
      return 0;
  }
}

function readVec3Accessor(gltf, dataView, accessorIndex){
  if(!gltf || !gltf.accessors || !Array.isArray(gltf.accessors)){
    return null;
  }
  const accessor = gltf.accessors[accessorIndex];
  if(!accessor || accessor.type !== "VEC3" || accessor.componentType !== 5126){
    return null;
  }
  const bufferViewIndex = accessor.bufferView;
  if(!gltf.bufferViews || !Array.isArray(gltf.bufferViews)){
    return null;
  }
  const bufferView = gltf.bufferViews[bufferViewIndex];
  if(!bufferView){
    return null;
  }
  const stride = bufferView.byteStride || 12;
  const count = accessor.count || 0;
  const baseOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
  const result = new Array(count);
  let offset = baseOffset;
  for(let i=0;i<count;i+=1){
    const x = dataView.getFloat32(offset, true);
    const y = dataView.getFloat32(offset + 4, true);
    const z = dataView.getFloat32(offset + 8, true);
    result[i] = [x, y, z];
    offset += stride;
  }
  return result;
}

function readIndicesAccessor(gltf, dataView, accessorIndex, fallbackCount){
  if(typeof accessorIndex !== "number"){
    const sequential = new Array(fallbackCount);
    for(let i=0;i<fallbackCount;i+=1){
      sequential[i] = i;
    }
    return sequential;
  }
  if(!gltf || !gltf.accessors || !Array.isArray(gltf.accessors)){
    return null;
  }
  const accessor = gltf.accessors[accessorIndex];
  if(!accessor || accessor.type !== "SCALAR"){
    return null;
  }
  const bufferViewIndex = accessor.bufferView;
  if(!gltf.bufferViews || !Array.isArray(gltf.bufferViews)){
    return null;
  }
  const bufferView = gltf.bufferViews[bufferViewIndex];
  if(!bufferView){
    return null;
  }
  const componentSize = getComponentSize(accessor.componentType);
  if(!componentSize){
    return null;
  }
  const stride = bufferView.byteStride || componentSize;
  const count = accessor.count || 0;
  const baseOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
  const result = new Array(count);
  let offset = baseOffset;
  for(let i=0;i<count;i+=1){
    let value = 0;
    switch(accessor.componentType){
      case 5121:
        value = dataView.getUint8(offset);
        break;
      case 5123:
        value = dataView.getUint16(offset, true);
        break;
      case 5125:
        value = dataView.getUint32(offset, true);
        break;
      default:
        return null;
    }
    result[i] = value;
    offset += stride;
  }
  return result;
}

function extractModelFromGLB(arrayBuffer, options = {}){
  const { json: gltf, binary } = parseBinaryGLTF(arrayBuffer);
  if(!gltf || !binary){
    throw new Error("GLB file is missing required chunks");
  }
  const nodes = Array.isArray(gltf.nodes) ? gltf.nodes : [];
  const meshes = Array.isArray(gltf.meshes) ? gltf.meshes : [];
  if(nodes.length === 0 || meshes.length === 0){
    return { triangles: [], lightDirection: { x: 0, y: 0, z: 1 }, heightMm: 0 };
  }
  const parentMap = nodes.map(()=>null);
  nodes.forEach((node, index)=>{
    const children = Array.isArray(node && node.children) ? node.children : [];
    children.forEach((childIndex)=>{
      if(typeof childIndex === "number" && childIndex >= 0 && childIndex < parentMap.length){
        parentMap[childIndex] = index;
      }
    });
  });
  const matrixCache = new Array(nodes.length);
  const worldMatrixForNode = (index)=>{
    if(matrixCache[index]){
      return matrixCache[index];
    }
    const node = nodes[index] || {};
    let localMatrix = null;
    if(Array.isArray(node.matrix) && node.matrix.length === 16){
      localMatrix = node.matrix.slice();
    } else {
      localMatrix = mat4FromTRS(node.translation, node.rotation, node.scale);
    }
    const parentIndex = parentMap[index];
    if(typeof parentIndex === "number" && parentIndex >= 0){
      const parentMatrix = worldMatrixForNode(parentIndex);
      localMatrix = mat4Multiply(parentMatrix, localMatrix);
    }
    matrixCache[index] = localMatrix;
    return localMatrix;
  };

  const dataView = new DataView(binary.buffer, binary.byteOffset, binary.byteLength);
  const axisMap = options.axisMap || { x: 0, y: 1, z: 2 };
  const axisFlip = options.axisFlip || { x: 1, y: 1, z: 1 };
  const triangles = [];
  let minX = Infinity;
  let minY = Infinity;
  let minZ = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;
  let maxZ = -Infinity;

  const mapAxes = (vector)=>{
    const arr = [vector[0], vector[1], vector[2]];
    return {
      x: arr[axisMap.x] * (axisFlip.x || 1),
      y: arr[axisMap.y] * (axisFlip.y || 1),
      z: arr[axisMap.z] * (axisFlip.z || 1),
    };
  };

  const updateBounds = (point)=>{
    if(point.x < minX) minX = point.x;
    if(point.y < minY) minY = point.y;
    if(point.z < minZ) minZ = point.z;
    if(point.x > maxX) maxX = point.x;
    if(point.y > maxY) maxY = point.y;
    if(point.z > maxZ) maxZ = point.z;
  };

  nodes.forEach((node, nodeIndex)=>{
    if(typeof node.mesh !== "number"){
      return;
    }
    const mesh = meshes[node.mesh];
    if(!mesh || !Array.isArray(mesh.primitives)){
      return;
    }
    const worldMatrix = worldMatrixForNode(nodeIndex);
    mesh.primitives.forEach((primitive)=>{
      if(!primitive || (primitive.mode != null && primitive.mode !== 4)){
        return;
      }
      if(!primitive.attributes || typeof primitive.attributes.POSITION !== "number"){
        return;
      }
      const positions = readVec3Accessor(gltf, dataView, primitive.attributes.POSITION);
      if(!positions || positions.length === 0){
        return;
      }
      const indices = readIndicesAccessor(gltf, dataView, primitive.indices, positions.length);
      if(!indices || indices.length < 3){
        return;
      }
      for(let i=0;i<indices.length;i+=3){
        const ia = indices[i];
        const ib = indices[i+1];
        const ic = indices[i+2];
        if(!(positions[ia] && positions[ib] && positions[ic])){
          continue;
        }
        const pa = mapAxes(mat4TransformPoint(worldMatrix, positions[ia]));
        const pb = mapAxes(mat4TransformPoint(worldMatrix, positions[ib]));
        const pc = mapAxes(mat4TransformPoint(worldMatrix, positions[ic]));
        updateBounds(pa);
        updateBounds(pb);
        updateBounds(pc);
        triangles.push({ vertices: [pa, pb, pc] });
      }
    });
  });

  if(!triangles.length){
    return { triangles: [], lightDirection: { x: 0, y: 0, z: 1 }, heightMm: 0 };
  }

  const centerAxes = options.centerAxes || { x: true, y: true };
  const alignBase = options.alignBase !== false;
  const centerX = centerAxes.x ? ((minX + maxX) / 2) : 0;
  const centerY = centerAxes.y ? ((minY + maxY) / 2) : 0;
  const baseZ = alignBase ? minZ : 0;
  const heightUnits = Math.max(1e-6, maxZ - minZ);
  let scale = typeof options.unitsToMm === "number" && options.unitsToMm > 0 ? options.unitsToMm : 1000;
  if(typeof options.targetHeightMm === "number" && options.targetHeightMm > 0){
    scale = options.targetHeightMm / heightUnits;
  }
  const translation = options.translation || { x: 0, y: 0, z: 0 };

  triangles.forEach((triangle)=>{
    triangle.vertices = triangle.vertices.map((vertex)=>{
      const adjusted = {
        x: (vertex.x - centerX),
        y: (vertex.y - centerY),
        z: vertex.z - baseZ,
      };
      return {
        x: adjusted.x * scale + translation.x,
        y: adjusted.y * scale + translation.y,
        z: adjusted.z * scale + translation.z,
      };
    });
    const edgeAB = vec3Sub(triangle.vertices[1], triangle.vertices[0]);
    const edgeAC = vec3Sub(triangle.vertices[2], triangle.vertices[0]);
    triangle.normal = vec3Normalize(vec3Cross(edgeAB, edgeAC));
  });

  const lightDirectionBase = options.lightDirection || { x: -0.25, y: -0.6, z: 1 };
  let lightDirection = vec3Normalize(lightDirectionBase);
  if(!(lightDirection.x || lightDirection.y || lightDirection.z)){
    lightDirection = { x: 0, y: 0, z: 1 };
  }

  return {
    triangles,
    lightDirection,
    heightMm: heightUnits * scale,
  };
}

async function loadGLBModel(url, options){
  const response = await fetch(url, { credentials: "same-origin" });
  if(!response.ok){
    throw new Error(`Failed to load GLB model (${response.status})`);
  }
  const buffer = await response.arrayBuffer();
  return extractModelFromGLB(buffer, options);
}


const MIN_POLAR_ANGLE = 0.12;
const MAX_POLAR_ANGLE = Math.PI - 0.12;
const WALL_CLEARANCE_MM = 2000;
const SCREEN_THICKNESS_MM = 40;
const SCREEN_BACK_OFFSET_MM = 1;
const RAD_TO_DEG = 180 / Math.PI;
const ROOM_GRID_SPACING_MM = 1000;
const ROOM_GRID_MAX_LINES = 200;
const ROOM_LIMIT_MM = 100000;
const MIN_ROOM_DIMENSION_MM = 1;
const DEFAULT_WALL_WIDTH_MM = 10000;
const DEFAULT_WALL_HEIGHT_MM = 4000;
const CAMERA_MIN_RADIUS_MM = 500;
const CAMERA_BACK_LIMIT_MM = ROOM_LIMIT_MM;
const CAMERA_PAN_BUFFER_MM = 1000;

const BODY_MODEL_LOAD_OPTIONS = {
  axisMap: { x: 0, y: 2, z: 1 },
  centerAxes: { x: true, y: true },
  alignBase: true,
  targetHeightMm: 1820,
  translation: { x: -1000, y: 2000, z: 0 },
  lightDirection: { x: -0.3, y: -0.55, z: 1.1 },
};

const BODY_MODEL_FILL_LIGHT = { r: 182, g: 208, b: 255 };
const BODY_MODEL_FILL_DARK = { r: 46, g: 72, b: 116 };
const BODY_MODEL_STROKE_LIGHT = { r: 222, g: 236, b: 255 };
const BODY_MODEL_STROKE_DARK = { r: 28, g: 48, b: 86 };

function clamp(value, min, max){
  if(!Number.isFinite(value)){
    return min;
  }
  return Math.min(Math.max(value, min), max);
}

function randomInRange(min, max){
  if(!Number.isFinite(min)){
    return Number.isFinite(max) ? max : 0;
  }
  if(!Number.isFinite(max)){
    return min;
  }
  if(min === max){
    return min;
  }
  const lower = Math.min(min, max);
  const upper = Math.max(min, max);
  return lower + (upper - lower) * Math.random();
}

function randomSign(){
  return Math.random() < 0.5 ? -1 : 1;
}

function mixRgba(colorA, colorB, t, alpha){
  const ratio = clamp(t, 0, 1);
  const r = Math.round(colorA.r + (colorB.r - colorA.r) * ratio);
  const g = Math.round(colorA.g + (colorB.g - colorA.g) * ratio);
  const b = Math.round(colorA.b + (colorB.b - colorA.b) * ratio);
  return `rgba(${r},${g},${b},${alpha})`;
}

function wrapAngle(angle){
  const twoPi = Math.PI * 2;
  let result = angle % twoPi;
  if(result < -Math.PI){
    result += twoPi;
  } else if(result > Math.PI){
    result -= twoPi;
  }
  return result;
}

function shortestAngleDifference(target, current){
  let diff = target - current;
  const twoPi = Math.PI * 2;
  while(diff > Math.PI){
    diff -= twoPi;
  }
  while(diff < -Math.PI){
    diff += twoPi;
  }
  return diff;
}

function easeInOutCubic(t){
  if(t <= 0){
    return 0;
  }
  if(t >= 1){
    return 1;
  }
  return t < 0.5
    ? 4 * t * t * t
    : 1 - Math.pow(-2 * t + 2, 3) / 2;
}

function interpolateNumber(from, to, progress){
  const start = Number.isFinite(from) ? from : 0;
  const end = Number.isFinite(to) ? to : 0;
  return start + (end - start) * progress;
}

function interpolateTarget(from, to, progress){
  const safeFrom = from || { x: 0, y: 0, z: 0 };
  const safeTo = to || { x: 0, y: 0, z: 0 };
  return {
    x: interpolateNumber(safeFrom.x, safeTo.x, progress),
    y: interpolateNumber(safeFrom.y, safeTo.y, progress),
    z: interpolateNumber(safeFrom.z, safeTo.z, progress),
  };
}

function interpolateAngle(from, to, progress){
  if(!Number.isFinite(from) || !Number.isFinite(to)){
    return 0;
  }
  const diff = shortestAngleDifference(to, from);
  return wrapAngle(from + diff * progress);
}

function clampRoomDimension(value){
  if(!Number.isFinite(value)){
    return MIN_ROOM_DIMENSION_MM;
  }
  return clamp(value, MIN_ROOM_DIMENSION_MM, ROOM_LIMIT_MM);
}

function clampOrbitTarget(scene, target){
  const safeTarget = target || { x: 0, y: 0, z: 0 };
  if(!scene){
    return { ...safeTarget };
  }
  const halfWallWidth = scene.wallWidth / 2;
  const maxDepth = Math.min(scene.floorDepth, CAMERA_BACK_LIMIT_MM);
  const maxDepthWithBuffer = Math.max(0, maxDepth - CAMERA_PAN_BUFFER_MM);
  return {
    x: clamp(safeTarget.x, -halfWallWidth, halfWallWidth),
    y: clamp(safeTarget.y, 0, maxDepthWithBuffer),
    z: clamp(safeTarget.z, 0, scene.wallHeight),
  };
}

function computeOrbitLimits(scene, target){
  if(!scene){
    return { minRadius: CAMERA_MIN_RADIUS_MM, maxRadius: CAMERA_BACK_LIMIT_MM };
  }
  const minRadius = Math.max(scene.minOrbitRadius ?? CAMERA_MIN_RADIUS_MM, CAMERA_MIN_RADIUS_MM);
  const maxDepth = Math.min(scene.floorDepth, CAMERA_BACK_LIMIT_MM);
  const depthAllowance = Math.max(minRadius, maxDepth - target.y);
  const maxRadius = Math.max(minRadius, Math.min(scene.maxOrbitRadius ?? CAMERA_BACK_LIMIT_MM, depthAllowance));
  return { minRadius, maxRadius };
}

function clampOrbitState(scene, state){
  if(!state){
    return state;
  }
  const target = clampOrbitTarget(scene, state.target);
  const { minRadius, maxRadius } = computeOrbitLimits(scene, target);
  const azimuth = wrapAngle(typeof state.azimuth === 'number' ? state.azimuth : 0);
  const polar = clamp(typeof state.polar === 'number' ? state.polar : Math.PI / 2, MIN_POLAR_ANGLE, MAX_POLAR_ANGLE);
  const radius = clamp(typeof state.radius === 'number' ? state.radius : minRadius, minRadius, maxRadius);
  const defaultRadius = clamp(typeof state.defaultRadius === 'number' ? state.defaultRadius : radius, minRadius, maxRadius);
  return {
    ...state,
    target,
    azimuth,
    polar,
    radius,
    minRadius,
    maxRadius,
    defaultRadius,
  };
}

function buildOrbitStateFromScene(scene, overrides = {}){
  if(!scene){
    const fallbackTarget = { x: 0, y: 0, z: 1800 };
    const fallbackRadius = 10000;
    const fallbackPolar = clamp(Math.PI / 2, MIN_POLAR_ANGLE, MAX_POLAR_ANGLE);
    return {
      target: fallbackTarget,
      radius: fallbackRadius,
      azimuth: 0,
      polar: fallbackPolar,
      minRadius: CAMERA_MIN_RADIUS_MM,
      maxRadius: 40000,
      defaultRadius: fallbackRadius,
      ...overrides,
    };
  }
  const target = clampOrbitTarget(scene, scene.defaultTarget);
  const cameraDistance = clamp(scene.cameraDistance, scene.minOrbitRadius ?? CAMERA_MIN_RADIUS_MM, scene.maxOrbitRadius ?? CAMERA_BACK_LIMIT_MM);
  const position = {
    x: target.x,
    y: target.y + cameraDistance,
    z: scene.cameraHeight,
  };
  const dx = position.x - target.x;
  const dy = position.y - target.y;
  const dz = position.z - target.z;
  const radius = Math.max(1, Math.sqrt(dx * dx + dy * dy + dz * dz));
  const azimuth = Math.atan2(dx, dy);
  const polar = clamp(Math.acos(clamp(dz / radius, -0.9999, 0.9999)), MIN_POLAR_ANGLE, MAX_POLAR_ANGLE);
  const baseState = {
    target,
    radius,
    azimuth,
    polar,
    defaultRadius: radius,
    minRadius: scene.minOrbitRadius,
    maxRadius: scene.maxOrbitRadius,
    ...overrides,
  };
  return clampOrbitState(scene, baseState);
}

function buildAutoFitOrbitState(scene, viewport, overrides = {}){
  if(!scene){
    return { ...buildOrbitStateFromScene(null), ...overrides };
  }
  const target = clampOrbitTarget(scene, scene.defaultTarget);
  const { minRadius, maxRadius } = computeOrbitLimits(scene, target);
  const desiredCameraHeight = 1800;
  const maxHeight = Math.max(desiredCameraHeight, scene.wallHeight ?? desiredCameraHeight);
  const cameraHeight = clamp(desiredCameraHeight, 0, maxHeight);
  const diffZ = cameraHeight - target.z;
  const viewportWidth = Number.isFinite(viewport?.width) ? viewport.width : 0;
  const viewportHeight = Number.isFinite(viewport?.height) ? viewport.height : 0;
  const baseRadius = Math.max(minRadius, Math.abs(diffZ) + 1);
  const safeBaseRadius = clamp(baseRadius, minRadius, maxRadius);
  const basePolarRaw = Math.acos(clamp(diffZ / Math.max(safeBaseRadius, 1), -0.9999, 0.9999));
  const fallbackState = clampOrbitState(scene, {
    target,
    radius: safeBaseRadius,
    azimuth: 0,
    polar: clamp(basePolarRaw, MIN_POLAR_ANGLE, MAX_POLAR_ANGLE),
    defaultRadius: safeBaseRadius,
    minRadius,
    maxRadius,
    ...overrides,
  });
  if(!(viewportWidth > 0) || !(viewportHeight > 0)){
    return fallbackState;
  }

  const screenHalfWidth = (scene.screenWidth ?? 0) / 2;
  const screenBottom = scene.clearanceMm ?? 0;
  const screenTop = screenBottom + (scene.screenHeight ?? 0);
  const screenBackY = SCREEN_BACK_OFFSET_MM;
  const screenFrontY = SCREEN_BACK_OFFSET_MM + SCREEN_THICKNESS_MM;
  const pointsToProject = [
    { x: -screenHalfWidth, y: screenFrontY, z: screenBottom },
    { x: screenHalfWidth, y: screenFrontY, z: screenBottom },
    { x: screenHalfWidth, y: screenFrontY, z: screenTop },
    { x: -screenHalfWidth, y: screenFrontY, z: screenTop },
    { x: -screenHalfWidth, y: screenBackY, z: screenBottom },
    { x: screenHalfWidth, y: screenBackY, z: screenBottom },
    { x: screenHalfWidth, y: screenBackY, z: screenTop },
    { x: -screenHalfWidth, y: screenBackY, z: screenTop },
  ];
  const marginRatio = 0.06;
  const fovRad = Math.PI / 3;

  const evaluateRadius = (radius)=>{
    if(!Number.isFinite(radius)){
      return null;
    }
    const clampedRadius = clamp(radius, minRadius, maxRadius);
    if(!(clampedRadius > Math.abs(diffZ))){
      return null;
    }
    const horizontalSq = clampedRadius * clampedRadius - diffZ * diffZ;
    if(!(horizontalSq > 1e-3)){
      return null;
    }
    const horizontalRadius = Math.sqrt(horizontalSq);
    const cosPolarRaw = clamp(diffZ / clampedRadius, -0.9999, 0.9999);
    const polar = Math.acos(cosPolarRaw);
    if(polar < MIN_POLAR_ANGLE || polar > MAX_POLAR_ANGLE){
      return null;
    }
    const cameraPosition = {
      x: target.x,
      y: target.y + horizontalRadius,
      z: cameraHeight,
    };
    const camera = createCameraBasis(cameraPosition, target, { x: 0, y: 0, z: 1 });
    const viewportInfo = { width: viewportWidth, height: viewportHeight };
    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;
    let visible = true;
    for(const point of pointsToProject){
      const projected = project3DPoint(point, camera, viewportInfo, fovRad);
      if(!projected){
        visible = false;
        break;
      }
      minX = Math.min(minX, projected.x);
      maxX = Math.max(maxX, projected.x);
      minY = Math.min(minY, projected.y);
      maxY = Math.max(maxY, projected.y);
    }
    if(!visible){
      const state = {
        target,
        radius: clampedRadius,
        azimuth: 0,
        polar,
        defaultRadius: clampedRadius,
        minRadius,
        maxRadius,
        ...overrides,
      };
      return { fits: false, state };
    }
    const marginX = viewportWidth * marginRatio;
    const marginY = viewportHeight * marginRatio;
    const fits =
      minX >= marginX &&
      maxX <= (viewportWidth - marginX) &&
      minY >= marginY &&
      maxY <= (viewportHeight - marginY);
    const state = {
      target,
      radius: clampedRadius,
      azimuth: 0,
      polar,
      defaultRadius: clampedRadius,
      minRadius,
      maxRadius,
      ...overrides,
    };
    return { fits, state };
  };

  const baseEval = evaluateRadius(safeBaseRadius);
  if(!baseEval){
    return fallbackState;
  }
  if(baseEval.fits){
    return clampOrbitState(scene, baseEval.state);
  }

  const maxEval = evaluateRadius(maxRadius);
  if(!maxEval){
    return clampOrbitState(scene, baseEval.state);
  }
  if(!maxEval.fits){
    return clampOrbitState(scene, maxEval.state);
  }

  let low = safeBaseRadius;
  let high = maxRadius;
  let bestState = maxEval.state;
  for(let i = 0; i < 40 && (high - low) > 0.5; i += 1){
    const mid = (low + high) / 2;
    const result = evaluateRadius(mid);
    if(result && result.fits){
      bestState = result.state;
      high = mid;
    } else {
      low = mid;
    }
  }

  return clampOrbitState(scene, bestState);
}

function orbitStateToCameraPosition(orbit){
  if(!orbit){
    return { x: 0, y: 0, z: 0 };
  }
  const sinPolar = Math.sin(orbit.polar);
  const cosPolar = Math.cos(orbit.polar);
  const sinAzimuth = Math.sin(orbit.azimuth);
  const cosAzimuth = Math.cos(orbit.azimuth);
  const horizontalRadius = orbit.radius * sinPolar;
  return {
    x: orbit.target.x + horizontalRadius * sinAzimuth,
    y: orbit.target.y + horizontalRadius * cosAzimuth,
    z: orbit.target.z + orbit.radius * cosPolar,
  };
}

function PixelCalculator(){
  const { useState, useEffect, useMemo } = React;
  const [activeTab, setActiveTab] = useState("visual");
  const [desiredW,setDesiredW]=useState(4.8);
  const [desiredH,setDesiredH]=useState(2.7);
  const [unit,setUnit]=useState("m");
  const [fitMode,setFitMode]=useState("nearest");
  const [desiredRes,setDesiredRes]=useState("any");
  const [visualScreenDesignCollapsed, setVisualScreenDesignCollapsed] = useState(false);
  const [visualContentCollapsed, setVisualContentCollapsed] = useState(true);
  const [screenContentEnabled, setScreenContentEnabled] = useState(false);
  const [exportingDesign, setExportingDesign] = useState(false);
  const [exportStatus, setExportStatus] = useState(null);
  const [screenContentImage, setScreenContentImage] = useState(SAMPLE_SCREEN_CONTENT_IMAGES[0]);
  const [screenContentFit, setScreenContentFit] = useState("stretch");
  const [screenContentSampleId, setScreenContentSampleId] = useState("");
  const [hideCabinetGridLines, setHideCabinetGridLines] = useState(false);

  useEffect(()=>{
    // Clear the export status when switching between calculator modes.
    setExportStatus(null);
  },[activeTab]);

  const [cabA,setCabA]=useState(DEFAULT_CABINET_IDS.A);
  const [cabB,setCabB]=useState(DEFAULT_CABINET_IDS.B);
  const [cabC,setCabC]=useState(DEFAULT_CABINET_IDS.C);

  const [pitchA,setPitchA]=useState(1.5);
  const [pitchB,setPitchB]=useState(1.5);
  const [pitchC,setPitchC]=useState(1.5);
  const manualPitchRef = React.useRef({ A: false, B: false, C: false });

  const [designCounter, setDesignCounter] = useState(()=>INITIAL_COUNTER);
  const incrementCounter = React.useCallback((delta)=>{
    if(!delta || delta <= 0) return;
    setDesignCounter(prev=>prev + delta);
    if (typeof PWLC_AJAX === "undefined" || !PWLC_AJAX.ajax_url || !PWLC_AJAX.nonce) {
      return;
    }
    const params = new URLSearchParams();
    params.append("action", "pwlc_increment_counter");
    params.append("nonce", PWLC_AJAX.nonce);
    params.append("delta", String(delta));
    fetch(PWLC_AJAX.ajax_url, {
      method: "POST",
      credentials: "same-origin",
      headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
      body: params.toString(),
    })
      .then((res)=>{ if(!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); })
      .then((json)=>{
        if(json && json.success && json.data && typeof json.data.count === "number"){
          setDesignCounter(json.data.count);
        }
      })
      .catch((err)=>{
        if (typeof console !== "undefined" && console.warn) {
          console.warn("Failed to persist design counter", err);
        }
      });
  },[]);
  const lastDesignRef = React.useRef({ A: null, B: null, C: null });
  const designInitRef = React.useRef(false);
  const lastMaxRowsRef = React.useRef(Infinity);

  const [manualCab, setManualCab] = useState(DEFAULT_CABINET_IDS.A);
  const cabinetsById = useMemo(()=>Object.fromEntries(CABINETS.map((c)=>[c.id, c])),[]);
  const [manualPitch, setManualPitch] = useState(1.5);
  const [manualColsInput, setManualColsInput] = useState("3");
  const [manualRowsInput, setManualRowsInput] = useState("3");
  const [visualColsInput, setVisualColsInput] = useState("3");
  const [visualRowsInput, setVisualRowsInput] = useState("3");
  const manualDesignLastRef = React.useRef(null);
  const manualDesignInitRef = React.useRef(false);
  const visualDesignLastRef = React.useRef(null);
  const visualDesignInitRef = React.useRef(false);
  const [hoveredDesign, setHoveredDesign] = useState(null);
  const hoverTimeoutRef = React.useRef(null);
  const [designActionStatus, setDesignActionStatus] = useState(null);
  const designActionTimeoutRef = React.useRef(null);
  const visualContainerRef = React.useRef(null);
  const [visualCanvasEl, setVisualCanvasEl] = useState(null);
  const visualSummaryRef = React.useRef(null);
  const screenDesignRef = React.useRef(null);
  const manualDesignRef = React.useRef(null);
  const [visualOrbitState, setVisualOrbitState] = useState(()=>buildOrbitStateFromScene(null));
  const visualOrbitStateRef = React.useRef(visualOrbitState);
  const visualOrbitTargetRef = React.useRef(visualOrbitState);
  const visualIntroAnimationRef = React.useRef({
    started: false,
    completed: false,
    active: false,
    startTime: null,
    durationMs: 2000,
    startState: null,
    finalState: null,
  });
  const lastSceneDefaultTargetRef = React.useRef({ x: 0, y: 0, z: 0 });
  const visualOrbitDragStartRef = React.useRef(null);
  const visualPointerModeRef = React.useRef(null);
  const visualPointerStartRef = React.useRef({ x: 0, y: 0 });
  const visualSceneRef = React.useRef(null);
  const [visualViewport, setVisualViewport] = useState({ width: 0, height: 0 });
  const [visualContainerEl, setVisualContainerEl] = useState(null);
  const [visualFloorClearanceInput, setVisualFloorClearanceInput] = useState("1");
  const [visualFloorClearanceUnit, setVisualFloorClearanceUnit] = useState("m");
  const [visualWallHeightInput, setVisualWallHeightInput] = useState("4");
  const [visualWallHeightUnit, setVisualWallHeightUnit] = useState("m");
  const [visualWallWidthInput, setVisualWallWidthInput] = useState("10");
  const [visualWallWidthUnit, setVisualWallWidthUnit] = useState("m");
  const [visualPersonEnabled, setVisualPersonEnabled] = useState(true);
  const [visualWallHeightCommittedInput, setVisualWallHeightCommittedInput] = useState("4");
  const [visualWallWidthCommittedInput, setVisualWallWidthCommittedInput] = useState("10");
  const [visualRowsError, setVisualRowsError] = useState(null);
  const [wallWidthWarning, setWallWidthWarning] = useState(null);
  const [bodyModel, setBodyModel] = useState(null);
  const [cabinetLogoAsset, setCabinetLogoAsset] = useState(null);
  const screenContentFileInputRef = React.useRef(null);
  const screenContentLoadTokenRef = React.useRef(0);
  const screenContentObjectUrlRef = React.useRef(null);
  const screenTextureBufferRef = React.useRef({ canvas: null, ctx: null, lastKey: null, width: 0, height: 0 });
  const attachVisualContainer = React.useCallback((node)=>{
    visualContainerRef.current = node;
    setVisualContainerEl(node);
  },[]);
  const attachVisualCanvas = React.useCallback((node)=>{
    setVisualCanvasEl(node);
  },[]);

  const handleExportDesign = React.useCallback(async ()=>{
    // Avoid starting another export while one is in progress.
    if(exportingDesign){
      return;
    }
    if(typeof window === "undefined"){
      return;
    }
    // Collect the currently visible sections that should be exported.
    const captureTargets = [];
    if(activeTab === "visual" && visualContainerEl && isElementVisible(visualContainerEl)){
      captureTargets.push({ element: visualContainerEl });
    }
    let designElement = null;
    if(activeTab === "visual"){
      designElement = visualSummaryRef.current;
    } else if(activeTab === "screen"){
      designElement = screenDesignRef.current;
    } else if(activeTab === "manual"){
      designElement = manualDesignRef.current;
    }
    if(designElement && isElementVisible(designElement)){
      captureTargets.push({ element: designElement });
    }
    if(captureTargets.length === 0){
      // Let the user know we need visible content before exporting.
      setExportStatus({ type: "error", message: "No exportable design content is visible yet." });
      return;
    }
    try {
      // Update UI state before running the async export steps.
      setExportingDesign(true);
      setExportStatus({ type: "info", message: "Generating PDF…" });
      // Load html2canvas and jsPDF on demand from the CDN.
      const html2canvas = await ensureHtml2Canvas();
      const JsPDFConstructor = await ensureJsPDF();
      const scale = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
      const capturedImages = [];
      for(const target of captureTargets){
        try {
          // Render each DOM node into an off-screen canvas.
          const canvas = await html2canvas(target.element, {
            backgroundColor: "#ffffff",
            useCORS: true,
            scale,
          });
          capturedImages.push({ title: target.title, dataUrl: canvas.toDataURL("image/png") });
        } catch(captureError){
          if(typeof console !== "undefined" && console.error){
            console.error("Failed to capture export section", captureError);
          }
        }
      }
      if(capturedImages.length === 0){
        throw new Error("No sections captured");
      }
      const pdf = new JsPDFConstructor({ orientation: "portrait", unit: "pt", format: "letter" });
      const marginX = 48;
      const marginTop = 60;
      const marginBottom = 60;
      let currentY = marginTop;
      const pageWidth = pdf.internal.pageSize.getWidth();
      const pageHeight = pdf.internal.pageSize.getHeight();
      const availableWidth = pageWidth - marginX * 2;
      const exportDate = new Date();
      const modeLabel = activeTab === "visual" ? "Visual Mode" : activeTab === "screen" ? "Screen-Based Mode" : "Manual Mode";
      pdf.setFont("helvetica", "bold");
      pdf.setFontSize(20);
      pdf.text("Pixel Calculator Design Export", pageWidth / 2, currentY, { align: "center" });
      currentY += 24;
      pdf.setFont("helvetica", "normal");
      pdf.setFontSize(12);
      pdf.setTextColor(55);
      let generatedLabel = "";
      try {
        generatedLabel = exportDate.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
      } catch(_) {
        generatedLabel = `${exportDate.toLocaleDateString()} ${exportDate.toLocaleTimeString()}`;
      }
      const subtitleLine = `${modeLabel} • Generated ${generatedLabel}`;
      pdf.text(subtitleLine, pageWidth / 2, currentY, { align: "center" });
      currentY += 22;
      pdf.setTextColor(0);

      // Precompute section dimensions so we can scale them to fit a single letter page.
      const sectionSpacing = 18;
      const sections = capturedImages.map((capture)=>{
        const imageProps = pdf.getImageProperties(capture.dataUrl);
        const naturalWidth = imageProps?.width || 1;
        const naturalHeight = imageProps?.height || 1;
        const imageHeight = naturalWidth ? (naturalHeight * availableWidth) / naturalWidth : availableWidth;
        return { ...capture, imageHeight };
      });
      const totalHeadingHeight = sections.reduce((sum, section)=>sum + (section.title ? 18 : 0), 0);
      const totalImageHeight = sections.reduce((sum, section)=>sum + section.imageHeight, 0);
      const totalSpacingUnits = sections.length > 1 ? sections.length - 1 : 0;
      const availableContentHeight = pageHeight - marginBottom - currentY;
      let imageScale = 1;
      if(totalImageHeight > 0){
        // Constrain the image stack to the remaining vertical space.
        const availableForImages = Math.max(availableContentHeight - totalHeadingHeight, 0);
        if(availableForImages < totalImageHeight){
          imageScale = Math.max(availableForImages / totalImageHeight, 0);
        }
      }
      const usedImageHeight = totalImageHeight * imageScale;
      const remainingHeightForSpacing = Math.max(availableContentHeight - totalHeadingHeight - usedImageHeight, 0);
      // Allow the inter-section spacing to shrink if the captures are tall.
      const spacingScale = totalSpacingUnits > 0 ? Math.min(1, remainingHeightForSpacing / (sectionSpacing * totalSpacingUnits)) : 1;
      const adjustedSpacing = sectionSpacing * spacingScale;

      sections.forEach((section, index)=>{
        if(section.title){
          pdf.setFont("helvetica", "bold");
          pdf.setFontSize(14);
          pdf.text(section.title, marginX, currentY);
          pdf.setFont("helvetica", "normal");
          pdf.setFontSize(12);
          currentY += 18;
        }
        const imageWidth = availableWidth * imageScale;
        const imageHeight = section.imageHeight * imageScale;
        const imageX = marginX + (availableWidth - imageWidth) / 2;
        pdf.addImage(section.dataUrl, "PNG", imageX, currentY, imageWidth, imageHeight, undefined, "FAST");
        currentY += imageHeight;
        if(index < sections.length - 1){
          currentY += adjustedSpacing;
        }
      });
      const footerText = `Pixel Calculator for LED Video Wall V${TOOL_VERSION} — PixelCal.com — © ${exportDate.getFullYear()} PixelCal.com. All rights reserved.`;
      pdf.setFont("helvetica", "normal");
      pdf.setFontSize(10);
      pdf.setTextColor(100);
      pdf.text(footerText, pageWidth / 2, pageHeight - 30, { align: "center" });
      const timestamp = `${exportDate.getFullYear()}${String(exportDate.getMonth() + 1).padStart(2, "0")}${String(exportDate.getDate()).padStart(2, "0")}_${String(exportDate.getHours()).padStart(2, "0")}${String(exportDate.getMinutes()).padStart(2, "0")}${String(exportDate.getSeconds()).padStart(2, "0")}`;
      const filename = `PixelCal_Design_${timestamp}.pdf`;
      pdf.save(filename);
      setExportStatus({ type: "success", message: "Export started – check your downloads." });
    } catch(exportError){
      if(typeof console !== "undefined" && console.error){
        console.error("Failed to export design", exportError);
      }
      setExportStatus({ type: "error", message: "Unable to export design right now. Please try again." });
    } finally {
      setExportingDesign(false);
    }
  },[activeTab, exportingDesign, visualContainerEl]);

  const handleVisualReset = React.useCallback(()=>{
    const scene = visualSceneRef.current;
    let nextState = null;
    if(scene){
      nextState = buildAutoFitOrbitState(scene, visualViewport);
    } else {
      nextState = buildOrbitStateFromScene(null);
    }
    if(!nextState){
      return;
    }
    visualOrbitDragStartRef.current = null;
    visualOrbitTargetRef.current = nextState;
    if(nextState.target){
      lastSceneDefaultTargetRef.current = nextState.target;
    }
  },[visualViewport]);

  React.useEffect(()=>{
    ensureMaterialSymbolFont();
  },[]);

  React.useEffect(()=>{
    let cancelled = false;
    const url = resolveAssetUrl(CABINET_LOGO_RELATIVE_PATH);
    if(!url){
      setCabinetLogoAsset(null);
      return ()=>{
        cancelled = true;
      };
    }
    const img = new Image();
    img.decoding = "async";
    img.onload = ()=>{
      if(cancelled){
        return;
      }
      const naturalWidth = img.naturalWidth || img.width || 0;
      const naturalHeight = img.naturalHeight || img.height || 0;
      if(naturalWidth > 0 && naturalHeight > 0){
        setCabinetLogoAsset({ image: img, width: naturalWidth, height: naturalHeight, aspectRatio: naturalWidth / naturalHeight });
      } else {
        setCabinetLogoAsset({ image: img, width: naturalWidth, height: naturalHeight, aspectRatio: 1 });
      }
    };
    img.onerror = (error)=>{
      if(cancelled){
        return;
      }
      if(typeof console !== "undefined" && console.warn){
        console.warn("Failed to load cabinet logo image", error);
      }
      setCabinetLogoAsset(null);
    };
    img.src = url;
    return ()=>{
      cancelled = true;
      img.onload = null;
      img.onerror = null;
    };
  },[]);

  React.useEffect(()=>{
    return ()=>{
      if(hoverTimeoutRef.current){
        clearTimeout(hoverTimeoutRef.current);
      }
      if(designActionTimeoutRef.current){
        clearTimeout(designActionTimeoutRef.current);
      }
    };
  },[]);

  React.useEffect(()=>{
    visualOrbitStateRef.current = visualOrbitState;
  },[visualOrbitState]);

  React.useEffect(()=>{
    const handle = setTimeout(()=>{
      setVisualWallHeightCommittedInput(visualWallHeightInput);
    }, 1000);
    return ()=>clearTimeout(handle);
  },[visualWallHeightInput]);

  React.useEffect(()=>{
    const handle = setTimeout(()=>{
      setVisualWallWidthCommittedInput(visualWallWidthInput);
    }, 1000);
    return ()=>clearTimeout(handle);
  },[visualWallWidthInput]);

  React.useEffect(()=>{
    let frame = null;
    let lastTime = null;
    const step = (time)=>{
      frame = requestAnimationFrame(step);
      const scene = visualSceneRef.current;
      if(!scene){
        lastTime = time;
        return;
      }
      if(lastTime === null){
        lastTime = time;
        return;
      }
      const deltaMs = time - lastTime;
      lastTime = time;
      const dt = Math.min(Math.max(deltaMs / 1000, 0), 0.05);
      const introStatus = visualIntroAnimationRef.current;
      if(introStatus && introStatus.active){
        if(introStatus.startTime === null){
          introStatus.startTime = time;
        }
        const duration = introStatus.durationMs || 1;
        const elapsed = time - introStatus.startTime;
        const rawProgress = clamp(elapsed / duration, 0, 1);
        const eased = easeInOutCubic(rawProgress);
        const startState = introStatus.startState;
        const finalState = introStatus.finalState;
        if(startState && finalState){
          const desiredTarget = interpolateTarget(startState.target, finalState.target, eased);
          const desiredRadius = interpolateNumber(startState.radius, finalState.radius, eased);
          const desiredPolar = interpolateNumber(startState.polar, finalState.polar, eased);
          const desiredAzimuth = interpolateAngle(startState.azimuth, finalState.azimuth, eased);
          const desiredDefaultRadius = interpolateNumber(
            startState.defaultRadius ?? startState.radius,
            finalState.defaultRadius ?? finalState.radius,
            eased
          );
          const desiredState = clampOrbitState(scene, {
            ...finalState,
            target: desiredTarget,
            radius: desiredRadius,
            polar: desiredPolar,
            azimuth: desiredAzimuth,
            defaultRadius: desiredDefaultRadius,
          });
          visualOrbitTargetRef.current = desiredState;
          visualOrbitStateRef.current = desiredState;
          setVisualOrbitState(desiredState);
          if(rawProgress >= 1){
            introStatus.active = false;
            introStatus.completed = true;
            introStatus.startTime = null;
            introStatus.startState = null;
            introStatus.finalState = finalState;
            visualOrbitTargetRef.current = finalState;
            visualOrbitStateRef.current = finalState;
            setVisualOrbitState(finalState);
          }
        } else {
          introStatus.active = false;
          introStatus.startTime = null;
          introStatus.startState = null;
          introStatus.started = false;
          introStatus.completed = false;
        }
        return;
      }
      const currentRaw = visualOrbitStateRef.current;
      const targetRaw = visualOrbitTargetRef.current;
      if(!currentRaw || !targetRaw){
        return;
      }
      const current = clampOrbitState(scene, currentRaw);
      const target = clampOrbitState(scene, targetRaw);
      visualOrbitTargetRef.current = target;
      const blend = 1 - Math.exp(-8 * dt);
      const currentTarget = current.target || { x: 0, y: 0, z: 0 };
      const targetTarget = target.target || { x: 0, y: 0, z: 0 };
      const interpolatedTarget = {
        x: currentTarget.x + (targetTarget.x - currentTarget.x) * blend,
        y: currentTarget.y + (targetTarget.y - currentTarget.y) * blend,
        z: currentTarget.z + (targetTarget.z - currentTarget.z) * blend,
      };
      const nextTarget = clampOrbitTarget(scene, interpolatedTarget);
      const nextAzimuth = wrapAngle(current.azimuth + shortestAngleDifference(target.azimuth, current.azimuth) * blend);
      const desiredPolar = clamp(current.polar + shortestAngleDifference(target.polar, current.polar) * blend, MIN_POLAR_ANGLE, MAX_POLAR_ANGLE);
      const limits = computeOrbitLimits(scene, nextTarget);
      const targetRadius = clamp(target.radius, limits.minRadius, limits.maxRadius);
      const nextRadius = clamp(current.radius + (targetRadius - current.radius) * blend, limits.minRadius, limits.maxRadius);
      const nextDefaultRadius = clamp(target.defaultRadius ?? current.defaultRadius ?? nextRadius, limits.minRadius, limits.maxRadius);
      const epsilon = 1e-2;
      const changed =
        Math.abs(nextTarget.x - currentTarget.x) > epsilon ||
        Math.abs(nextTarget.y - currentTarget.y) > epsilon ||
        Math.abs(nextTarget.z - currentTarget.z) > epsilon ||
        Math.abs(nextAzimuth - current.azimuth) > 1e-4 ||
        Math.abs(desiredPolar - current.polar) > 1e-4 ||
        Math.abs(nextRadius - current.radius) > epsilon ||
        Math.abs((limits.minRadius ?? 0) - (current.minRadius ?? 0)) > epsilon ||
        Math.abs((limits.maxRadius ?? 0) - (current.maxRadius ?? 0)) > epsilon;
      if(changed){
        const nextState = clampOrbitState(scene, {
          target: nextTarget,
          azimuth: nextAzimuth,
          polar: desiredPolar,
          radius: nextRadius,
          minRadius: limits.minRadius,
          maxRadius: limits.maxRadius,
          defaultRadius: nextDefaultRadius,
        });
        visualOrbitStateRef.current = nextState;
        setVisualOrbitState(nextState);
      }
    };
    frame = requestAnimationFrame(step);
    return ()=>{
      if(frame !== null){
        cancelAnimationFrame(frame);
      }
    };
  },[]);

  React.useEffect(()=>{
    if(!visualContainerEl){
      return;
    }
    const el = visualContainerEl;
    const updateSize=()=>{
      setVisualViewport({ width: el.clientWidth, height: el.clientHeight });
    };
    updateSize();
    if(typeof ResizeObserver !== "undefined"){
      const observer=new ResizeObserver((entries)=>{
        const entry=entries && entries[0];
        if(entry){
          const { width, height } = entry.contentRect;
          setVisualViewport({ width, height });
        }
      });
      observer.observe(el);
      return ()=>observer.disconnect();
    }
    if(typeof window !== "undefined"){
      window.addEventListener('resize', updateSize);
      return ()=>window.removeEventListener('resize', updateSize);
    }
    return undefined;
  },[visualContainerEl]);

  const manualCols = React.useMemo(()=>{
    const parsed = parseInt(manualColsInput, 10);
    if(!Number.isFinite(parsed) || parsed < 1){
      return 1;
    }
    return parsed;
  },[manualColsInput]);

  const manualRows = React.useMemo(()=>{
    const parsed = parseInt(manualRowsInput, 10);
    if(!Number.isFinite(parsed) || parsed < 1){
      return 1;
    }
    return parsed;
  },[manualRowsInput]);

  const visualCols = React.useMemo(()=>{
    const parsed = parseInt(visualColsInput, 10);
    if(!Number.isFinite(parsed) || parsed < 1){
      return 1;
    }
    return parsed;
  },[visualColsInput]);

  const visualRows = React.useMemo(()=>{
    const parsed = parseInt(visualRowsInput, 10);
    if(!Number.isFinite(parsed) || parsed < 1){
      return 1;
    }
    return parsed;
  },[visualRowsInput]);

  const visualFloorClearanceMm = React.useMemo(()=>{
    const parsed = parseFloat(visualFloorClearanceInput);
    if(!Number.isFinite(parsed) || parsed < 0){
      return 0;
    }
    const mmValue = Math.max(0, toMM(parsed, visualFloorClearanceUnit));
    return clamp(mmValue, 0, ROOM_LIMIT_MM);
  },[visualFloorClearanceInput, visualFloorClearanceUnit]);

  const visualWallHeightMm = React.useMemo(()=>{
    const parsed = parseFloat(visualWallHeightCommittedInput);
    const mmValue = Number.isFinite(parsed) && parsed > 0
      ? toMM(parsed, visualWallHeightUnit)
      : DEFAULT_WALL_HEIGHT_MM;
    return clampRoomDimension(mmValue);
  },[visualWallHeightCommittedInput, visualWallHeightUnit]);

  const visualWallWidthMm = React.useMemo(()=>{
    const parsed = parseFloat(visualWallWidthCommittedInput);
    const mmValue = Number.isFinite(parsed) && parsed > 0
      ? toMM(parsed, visualWallWidthUnit)
      : DEFAULT_WALL_WIDTH_MM;
    return clampRoomDimension(mmValue);
  },[visualWallWidthCommittedInput, visualWallWidthUnit]);

  const manualData = React.useMemo(()=>{
    const cab = cabinetsById[manualCab];
    if(!cab){
      return null;
    }
    const cols = manualCols;
    const rows = manualRows;
    if(cols<=0 || rows<=0){
      return null;
    }
    const widthMm = cols * cab.w;
    const heightMm = rows * cab.h;
    const diagIn = diagonalInches(widthMm, heightMm);
    const tile = getCabinetTileResolution(manualCab, manualPitch);
    let resX;
    let resY;
    if(tile){
      resX = tile.x * cols;
      resY = tile.y * rows;
    } else {
      resX = Math.round(widthMm / manualPitch);
      resY = Math.round(heightMm / manualPitch);
    }
    const totalPixels = resX * resY;
    const areaM2 = (widthMm * heightMm) / 1_000_000;
    const pixelDensityMpixPerSqm = areaM2 > 0 ? (totalPixels / areaM2) / 1_000_000 : null;
    const totalPixelsMP = totalPixels / 1_000_000;
    const processor = computeProcessorRecommendation(PROCESSOR_CATALOG, totalPixels, resX, resY);
    return {
      cab,
      cols,
      rows,
      widthMm,
      heightMm,
      diagIn,
      resX,
      resY,
      totalPixels,
      areaM2,
      pitch: manualPitch,
      pixelDensityMpixPerSqm,
      totalPixelsMP,
      processor,
    };
  },[cabinetsById, manualCab, manualCols, manualRows, manualPitch]);

  const visualData = React.useMemo(()=>{
    const cab = cabinetsById[manualCab];
    if(!cab){
      return null;
    }
    const cols = visualCols;
    const rows = visualRows;
    if(cols<=0 || rows<=0){
      return null;
    }
    const widthMm = cols * cab.w;
    const heightMm = rows * cab.h;
    if(!(widthMm > 0) || !(heightMm > 0)){
      return null;
    }
    if(widthMm > visualWallWidthMm + 1e-6){
      return null;
    }
    const clearanceMm = Math.max(0, visualFloorClearanceMm);
    const availableHeight = visualWallHeightMm - clearanceMm;
    if(!(availableHeight > 0)){
      return null;
    }
    if(heightMm > availableHeight + 1e-6){
      return null;
    }
    const diagIn = diagonalInches(widthMm, heightMm);
    const tile = getCabinetTileResolution(manualCab, manualPitch);
    let resX;
    let resY;
    if(tile){
      resX = tile.x * cols;
      resY = tile.y * rows;
    } else {
      resX = Math.round(widthMm / manualPitch);
      resY = Math.round(heightMm / manualPitch);
    }
    const totalPixels = resX * resY;
    const areaM2 = (widthMm * heightMm) / 1_000_000;
    const pixelDensityMpixPerSqm = areaM2 > 0 ? (totalPixels / areaM2) / 1_000_000 : null;
    const totalPixelsMP = totalPixels / 1_000_000;
    const processor = computeProcessorRecommendation(PROCESSOR_CATALOG, totalPixels, resX, resY);
    return {
      cab,
      cols,
      rows,
      widthMm,
      heightMm,
      diagIn,
      resX,
      resY,
      totalPixels,
      areaM2,
      pitch: manualPitch,
      pixelDensityMpixPerSqm,
      totalPixelsMP,
      processor,
    };
  },[cabinetsById, manualCab, visualCols, visualRows, manualPitch, visualFloorClearanceMm, visualWallHeightMm, visualWallWidthMm]);

  const formatDesignSummary = React.useCallback((data)=>{
    if(!data){
      return null;
    }
    const { widthMm, heightMm, diagIn, resX, resY, totalPixels, areaM2, cols, rows, pitch, pixelDensityMpixPerSqm } = data;
    const widthM = widthMm / 1000;
    const heightM = heightMm / 1000;
    const widthFt = fromMM(widthMm, "ft");
    const heightFt = fromMM(heightMm, "ft");
    const areaFt2 = widthFt * heightFt;
    const minViewingDistanceM = ceilToDecimals(pitch,1);
    const minViewingDistanceFt = ceilToDecimals(pitch * FT_PER_M,0);
    const minViewingDistanceMLabel = Number.isFinite(minViewingDistanceM) ? fmtFixed(minViewingDistanceM,1) : null;
    const minViewingDistanceFtLabel = Number.isFinite(minViewingDistanceFt) ? minViewingDistanceFt.toLocaleString() : null;
    let processorLabel = "—";
    if(data.processor && data.processor.summary){
      processorLabel = data.processor.summary;
    }
    return {
      layoutLabel: `${cols} × ${rows}`,
      dimensionLabel: `${fmt(widthMm,0)} mm × ${fmt(heightMm,0)} mm (${fmt(widthM,3)} m × ${fmt(heightM,3)} m)` ,
      dimensionFeetLabel: `${fmt(widthFt,3)} ft × ${fmt(heightFt,3)} ft`,
      diagLabel: `${fmt(diagIn,2)} in`,
      resolutionLabel: `${resX.toLocaleString()} × ${resY.toLocaleString()}`,
      aspectLabel: formatAspect(resX, resY),
      areaLabel: `${fmt(areaM2,3)} m² (${fmt(areaFt2,3)} ft²)`,
      totalPixelsLabel: formatPixelsWithStandard(totalPixels),
      totalCabinetsLabel: `${(cols*rows).toLocaleString()} cabinets`,
      pitchLabel: fmtPitch(pitch),
      pixelDensityLabel: Number.isFinite(pixelDensityMpixPerSqm) ? `${fmt(pixelDensityMpixPerSqm,3)} Mpix/sqm` : "—",
      minViewingDistanceLabel: (minViewingDistanceMLabel && minViewingDistanceFtLabel)
        ? `> ${minViewingDistanceMLabel} m (> ${minViewingDistanceFtLabel} ft)`
        : "—",
      processorRecommendation: processorLabel,
    };
  },[]);

  const manualFormatted = React.useMemo(()=>formatDesignSummary(manualData),[formatDesignSummary, manualData]);

  const visualFormatted = React.useMemo(()=>formatDesignSummary(visualData),[formatDesignSummary, visualData]);

  const manualDesignSignature = React.useMemo(()=>{
    if(!manualData){
      return null;
    }
    const cabId = manualData.cab && manualData.cab.id ? manualData.cab.id : (manualCab || "");
    const pitchVal = Number.isFinite(manualData.pitch) ? manualData.pitch : 0;
    const colsVal = Number.isFinite(manualData.cols) ? manualData.cols : 0;
    const rowsVal = Number.isFinite(manualData.rows) ? manualData.rows : 0;
    const widthKey = Number.isFinite(manualData.widthMm) ? Math.round(manualData.widthMm) : 0;
    const heightKey = Number.isFinite(manualData.heightMm) ? Math.round(manualData.heightMm) : 0;
    const resXVal = Number.isFinite(manualData.resX) ? manualData.resX : 0;
    const resYVal = Number.isFinite(manualData.resY) ? manualData.resY : 0;
    return [cabId, pitchVal, colsVal, rowsVal, widthKey, heightKey, resXVal, resYVal].join("|");
  },[manualData, manualCab]);

  React.useEffect(()=>{
    if(!manualDesignSignature){
      manualDesignLastRef.current = null;
      return;
    }
    if(!manualDesignInitRef.current){
      manualDesignLastRef.current = manualDesignSignature;
      manualDesignInitRef.current = true;
      return;
    }
    if(manualDesignLastRef.current !== manualDesignSignature){
      manualDesignLastRef.current = manualDesignSignature;
      incrementCounter(1);
    }
  },[manualDesignSignature, incrementCounter]);

  const visualDesignSignature = React.useMemo(()=>{
    if(!visualData){
      return null;
    }
    const cabId = visualData.cab && visualData.cab.id ? visualData.cab.id : (manualCab || "");
    const pitchVal = Number.isFinite(visualData.pitch) ? visualData.pitch : 0;
    const colsVal = Number.isFinite(visualData.cols) ? visualData.cols : 0;
    const rowsVal = Number.isFinite(visualData.rows) ? visualData.rows : 0;
    const widthKey = Number.isFinite(visualData.widthMm) ? Math.round(visualData.widthMm) : 0;
    const heightKey = Number.isFinite(visualData.heightMm) ? Math.round(visualData.heightMm) : 0;
    const resXVal = Number.isFinite(visualData.resX) ? visualData.resX : 0;
    const resYVal = Number.isFinite(visualData.resY) ? visualData.resY : 0;
    return [cabId, pitchVal, colsVal, rowsVal, widthKey, heightKey, resXVal, resYVal].join("|");
  },[visualData, manualCab]);

  React.useEffect(()=>{
    if(!visualDesignSignature){
      visualDesignLastRef.current = null;
      return;
    }
    if(!visualDesignInitRef.current){
      visualDesignLastRef.current = visualDesignSignature;
      visualDesignInitRef.current = true;
      return;
    }
    if(visualDesignLastRef.current !== visualDesignSignature){
      visualDesignLastRef.current = visualDesignSignature;
      incrementCounter(1);
    }
  },[visualDesignSignature, incrementCounter]);

  const visualScene = React.useMemo(()=>{
    if(!visualData){
      return null;
    }
    const screenWidth = visualData.widthMm;
    const screenHeight = visualData.heightMm;
    const clearanceMm = Math.max(0, visualFloorClearanceMm);
    const totalHeight = clearanceMm + screenHeight;
    const minWallHeight = clearanceMm + screenHeight;
    const wallHeight = clampRoomDimension(Math.max(visualWallHeightMm, minWallHeight));
    const wallWidth = clampRoomDimension(Math.max(visualWallWidthMm, screenWidth));
    const floorWidth = wallWidth;
    const desiredDepth = Math.max(
      wallWidth,
      screenWidth * 2.2,
      totalHeight * 1.6,
      SCREEN_THICKNESS_MM * 80,
      4000
    );
    const floorDepth = clampRoomDimension(desiredDepth);
    const baseSize = Math.max(wallWidth, wallHeight, floorDepth);
    const defaultTarget = { x: 0, y: 0, z: clearanceMm + screenHeight / 2 };
    const targetZ = clamp(defaultTarget.z, 0, wallHeight);
    const minOrbitRadius = CAMERA_MIN_RADIUS_MM;
    const maxOrbitRadius = Math.max(minOrbitRadius, Math.min(floorDepth, CAMERA_BACK_LIMIT_MM));
    const baseRadius = clamp(Math.max(wallWidth, wallHeight, screenWidth) * 1.1, minOrbitRadius, maxOrbitRadius);
    const cameraDistance = clamp(baseRadius, minOrbitRadius, Math.min(maxOrbitRadius, CAMERA_BACK_LIMIT_MM - defaultTarget.y));
    const cameraHeight = targetZ;
    return {
      screenWidth,
      screenHeight,
      clearanceMm,
      totalHeight,
      baseSize,
      floorWidth,
      floorDepth,
      wallHeight,
      wallWidth,
      cameraDistance,
      cameraHeight,
      defaultTarget: { x: 0, y: 0, z: targetZ },
      minOrbitRadius,
      maxOrbitRadius,
    };
  },[visualData, visualFloorClearanceMm, visualWallHeightMm, visualWallWidthMm]);

  React.useEffect(()=>{
    visualSceneRef.current = visualScene;
  },[visualScene]);

  React.useEffect(()=>{
    if(!visualScene){
      const fallback = buildOrbitStateFromScene(null);
      lastSceneDefaultTargetRef.current = fallback.target;
      visualOrbitTargetRef.current = fallback;
      visualOrbitStateRef.current = fallback;
      setVisualOrbitState(fallback);
      return;
    }
    const defaults = buildOrbitStateFromScene(visualScene);
    const previous = visualOrbitStateRef.current;
    if(!previous || !previous.target){
      const constrainedDefaults = clampOrbitState(visualScene, defaults);
      lastSceneDefaultTargetRef.current = constrainedDefaults.target;
      visualOrbitTargetRef.current = constrainedDefaults;
      visualOrbitStateRef.current = constrainedDefaults;
      setVisualOrbitState(constrainedDefaults);
      return;
    }
    const previousCameraPosition = orbitStateToCameraPosition(previous);
    const previousDefaultTarget = lastSceneDefaultTargetRef.current || defaults.target;
    const target = clampOrbitTarget(visualScene, {
      x: defaults.target.x + (previous.target.x - previousDefaultTarget.x),
      y: defaults.target.y + (previous.target.y - previousDefaultTarget.y),
      z: defaults.target.z + (previous.target.z - previousDefaultTarget.z),
    });
    const dx = previousCameraPosition.x - target.x;
    const dy = previousCameraPosition.y - target.y;
    const dz = previousCameraPosition.z - target.z;
    const radius = Math.max(1, Math.sqrt(dx * dx + dy * dy + dz * dz));
    const azimuth = Math.atan2(dx, dy);
    const polar = clamp(Math.acos(clamp(dz / radius, -0.9999, 0.9999)), MIN_POLAR_ANGLE, MAX_POLAR_ANGLE);
    const baseState = {
      ...defaults,
      target,
      radius,
      azimuth,
      polar,
      defaultRadius: previous.defaultRadius ?? defaults.defaultRadius ?? radius,
    };
    const nextState = clampOrbitState(visualScene, baseState);
    visualOrbitTargetRef.current = nextState;
    visualOrbitStateRef.current = nextState;
    setVisualOrbitState(nextState);
    lastSceneDefaultTargetRef.current = defaults.target;
  },[visualScene]);

  React.useEffect(()=>{
    const scene = visualScene;
    const status = visualIntroAnimationRef.current;
    if(!scene || status.started || status.completed){
      return undefined;
    }

    status.started = true;
    status.completed = false;
    status.active = false;
    status.startTime = null;
    status.startState = null;
    const finalState = buildOrbitStateFromScene(scene);
    const { minRadius, maxRadius } = computeOrbitLimits(scene, finalState.target);
    const sweepRadius = clamp(
      finalState.radius * randomInRange(1.45, 1.75),
      minRadius,
      maxRadius
    );
    const spanCandidates = [
      finalState.radius,
      sweepRadius,
      scene.baseSize,
      scene.floorDepth,
      scene.wallWidth,
    ].filter((value)=>Number.isFinite(value) && value > 0);
    const baseSpan = spanCandidates.length ? Math.max(...spanCandidates) : finalState.radius;
    const lateralOffset = baseSpan * 0.35;
    const wallHeightForLift = (Number.isFinite(scene.wallHeight) && scene.wallHeight > 0)
      ? scene.wallHeight
      : finalState.target.z + baseSpan;
    const verticalLift = wallHeightForLift * 0.2 * randomInRange(0.75, 1.3);
    const lateralSign = randomSign();
    const azimuthOffset = randomInRange(Math.PI / 4, Math.PI * 0.55) * lateralSign;
    const startTarget = clampOrbitTarget(scene, {
      x: finalState.target.x + lateralOffset * randomInRange(0.45, 0.85) * lateralSign,
      y: finalState.target.y - lateralOffset * randomInRange(0.8, 1.2),
      z: finalState.target.z + verticalLift,
    });
    const startState = clampOrbitState(scene, {
      ...finalState,
      target: startTarget,
      radius: sweepRadius,
      azimuth: wrapAngle(finalState.azimuth + azimuthOffset),
      polar: clamp(finalState.polar * randomInRange(0.5, 0.75), MIN_POLAR_ANGLE, MAX_POLAR_ANGLE),
      defaultRadius: clamp(finalState.defaultRadius, minRadius, maxRadius),
    });

    visualOrbitTargetRef.current = startState;
    visualOrbitStateRef.current = startState;
    setVisualOrbitState(startState);
    status.finalState = finalState;
    status.startState = startState;
    status.durationMs = 2000;
    status.active = true;
    status.startTime = null;

    return ()=>{
      const introStatus = visualIntroAnimationRef.current;
      if(introStatus){
        const hadStarted = introStatus.startTime !== null;
        if(introStatus.active && introStatus.finalState){
          visualOrbitTargetRef.current = introStatus.finalState;
          visualOrbitStateRef.current = introStatus.finalState;
          setVisualOrbitState(introStatus.finalState);
        }
        introStatus.active = false;
        introStatus.startTime = null;
        introStatus.startState = null;
        if(!introStatus.completed){
          if(!hadStarted){
            introStatus.started = false;
          } else {
            introStatus.completed = true;
          }
        }
      }
    };
  },[visualScene]);

  const manualPitchOptions = React.useMemo(()=>{
    const raw = getSupportedPitches(manualCab) || [];
    return raw.slice().sort((a,b)=>a-b);
  },[manualCab]);

  const getMaxRowsAllowed = React.useCallback((cab)=>{
    if(!cab || !(cab.h > 0)){
      return Infinity;
    }
    const clearance = Math.max(0, visualFloorClearanceMm);
    const available = visualWallHeightMm - clearance;
    if(!(available > 0)){
      return 0;
    }
    const maxRows = Math.floor((available + 1e-6) / cab.h);
    return Math.max(0, maxRows);
  },[visualFloorClearanceMm, visualWallHeightMm]);

  const getMaxColsAllowed = React.useCallback((cab)=>{
    if(!cab || !(cab.w > 0)){
      return Infinity;
    }
    const available = visualWallWidthMm;
    if(!(available > 0)){
      return 0;
    }
    const maxCols = Math.floor((available + 1e-6) / cab.w);
    return Math.max(0, maxCols);
  },[visualWallWidthMm]);

  const wallHeightClearanceError = React.useMemo(()=>{
    return visualWallHeightMm > 0 && visualWallHeightMm <= visualFloorClearanceMm;
  },[visualWallHeightMm, visualFloorClearanceMm]);

  React.useEffect(()=>{
    setManualPitch(prev=>ensurePitchSupported(prev, manualPitchOptions));
  },[manualPitchOptions]);

  React.useEffect(()=>{
    setWallWidthWarning(null);
  },[visualWallWidthMm]);

  React.useEffect(()=>{
    const cab = cabinetsById[manualCab];
    if(!cab){
      lastMaxRowsRef.current = Infinity;
      if(visualRowsError){
        setVisualRowsError(null);
      }
      return;
    }
    const maxRows = getMaxRowsAllowed(cab);
    const prevMaxRows = lastMaxRowsRef.current;
    lastMaxRowsRef.current = maxRows;
    if(!Number.isFinite(maxRows)){
      if(visualRowsError){
        setVisualRowsError(null);
      }
      return;
    }
    let adjusted = false;
    setVisualRowsInput(prev=>{
      let parsed = parseInt(prev, 10);
      if(!Number.isFinite(parsed) || parsed < 1){
        parsed = 1;
      }
      let result = parsed;
      if(maxRows <= 0){
        if(parsed !== 1){
          adjusted = true;
        }
        result = 1;
      } else if(parsed > maxRows){
        adjusted = true;
        result = Math.max(1, maxRows);
      }
      return String(result);
    });
    if(maxRows <= 0){
      setVisualRowsError("Wall height is too low for the selected clearance and cabinet height.");
    } else if(adjusted){
      setVisualRowsError("Row count adjusted to fit within the wall height.");
    } else if(prevMaxRows !== maxRows && visualRowsError){
      setVisualRowsError(null);
    }
  },[manualCab, cabinetsById, getMaxRowsAllowed, visualRowsError]);

  React.useEffect(()=>{
    const cab = cabinetsById[manualCab];
    if(!cab){
      setWallWidthWarning(null);
      return;
    }
    const maxCols = getMaxColsAllowed(cab);
    if(!Number.isFinite(maxCols)){
      setWallWidthWarning(null);
      return;
    }
    let adjusted = false;
    setVisualColsInput(prev=>{
      let parsed = parseInt(prev, 10);
      if(!Number.isFinite(parsed) || parsed < 1){
        parsed = 1;
      }
      let result = parsed;
      if(maxCols <= 0){
        if(parsed !== 1){
          adjusted = true;
        }
        result = 1;
      } else if(parsed > maxCols){
        adjusted = true;
        result = Math.max(1, maxCols);
      }
      return String(result);
    });
    if(maxCols <= 0){
      setWallWidthWarning("Wall width is too narrow for the selected cabinet width.");
    } else if(adjusted){
      setWallWidthWarning("Column count adjusted to fit within the wall width.");
    } else {
      setWallWidthWarning(null);
    }
  },[manualCab, cabinetsById, getMaxColsAllowed]);

  const handleManualColsBlur = React.useCallback(()=>{
    setManualColsInput((prev)=>{
      const parsed = parseInt(prev, 10);
      const safeValue = Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
      return String(safeValue);
    });
  },[]);

  const handleManualRowsBlur = React.useCallback(()=>{
    setManualRowsInput((prev)=>{
      const parsed = parseInt(prev, 10);
      const safeValue = Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
      return String(safeValue);
    });
  },[]);

  const handleVisualColsBlur = React.useCallback(()=>{
    const cab = cabinetsById[manualCab];
    const maxCols = getMaxColsAllowed(cab);
    let message = null;
    setVisualColsInput(prev=>{
      let parsed = parseInt(prev, 10);
      if(!Number.isFinite(parsed) || parsed < 1){
        parsed = 1;
      }
      if(Number.isFinite(maxCols)){
        if(maxCols <= 0){
          message = "Wall width is too narrow for the selected cabinet width.";
          return "1";
        }
        if(parsed > maxCols){
          message = "Column count adjusted to fit within the wall width.";
          return String(Math.max(1, maxCols));
        }
      }
      return String(parsed);
    });
    setWallWidthWarning(message);
  },[cabinetsById, manualCab, getMaxColsAllowed]);

  const handleVisualRowsBlur = React.useCallback(()=>{
    const cab = cabinetsById[manualCab];
    const maxRows = getMaxRowsAllowed(cab);
    let message = null;
    let nextValue = "1";
    setVisualRowsInput(prev=>{
      let parsed = parseInt(prev, 10);
      if(!Number.isFinite(parsed) || parsed < 1){
        parsed = 1;
      }
      if(Number.isFinite(maxRows)){
        if(maxRows <= 0){
          message = "Wall height is too low for the selected clearance and cabinet height.";
          nextValue = "1";
          return nextValue;
        }
        if(parsed > maxRows){
          message = "Row count adjusted to fit within the wall height.";
          nextValue = String(Math.max(1, maxRows));
          return nextValue;
        }
      }
      nextValue = String(parsed);
      return nextValue;
    });
    setVisualRowsError(message);
  },[cabinetsById, manualCab, getMaxRowsAllowed]);

  const handleVisualFloorClearanceBlur = React.useCallback(()=>{
    setVisualFloorClearanceInput(prev=>{
      const parsed = parseFloat(prev);
      if(!Number.isFinite(parsed) || parsed < 0){
        return formatUnitInput(0, visualFloorClearanceUnit);
      }
      const mmValue = clamp(Math.max(0, toMM(parsed, visualFloorClearanceUnit)), 0, ROOM_LIMIT_MM);
      return formatUnitInput(mmValue, visualFloorClearanceUnit);
    });
  },[visualFloorClearanceUnit]);

  const handleVisualWallHeightBlur = React.useCallback(()=>{
    setVisualWallHeightInput(prev=>{
      const parsed = parseFloat(prev);
      const mmValue = Number.isFinite(parsed) && parsed > 0
        ? toMM(parsed, visualWallHeightUnit)
        : DEFAULT_WALL_HEIGHT_MM;
      const next = formatUnitInput(clampRoomDimension(mmValue), visualWallHeightUnit);
      setVisualWallHeightCommittedInput(next);
      return next;
    });
  },[visualWallHeightUnit]);

  const handleVisualWallWidthBlur = React.useCallback(()=>{
    setVisualWallWidthInput(prev=>{
      const parsed = parseFloat(prev);
      const mmValue = Number.isFinite(parsed) && parsed > 0
        ? toMM(parsed, visualWallWidthUnit)
        : DEFAULT_WALL_WIDTH_MM;
      const next = formatUnitInput(clampRoomDimension(mmValue), visualWallWidthUnit);
      setVisualWallWidthCommittedInput(next);
      return next;
    });
  },[visualWallWidthUnit]);

  const adjustManualCols = React.useCallback((delta)=>{
    setManualColsInput((prev)=>{
      const parsed = parseInt(prev, 10);
      const base = Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
      return String(Math.max(1, base + delta));
    });
  },[]);

  const adjustVisualCols = React.useCallback((delta)=>{
    const cab = cabinetsById[manualCab];
    const maxCols = getMaxColsAllowed(cab);
    let message = null;
    setVisualColsInput(prev=>{
      const parsed = parseInt(prev, 10);
      const base = Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
      let next = Math.max(1, base + delta);
      if(Number.isFinite(maxCols)){
        if(maxCols <= 0){
          message = "Wall width is too narrow for the selected cabinet width.";
          next = 1;
        } else if(next > maxCols){
          if(delta > 0){
            message = "Cannot add another column without exceeding the wall width.";
          } else if(base > maxCols){
            message = "Column count adjusted to fit within the wall width.";
          }
          next = Math.max(1, maxCols);
        }
      }
      return String(next);
    });
    setWallWidthWarning(message);
  },[cabinetsById, manualCab, getMaxColsAllowed]);

  const handleVisualFloorClearanceUnitChange = React.useCallback((nextUnit)=>{
    setVisualFloorClearanceUnit(nextUnit);
    const clearanceMm = clamp(Math.max(0, visualFloorClearanceMm), 0, ROOM_LIMIT_MM);
    const clearanceFormatted = formatUnitInput(clearanceMm, nextUnit);
    setVisualFloorClearanceInput(clearanceFormatted);
    setVisualWallHeightUnit(nextUnit);
    const heightMm = clampRoomDimension(visualWallHeightMm);
    const heightFormatted = formatUnitInput(heightMm, nextUnit);
    setVisualWallHeightInput(heightFormatted);
    setVisualWallHeightCommittedInput(heightFormatted);
    setVisualWallWidthUnit(nextUnit);
    const widthMm = clampRoomDimension(visualWallWidthMm);
    const widthFormatted = formatUnitInput(widthMm, nextUnit);
    setVisualWallWidthInput(widthFormatted);
    setVisualWallWidthCommittedInput(widthFormatted);
  },[visualFloorClearanceMm, visualWallHeightMm, visualWallWidthMm]);

  const handleVisualWallHeightUnitChange = React.useCallback((nextUnit)=>{
    setVisualWallHeightUnit(nextUnit);
    const heightMm = clampRoomDimension(visualWallHeightMm);
    const heightFormatted = formatUnitInput(heightMm, nextUnit);
    setVisualWallHeightInput(heightFormatted);
    setVisualWallHeightCommittedInput(heightFormatted);
    setVisualFloorClearanceUnit(nextUnit);
    const clearanceMm = clamp(Math.max(0, visualFloorClearanceMm), 0, ROOM_LIMIT_MM);
    setVisualFloorClearanceInput(formatUnitInput(clearanceMm, nextUnit));
    setVisualWallWidthUnit(nextUnit);
    const widthMm = clampRoomDimension(visualWallWidthMm);
    const widthFormatted = formatUnitInput(widthMm, nextUnit);
    setVisualWallWidthInput(widthFormatted);
    setVisualWallWidthCommittedInput(widthFormatted);
  },[visualWallHeightMm, visualFloorClearanceMm, visualWallWidthMm]);

  const handleVisualWallWidthUnitChange = React.useCallback((nextUnit)=>{
    setVisualWallWidthUnit(nextUnit);
    const widthMm = clampRoomDimension(visualWallWidthMm);
    const widthFormatted = formatUnitInput(widthMm, nextUnit);
    setVisualWallWidthInput(widthFormatted);
    setVisualWallWidthCommittedInput(widthFormatted);
    setVisualFloorClearanceUnit(nextUnit);
    const clearanceMm = clamp(Math.max(0, visualFloorClearanceMm), 0, ROOM_LIMIT_MM);
    setVisualFloorClearanceInput(formatUnitInput(clearanceMm, nextUnit));
    setVisualWallHeightUnit(nextUnit);
    const heightMm = clampRoomDimension(visualWallHeightMm);
    const heightFormatted = formatUnitInput(heightMm, nextUnit);
    setVisualWallHeightInput(heightFormatted);
    setVisualWallHeightCommittedInput(heightFormatted);
  },[visualWallWidthMm, visualFloorClearanceMm, visualWallHeightMm]);

  const handleVisualPersonToggle = React.useCallback(()=>{
    setVisualPersonEnabled((prev)=>!prev);
  },[]);

  const handleVisualScreenDesignToggle = React.useCallback(()=>{
    setVisualScreenDesignCollapsed((prev)=>!prev);
  },[]);

  const handleVisualContentCollapseToggle = React.useCallback(()=>{
    setVisualContentCollapsed((prev)=>!prev);
  },[]);

  const handleScreenContentSampleChange = React.useCallback((event)=>{
    const selectedId = event.target ? event.target.value : "";
    setScreenContentSampleId(selectedId);
    screenContentLoadTokenRef.current += 1;
    const currentToken = screenContentLoadTokenRef.current;
    if(!selectedId){
      return;
    }
    const sample = SAMPLE_SCREEN_CONTENT_IMAGES.find((item)=>item.id === selectedId);
    if(!sample){
      setScreenContentSampleId("");
      return;
    }
    const url = resolveAssetUrl(sample.path);
    if(!url){
      setScreenContentSampleId("");
      return;
    }
    if(screenContentObjectUrlRef.current){
      URL.revokeObjectURL(screenContentObjectUrlRef.current);
      screenContentObjectUrlRef.current = null;
    }
    const img = new Image();
    img.decoding = "async";
    img.onload = ()=>{
      if(screenContentLoadTokenRef.current !== currentToken){
        return;
      }
      const naturalWidth = img.naturalWidth || img.width || 0;
      const naturalHeight = img.naturalHeight || img.height || 0;
      setScreenContentImage({
        image: img,
        width: naturalWidth,
        height: naturalHeight,
        name: sample.label,
        id: `${sample.id}-${Date.now()}`,
      });
      setScreenContentEnabled(true);
      if(screenTextureBufferRef.current){
        screenTextureBufferRef.current.lastKey = null;
        screenTextureBufferRef.current.imageData = null;
      }
      if(screenContentFileInputRef.current){
        screenContentFileInputRef.current.value = "";
      }
    };
    img.onerror = ()=>{
      if(screenContentLoadTokenRef.current === currentToken){
        setScreenContentSampleId("");
      }
    };
    img.src = url;
  },[]);

  const handleScreenContentToggle = React.useCallback(()=>{
    setScreenContentEnabled((prev)=>!prev);
  },[]);

  const handleHideCabinetGridLinesToggle = React.useCallback(()=>{
    setHideCabinetGridLines((prev)=>!prev);
  },[]);

  const handleScreenContentClear = React.useCallback(()=>{
    screenContentLoadTokenRef.current += 1;
    if(screenContentObjectUrlRef.current){
      URL.revokeObjectURL(screenContentObjectUrlRef.current);
      screenContentObjectUrlRef.current = null;
    }
    setScreenContentImage(null);
    if(screenTextureBufferRef.current){
      screenTextureBufferRef.current.lastKey = null;
      screenTextureBufferRef.current.imageData = null;
    }
    if(screenContentFileInputRef.current){
      screenContentFileInputRef.current.value = "";
    }
    setScreenContentSampleId("");
  },[]);

  const handleScreenContentFitChange = React.useCallback((event)=>{
    setScreenContentFit(event.target.value);
  },[]);

  const handleScreenContentFileChange = React.useCallback((event)=>{
    const file = event.target && event.target.files ? event.target.files[0] : null;
    screenContentLoadTokenRef.current += 1;
    const currentToken = screenContentLoadTokenRef.current;
    if(!file){
      if(screenContentObjectUrlRef.current){
        URL.revokeObjectURL(screenContentObjectUrlRef.current);
        screenContentObjectUrlRef.current = null;
      }
      return;
    }
    const type = (file.type || "").toLowerCase();
    const name = (file.name || "").toLowerCase();
    const isSupported = type.startsWith("image/") ? /^(image\/(png|jpe?g|gif))$/.test(type) : /(\.png|\.jpe?g|\.jpg|\.gif)$/.test(name);
    if(!isSupported){
      if(screenContentFileInputRef.current){
        screenContentFileInputRef.current.value = "";
      }
      if(screenContentObjectUrlRef.current){
        URL.revokeObjectURL(screenContentObjectUrlRef.current);
        screenContentObjectUrlRef.current = null;
      }
      return;
    }
    if(screenContentObjectUrlRef.current){
      URL.revokeObjectURL(screenContentObjectUrlRef.current);
      screenContentObjectUrlRef.current = null;
    }
    const objectUrl = URL.createObjectURL(file);
    screenContentObjectUrlRef.current = objectUrl;
    const img = new Image();
    img.onload = ()=>{
      if(screenContentLoadTokenRef.current !== currentToken){
        URL.revokeObjectURL(objectUrl);
        return;
      }
      screenContentObjectUrlRef.current = null;
      URL.revokeObjectURL(objectUrl);
      const naturalWidth = img.naturalWidth || img.width || 0;
      const naturalHeight = img.naturalHeight || img.height || 0;
      setScreenContentImage({
        image: img,
        width: naturalWidth,
        height: naturalHeight,
        name: file.name,
        id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
      });
      setScreenContentEnabled(true);
      setScreenContentSampleId("");
      if(screenTextureBufferRef.current){
        screenTextureBufferRef.current.lastKey = null;
        screenTextureBufferRef.current.imageData = null;
      }
    };
    img.onerror = ()=>{
      URL.revokeObjectURL(objectUrl);
      if(screenContentLoadTokenRef.current === currentToken){
        screenContentObjectUrlRef.current = null;
        if(screenContentFileInputRef.current){
          screenContentFileInputRef.current.value = "";
        }
      }
    };
    img.src = objectUrl;
  },[]);

  React.useEffect(()=>{
    return ()=>{
      if(screenContentObjectUrlRef.current){
        URL.revokeObjectURL(screenContentObjectUrlRef.current);
        screenContentObjectUrlRef.current = null;
      }
    };
  },[]);

  React.useEffect(()=>{
    if(screenTextureBufferRef.current){
      screenTextureBufferRef.current.lastKey = null;
      screenTextureBufferRef.current.imageData = null;
    }
  },[screenContentImage, screenContentFit]);

  useEffect(()=>{
    let cancelled = false;
    const url = resolveAssetUrl("body.glb");
    if(!url){
      setBodyModel(null);
      return ()=>{ cancelled = true; };
    }
    loadGLBModel(url, BODY_MODEL_LOAD_OPTIONS)
      .then((model)=>{
        if(cancelled){
          return;
        }
        setBodyModel(model);
      })
      .catch((error)=>{
        if(typeof console !== "undefined" && console.error){
          console.error("Failed to load body visualization model", error);
        }
        if(!cancelled){
          setBodyModel(null);
        }
      });
    return ()=>{
      cancelled = true;
    };
  },[]);

  const adjustManualRows = React.useCallback((delta)=>{
    setManualRowsInput((prev)=>{
      const parsed = parseInt(prev, 10);
      const base = Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
      return String(Math.max(1, base + delta));
    });
  },[]);

  const adjustVisualRows = React.useCallback((delta)=>{
    const cab = cabinetsById[manualCab];
    const maxRows = getMaxRowsAllowed(cab);
    const base = visualRows;
    let next = Math.max(1, base + delta);
    let message = null;
    if(Number.isFinite(maxRows)){
      if(maxRows <= 0){
        message = "Wall height is too low for the selected clearance and cabinet height.";
        next = 1;
      } else if(next > maxRows){
        message = "Cannot add another row without exceeding the wall height.";
        next = Math.max(1, maxRows);
      }
    }
    setVisualRowsInput(String(next));
    setVisualRowsError(message);
  },[visualRows, cabinetsById, manualCab, getMaxRowsAllowed]);

  const desiredWmm=useMemo(()=>toMM(Number(desiredW)||0,unit),[desiredW,unit]);
  const desiredHmm=useMemo(()=>toMM(Number(desiredH)||0,unit),[desiredH,unit]);
  const desiredAreaMM2=desiredWmm*desiredHmm;
  const desiredAreaM2=desiredAreaMM2/1_000_000;

  React.useEffect(()=>{
    const canvas = visualCanvasEl;
    const viewWidth = visualViewport.width;
    const viewHeight = visualViewport.height;
    if(!canvas || !(viewWidth > 0) || !(viewHeight > 0)){
      return;
    }
    const dpr = (typeof window !== "undefined" && Number.isFinite(window.devicePixelRatio)) ? window.devicePixelRatio : 1;
    const desiredWidth = Math.max(1, Math.round(viewWidth * dpr));
    const desiredHeight = Math.max(1, Math.round(viewHeight * dpr));
    if(canvas.width !== desiredWidth || canvas.height !== desiredHeight){
      canvas.width = desiredWidth;
      canvas.height = desiredHeight;
    }
    if(canvas.style.width !== `${viewWidth}px`){
      canvas.style.width = `${viewWidth}px`;
    }
    if(canvas.style.height !== `${viewHeight}px`){
      canvas.style.height = `${viewHeight}px`;
    }
    const ctx = canvas.getContext("2d");
    if(!ctx){
      return;
    }
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.scale(dpr, dpr);
    const backgroundGradient = ctx.createLinearGradient(0, 0, 0, viewHeight);
    backgroundGradient.addColorStop(0, "#1e293b");
    backgroundGradient.addColorStop(0.55, "#0f172a");
    backgroundGradient.addColorStop(1, "#020617");
    ctx.fillStyle = backgroundGradient;
    ctx.fillRect(0,0,viewWidth,viewHeight);

    if(!visualData || !visualScene){
      return;
    }

    const scene = visualScene;
    const orbit = visualOrbitState && visualOrbitState.target ? visualOrbitState : buildOrbitStateFromScene(scene);
    const cameraTarget = orbit.target || scene.defaultTarget;
    const cameraPosition = orbitStateToCameraPosition({ ...orbit, target: cameraTarget });
    const camera = createCameraBasis(cameraPosition, cameraTarget, { x: 0, y: 0, z: 1 });
    const viewport = { width: viewWidth, height: viewHeight };
    const fovRad = Math.PI / 3;

    const projectPointToScreen = (point)=>{
      const projected = project3DPoint(point, camera, viewport, fovRad);
      if(!projected){
        return null;
      }
      return projected;
    };

    const projectPolygon = (points)=>{
      const mapped = points.map(projectPointToScreen);
      if(mapped.some((pt)=>!pt)){
        return null;
      }
      return mapped;
    };

    const drawPolygon = (points, fillStyle, strokeStyle, lineWidth = 1)=>{
      if(!points || points.length === 0){
        return;
      }
      ctx.beginPath();
      ctx.moveTo(points[0].x, points[0].y);
      for(let i=1;i<points.length;i+=1){
        ctx.lineTo(points[i].x, points[i].y);
      }
      ctx.closePath();
      if(fillStyle){
        ctx.fillStyle = fillStyle;
        ctx.fill();
      }
      if(strokeStyle){
        ctx.strokeStyle = strokeStyle;
        ctx.lineWidth = lineWidth;
        ctx.stroke();
      }
    };

    const drawLine = (a,b,strokeStyle,lineWidth=1,dash)=>{
      if(!a || !b){
        return;
      }
      ctx.save();
      ctx.lineWidth = lineWidth;
      ctx.strokeStyle = strokeStyle;
      ctx.lineCap = "round";
      ctx.lineJoin = "round";
      if(dash && dash.length){
        ctx.setLineDash(dash);
      }
      ctx.beginPath();
      ctx.moveTo(a.x, a.y);
      ctx.lineTo(b.x, b.y);
      ctx.stroke();
      ctx.restore();
    };

    const zoomFactor = orbit.defaultRadius ? orbit.defaultRadius / Math.max(orbit.radius, 1e-3) : 1;
    const scaledLineWidth = (base, minFactor = 0.25, maxFactor = 1.15)=>{
      return base * clamp(zoomFactor, minFactor, maxFactor);
    };
    const cabinetLineWidth = (base)=>{
      return Math.max(0.6, base * clamp(zoomFactor, 0.35, 1.2));
    };

    const { screenWidth, screenHeight, clearanceMm, baseSize, floorWidth, floorDepth, wallHeight, wallWidth } = scene;
    const halfFloorWidth = floorWidth / 2;
    const halfWallWidth = wallWidth / 2;
    const cabinetCols = visualData.cols || 0;
    const cabinetRows = visualData.rows || 0;
    const cabinetWidth = visualData.cab ? visualData.cab.w : 0;
    const cabinetHeight = visualData.cab ? visualData.cab.h : 0;
    const frontFaceGridLinesVisible = !hideCabinetGridLines;
    const hasCabinetLogo = !!(cabinetLogoAsset && cabinetLogoAsset.image && cabinetLogoAsset.width > 0 && cabinetLogoAsset.height > 0);
    const cabinetLogoImage = hasCabinetLogo ? cabinetLogoAsset.image : null;
    const cabinetLogoNaturalWidth = hasCabinetLogo ? cabinetLogoAsset.width : 0;
    const cabinetLogoNaturalHeight = hasCabinetLogo ? cabinetLogoAsset.height : 0;
    const cabinetLogoAspect = hasCabinetLogo && Number.isFinite(cabinetLogoAsset.aspectRatio) && cabinetLogoAsset.aspectRatio > 0
      ? cabinetLogoAsset.aspectRatio
      : (hasCabinetLogo && cabinetLogoNaturalHeight > 0 ? cabinetLogoNaturalWidth / cabinetLogoNaturalHeight : 1);
    const screenTextureEnabled = !!(screenContentEnabled && screenContentImage && screenContentImage.image && screenWidth > 0 && screenHeight > 0);
    let screenTextureSource = null;
    if(screenTextureEnabled && typeof document !== "undefined"){
      const aspect = screenHeight > 0 ? screenWidth / screenHeight : 0;
      if(aspect > 0){
        let bufferState = screenTextureBufferRef.current;
        if(!bufferState || !bufferState.canvas || !bufferState.ctx){
          const canvasEl = document.createElement("canvas");
          const bufferCtx = canvasEl.getContext("2d");
          if(bufferCtx){
            bufferCtx.imageSmoothingEnabled = true;
            bufferCtx.imageSmoothingQuality = "high";
            bufferState = { canvas: canvasEl, ctx: bufferCtx, lastKey: null, width: 0, height: 0, imageData: null };
          } else {
            bufferState = null;
          }
          screenTextureBufferRef.current = bufferState;
        }
        const buffer = screenTextureBufferRef.current;
        if(buffer && buffer.ctx){
          const baseSize = 1024;
          let targetWidth = baseSize;
          let targetHeight = Math.max(1, Math.round(targetWidth / aspect));
          if(targetHeight > baseSize){
            targetHeight = baseSize;
            targetWidth = Math.max(1, Math.round(targetHeight * aspect));
          }
          const fitKey = `${screenContentImage.id}|${screenContentFit}|${targetWidth}|${targetHeight}`;
          if(buffer.lastKey !== fitKey || !buffer.imageData){
            buffer.canvas.width = targetWidth;
            buffer.canvas.height = targetHeight;
            const bctx = buffer.ctx;
            bctx.setTransform(1,0,0,1,0,0);
            bctx.clearRect(0,0,targetWidth,targetHeight);
            bctx.fillStyle = "#0f172a";
            bctx.fillRect(0,0,targetWidth,targetHeight);
            const naturalW = screenContentImage.width;
            const naturalH = screenContentImage.height;
            if(naturalW > 0 && naturalH > 0){
              if(screenContentFit === "stretch"){
                bctx.drawImage(screenContentImage.image, 0, 0, targetWidth, targetHeight);
              } else if(screenContentFit === "fill"){
                const scale = Math.min(targetWidth / naturalW, targetHeight / naturalH);
                const drawW = naturalW * scale;
                const drawH = naturalH * scale;
                const dx = (targetWidth - drawW) / 2;
                const dy = (targetHeight - drawH) / 2;
                bctx.drawImage(screenContentImage.image, dx, dy, drawW, drawH);
              } else {
                const targetAspect = targetWidth / targetHeight;
                const sourceAspect = naturalW / naturalH;
                if(sourceAspect > targetAspect){
                  const desiredWidth = naturalH * targetAspect;
                  const sx = (naturalW - desiredWidth) / 2;
                  bctx.drawImage(screenContentImage.image, sx, 0, desiredWidth, naturalH, 0, 0, targetWidth, targetHeight);
                } else if(sourceAspect < targetAspect){
                  const desiredHeight = naturalW / targetAspect;
                  const sy = (naturalH - desiredHeight) / 2;
                  bctx.drawImage(screenContentImage.image, 0, sy, naturalW, desiredHeight, 0, 0, targetWidth, targetHeight);
                } else {
                  bctx.drawImage(screenContentImage.image, 0, 0, naturalW, naturalH, 0, 0, targetWidth, targetHeight);
                }
              }
            }
            try {
              buffer.imageData = bctx.getImageData(0, 0, targetWidth, targetHeight);
            } catch(_error){
              buffer.imageData = null;
            }
            buffer.lastKey = fitKey;
            buffer.width = targetWidth;
            buffer.height = targetHeight;
          }
          screenTextureSource = buffer.canvas && buffer.imageData
            ? { image: buffer.canvas, width: buffer.width, height: buffer.height, imageData: buffer.imageData }
            : null;
        }
      }
    } else if(screenTextureBufferRef.current){
      screenTextureBufferRef.current.lastKey = null;
      screenTextureBufferRef.current.imageData = null;
    }
    const floorPlane = [
      { x: -halfFloorWidth, y: 0, z: 0 },
      { x: halfFloorWidth, y: 0, z: 0 },
      { x: halfFloorWidth, y: floorDepth, z: 0 },
      { x: -halfFloorWidth, y: floorDepth, z: 0 },
    ];
    const ceilingPlane = [
      { x: -halfFloorWidth, y: 0, z: wallHeight },
      { x: halfFloorWidth, y: 0, z: wallHeight },
      { x: halfFloorWidth, y: floorDepth, z: wallHeight },
      { x: -halfFloorWidth, y: floorDepth, z: wallHeight },
    ];
    const wallPlane = [
      { x: -halfWallWidth, y: 0, z: 0 },
      { x: halfWallWidth, y: 0, z: 0 },
      { x: halfWallWidth, y: 0, z: wallHeight },
      { x: -halfWallWidth, y: 0, z: wallHeight },
    ];
    const leftWallPlane = [
      { x: -halfWallWidth, y: 0, z: 0 },
      { x: -halfWallWidth, y: floorDepth, z: 0 },
      { x: -halfWallWidth, y: floorDepth, z: wallHeight },
      { x: -halfWallWidth, y: 0, z: wallHeight },
    ];
    const rightWallPlane = [
      { x: halfWallWidth, y: 0, z: 0 },
      { x: halfWallWidth, y: floorDepth, z: 0 },
      { x: halfWallWidth, y: floorDepth, z: wallHeight },
      { x: halfWallWidth, y: 0, z: wallHeight },
    ];

    const wallPolygon = projectPolygon(wallPlane);
    if(wallPolygon){
      const wallGradient = ctx.createLinearGradient(
        wallPolygon[3].x,
        wallPolygon[3].y,
        wallPolygon[0].x,
        wallPolygon[0].y
      );
      wallGradient.addColorStop(0, "rgba(51,65,85,0.9)");
      wallGradient.addColorStop(0.65, "rgba(30,41,59,0.88)");
      wallGradient.addColorStop(1, "rgba(15,23,42,0.92)");
      drawPolygon(wallPolygon, wallGradient, "rgba(148,163,184,0.25)", 1.2);
    }

    const floorPolygon = projectPolygon(floorPlane);
    if(floorPolygon){
      const floorGradient = ctx.createLinearGradient(
        floorPolygon[0].x,
        floorPolygon[0].y,
        floorPolygon[2].x,
        floorPolygon[2].y
      );
      floorGradient.addColorStop(0, "rgba(30,41,59,0.92)");
      floorGradient.addColorStop(0.5, "rgba(15,23,42,0.86)");
      floorGradient.addColorStop(1, "rgba(8,11,19,0.85)");
      drawPolygon(floorPolygon, floorGradient, "rgba(148,163,184,0.2)", 1.1);
    }

    const ceilingPolygon = projectPolygon(ceilingPlane);
    if(ceilingPolygon){
      const ceilingGradient = ctx.createLinearGradient(
        ceilingPolygon[0].x,
        ceilingPolygon[0].y,
        ceilingPolygon[2].x,
        ceilingPolygon[2].y
      );
      ceilingGradient.addColorStop(0, "rgba(35,48,78,0.74)");
      ceilingGradient.addColorStop(0.55, "rgba(18,27,47,0.68)");
      ceilingGradient.addColorStop(1, "rgba(7,10,18,0.64)");
      drawPolygon(ceilingPolygon, ceilingGradient, "rgba(148,163,184,0.26)", 1.05);
    }

    const leftWallPolygon = projectPolygon(leftWallPlane);
    if(leftWallPolygon){
      const leftGradient = ctx.createLinearGradient(
        leftWallPolygon[0].x,
        leftWallPolygon[0].y,
        leftWallPolygon[1].x,
        leftWallPolygon[1].y
      );
      leftGradient.addColorStop(0, "rgba(168,204,255,0.95)");
      leftGradient.addColorStop(0.48, "rgba(94,134,214,0.94)");
      leftGradient.addColorStop(1, "rgba(20,32,72,0.98)");
      drawPolygon(leftWallPolygon, leftGradient, "rgba(234,245,255,0.52)", 1.45);
      const leftFrontBottom = projectPointToScreen({ x: -halfWallWidth, y: floorDepth, z: 0 });
      const leftFrontTop = projectPointToScreen({ x: -halfWallWidth, y: floorDepth, z: wallHeight });
      const leftBackTop = projectPointToScreen({ x: -halfWallWidth, y: 0, z: wallHeight });
      const leftBackBottom = projectPointToScreen({ x: -halfWallWidth, y: 0, z: 0 });
      drawLine(leftFrontBottom, leftFrontTop, "rgba(249,250,255,0.92)", scaledLineWidth(2.45, 0.55, 1.75));
      drawLine(leftFrontTop, leftBackTop, "rgba(226,241,255,0.65)", scaledLineWidth(1.85, 0.45, 1.35));
      drawLine(leftFrontBottom, leftBackBottom, "rgba(210,231,255,0.55)", scaledLineWidth(1.75, 0.45, 1.25));
      drawLine(leftBackBottom, leftBackTop, "rgba(199,220,255,0.6)", scaledLineWidth(1.6, 0.45, 1.2));
    }

    const rightWallPolygon = projectPolygon(rightWallPlane);
    if(rightWallPolygon){
      const rightGradient = ctx.createLinearGradient(
        rightWallPolygon[0].x,
        rightWallPolygon[0].y,
        rightWallPolygon[1].x,
        rightWallPolygon[1].y
      );
      rightGradient.addColorStop(0, "rgba(18,28,60,0.97)");
      rightGradient.addColorStop(0.52, "rgba(52,102,189,0.93)");
      rightGradient.addColorStop(1, "rgba(118,176,255,0.95)");
      drawPolygon(rightWallPolygon, rightGradient, "rgba(180,220,255,0.5)", 1.45);
      const rightFrontBottom = projectPointToScreen({ x: halfWallWidth, y: floorDepth, z: 0 });
      const rightFrontTop = projectPointToScreen({ x: halfWallWidth, y: floorDepth, z: wallHeight });
      const rightBackTop = projectPointToScreen({ x: halfWallWidth, y: 0, z: wallHeight });
      const rightBackBottom = projectPointToScreen({ x: halfWallWidth, y: 0, z: 0 });
      drawLine(rightFrontBottom, rightFrontTop, "rgba(248,252,255,0.9)", scaledLineWidth(2.35, 0.55, 1.7));
      drawLine(rightFrontTop, rightBackTop, "rgba(210,233,255,0.62)", scaledLineWidth(1.8, 0.45, 1.3));
      drawLine(rightFrontBottom, rightBackBottom, "rgba(196,221,255,0.54)", scaledLineWidth(1.7, 0.45, 1.2));
      drawLine(rightBackBottom, rightBackTop, "rgba(188,214,255,0.58)", scaledLineWidth(1.55, 0.45, 1.15));
    }

    const gridSpacing = ROOM_GRID_SPACING_MM;
    const maxExtent = Math.max(wallWidth, wallHeight, floorDepth, gridSpacing);
    const maxLines = Math.max(1, Math.min(ROOM_GRID_MAX_LINES, Math.ceil(maxExtent / gridSpacing) + 2));
    const sideWallSpacing = gridSpacing;

    if(sideWallSpacing > 0 && wallHeight > 0){
      let sideLineCount = 0;
      for(let depth = sideWallSpacing; depth <= floorDepth && sideLineCount < maxLines; depth += sideWallSpacing){
        sideLineCount += 1;
        const leftBottom = projectPointToScreen({ x: -halfWallWidth, y: depth, z: 0 });
        const leftTop = projectPointToScreen({ x: -halfWallWidth, y: depth, z: wallHeight });
        const rightBottom = projectPointToScreen({ x: halfWallWidth, y: depth, z: 0 });
        const rightTop = projectPointToScreen({ x: halfWallWidth, y: depth, z: wallHeight });
        drawLine(leftBottom, leftTop, "rgba(206,222,240,0.28)", Math.max(1, scaledLineWidth(1.05)));
        drawLine(rightBottom, rightTop, "rgba(188,210,236,0.26)", Math.max(1, scaledLineWidth(1.02)));
      }

      sideLineCount = 0;
      for(let height = sideWallSpacing; height <= wallHeight && sideLineCount < maxLines; height += sideWallSpacing){
        sideLineCount += 1;
        const leftBack = projectPointToScreen({ x: -halfWallWidth, y: 0, z: height });
        const leftFront = projectPointToScreen({ x: -halfWallWidth, y: floorDepth, z: height });
        const rightBack = projectPointToScreen({ x: halfWallWidth, y: 0, z: height });
        const rightFront = projectPointToScreen({ x: halfWallWidth, y: floorDepth, z: height });
        drawLine(leftBack, leftFront, "rgba(198,216,236,0.24)", Math.max(0.95, scaledLineWidth(0.95)));
        drawLine(rightBack, rightFront, "rgba(178,202,226,0.22)", Math.max(0.9, scaledLineWidth(0.9)));
      }
    }

    let lineCount = 0;
    const floorXStart = Math.ceil((-halfFloorWidth) / gridSpacing) * gridSpacing;
    const floorXEnd = Math.floor(halfFloorWidth / gridSpacing) * gridSpacing;
    if(floorXStart <= floorXEnd){
      for(let x = floorXStart; x <= floorXEnd && lineCount < maxLines; x += gridSpacing){
        lineCount += 1;
        const start = projectPointToScreen({ x, y: 0, z: 0 });
        const end = projectPointToScreen({ x, y: floorDepth, z: 0 });
        drawLine(start, end, "rgba(160,176,198,0.2)", Math.max(0.95, scaledLineWidth(0.9)));
      }
    }

    lineCount = 0;
    for(let y = gridSpacing; y <= floorDepth && lineCount < maxLines; y += gridSpacing){
      lineCount += 1;
      const start = projectPointToScreen({ x: -halfFloorWidth, y, z: 0 });
      const end = projectPointToScreen({ x: halfFloorWidth, y, z: 0 });
      drawLine(start, end, "rgba(156,172,194,0.18)", Math.max(0.9, scaledLineWidth(0.85)));
    }

    lineCount = 0;
    if(floorXStart <= floorXEnd){
      for(let x = floorXStart; x <= floorXEnd && lineCount < maxLines; x += gridSpacing){
        lineCount += 1;
        const start = projectPointToScreen({ x, y: 0, z: wallHeight });
        const end = projectPointToScreen({ x, y: floorDepth, z: wallHeight });
        drawLine(start, end, "rgba(170,186,210,0.2)", Math.max(0.85, scaledLineWidth(0.8)));
      }
    }

    lineCount = 0;
    for(let y = gridSpacing; y <= floorDepth && lineCount < maxLines; y += gridSpacing){
      lineCount += 1;
      const start = projectPointToScreen({ x: -halfFloorWidth, y, z: wallHeight });
      const end = projectPointToScreen({ x: halfFloorWidth, y, z: wallHeight });
      drawLine(start, end, "rgba(168,184,208,0.18)", Math.max(0.85, scaledLineWidth(0.75)));
    }

    lineCount = 0;
    const wallXStart = Math.ceil((-halfWallWidth) / gridSpacing) * gridSpacing;
    const wallXEnd = Math.floor(halfWallWidth / gridSpacing) * gridSpacing;
    if(wallXStart <= wallXEnd){
      for(let x = wallXStart; x <= wallXEnd && lineCount < maxLines; x += gridSpacing){
        lineCount += 1;
        const start = projectPointToScreen({ x, y: 0, z: 0 });
        const end = projectPointToScreen({ x, y: 0, z: wallHeight });
        drawLine(start, end, "rgba(176,194,220,0.22)", Math.max(0.9, scaledLineWidth(0.85)));
      }
    }

    lineCount = 0;
    for(let z = gridSpacing; z <= wallHeight && lineCount < maxLines; z += gridSpacing){
      lineCount += 1;
      const start = projectPointToScreen({ x: -halfWallWidth, y: 0, z });
      const end = projectPointToScreen({ x: halfWallWidth, y: 0, z });
      drawLine(start, end, "rgba(174,192,218,0.2)", Math.max(0.85, scaledLineWidth(0.8)));
    }

    const wallBaseLeft = projectPointToScreen({ x: -halfWallWidth, y: 0, z: 0 });
    const wallBaseRight = projectPointToScreen({ x: halfWallWidth, y: 0, z: 0 });
    drawLine(wallBaseLeft, wallBaseRight, "rgba(250,252,255,0.95)", scaledLineWidth(2.8, 0.5, 1.35));

    const floorBackLeft = projectPointToScreen({ x: -halfFloorWidth, y: 0, z: 0 });
    const floorBackRight = projectPointToScreen({ x: halfFloorWidth, y: 0, z: 0 });
    const floorFrontLeft = projectPointToScreen({ x: -halfFloorWidth, y: floorDepth, z: 0 });
    const floorFrontRight = projectPointToScreen({ x: halfFloorWidth, y: floorDepth, z: 0 });
    drawLine(floorBackLeft, floorBackRight, "rgba(245,248,255,0.7)", scaledLineWidth(1.9, 0.45, 1.25));
    drawLine(floorFrontLeft, floorFrontRight, "rgba(224,236,255,0.55)", scaledLineWidth(1.8, 0.45, 1.2));
    drawLine(floorBackLeft, floorFrontLeft, "rgba(228,239,255,0.62)", scaledLineWidth(1.95, 0.45, 1.25));
    drawLine(floorBackRight, floorFrontRight, "rgba(226,239,255,0.6)", scaledLineWidth(1.95, 0.45, 1.25));

    if(wallHeight > 0){
      const ceilingBackLeft = projectPointToScreen({ x: -halfWallWidth, y: 0, z: wallHeight });
      const ceilingBackRight = projectPointToScreen({ x: halfWallWidth, y: 0, z: wallHeight });
      const ceilingFrontLeft = projectPointToScreen({ x: -halfWallWidth, y: floorDepth, z: wallHeight });
      const ceilingFrontRight = projectPointToScreen({ x: halfWallWidth, y: floorDepth, z: wallHeight });
      drawLine(ceilingBackLeft, ceilingBackRight, "rgba(242,247,255,0.66)", scaledLineWidth(2, 0.45, 1.35));
      drawLine(ceilingFrontLeft, ceilingFrontRight, "rgba(222,234,255,0.52)", scaledLineWidth(1.9, 0.45, 1.3));
      drawLine(ceilingBackLeft, ceilingFrontLeft, "rgba(226,237,255,0.58)", scaledLineWidth(1.85, 0.45, 1.25));
      drawLine(ceilingBackRight, ceilingFrontRight, "rgba(220,234,255,0.55)", scaledLineWidth(1.85, 0.45, 1.25));
    }

    const screenHalfWidth = screenWidth / 2;
    const screenBottom = clearanceMm;
    const screenTop = clearanceMm + screenHeight;
    const screenBackY = SCREEN_BACK_OFFSET_MM;
    const screenFrontY = screenBackY + SCREEN_THICKNESS_MM;
    const screenFrontPlane = [
      { x: -screenHalfWidth, y: screenFrontY, z: screenBottom },
      { x: screenHalfWidth, y: screenFrontY, z: screenBottom },
      { x: screenHalfWidth, y: screenFrontY, z: screenTop },
      { x: -screenHalfWidth, y: screenFrontY, z: screenTop },
    ];
    const screenBackPlane = [
      { x: -screenHalfWidth, y: screenBackY, z: screenBottom },
      { x: screenHalfWidth, y: screenBackY, z: screenBottom },
      { x: screenHalfWidth, y: screenBackY, z: screenTop },
      { x: -screenHalfWidth, y: screenBackY, z: screenTop },
    ];
    const screenLeftPlane = [
      { x: -screenHalfWidth, y: screenBackY, z: screenBottom },
      { x: -screenHalfWidth, y: screenFrontY, z: screenBottom },
      { x: -screenHalfWidth, y: screenFrontY, z: screenTop },
      { x: -screenHalfWidth, y: screenBackY, z: screenTop },
    ];
    const screenRightPlane = [
      { x: screenHalfWidth, y: screenBackY, z: screenBottom },
      { x: screenHalfWidth, y: screenFrontY, z: screenBottom },
      { x: screenHalfWidth, y: screenFrontY, z: screenTop },
      { x: screenHalfWidth, y: screenBackY, z: screenTop },
    ];
    const screenTopPlane = [
      { x: -screenHalfWidth, y: screenBackY, z: screenTop },
      { x: screenHalfWidth, y: screenBackY, z: screenTop },
      { x: screenHalfWidth, y: screenFrontY, z: screenTop },
      { x: -screenHalfWidth, y: screenFrontY, z: screenTop },
    ];
    const screenBottomPlane = [
      { x: -screenHalfWidth, y: screenBackY, z: screenBottom },
      { x: screenHalfWidth, y: screenBackY, z: screenBottom },
      { x: screenHalfWidth, y: screenFrontY, z: screenBottom },
      { x: -screenHalfWidth, y: screenFrontY, z: screenBottom },
    ];

    const screenFrontPolygon = projectPolygon(screenFrontPlane);
    const screenBackPolygon = projectPolygon(screenBackPlane);
    const screenLeftPolygon = projectPolygon(screenLeftPlane);
    const screenRightPolygon = projectPolygon(screenRightPlane);
    const screenTopPolygon = projectPolygon(screenTopPlane);
    const screenBottomPolygon = projectPolygon(screenBottomPlane);

    const renderScreen = (isBackView = false)=>{
      const faceRenderers = [];
      const visibleFaces = new Set();

      const computeCenter3D = (points)=>{
        if(!points || !points.length){
          return { x: 0, y: 0, z: 0 };
        }
        let sumX = 0;
        let sumY = 0;
        let sumZ = 0;
        for(let i = 0; i < points.length; i += 1){
          sumX += points[i].x;
          sumY += points[i].y;
          sumZ += points[i].z;
        }
        const inv = 1 / points.length;
        return { x: sumX * inv, y: sumY * inv, z: sumZ * inv };
      };

      const addFace = (name, polygon, planePoints, normal, drawFace)=>{
        if(!polygon || !planePoints){
          return;
        }
        const center = computeCenter3D(planePoints);
        const faceToCamera = vec3Sub(camera.position, center);
        if(vec3Dot(normal, faceToCamera) <= 1e-3){
          return;
        }
        const avgDepth = polygon.reduce((sum, point)=>sum + (point.depth || 0), 0) / polygon.length;
        faceRenderers.push({
          name,
          avgDepth,
          draw: ()=>drawFace(polygon)
        });
        visibleFaces.add(name);
      };

      const drawCabinetSeam = (startPoint, endPoint, strokeStyle, strokeWidth)=>{
        const start = projectPointToScreen(startPoint);
        const end = projectPointToScreen(endPoint);
        if(start && end){
          drawLine(start, end, strokeStyle, strokeWidth);
        }
      };

      const drawLogoQuad = (topLeft, topRight, bottomLeft, bottomRight)=>{
        if(!cabinetLogoImage || cabinetLogoNaturalWidth <= 0 || cabinetLogoNaturalHeight <= 0){
          return;
        }
        if(!topLeft || !topRight || !bottomLeft || !bottomRight){
          return;
        }
        ctx.save();
        ctx.beginPath();
        ctx.moveTo(topLeft.x, topLeft.y);
        ctx.lineTo(topRight.x, topRight.y);
        ctx.lineTo(bottomRight.x, bottomRight.y);
        ctx.lineTo(bottomLeft.x, bottomLeft.y);
        ctx.closePath();
        ctx.clip();
        ctx.transform(
          (topLeft.x - topRight.x) / cabinetLogoNaturalWidth,
          (topLeft.y - topRight.y) / cabinetLogoNaturalWidth,
          (bottomRight.x - topRight.x) / cabinetLogoNaturalHeight,
          (bottomRight.y - topRight.y) / cabinetLogoNaturalHeight,
          topRight.x,
          topRight.y
        );
        ctx.drawImage(cabinetLogoImage, 0, 0);
        ctx.restore();
      };

      addFace("back", screenBackPolygon, screenBackPlane, { x: 0, y: -1, z: 0 }, (polygon)=>{
        const metallicBackGradient = ctx.createLinearGradient(
          polygon[3].x,
          polygon[3].y,
          polygon[1].x,
          polygon[1].y
        );
        metallicBackGradient.addColorStop(0, "rgba(12,13,15,1)");
        metallicBackGradient.addColorStop(0.35, "rgba(28,30,34,1)");
        metallicBackGradient.addColorStop(0.65, "rgba(45,47,52,1)");
        metallicBackGradient.addColorStop(1, "rgba(11,12,14,1)");
        drawPolygon(polygon, metallicBackGradient, "#4b5563", 1.35);

        const metallicSheen = ctx.createLinearGradient(
          polygon[0].x,
          polygon[0].y,
          polygon[1].x,
          polygon[1].y
        );
        metallicSheen.addColorStop(0, "rgba(255,255,255,0.05)");
        metallicSheen.addColorStop(0.5, "rgba(255,255,255,0.12)");
        metallicSheen.addColorStop(1, "rgba(255,255,255,0.04)");
        drawPolygon(polygon, metallicSheen);

        if(isBackView && hasCabinetLogo && cabinetCols > 0 && cabinetRows > 0 && cabinetWidth > 0 && cabinetHeight > 0){
          const maxLogoWidthMm = Math.min(CABINET_LOGO_MAX_SIZE_MM, cabinetWidth);
          const maxLogoHeightMm = Math.min(CABINET_LOGO_MAX_SIZE_MM, cabinetHeight);
          if(maxLogoWidthMm > 0 && maxLogoHeightMm > 0){
            const aspect = Number.isFinite(cabinetLogoAspect) && cabinetLogoAspect > 0 ? cabinetLogoAspect : 1;
            let logoWidthMm = maxLogoWidthMm;
            let logoHeightMm = logoWidthMm / aspect;
            if(!Number.isFinite(logoHeightMm) || logoHeightMm <= 0 || logoHeightMm > maxLogoHeightMm){
              logoHeightMm = maxLogoHeightMm;
              logoWidthMm = logoHeightMm * aspect;
            }
            if(!Number.isFinite(logoWidthMm) || logoWidthMm <= 0 || logoWidthMm > maxLogoWidthMm){
              logoWidthMm = maxLogoWidthMm;
              logoHeightMm = logoWidthMm / aspect;
            }
            if(logoWidthMm > 0 && logoHeightMm > 0){
              const halfLogoWidthMm = logoWidthMm / 2;
              const halfLogoHeightMm = logoHeightMm / 2;
              for(let rowIndex = 0; rowIndex < cabinetRows; rowIndex += 1){
                const centerZ = screenBottom + cabinetHeight * (rowIndex + 0.5);
                for(let colIndex = 0; colIndex < cabinetCols; colIndex += 1){
                  const centerX = -screenHalfWidth + cabinetWidth * (colIndex + 0.5);
                  const topLeft = projectPointToScreen({ x: centerX - halfLogoWidthMm, y: screenBackY, z: centerZ + halfLogoHeightMm });
                  const topRight = projectPointToScreen({ x: centerX + halfLogoWidthMm, y: screenBackY, z: centerZ + halfLogoHeightMm });
                  const bottomLeft = projectPointToScreen({ x: centerX - halfLogoWidthMm, y: screenBackY, z: centerZ - halfLogoHeightMm });
                  const bottomRight = projectPointToScreen({ x: centerX + halfLogoWidthMm, y: screenBackY, z: centerZ - halfLogoHeightMm });
                  if(topLeft && topRight && bottomLeft && bottomRight){
                    drawLogoQuad(topLeft, topRight, bottomLeft, bottomRight);
                  }
                }
              }
            }
          }
        }
      });

      addFace("left", screenLeftPolygon, screenLeftPlane, { x: -1, y: 0, z: 0 }, (polygon)=>{
        const leftGradient = ctx.createLinearGradient(
          polygon[0].x,
          polygon[0].y,
          polygon[1].x,
          polygon[1].y
        );
        leftGradient.addColorStop(0, "rgba(14,15,18,1)");
        leftGradient.addColorStop(0.45, "rgba(34,35,40,1)");
        leftGradient.addColorStop(1, "rgba(10,11,13,1)");
        drawPolygon(polygon, leftGradient, "#4b5563", 1.2);
      });

      addFace("right", screenRightPolygon, screenRightPlane, { x: 1, y: 0, z: 0 }, (polygon)=>{
        const rightGradient = ctx.createLinearGradient(
          polygon[0].x,
          polygon[0].y,
          polygon[1].x,
          polygon[1].y
        );
        rightGradient.addColorStop(0, "rgba(32,34,38,1)");
        rightGradient.addColorStop(0.5, "rgba(18,19,23,1)");
        rightGradient.addColorStop(1, "rgba(8,9,11,1)");
        drawPolygon(polygon, rightGradient, "#4b5563", 1.2);
      });

      addFace("top", screenTopPolygon, screenTopPlane, { x: 0, y: 0, z: 1 }, (polygon)=>{
        const topGradient = ctx.createLinearGradient(
          polygon[0].x,
          polygon[0].y,
          polygon[2].x,
          polygon[2].y
        );
        topGradient.addColorStop(0, "rgba(26,27,31,1)");
        topGradient.addColorStop(0.55, "rgba(18,19,23,1)");
        topGradient.addColorStop(1, "rgba(9,10,12,1)");
        drawPolygon(polygon, topGradient, "#4b5563", 1);
      });

      addFace("bottom", screenBottomPolygon, screenBottomPlane, { x: 0, y: 0, z: -1 }, (polygon)=>{
        const bottomGradient = ctx.createLinearGradient(
          polygon[0].x,
          polygon[0].y,
          polygon[2].x,
          polygon[2].y
        );
        bottomGradient.addColorStop(0, "rgba(10,11,13,1)");
        bottomGradient.addColorStop(0.55, "rgba(22,23,27,1)");
        bottomGradient.addColorStop(1, "rgba(30,32,36,1)");
        drawPolygon(polygon, bottomGradient, "#4b5563", 1);
      });

      if(!isBackView){
        addFace("front", screenFrontPolygon, screenFrontPlane, { x: 0, y: 1, z: 0 }, (polygon)=>{
          ctx.save();
          ctx.shadowColor = "rgba(15,23,42,0.78)";
          ctx.shadowBlur = 24;
          ctx.shadowOffsetY = 18;
          drawPolygon(polygon, "rgba(24,27,33,1)");
          ctx.restore();

          const screenGradient = ctx.createLinearGradient(
            polygon[3].x,
            polygon[3].y,
            polygon[1].x,
            polygon[1].y
          );
          screenGradient.addColorStop(0, "rgba(70,76,86,1)");
          screenGradient.addColorStop(0.5, "rgba(54,59,67,1)");
          screenGradient.addColorStop(1, "rgba(36,41,48,1)");
          drawPolygon(polygon, screenGradient, "#6b7280", 1.6);

          if(screenTextureSource && screenTextureSource.imageData && screenTextureSource.width > 0 && screenTextureSource.height > 0){
            const bottomLeft = polygon[0];
            const bottomRight = polygon[1];
            const topRight = polygon[2];
            const topLeft = polygon[3];
            if(bottomLeft && bottomRight && topRight && topLeft){
              const textureWidth = screenTextureSource.width;
              const textureHeight = screenTextureSource.height;
              const sourceImageData = screenTextureSource.imageData;
              const baseMatrix = typeof ctx.getTransform === "function"
                ? ctx.getTransform()
                : { a: dpr, b: 0, c: 0, d: dpr, e: 0, f: 0 };
              const scaleX = baseMatrix.a;
              const scaleY = baseMatrix.d;
              const offsetX = baseMatrix.e || 0;
              const offsetY = baseMatrix.f || 0;
              const destinationQuad = [
                { x: topLeft.x * scaleX + offsetX, y: topLeft.y * scaleY + offsetY },
                { x: topRight.x * scaleX + offsetX, y: topRight.y * scaleY + offsetY },
                { x: bottomRight.x * scaleX + offsetX, y: bottomRight.y * scaleY + offsetY },
                { x: bottomLeft.x * scaleX + offsetX, y: bottomLeft.y * scaleY + offsetY },
              ];
              const sourceQuad = [
                { x: 0, y: 0 },
                { x: textureWidth, y: 0 },
                { x: textureWidth, y: textureHeight },
                { x: 0, y: textureHeight },
              ];
              const homography = computeHomographyMatrix(sourceQuad, destinationQuad);
              const inverseHomography = homography ? invertHomographyMatrix(homography) : null;
              if(homography && inverseHomography){
                const canvasWidth = ctx.canvas.width;
                const canvasHeight = ctx.canvas.height;
                const minX = Math.max(0, Math.floor(Math.min(destinationQuad[0].x, destinationQuad[1].x, destinationQuad[2].x, destinationQuad[3].x)));
                const maxX = Math.min(canvasWidth, Math.ceil(Math.max(destinationQuad[0].x, destinationQuad[1].x, destinationQuad[2].x, destinationQuad[3].x)));
                const minY = Math.max(0, Math.floor(Math.min(destinationQuad[0].y, destinationQuad[1].y, destinationQuad[2].y, destinationQuad[3].y)));
                const maxY = Math.min(canvasHeight, Math.ceil(Math.max(destinationQuad[0].y, destinationQuad[1].y, destinationQuad[2].y, destinationQuad[3].y)));
                const destWidth = maxX - minX;
                const destHeight = maxY - minY;
                const sourceData = sourceImageData.data || sourceImageData;
                const srcWidth = sourceImageData.width || textureWidth;
                const srcHeight = sourceImageData.height || textureHeight;
                if(destWidth > 0 && destHeight > 0 && sourceData && srcWidth > 0 && srcHeight > 0){
                  ctx.save();
                  ctx.setTransform(1, 0, 0, 1, 0, 0);
                  const output = ctx.getImageData(minX, minY, destWidth, destHeight);
                  ctx.restore();
                  const outputData = output.data;
                  const epsilon = 1e-3;
                  for(let yIndex = 0; yIndex < destHeight; yIndex += 1){
                    const deviceY = minY + yIndex + 0.5;
                    for(let xIndex = 0; xIndex < destWidth; xIndex += 1){
                      const deviceX = minX + xIndex + 0.5;
                      if(!isPointInConvexPolygon({ x: deviceX, y: deviceY }, destinationQuad)){
                        continue;
                      }
                      const denom = inverseHomography[6] * deviceX + inverseHomography[7] * deviceY + inverseHomography[8];
                      if(Math.abs(denom) <= 1e-12){
                        continue;
                      }
                      const sourceX = (inverseHomography[0] * deviceX + inverseHomography[1] * deviceY + inverseHomography[2]) / denom;
                      const sourceY = (inverseHomography[3] * deviceX + inverseHomography[4] * deviceY + inverseHomography[5]) / denom;
                      if(sourceX >= -epsilon && sourceX <= srcWidth - 1 + epsilon && sourceY >= -epsilon && sourceY <= srcHeight - 1 + epsilon){
                        const [r, g, b, a] = bilinearSample(sourceData, srcWidth, srcHeight, sourceX, sourceY);
                        const offset = (yIndex * destWidth + xIndex) * 4;
                        outputData[offset] = r;
                        outputData[offset + 1] = g;
                        outputData[offset + 2] = b;
                        outputData[offset + 3] = a;
                      }
                    }
                  }
                  ctx.save();
                  ctx.setTransform(1,0,0,1,0,0);
                  ctx.putImageData(output, minX, minY);
                  ctx.restore();
                }
              }
            }
          }

          const highlightGradient = ctx.createRadialGradient(
            (polygon[0].x + polygon[1].x) / 2,
            (polygon[0].y + polygon[3].y) / 2,
            Math.max(12, Math.abs(polygon[0].x - polygon[1].x) * 0.05),
            (polygon[0].x + polygon[1].x) / 2,
            (polygon[0].y + polygon[3].y) / 2,
            Math.max(40, Math.abs(polygon[0].x - polygon[1].x) * 0.75)
          );
          highlightGradient.addColorStop(0, "rgba(148,163,184,0.22)");
          highlightGradient.addColorStop(1, "rgba(36,42,52,0)");
          drawPolygon(polygon, highlightGradient);
        });
      }

      faceRenderers.sort((a, b)=>b.avgDepth - a.avgDepth);
      for(let i = 0; i < faceRenderers.length; i += 1){
        faceRenderers[i].draw();
      }

      const frontFaceName = isBackView ? "back" : "front";
      const faceY = isBackView ? screenBackY : screenFrontY;
      const columnStrokeColor = isBackView
        ? "rgba(156,163,175,0.55)"
        : "rgba(203,213,245,0.45)";
      const rowStrokeColor = isBackView
        ? "rgba(156,163,175,0.55)"
        : "rgba(203,213,245,0.45)";

      const shouldRenderFacingGridLines = (frontFaceGridLinesVisible || isBackView) && visibleFaces.has(frontFaceName);
      if(shouldRenderFacingGridLines){
        if(cabinetCols > 1 && cabinetWidth > 0){
          const columnStrokeWidth = cabinetLineWidth(1.05);
          for(let colIndex = 1; colIndex < cabinetCols; colIndex += 1){
            const x = -screenHalfWidth + cabinetWidth * colIndex;
            drawCabinetSeam(
              { x, y: faceY, z: screenBottom },
              { x, y: faceY, z: screenTop },
              columnStrokeColor,
              columnStrokeWidth
            );
          }
        }

        if(cabinetRows > 1 && cabinetHeight > 0){
          const rowStrokeWidth = cabinetLineWidth(1.05);
          for(let rowIndex = 1; rowIndex < cabinetRows; rowIndex += 1){
            const z = screenBottom + cabinetHeight * rowIndex;
            drawCabinetSeam(
              { x: -screenHalfWidth, y: faceY, z },
              { x: screenHalfWidth, y: faceY, z },
              rowStrokeColor,
              rowStrokeWidth
            );
          }
        }
      }

      const sideRowStrokeColor = isBackView
        ? "rgba(156,163,175,0.5)"
        : "rgba(203,213,225,0.42)";
      if(visibleFaces.has("left") && cabinetRows > 1 && cabinetHeight > 0){
        const sideRowWidth = cabinetLineWidth(0.95);
        for(let rowIndex = 1; rowIndex < cabinetRows; rowIndex += 1){
          const z = screenBottom + cabinetHeight * rowIndex;
          drawCabinetSeam(
            { x: -screenHalfWidth, y: screenBackY, z },
            { x: -screenHalfWidth, y: screenFrontY, z },
            sideRowStrokeColor,
            sideRowWidth
          );
        }
      }

      if(visibleFaces.has("right") && cabinetRows > 1 && cabinetHeight > 0){
        const sideRowWidth = cabinetLineWidth(0.95);
        for(let rowIndex = 1; rowIndex < cabinetRows; rowIndex += 1){
          const z = screenBottom + cabinetHeight * rowIndex;
          drawCabinetSeam(
            { x: screenHalfWidth, y: screenBackY, z },
            { x: screenHalfWidth, y: screenFrontY, z },
            sideRowStrokeColor,
            sideRowWidth
          );
        }
      }

      const topColumnStrokeColor = isBackView
        ? "rgba(156,163,175,0.5)"
        : "rgba(211,219,235,0.4)";
      if(visibleFaces.has("top") && cabinetCols > 1 && cabinetWidth > 0){
        const topColumnWidth = cabinetLineWidth(0.9);
        for(let colIndex = 1; colIndex < cabinetCols; colIndex += 1){
          const x = -screenHalfWidth + cabinetWidth * colIndex;
          drawCabinetSeam(
            { x, y: screenBackY, z: screenTop },
            { x, y: screenFrontY, z: screenTop },
            topColumnStrokeColor,
            topColumnWidth
          );
        }
      }

      const bottomColumnStrokeColor = isBackView
        ? "rgba(140,148,160,0.48)"
        : "rgba(198,206,220,0.4)";
      if(visibleFaces.has("bottom") && cabinetCols > 1 && cabinetWidth > 0){
        const bottomColumnWidth = cabinetLineWidth(0.9);
        for(let colIndex = 1; colIndex < cabinetCols; colIndex += 1){
          const x = -screenHalfWidth + cabinetWidth * colIndex;
          drawCabinetSeam(
            { x, y: screenBackY, z: screenBottom },
            { x, y: screenFrontY, z: screenBottom },
            bottomColumnStrokeColor,
            bottomColumnWidth
          );
        }
      }
    };

    const screenNormal = { x: 0, y: 1, z: 0 };
    const cameraFacingDot = vec3Dot(camera.forward, screenNormal);
    const cameraBehindScreen = cameraFacingDot > 0.05;

    if(!cameraBehindScreen){
      renderScreen(false);
    }

    if(visualPersonEnabled && bodyModel && bodyModel.triangles && bodyModel.triangles.length){
      let lightDirection = bodyModel.lightDirection;
      if(!lightDirection){
        lightDirection = { x: 0, y: 0, z: 1 };
      }
      const lightLength = vec3Length(lightDirection);
      const normalizedLight = lightLength > 1e-6 ? vec3Normalize(lightDirection) : { x: 0, y: 0, z: 1 };
      const bodyTriangles = [];
      for(let i=0;i<bodyModel.triangles.length;i+=1){
        const tri = bodyModel.triangles[i];
        const projected = tri.vertices.map(projectPointToScreen);
        if(projected.some((pt)=>!pt)){
          continue;
        }
        const depthAvg = (projected[0].depth + projected[1].depth + projected[2].depth) / 3;
        const normal = tri.normal || { x: 0, y: 0, z: 1 };
        const shade = clamp((vec3Dot(normal, normalizedLight) + 1) / 2, 0, 1);
        bodyTriangles.push({ points: projected, depth: depthAvg, shade });
      }
      bodyTriangles.sort((a,b)=>b.depth - a.depth);
      const strokeWidth = scaledLineWidth(0.95, 0.4, 1.1);
      bodyTriangles.forEach((tri)=>{
        const fillColor = mixRgba(BODY_MODEL_FILL_DARK, BODY_MODEL_FILL_LIGHT, tri.shade, 0.9);
        const strokeColor = mixRgba(BODY_MODEL_STROKE_DARK, BODY_MODEL_STROKE_LIGHT, tri.shade, 0.85);
        drawPolygon(tri.points, fillColor, strokeColor, strokeWidth);
      });
    }

    if(cameraBehindScreen){
      renderScreen(true);
    }

    const clearanceStart = projectPointToScreen({ x: 0, y: screenFrontY, z: screenBottom });
    const clearanceEnd = projectPointToScreen({ x: 0, y: 0, z: 0 });
    if(clearanceStart && clearanceEnd){
      drawLine(clearanceStart, clearanceEnd, "rgba(226,232,240,0.55)", scaledLineWidth(1.2, 0.35, 1.05), [6,6]);
      const markerSize = 6;
      const markerTop = projectPolygon([
        { x: -markerSize, y: 0, z: 0 },
        { x: markerSize, y: 0, z: 0 },
        { x: markerSize, y: 0, z: markerSize },
        { x: -markerSize, y: 0, z: markerSize },
      ]);
      if(markerTop){
        drawPolygon(markerTop, "rgba(148,163,184,0.25)", "rgba(226,232,240,0.4)", 0.8);
      }
    }
  },[visualData, visualViewport.width, visualViewport.height, visualScene, visualOrbitState, bodyModel, visualPersonEnabled, cabinetLogoAsset, visualCanvasEl, screenContentEnabled, screenContentImage, screenContentFit, hideCabinetGridLines]);

  const visualClearanceLabel = React.useMemo(()=>{
    const mm = Math.max(0, visualFloorClearanceMm);
    const meters = fromMM(mm, "m");
    const feet = fromMM(mm, "ft");
    return `${fmt(meters,3)} m (${fmtFixed(feet,1)} ft)`;
  },[visualFloorClearanceMm]);

  const visualOverallHeightLabel = React.useMemo(()=>{
    if(!visualData){
      return null;
    }
    const screenHeightMm = Number.isFinite(visualData.heightMm) ? visualData.heightMm : 0;
    const clearanceMm = Math.max(0, visualFloorClearanceMm);
    const totalMm = clearanceMm + screenHeightMm;
    const meters = fromMM(totalMm, "m");
    const feet = fromMM(totalMm, "ft");
    return `${fmt(meters,3)} m (${fmtFixed(feet,1)} ft)`;
  },[visualData, visualFloorClearanceMm]);

  function buildColumn(cabId,pitch){
    const cab=cabinetsById[cabId];
    if(!cab) return null;
    const grid=computeGrid(desiredWmm,desiredHmm,cab.w,cab.h,fitMode);
    const actualWmm=grid.cols*cab.w;
    const actualHmm=grid.rows*cab.h;
    const actualAreaMM2=actualWmm*actualHmm;
    const actualAreaM2=actualAreaMM2/1_000_000;

    const diagIn=diagonalInches(actualWmm,actualHmm);

    const tile=getCabinetTileResolution(cabId,pitch);
    let resX,resY;
    if(tile){ resX=tile.x*grid.cols; resY=tile.y*grid.rows; }
    else { resX=Math.round(actualWmm/pitch); resY=Math.round(actualHmm/pitch); }

    const deltaWmm=actualWmm-desiredWmm;
    const deltaHmm=actualHmm-desiredHmm;
    const deltaWpct=desiredWmm>0?(deltaWmm/desiredWmm)*100:0;
    const deltaHpct=desiredHmm>0?(deltaHmm/desiredHmm)*100:0;

    const matchPct=desiredAreaMM2>0?(actualAreaMM2/desiredAreaMM2)*100:0;

    const totalPixels=resX*resY;
    const pixelDensityMpixPerSqm = actualAreaM2>0 ? (totalPixels/actualAreaM2)/1_000_000 : null;
    const totalCabinets = grid.cols * grid.rows;
    const totalPixelsMP = totalPixels / 1_000_000;
    const processor = computeProcessorRecommendation(PROCESSOR_CATALOG, totalPixels, resX, resY);

    return { cab, pitch, grid, actualWmm, actualHmm, actualAreaMM2, actualAreaM2, diagIn, resX, resY, deltaWmm, deltaHmm, deltaWpct, deltaHpct, matchPct, totalPixels, pixelDensityMpixPerSqm, totalCabinets, totalPixelsMP, processor };
  }

  React.useEffect(()=>{ setPitchA(prev=>ensurePitchSupported(prev,getSupportedPitches(cabA))); },[cabA]);
  React.useEffect(()=>{ setPitchB(prev=>ensurePitchSupported(prev,getSupportedPitches(cabB))); },[cabB]);
  React.useEffect(()=>{ setPitchC(prev=>ensurePitchSupported(prev,getSupportedPitches(cabC))); },[cabC]);

  const colA=React.useMemo(()=>buildColumn(cabA,pitchA),[cabA,pitchA,desiredWmm,desiredHmm,fitMode]);
  const colB=React.useMemo(()=>buildColumn(cabB,pitchB),[cabB,pitchB,desiredWmm,desiredHmm,fitMode]);
  const colC=React.useMemo(()=>buildColumn(cabC,pitchC),[cabC,pitchC,desiredWmm,desiredHmm,fitMode]);

  const displayUnit=unit;

  const designSignature = React.useCallback((data)=>{
    if(!data || !data.cab || !data.grid) return null;
    const cabId = data.cab.id || "";
    const pitchVal = Number.isFinite(data.pitch) ? data.pitch : 0;
    const cols = data.grid && Number.isFinite(data.grid.cols) ? data.grid.cols : 0;
    const rows = data.grid && Number.isFinite(data.grid.rows) ? data.grid.rows : 0;
    const widthKey = Number.isFinite(data.actualWmm) ? Math.round(data.actualWmm) : 0;
    const heightKey = Number.isFinite(data.actualHmm) ? Math.round(data.actualHmm) : 0;
    const resX = Number.isFinite(data.resX) ? data.resX : 0;
    const resY = Number.isFinite(data.resY) ? data.resY : 0;
    return [cabId, pitchVal, cols, rows, widthKey, heightKey, resX, resY].join("|");
  },[]);

  const sigA = designSignature(colA);
  const sigB = designSignature(colB);
  const sigC = designSignature(colC);

  React.useEffect(()=>{
    const mapping = { A: sigA, B: sigB, C: sigC };
    const last = lastDesignRef.current;
    if(!designInitRef.current){
      lastDesignRef.current = { ...mapping };
      designInitRef.current = true;
      return;
    }
    let delta = 0;
    const next = { ...last };
    ["A","B","C"].forEach((key)=>{
      const sig = mapping[key];
      if(sig !== last[key]){
        next[key] = sig;
        if(sig){ delta += 1; }
      }
    });
    lastDesignRef.current = next;
    if(delta>0){
      incrementCounter(delta);
    }
  },[sigA,sigB,sigC,incrementCounter]);

  function HeaderSelect({value,onChange}){
    return (
      <select className="pwlc-field-control pwlc-field-control--sm" value={value} onChange={(e)=>onChange(e.target.value)}>
        {CABINETS.map(c=>(<option key={c.id} value={c.id}>{c.label}</option>))}
      </select>
    );
  }
  function PitchSelect({value,onChange,options}){
    return (
      <select
        className="pwlc-field-control pwlc-field-control--sm"
        value={value}
        onChange={(e)=>onChange(parseFloat(e.target.value))}
      >
        {options.map(p=>(<option key={p} value={p}>{fmtPitch(p)}</option>))}
      </select>
    );
  }

  function cellData(data, field){
    if(!data) return "—";
    const { pitch, grid, actualWmm, actualHmm, actualAreaM2, diagIn, resX, resY, deltaWmm, deltaHmm, deltaWpct, deltaHpct, matchPct, totalPixels, pixelDensityMpixPerSqm, totalCabinets, processor, totalPixelsMP } = data;
    const actualW=fromMM(actualWmm,displayUnit); const actualH=fromMM(actualHmm,displayUnit);
    const actualWft=fromMM(actualWmm,"ft"); const actualHft=fromMM(actualHmm,"ft");
    const actualAreaFt2=actualWft * actualHft;
    switch(field){
      case "layout": return `${grid.cols} × ${grid.rows}`;
      case "totalCabinets": {
        if(!Number.isFinite(totalCabinets)){
          return "—";
        }
        return `${totalCabinets.toLocaleString()} cabinets`;
      }
      case "dimension": return `${fmt(actualW,3)} ${displayUnit} × ${fmt(actualH,3)} ${displayUnit}`;
      case "diag": return `${Math.ceil(diagIn)} in`;
      case "res": return `${resX.toLocaleString()} × ${resY.toLocaleString()}`;
      case "aspect": return formatAspect(resX,resY);
      case "area": return `${fmt(actualAreaM2,3)} m² (${fmt(actualAreaFt2,3)} ft²)`;
      case "pixels": return formatPixelsWithStandard(totalPixels);
      case "pixelDensity": return Number.isFinite(pixelDensityMpixPerSqm) ? `${fmt(pixelDensityMpixPerSqm,3)} Mpix/sqm` : "—";
      case "minViewingDistance": {
        const minViewingDistanceM = ceilToDecimals(pitch,1);
        const minViewingDistanceFt = ceilToDecimals(pitch * FT_PER_M,0);
        if(!Number.isFinite(minViewingDistanceM) || !Number.isFinite(minViewingDistanceFt)){
          return "—";
        }
        const minViewingDistanceMLabel = fmtFixed(minViewingDistanceM,1);
        const minViewingDistanceFtLabel = minViewingDistanceFt.toLocaleString();
        return `> ${minViewingDistanceMLabel} m (> ${minViewingDistanceFtLabel} ft)`;
      }
      case "processor": {
        if(processor && processor.summary){
          return processor.summary;
        }
        if(Number.isFinite(totalPixelsMP)){
          return `${fmt(totalPixelsMP, 2)} MP load | Recommended: None available`;
        }
        if(Number.isFinite(totalPixels)){
          return `${fmt(totalPixels / 1_000_000, 2)} MP load | Recommended: None available`;
        }
        return "—";
      }
      case "match": return `${fmt(matchPct,2)} %`;
      case "dW": return `${fmt(deltaWmm,0)} mm (${fmt(deltaWpct,2)}%)`;
      case "dH": return `${fmt(deltaHmm,0)} mm (${fmt(deltaHpct,2)}%)`;
      case "pitchDisplay":
      case "pitchLabel": return fmtPitch(pitch);
      default: return "—";
    }
  }

  const resTarget = React.useMemo(()=>{
    const choice = RES_CHOICES.find(r=>r.id===desiredRes);
    return choice && choice.target ? choice.target : null;
  },[desiredRes]);

  const bestPitches = React.useMemo(()=>{
    if(!resTarget) return null;
    const meetsTarget=(data)=>{
      if(!data) return false;
      return data.resX>=resTarget.x && data.resY>=resTarget.y;
    };
    const compute=(cabId)=>{
      const rawOptions=getSupportedPitches(cabId);
      if(!rawOptions.length) return null;
      const options=rawOptions.slice().sort((a,b)=>a-b);
      let largestMeeting=null;
      let fallbackPitch=null;
      let fallbackScore=Infinity;
      options.forEach((pitch)=>{
        const data=buildColumn(cabId,pitch);
        if(!data) return;
        if(meetsTarget(data)){
          if(largestMeeting==null || pitch>largestMeeting){
            largestMeeting=pitch;
          }
        }
        const score=resScore(data.resX,data.resY,resTarget);
        if(score<fallbackScore-1e-9 || (Math.abs(score-fallbackScore)<=1e-9 && (fallbackPitch==null || pitch<fallbackPitch))){
          fallbackScore=score;
          fallbackPitch=pitch;
        }
      });
      if(largestMeeting!=null) return largestMeeting;
      return fallbackPitch;
    };
    return { A: compute(cabA), B: compute(cabB), C: compute(cabC) };
  },[resTarget,cabA,cabB,cabC,desiredWmm,desiredHmm,fitMode]);

  const handlePitchAChange = React.useCallback((value)=>{
    manualPitchRef.current.A = true;
    setPitchA(value);
  },[]);
  const handlePitchBChange = React.useCallback((value)=>{
    manualPitchRef.current.B = true;
    setPitchB(value);
  },[]);
  const handlePitchCChange = React.useCallback((value)=>{
    manualPitchRef.current.C = true;
    setPitchC(value);
  },[]);

  React.useEffect(()=>{
    manualPitchRef.current = { A: false, B: false, C: false };
  },[desiredRes,desiredWmm,desiredHmm,fitMode]);

  React.useEffect(()=>{
    manualPitchRef.current.A = false;
  },[cabA]);
  React.useEffect(()=>{
    manualPitchRef.current.B = false;
  },[cabB]);
  React.useEffect(()=>{
    manualPitchRef.current.C = false;
  },[cabC]);

  React.useEffect(()=>{
    if(!bestPitches) return;
    const ensurePitch=(key,recommended,setPitch,currentPitch,columnData)=>{
      if(recommended==null || !Number.isFinite(recommended) || !resTarget) return;
      const meetsTarget = columnData && columnData.resX>=resTarget.x && columnData.resY>=resTarget.y;
      if(manualPitchRef.current[key] && meetsTarget){
        return;
      }
      manualPitchRef.current[key] = false;
      if(!Number.isFinite(currentPitch) || Math.abs(currentPitch-recommended)>1e-9){
        setPitch(recommended);
      }
    };
    ensurePitch("A",bestPitches.A,setPitchA,pitchA,colA);
    ensurePitch("B",bestPitches.B,setPitchB,pitchB,colB);
    ensurePitch("C",bestPitches.C,setPitchC,pitchC,colC);
  },[bestPitches,resTarget,pitchA,pitchB,pitchC,colA,colB,colC]);

  const screenInputRows = React.useMemo(()=>([
    { label: "Cabinet Size", field: "cabinetSelect" },
    { label: "Pixel Pitch", field: "pitchSelect" },
  ]),[]);
  const screenMetricRows = React.useMemo(()=>([
    { label: "Cabinet Layout (C×R)", field: "layout" },
    { label: "Total Cabinets", field: "totalCabinets" },
    { label: "Actual Screen Dimension", field: "dimension" },
    { label: "Actual Screen Area", field: "area" },
    { label: "Screen Diagonal (in)", field: "diag" },
    { label: "Pixel Pitch", field: "pitchDisplay" },
    { label: "Screen Resolution", field: "res" },
    { label: "Aspect Ratio", field: "aspect" },
    { label: "Total Pixels", field: "pixels" },
    { label: "Pixel Density", field: "pixelDensity" },
    { label: "Processor Recommendation", field: "processor" },
    { label: "Minimum Viewing Distance", field: "minViewingDistance" },
    { label: "Δ Width vs Desired", field: "dW" },
    { label: "Δ Height vs Desired", field: "dH" },
    { label: "Matching Percent (area)", field: "match" },
  ]),[]);
  const screenTableRows = React.useMemo(()=>([
    ...screenInputRows,
    ...screenMetricRows,
  ]),[screenInputRows,screenMetricRows]);

  function bestIndex(){ if(!resTarget) return -1; const scores=[colA,colB,colC].map(c=>c?resScore(c.resX,c.resY,resTarget):Infinity); let min=Infinity,idx=-1; scores.forEach((s,i)=>{ if(s<min){min=s;idx=i;} }); return idx; }
  const best=bestIndex();

  function buildExportText(label,cabId,pitch,data){
    const cabLabel=(cabinetsById[cabId]||{}).label||cabId;
    const lines=[];
    lines.push(`${label}:`);
    lines.push(`Cabinet Size: ${cabLabel}`);
    lines.push(`Cabinet Layout (C×R): ${data? cellData(data,"layout") : "—"}`);
    lines.push(`Total Cabinets: ${data? cellData(data,"totalCabinets") : "—"}`);
    lines.push(`Actual Screen Dimension: ${data? cellData(data,"dimension") : "—"}`);
    lines.push(`Actual Screen Area: ${data? cellData(data,"area") : "—"}`);
    lines.push(`Screen Diagonal (in): ${data? cellData(data,"diag") : "—"}`);
    lines.push(`Pixel Pitch: ${fmtPitch(pitch)}`);
    lines.push(`Screen Resolution: ${data? cellData(data,"res") : "—"}`);
    lines.push(`Aspect Ratio: ${data? cellData(data,"aspect") : "—"}`);
    lines.push(`Total Pixels: ${data? cellData(data,"pixels") : "—"}`);
    lines.push(`Pixel Density: ${data? cellData(data,"pixelDensity") : "—"}`);
    lines.push(`Processor Recommendation: ${data? cellData(data,"processor") : "—"}`);
    lines.push(`Minimum Viewing Distance: ${data? cellData(data,"minViewingDistance") : "—"}`);
    lines.push(`Δ Width vs Desired: ${data? cellData(data,"dW") : "—"}`);
    lines.push(`Δ Height vs Desired: ${data? cellData(data,"dH") : "—"}`);
    lines.push(`Matching Percent (area): ${data? cellData(data,"match") : "—"}`);
    const year=(new Date()).getFullYear();
    lines.push("");
    lines.push(`Designs Calculated with this Tool: ${designCounter.toLocaleString()}`);
    lines.push(`Pixel Calculator for LED Video Wall V${TOOL_VERSION} — Made by PixelCal.com`);
    lines.push(`© {year} PixelCal.com All rights reserved.`.replace("{year}", String(year)));
    return lines.join("\n");
  }

  function buildManualExportText(label,cabId,pitch,formatted){
    const cabLabel=(cabinetsById[cabId]||{}).label||cabId;
    const lines=[];
    lines.push(`${label}:`);
    lines.push(`Cabinet Size: ${cabLabel}`);
    lines.push(`Cabinet Layout (C×R): ${formatted ? formatted.layoutLabel : "—"}`);
    lines.push(`Total Cabinets: ${formatted ? formatted.totalCabinetsLabel : "—"}`);
    lines.push(`Screen Dimension: ${formatted ? formatted.dimensionLabel : "—"}`);
    if(formatted){
      lines.push(`Screen Dimension (ft): ${formatted.dimensionFeetLabel}`);
    }
    lines.push(`Screen Area: ${formatted ? formatted.areaLabel : "—"}`);
    lines.push(`Screen Diagonal (in): ${formatted ? formatted.diagLabel : "—"}`);
    lines.push(`Pixel Pitch: ${formatted ? formatted.pitchLabel : fmtPitch(pitch)}`);
    lines.push(`Screen Resolution: ${formatted ? formatted.resolutionLabel : "—"}`);
    lines.push(`Aspect Ratio: ${formatted ? formatted.aspectLabel : "—"}`);
    lines.push(`Total Pixels: ${formatted ? formatted.totalPixelsLabel : "—"}`);
    lines.push(`Pixel Density: ${formatted ? formatted.pixelDensityLabel : "—"}`);
    lines.push(`Processor Recommendation: ${formatted ? formatted.processorRecommendation : "—"}`);
    lines.push(`Minimum Viewing Distance: ${formatted ? formatted.minViewingDistanceLabel : "—"}`);
    const year=(new Date()).getFullYear();
    lines.push("");
    lines.push(`Designs Calculated with this Tool: ${designCounter.toLocaleString()}`);
    lines.push(`Pixel Calculator for LED Video Wall V${TOOL_VERSION} — Made by PixelCal.com`);
    lines.push(`© {year} PixelCal.com All rights reserved.`.replace("{year}", String(year)));
    return lines.join("\n");
  }

  function buildVisualExportText(label,cabId,pitch,formatted,clearanceMm,screenHeightMm){
    const cabLabel=(cabinetsById[cabId]||{}).label||cabId;
    const lines=[];
    lines.push(`${label}:`);
    lines.push(`Cabinet Size: ${cabLabel}`);
    lines.push(`Cabinet Layout (C×R): ${formatted ? formatted.layoutLabel : "—"}`);
    lines.push(`Total Cabinets: ${formatted ? formatted.totalCabinetsLabel : "—"}`);
    lines.push(`Screen Dimension: ${formatted ? formatted.dimensionLabel : "—"}`);
    lines.push(`Screen Dimension (ft): ${formatted ? formatted.dimensionFeetLabel : "—"}`);
    lines.push(`Screen Area: ${formatted ? formatted.areaLabel : "—"}`);
    lines.push(`Screen Diagonal (in): ${formatted ? formatted.diagLabel : "—"}`);
    lines.push(`Pixel Pitch: ${formatted ? formatted.pitchLabel : fmtPitch(pitch)}`);
    lines.push(`Screen Resolution: ${formatted ? formatted.resolutionLabel : "—"}`);
    lines.push(`Aspect Ratio: ${formatted ? formatted.aspectLabel : "—"}`);
    lines.push(`Total Pixels: ${formatted ? formatted.totalPixelsLabel : "—"}`);
    lines.push(`Pixel Density: ${formatted ? formatted.pixelDensityLabel : "—"}`);
    lines.push(`Processor Recommendation: ${formatted ? formatted.processorRecommendation : "—"}`);
    lines.push(`Minimum Viewing Distance: ${formatted ? formatted.minViewingDistanceLabel : "—"}`);
    const clearanceFt=fromMM(clearanceMm, "ft");
    const clearanceM=fromMM(clearanceMm, "m");
    lines.push(`Bottom Edge Above Floor: ${fmt(clearanceM,3)} m (${fmtFixed(clearanceFt,1)} ft)`);
    if(Number.isFinite(screenHeightMm)){
      const overallMm=clearanceMm + screenHeightMm;
      const overallFt=fromMM(overallMm, "ft");
      const overallM=fromMM(overallMm, "m");
      lines.push(`Overall Screen Height: ${fmt(overallM,3)} m (${fmtFixed(overallFt,1)} ft)`);
    }
    const year=(new Date()).getFullYear();
    lines.push("");
    lines.push(`Designs Calculated with this Tool: ${designCounter.toLocaleString()}`);
    lines.push(`Pixel Calculator for LED Video Wall V${TOOL_VERSION} — Made by PixelCal.com`);
    lines.push(`© {year} PixelCal.com All rights reserved.`.replace("{year}", String(year)));
    return lines.join("\n");
  }

  function buildShareInputRows(options={}){
    const { includeDesiredInputs=true }=options;
    if(!includeDesiredInputs){
      return [];
    }
    const rows=[];
    const desiredWNum=Number(desiredW);
    const desiredHNum=Number(desiredH);
    if(Number.isFinite(desiredWNum) && Number.isFinite(desiredHNum) && desiredWNum>0 && desiredHNum>0){
      rows.push({
        label: "Desired Dimensions",
        value: `${fmt(desiredWNum,3)} ${unit} × ${fmt(desiredHNum,3)} ${unit}`,
      });
    }
    const fitChoice=FIT_MODES.find((mode)=>mode.id===fitMode);
    if(fitChoice && fitChoice.label){
      rows.push({ label: "Fit Strategy", value: fitChoice.label });
    }
    const resChoice=RES_CHOICES.find((choice)=>choice.id===desiredRes);
    if(resChoice && resChoice.label){
      rows.push({ label: "Desired Resolution", value: resChoice.label });
    }
    return rows;
  }

  function buildScreenSharePayload(label, cabId, pitch, data){
    if(!data){
      return null;
    }
    const cabLabel=(cabinetsById[cabId]||{}).label||cabId;
    const rows=[
      { label: "Cabinet Size", value: cabLabel },
      { label: "Cabinet Layout (C×R)", value: cellData(data,"layout") },
      { label: "Total Cabinets", value: cellData(data,"totalCabinets") },
      { label: "Actual Screen Dimension", value: cellData(data,"dimension") },
      { label: "Actual Screen Area", value: cellData(data,"area") },
      { label: "Screen Diagonal (in)", value: cellData(data,"diag") },
      { label: "Pixel Pitch", value: fmtPitch(pitch) },
      { label: "Screen Resolution", value: cellData(data,"res") },
      { label: "Aspect Ratio", value: cellData(data,"aspect") },
      { label: "Total Pixels", value: cellData(data,"pixels") },
      { label: "Pixel Density", value: cellData(data,"pixelDensity") },
      { label: "Processor Recommendation", value: cellData(data,"processor") },
      { label: "Minimum Viewing Distance", value: cellData(data,"minViewingDistance") },
      { label: "Δ Width vs Desired", value: cellData(data,"dW") },
      { label: "Δ Height vs Desired", value: cellData(data,"dH") },
      { label: "Matching Percent (area)", value: cellData(data,"match") },
    ].filter((row)=>row.value && row.value !== "—");
    const summaryRows=[...buildShareInputRows(), ...rows];
    const plainLines=[`${label} — Pixel Calculator`, ...summaryRows.map((row)=>`${row.label}: ${row.value}`)];
    plainLines.push("");
    plainLines.push(`© ${(new Date()).getFullYear()} PixelCal.com. All rights reserved.`);
    return {
      designLabel: label,
      shareKind: "screen",
      summary: summaryRows,
      plainText: plainLines.join("\n"),
    };
  }

  function buildManualSharePayload(label, cabId, pitch, formatted){
    if(!formatted){
      return null;
    }
    const cabLabel=(cabinetsById[cabId]||{}).label||cabId;
    const rows=[
      { label: "Cabinet Size", value: cabLabel },
      { label: "Cabinet Layout (C×R)", value: formatted.layoutLabel },
      { label: "Total Cabinets", value: formatted.totalCabinetsLabel },
      { label: "Screen Dimension", value: formatted.dimensionLabel },
      { label: "Screen Dimension (ft)", value: formatted.dimensionFeetLabel },
      { label: "Screen Area", value: formatted.areaLabel },
      { label: "Screen Diagonal (in)", value: formatted.diagLabel },
      { label: "Pixel Pitch", value: formatted.pitchLabel || fmtPitch(pitch) },
      { label: "Screen Resolution", value: formatted.resolutionLabel },
      { label: "Aspect Ratio", value: formatted.aspectLabel },
      { label: "Total Pixels", value: formatted.totalPixelsLabel },
      { label: "Pixel Density", value: formatted.pixelDensityLabel },
      { label: "Processor Recommendation", value: formatted.processorRecommendation },
      { label: "Minimum Viewing Distance", value: formatted.minViewingDistanceLabel },
    ].filter((row)=>row.value && row.value !== "—");
    const summaryRows=[...buildShareInputRows({ includeDesiredInputs:false }), ...rows];
    const plainLines=[`${label} — Pixel Calculator`, ...summaryRows.map((row)=>`${row.label}: ${row.value}`)];
    plainLines.push("");
    plainLines.push(`© ${(new Date()).getFullYear()} PixelCal.com. All rights reserved.`);
    return {
      designLabel: label,
      shareKind: "manual",
      summary: summaryRows,
      plainText: plainLines.join("\n"),
    };
  }

  function buildVisualSharePayload(label, cabId, pitch, formatted, clearanceMm, screenHeightMm){
    if(!formatted){
      return null;
    }
    const cabLabel=(cabinetsById[cabId]||{}).label||cabId;
    const clearanceFt=fromMM(clearanceMm, "ft");
    const clearanceM=fromMM(clearanceMm, "m");
    const rows=[
      { label: "Cabinet Size", value: cabLabel },
      { label: "Cabinet Layout (C×R)", value: formatted.layoutLabel },
      { label: "Total Cabinets", value: formatted.totalCabinetsLabel },
      { label: "Screen Dimension", value: formatted.dimensionLabel },
      { label: "Screen Dimension (ft)", value: formatted.dimensionFeetLabel },
      { label: "Screen Area", value: formatted.areaLabel },
      { label: "Screen Diagonal (in)", value: formatted.diagLabel },
      { label: "Pixel Pitch", value: formatted.pitchLabel || fmtPitch(pitch) },
      { label: "Screen Resolution", value: formatted.resolutionLabel },
      { label: "Aspect Ratio", value: formatted.aspectLabel },
      { label: "Total Pixels", value: formatted.totalPixelsLabel },
      { label: "Pixel Density", value: formatted.pixelDensityLabel },
      { label: "Processor Recommendation", value: formatted.processorRecommendation },
      { label: "Minimum Viewing Distance", value: formatted.minViewingDistanceLabel },
      { label: "Bottom Edge Above Floor", value: `${fmt(clearanceM,3)} m (${fmtFixed(clearanceFt,1)} ft)` },
    ].filter((row)=>row.value && row.value !== "—");
    if(Number.isFinite(screenHeightMm)){
      const overallMm=clearanceMm + screenHeightMm;
      const overallFt=fromMM(overallMm, "ft");
      const overallM=fromMM(overallMm, "m");
      rows.push({ label: "Overall Screen Height", value: `${fmt(overallM,3)} m (${fmtFixed(overallFt,1)} ft)` });
    }
    const summaryRows=[...buildShareInputRows({ includeDesiredInputs:false }), ...rows];
    const plainLines=[`${label} — Pixel Calculator`, ...summaryRows.map((row)=>`${row.label}: ${row.value}`)];
    plainLines.push("");
    plainLines.push(`© ${(new Date()).getFullYear()} PixelCal.com. All rights reserved.`);
    return {
      designLabel: label,
      shareKind: "visual",
      summary: summaryRows,
      plainText: plainLines.join("\n"),
    };
  }

  // RFQ state & helpers (with phone + cookies)
  const [rqSelA, setRqSelA] = React.useState(false);
  const [rqSelB, setRqSelB] = React.useState(false);
  const [rqSelC, setRqSelC] = React.useState(false);
  const [rqSelM1, setRqSelM1] = React.useState(false);
  const [rqSelV1, setRqSelV1] = React.useState(false);
  const [rqName, setRqName] = React.useState("");
  const [rqEmail, setRqEmail] = React.useState("");
  const [rqPhone, setRqPhone] = React.useState("");
  const [rqInstallEnvironment, setRqInstallEnvironment] = React.useState("");
  const [rqInstallCity, setRqInstallCity] = React.useState("");
  const [rqInstallCountry, setRqInstallCountry] = React.useState("");
  const [rqServiceScope, setRqServiceScope] = React.useState("");
  const [rqMsg, setRqMsg] = React.useState("");
  const [rqError, setRqError] = React.useState(null);
  const [rqSending, setRqSending] = React.useState(false);
  const [rqSuccessMessage, setRqSuccessMessage] = React.useState("");
  const [rfqOpen, setRfqOpen] = React.useState(false);
  const rfqContentId = "pwlc-rfq-section";

  // Load cookies on mount
  React.useEffect(()=>{
    const n=getCookie("pwlc_name"); if(n) setRqName(n);
    const e=getCookie("pwlc_email"); if(e) setRqEmail(e);
    const p=getCookie("pwlc_phone"); if(p) setRqPhone(p);
  },[]);

  const AUTO_START = '--- DESIGN SUMMARY (auto) START ---';
  const AUTO_END = '--- DESIGN SUMMARY (auto) END ---';

  const [shareState, setShareState] = React.useState({
    open: false,
    designLabel: '',
    loading: false,
    error: null,
    result: null,
  });
  const [shareCopyStatus, setShareCopyStatus] = React.useState(null);

  const captureVisualSnapshot = React.useCallback(()=>{
    const canvas = visualCanvasEl;
    if(!canvas || typeof canvas.toDataURL !== "function"){
      return null;
    }
    try {
      const canvasWidth = canvas.width || 0;
      const canvasHeight = canvas.height || 0;
      if(!(canvasWidth > 0) || !(canvasHeight > 0)){
        return null;
      }
      const maxWidth = 1600;
      let dataUrl = '';
      if(canvasWidth > maxWidth){
        const scale = maxWidth / canvasWidth;
        const targetWidth = Math.round(canvasWidth * scale);
        const targetHeight = Math.max(1, Math.round(canvasHeight * scale));
        const scaledCanvas = document.createElement('canvas');
        scaledCanvas.width = targetWidth;
        scaledCanvas.height = targetHeight;
        const ctx = scaledCanvas.getContext('2d');
        if(!ctx){
          return null;
        }
        ctx.drawImage(canvas, 0, 0, targetWidth, targetHeight);
        dataUrl = scaledCanvas.toDataURL('image/png');
      } else {
        dataUrl = canvas.toDataURL('image/png');
      }
      if(typeof dataUrl === 'string' && dataUrl.startsWith('data:image/')){
        return dataUrl;
      }
    } catch (err) {
      return null;
    }
    return null;
  },[visualCanvasEl]);

  const closeShare = React.useCallback(()=>{
    setShareState({
      open: false,
      designLabel: '',
      loading: false,
      error: null,
      result: null,
    });
    setShareCopyStatus(null);
  },[]);

  const sendShareRequest = React.useCallback(async(payload, designLabel)=>{
    if (!payload) {
      setShareState({
        open: true,
        designLabel,
        loading: false,
        error: 'Design details are not available yet. Adjust the inputs and try again.',
        result: null,
      });
      return;
    }
    if (typeof PWLC_AJAX === "undefined" || !PWLC_AJAX.ajax_url || !PWLC_AJAX.nonce) {
      setShareState({
        open: true,
        designLabel,
        loading: false,
        error: 'Sharing is unavailable right now. Please refresh and try again.',
        result: null,
      });
      return;
    }
    try {
      setShareState({
        open: true,
        designLabel,
        loading: true,
        error: null,
        result: null,
      });
      const fd = new FormData();
      fd.append('action', 'pwlc_share_design');
      fd.append('nonce', PWLC_AJAX.nonce);
      fd.append('payload', JSON.stringify({
        ...payload,
        meta: {
          toolVersion: TOOL_VERSION,
          designCounter,
        },
      }));
      const res = await fetch(PWLC_AJAX.ajax_url, {
        method: 'POST',
        body: fd,
        credentials: 'same-origin',
      });
      if (!res.ok) {
        throw new Error(`HTTP ${res.status}`);
      }
      const json = await res.json();
      if (!json || !json.success || !json.data) {
        throw new Error('Unexpected response.');
      }
      setShareState({
        open: true,
        designLabel,
        loading: false,
        error: null,
        result: json.data,
      });
    } catch (err) {
      setShareState({
        open: true,
        designLabel,
        loading: false,
        error: 'We could not prepare the share link. Please try again in a moment.',
        result: null,
      });
    }
  },[designCounter]);

  const copyValueToClipboard = React.useCallback(async(value)=>{
    if(!value){
      return 'failed';
    }
    try {
      if(typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText){
        await navigator.clipboard.writeText(value);
        return 'success';
      }
    } catch (err) {
      return 'failed';
    }
    if(typeof document !== "undefined"){
      let textarea;
      try {
        textarea=document.createElement('textarea');
        textarea.value=value;
        textarea.setAttribute('readonly','');
        textarea.style.position='absolute';
        textarea.style.left='-9999px';
        document.body.appendChild(textarea);
        textarea.select();
        const successful=document.execCommand('copy');
        return successful ? 'success' : 'failed';
      } catch (err) {
        return 'failed';
      } finally {
        if(textarea && textarea.parentNode){
          textarea.parentNode.removeChild(textarea);
        }
      }
    }
    return 'unavailable';
  },[]);

  const handleShareCopy = React.useCallback(async(value,label)=>{
    if(!value){
      return;
    }
    const result=await copyValueToClipboard(value);
    if(result==='success'){
      setShareCopyStatus({ target: label, message: 'Copied!' });
      setTimeout(()=>setShareCopyStatus(null),1500);
    } else if(result==='unavailable'){
      setShareCopyStatus({ target: label, message: 'Copy not supported' });
      setTimeout(()=>setShareCopyStatus(null),2000);
    } else {
      setShareCopyStatus({ target: label, message: 'Copy failed' });
      setTimeout(()=>setShareCopyStatus(null),2000);
    }
  },[copyValueToClipboard]);

  const adjustOrbitRadius = React.useCallback((multiplier)=>{
    const baseState = visualOrbitTargetRef.current || visualOrbitStateRef.current;
    if(!baseState){
      return;
    }
    const scene = visualSceneRef.current;
    const clampedBase = clampOrbitState(scene, baseState);
    const limits = computeOrbitLimits(scene, clampedBase.target);
    const desiredRadius = clamp(clampedBase.radius * multiplier, limits.minRadius, limits.maxRadius);
    if(Math.abs(desiredRadius - baseState.radius) < 1e-2){
      return;
    }
    const nextTargetState = clampOrbitState(scene, {
      ...clampedBase,
      radius: desiredRadius,
      minRadius: limits.minRadius,
      maxRadius: limits.maxRadius,
    });
    visualOrbitTargetRef.current = nextTargetState;
    const currentState = visualOrbitStateRef.current;
    if(currentState){
      const currentClamped = clampOrbitState(scene, currentState);
      const nextImmediateState = clampOrbitState(scene, {
        ...currentClamped,
        radius: clamp(currentClamped.radius * multiplier, limits.minRadius, limits.maxRadius),
        minRadius: limits.minRadius,
        maxRadius: limits.maxRadius,
      });
      visualOrbitStateRef.current = nextImmediateState;
      setVisualOrbitState(nextImmediateState);
    }
  },[]);

  const panOrbitByPixels = React.useCallback((deltaX,deltaY)=>{
    const scene = visualSceneRef.current;
    if(!scene){
      return;
    }
    const baseState = visualOrbitTargetRef.current || visualOrbitStateRef.current;
    if(!baseState){
      return;
    }
    const clampedBase = clampOrbitState(scene, baseState);
    if(!clampedBase.target){
      return;
    }
    const panSpeed = scene.baseSize / 650;
    const cameraOrbit = {
      target: clampedBase.target,
      azimuth: clampedBase.azimuth,
      polar: clampedBase.polar,
      radius: clampedBase.radius,
    };
    const cameraPosition = orbitStateToCameraPosition(cameraOrbit);
    const basis = createCameraBasis(cameraPosition, clampedBase.target, { x: 0, y: 0, z: 1 });
    const moveRight = -deltaX * panSpeed;
    const moveUp = deltaY * panSpeed;
    const delta = {
      x: basis.right.x * moveRight + basis.up.x * moveUp,
      y: basis.right.y * moveRight + basis.up.y * moveUp,
      z: basis.right.z * moveRight + basis.up.z * moveUp,
    };
    const nextTarget = {
      x: clampedBase.target.x + delta.x,
      y: clampedBase.target.y + delta.y,
      z: clampedBase.target.z + delta.z,
    };
    const targetState = clampOrbitState(scene, {
      ...clampedBase,
      target: nextTarget,
    });
    visualOrbitTargetRef.current = targetState;
    const currentState = visualOrbitStateRef.current;
    if(currentState){
      const currentClamped = clampOrbitState(scene, currentState);
      const referenceTarget = currentClamped.target || clampedBase.target;
      const immediateState = clampOrbitState(scene, {
        ...currentClamped,
        target: {
          x: (referenceTarget?.x ?? 0) + delta.x,
          y: (referenceTarget?.y ?? 0) + delta.y,
          z: (referenceTarget?.z ?? 0) + delta.z,
        },
      });
      visualOrbitStateRef.current = immediateState;
      setVisualOrbitState(immediateState);
    }
  },[]);

  const handleVisualZoomIn = React.useCallback(()=>{
    adjustOrbitRadius(0.9);
  },[adjustOrbitRadius]);

  const handleVisualZoomOut = React.useCallback(()=>{
    adjustOrbitRadius(1.1);
  },[adjustOrbitRadius]);

  const PAN_BUTTON_PIXEL_DELTA = 48;

  const handleVisualPanLeft = React.useCallback(()=>{
    panOrbitByPixels(PAN_BUTTON_PIXEL_DELTA,0);
  },[panOrbitByPixels]);

  const handleVisualPanRight = React.useCallback(()=>{
    panOrbitByPixels(-PAN_BUTTON_PIXEL_DELTA,0);
  },[panOrbitByPixels]);

  const handleVisualWheel = React.useCallback((event)=>{
    if(!visualSceneRef.current){
      return;
    }
    if(event.cancelable){
      event.preventDefault();
    }
    const multiplier = event.deltaY < 0 ? 0.9 : 1.1;
    adjustOrbitRadius(multiplier);
  },[adjustOrbitRadius]);

  const handleVisualPointerDown = React.useCallback((event)=>{
    if(event.pointerType === 'mouse' && event.button !== 0 && event.button !== 2){
      return;
    }
    const mode = event.pointerType === 'mouse' && event.button === 2 ? 'pan' : 'rotate';
    visualPointerModeRef.current = mode;
    visualPointerStartRef.current = { x: event.clientX, y: event.clientY };
    const scene = visualSceneRef.current;
    const targetState = visualOrbitTargetRef.current || visualOrbitStateRef.current;
    if(targetState){
      const clamped = clampOrbitState(scene, targetState);
      visualOrbitTargetRef.current = clamped;
      visualOrbitDragStartRef.current = {
        target: clamped.target ? { ...clamped.target } : { x: 0, y: 0, z: 0 },
        azimuth: clamped.azimuth,
        polar: clamped.polar,
        radius: clamped.radius,
      };
    } else {
      visualOrbitDragStartRef.current = null;
    }
    const targetEl = event.currentTarget;
    if(targetEl.setPointerCapture){
      try {
        targetEl.setPointerCapture(event.pointerId);
      } catch(_err) {
        // ignore
      }
    }
    event.preventDefault();
  },[]);

  const handleVisualPointerMove = React.useCallback((event)=>{
    const mode = visualPointerModeRef.current;
    if(!mode){
      return;
    }
    const dragStart = visualOrbitDragStartRef.current;
    if(!dragStart){
      return;
    }
    const start = visualPointerStartRef.current;
    const dx = event.clientX - start.x;
    const dy = event.clientY - start.y;
    if(mode === 'rotate'){
      const rotateSpeed = 0.0045;
      const nextAzimuth = wrapAngle(dragStart.azimuth - dx * rotateSpeed);
      const nextPolar = clamp(dragStart.polar + dy * rotateSpeed, MIN_POLAR_ANGLE, MAX_POLAR_ANGLE);
      const scene = visualSceneRef.current;
      const targetState = visualOrbitTargetRef.current || dragStart;
      const nextState = clampOrbitState(scene, {
        ...targetState,
        target: dragStart.target,
        azimuth: nextAzimuth,
        polar: nextPolar,
      });
      visualOrbitTargetRef.current = nextState;
      return;
    }
    if(mode === 'pan'){
      const scene = visualSceneRef.current;
      if(!scene){
        return;
      }
      const panSpeed = scene.baseSize / 650;
      const cameraOrbit = {
        target: dragStart.target,
        azimuth: dragStart.azimuth,
        polar: dragStart.polar,
        radius: dragStart.radius,
      };
      const cameraPosition = orbitStateToCameraPosition(cameraOrbit);
      const basis = createCameraBasis(cameraPosition, dragStart.target, { x: 0, y: 0, z: 1 });
      const moveRight = -dx * panSpeed;
      const moveUp = dy * panSpeed;
      const delta = {
        x: basis.right.x * moveRight + basis.up.x * moveUp,
        y: basis.right.y * moveRight + basis.up.y * moveUp,
        z: basis.right.z * moveRight + basis.up.z * moveUp,
      };
      const nextTarget = {
        x: dragStart.target.x + delta.x,
        y: dragStart.target.y + delta.y,
        z: dragStart.target.z + delta.z,
      };
      const targetState = visualOrbitTargetRef.current || dragStart;
      const nextState = clampOrbitState(scene, {
        ...targetState,
        target: nextTarget,
      });
      visualOrbitTargetRef.current = nextState;
    }
  },[]);

  const handleVisualPointerEnd = React.useCallback((event)=>{
    if(!visualPointerModeRef.current){
      return;
    }
    visualPointerModeRef.current = null;
    visualOrbitDragStartRef.current = null;
    const targetEl = event.currentTarget;
    if(targetEl.releasePointerCapture){
      try {
        targetEl.releasePointerCapture(event.pointerId);
      } catch(_err) {
        // ignore
      }
    }
  },[]);

  const handleVisualPointerCancel = React.useCallback((event)=>{
    visualPointerModeRef.current = null;
    visualOrbitDragStartRef.current = null;
    if(event && event.currentTarget && typeof event.pointerId !== 'undefined' && event.currentTarget.releasePointerCapture){
      try {
        event.currentTarget.releasePointerCapture(event.pointerId);
      } catch(_err) {
        // ignore
      }
    }
  },[]);

  React.useEffect(()=>{
    if(!visualContainerEl || typeof visualContainerEl.addEventListener !== 'function'){
      return undefined;
    }
    const wheelListener = (event)=>{
      handleVisualWheel(event);
    };
    visualContainerEl.addEventListener('wheel', wheelListener, { passive: false });
    return ()=>{
      visualContainerEl.removeEventListener('wheel', wheelListener);
    };
  },[visualContainerEl, handleVisualWheel]);

  const handleColumnMouseEnter = React.useCallback((designKey)=>{
    if(hoverTimeoutRef.current){
      clearTimeout(hoverTimeoutRef.current);
      hoverTimeoutRef.current=null;
    }
    setHoveredDesign(designKey);
  },[]);

  const handleColumnMouseLeave = React.useCallback((designKey)=>{
    if(hoverTimeoutRef.current){
      clearTimeout(hoverTimeoutRef.current);
    }
    hoverTimeoutRef.current=setTimeout(()=>{
      setHoveredDesign((current)=> (current===designKey ? null : current));
      hoverTimeoutRef.current=null;
    },100);
  },[]);

  const clearHoveredDesign = React.useCallback(()=>{
    if(hoverTimeoutRef.current){
      clearTimeout(hoverTimeoutRef.current);
      hoverTimeoutRef.current=null;
    }
    setHoveredDesign(null);
  },[]);

  const handleManualHoverIn = React.useCallback(()=>handleColumnMouseEnter('M1'),[handleColumnMouseEnter]);
  const handleManualHoverOut = React.useCallback(()=>handleColumnMouseLeave('M1'),[handleColumnMouseLeave]);
  const handleVisualSummaryHoverIn = React.useCallback(()=>handleColumnMouseEnter('V1'),[handleColumnMouseEnter]);
  const handleVisualSummaryHoverOut = React.useCallback(()=>handleColumnMouseLeave('V1'),[handleColumnMouseLeave]);

  const handleDesignCopy = React.useCallback(async(designKey)=>{
    let payload=null;
    let label='';
    switch(designKey){
      case 'A':
        payload=colA ? buildScreenSharePayload('Design A', cabA, pitchA, colA) : null;
        label='Design A';
        break;
      case 'B':
        payload=colB ? buildScreenSharePayload('Design B', cabB, pitchB, colB) : null;
        label='Design B';
        break;
      case 'C':
        payload=colC ? buildScreenSharePayload('Design C', cabC, pitchC, colC) : null;
        label='Design C';
        break;
      case 'M1':
        payload=manualFormatted ? buildManualSharePayload('Design M1', manualCab, manualPitch, manualFormatted) : null;
        label='Design M1';
        break;
      case 'V1':
        payload=visualFormatted ? buildVisualSharePayload('Design V1', manualCab, manualPitch, visualFormatted, visualFloorClearanceMm, visualData ? visualData.heightMm : null) : null;
        label='Design V1';
        break;
      default:
        payload=null;
        label='Design';
    }
    if(!payload){
      setDesignActionStatus({ variant: 'info', message: `${label} is not ready to copy yet.` });
      if(designActionTimeoutRef.current){
        clearTimeout(designActionTimeoutRef.current);
      }
      designActionTimeoutRef.current=setTimeout(()=>setDesignActionStatus(null),2000);
      return;
    }
    const result=await copyValueToClipboard(payload.plainText);
    let message='Copy failed. Please try again.';
    let variant='error';
    if(result==='success'){
      message=`${payload.designLabel} copied to clipboard.`;
      variant='success';
    } else if(result==='unavailable'){
      message='Copy is not supported in this environment.';
      variant='info';
    }
    setDesignActionStatus({ variant, message });
    if(designActionTimeoutRef.current){
      clearTimeout(designActionTimeoutRef.current);
    }
    designActionTimeoutRef.current=setTimeout(()=>setDesignActionStatus(null),2500);
  },[cabA, pitchA, colA, cabB, pitchB, colB, cabC, pitchC, colC, manualCab, manualPitch, manualFormatted, visualFormatted, visualFloorClearanceMm, visualData, copyValueToClipboard]);

  const handleShareClick = React.useCallback((key)=>{
    switch(key){
      case 'A':
        sendShareRequest(colA ? { ...buildScreenSharePayload('Design A', cabA, pitchA, colA), designKey: 'A' } : null, 'Design A');
        break;
      case 'B':
        sendShareRequest(colB ? { ...buildScreenSharePayload('Design B', cabB, pitchB, colB), designKey: 'B' } : null, 'Design B');
        break;
      case 'C':
        sendShareRequest(colC ? { ...buildScreenSharePayload('Design C', cabC, pitchC, colC), designKey: 'C' } : null, 'Design C');
        break;
      case 'M1':
        sendShareRequest(manualFormatted ? { ...buildManualSharePayload('Design M1', manualCab, manualPitch, manualFormatted), designKey: 'M1' } : null, 'Design M1');
        break;
      case 'V1': {
        const basePayload = visualFormatted ? buildVisualSharePayload('Design V1', manualCab, manualPitch, visualFormatted, visualFloorClearanceMm, visualData ? visualData.heightMm : null) : null;
        if(!basePayload){
          sendShareRequest(null, 'Design V1');
          break;
        }
        const snapshot = captureVisualSnapshot();
        const payloadWithSnapshot = snapshot ? { ...basePayload, canvasSnapshot: snapshot, designKey: 'V1' } : { ...basePayload, designKey: 'V1' };
        sendShareRequest(payloadWithSnapshot, 'Design V1');
        break;
      }
      default:
        break;
    }
  },[cabA,cabB,cabC,manualCab,colA,colB,colC,manualFormatted,visualFormatted,manualPitch,pitchA,pitchB,pitchC,visualFloorClearanceMm,visualData,sendShareRequest,captureVisualSnapshot]);

  const shareExpires = React.useMemo(()=>{
    if(!shareState.result || !shareState.result.expires_at){
      return null;
    }
    const d = new Date(shareState.result.expires_at);
    if(Number.isNaN(d.getTime())){
      return null;
    }
    return d;
  },[shareState.result]);

  function buildRFQAuto(){
    const sections=[];
    if(rqSelA) sections.push(buildExportText("Design A",cabA,pitchA,colA));
    if(rqSelB) sections.push(buildExportText("Design B",cabB,pitchB,colB));
    if(rqSelC) sections.push(buildExportText("Design C",cabC,pitchC,colC));
    if(rqSelM1) sections.push(buildManualExportText("Design M1", manualCab, manualPitch, manualFormatted));
    if(rqSelV1) sections.push(buildVisualExportText("Design V1", manualCab, manualPitch, visualFormatted, visualFloorClearanceMm, visualData ? visualData.heightMm : null));
    return sections.join("\n\n");
  }

  function injectAuto(prev,auto){
    const s=prev.indexOf(AUTO_START);
    const e=prev.indexOf(AUTO_END);
    const block=`${AUTO_START}\n\n${auto}\n\n${AUTO_END}`;
    if(s!==-1 && e!==-1 && e>s){
      const before=prev.slice(0,s);
      const after=prev.slice(e+AUTO_END.length);
      return `${before}${block}${after}`;
    }
    if(prev.trim().length>0){ return `${block}\n\n${prev}`; }
    return `${block}\n`;
  }

  React.useEffect(()=>{
    const auto=buildRFQAuto();
    setRqMsg(prev=>injectAuto(prev,auto));
  },[rqSelA,rqSelB,rqSelC,rqSelM1,rqSelV1,cabA,cabB,cabC,manualCab,pitchA,pitchB,pitchC,manualPitch,colA,colB,colC,manualData,manualFormatted,visualFormatted,visualFloorClearanceMm,visualData]);

  async function onSendRFQ(){
    setRqError(null); setRqSuccessMessage("");
    if(!(rqSelA||rqSelB||rqSelC||rqSelM1||rqSelV1)){ setRqError("Please select at least one design to include."); return; }
    if(!rqName.trim()){ setRqError("Please enter your name."); return; }
    const emailOk = rqEmail.includes("@") && rqEmail.includes(".");
    if(!emailOk){ setRqError("Please enter a valid email address."); return; }
    if(!rqPhone.trim()){ setRqError("Please enter your phone number."); return; }
    if(!rqInstallEnvironment){ setRqError("Please specify the installation environment."); return; }
    if(!rqInstallCity.trim()){ setRqError("Please enter the installation city."); return; }
    if(!rqInstallCountry){ setRqError("Please select the installation country."); return; }
    if(!rqServiceScope){ setRqError("Please select whether you need equipment only or full service installation."); return; }

    if (typeof PWLC_AJAX === "undefined" || !PWLC_AJAX.ajax_url || !PWLC_AJAX.nonce) {
      setRqError("Form configuration missing (AJAX). Please refresh the page.");
      return;
    }

    const selected = ["A","B","C","M1","V1"].filter((k,i)=>[rqSelA,rqSelB,rqSelC,rqSelM1,rqSelV1][i]).join(",");
    const environmentLabel = INSTALL_ENVIRONMENT_OPTIONS.find(opt=>opt.value===rqInstallEnvironment)?.label || rqInstallEnvironment;
    const countryLabel = COUNTRY_OPTIONS.find(opt=>opt.value===rqInstallCountry)?.label || rqInstallCountry;
    const scopeLabel = SERVICE_SCOPE_OPTIONS.find(opt=>opt.value===rqServiceScope)?.label || rqServiceScope;
    const locationLabel = [rqInstallCity.trim(), countryLabel].filter(Boolean).join(", ");
    const detailsLines = [
      "--- RFQ Details ---",
      `Installation Environment: ${environmentLabel || "Not specified"}`,
      `Installation Location: ${locationLabel || "Not specified"}`,
      `Service Scope: ${scopeLabel || "Not specified"}`,
      "--- RFQ Details End ---",
      "",
    ];
    const finalMessage = `${detailsLines.join("\n")}${rqMsg}`;
    if(!finalMessage.trim()){ setRqError("Please provide additional details for your request."); return; }
    const fd = new FormData();
    fd.append("action","pwlc_send_rfq");
    fd.append("nonce", PWLC_AJAX.nonce);
    fd.append("name", rqName);
    fd.append("email", rqEmail);
    fd.append("phone", rqPhone);
    fd.append("install_environment", rqInstallEnvironment);
    fd.append("install_city", rqInstallCity);
    fd.append("install_country", rqInstallCountry);
    fd.append("service_scope", rqServiceScope);
    fd.append("message", finalMessage);
    fd.append("selected", selected);

    setRqSending(true);
    try{
      const res = await fetch(PWLC_AJAX.ajax_url, { method:"POST", body: fd, credentials:"same-origin" });
      let data = null;
      try {{ data = await res.json(); }} catch(_e) {{}}
      if(!res.ok || !data || data.success !== true){
        const msg = (data && data.data && data.data.message) ? data.data.message : "Server error. Please try again later.";
        setRqError(msg);
      } else {
        // Persist cookies for 180 days
        setCookie("pwlc_name", rqName, 180);
        setCookie("pwlc_email", rqEmail, 180);
        setCookie("pwlc_phone", rqPhone, 180);
        const successMsg = (data && data.data && data.data.message) ? data.data.message : "Your RFQ is scheduled to be sent momentarily.";
        setRqSuccessMessage(successMsg);
      }
    } catch(err){
      setRqError("Network error. Please check your connection and try again.");
    } finally{
      setRqSending(false);
    }
  }

  function DesignHoverActions({ label, show, onShare, shareEnabled, onCopy, copyEnabled, onHoverIn, onHoverOut }) {
    if(!(shareEnabled || copyEnabled)){
      return null;
    }
    const containerClasses=[
      "absolute right-2 top-1/2 flex -translate-y-1/2 gap-0.5 rounded-md bg-white/95 px-1 py-[1px] text-gray-500 shadow-sm ring-1 ring-gray-200 transition-all duration-150",
      show ? "opacity-100 pointer-events-auto" : "pointer-events-none opacity-0"
    ].join(" ");
    const actionBaseClass="flex items-center justify-center";
    const sharedInteractive="focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400";
    const buildActionClass=(enabled)=>[
      actionBaseClass,
      "rounded-md p-0.5 text-gray-500 transition-colors",
      enabled ? "cursor-pointer hover:bg-blue-50 hover:text-blue-600" : "cursor-default opacity-40",
      sharedInteractive
    ].join(" ");
    const handleKeyActivate=(event, handler, enabled)=>{
      if(!enabled){
        return;
      }
      if(event.key === 'Enter' || event.key === ' '){
        event.preventDefault();
        handler();
      }
    };
    return (
      <div className={containerClasses}>
        {copyEnabled && (
          <span
            role="button"
            tabIndex={copyEnabled ? 0 : -1}
            aria-disabled={!copyEnabled}
            aria-label={`Copy ${label} details`}
            className={buildActionClass(copyEnabled)}
            onClick={copyEnabled ? onCopy : undefined}
            onKeyDown={(event)=>handleKeyActivate(event,onCopy,copyEnabled)}
            onFocus={onHoverIn}
            onBlur={onHoverOut}
            title="Copy"
          >
            <span className="material-symbols-outlined text-[4px] leading-none" aria-hidden="true">content_copy</span>
          </span>
        )}
        {shareEnabled && (
          <span
            role="button"
            tabIndex={shareEnabled ? 0 : -1}
            aria-disabled={!shareEnabled}
            aria-label={`Share ${label}`}
            className={buildActionClass(shareEnabled)}
            onClick={shareEnabled ? onShare : undefined}
            onKeyDown={(event)=>handleKeyActivate(event,onShare,shareEnabled)}
            onFocus={onHoverIn}
            onBlur={onHoverOut}
            title="Share"
          >
            <span className="material-symbols-outlined text-[4px] leading-none" aria-hidden="true">share</span>
          </span>
        )}
      </div>
    );
  }

  function HeaderCell({label,idx,hideCls,onShare,shareDisabled,onCopy,copyDisabled,hovered,onHoverIn,onHoverOut}){
    const isBest = best===idx && !!resTarget;
    const classes=["relative px-3 py-2 flex items-center gap-2", hideCls||"", isBest?"bg-green-50 ring-1 ring-inset ring-green-400":""].filter(Boolean).join(" ");
    const shareEnabled=!!onShare && !shareDisabled;
    const copyEnabled=!!onCopy && !copyDisabled;
    const hasActions=!!onShare || !!onCopy;
    const showActions=hovered && (shareEnabled || copyEnabled);
    return (
      <div className={classes} onMouseEnter={onHoverIn} onMouseLeave={onHoverOut}>
        <span className="font-semibold">{label}</span>
        {isBest && (<span className="ml-2 inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700">Best Match</span>)}
        {hasActions && (
          <DesignHoverActions
            label={label}
            show={showActions}
            onShare={onShare}
            shareEnabled={shareEnabled}
            onCopy={onCopy}
            copyEnabled={copyEnabled}
            onHoverIn={onHoverIn}
            onHoverOut={onHoverOut}
          />
        )}
      </div>
    );
  }

  const columnCellClass=(rowIdx,columnIdx,extraClasses="")=>{
    const highlight=best===columnIdx && !!resTarget;
    const zebra=rowIdx%2 ? "bg-gray-50" : "";
    const bgClass=highlight?"bg-green-50 ring-1 ring-inset ring-green-400":zebra;
    return ["border-t px-3 py-2", bgClass, extraClasses].filter(Boolean).join(" ");
  };

  const tabButtonClass=(tab)=>[
    "pwlc-tab-button",
    activeTab===tab ? "pwlc-tab-button--active" : ""
  ].filter(Boolean).join(" ");

  const hasVisualScene = !!(visualScene && visualViewport.width > 0 && visualViewport.height > 0);
  const visualZoomPercent = React.useMemo(()=>{
    if(!visualOrbitState || !(visualOrbitState.radius > 0) || !(visualOrbitState.defaultRadius > 0)){
      return 100;
    }
    return Math.round((visualOrbitState.defaultRadius / visualOrbitState.radius) * 100);
  },[visualOrbitState]);
  const visualCameraMetrics = React.useMemo(()=>{
    if(!visualOrbitState){
      return {
        distance: "—",
        height: "—",
        elevation: "—",
        azimuth: "—",
        offset: "—",
      };
    }
    const cameraPosition = orbitStateToCameraPosition(visualOrbitState);
    const distanceM = Number.isFinite(visualOrbitState.radius) ? visualOrbitState.radius / 1000 : null;
    const heightM = cameraPosition && Number.isFinite(cameraPosition.z) ? cameraPosition.z / 1000 : null;
    const polarDeg = Number.isFinite(visualOrbitState.polar) ? visualOrbitState.polar * RAD_TO_DEG : null;
    const elevationDeg = polarDeg === null ? null : (90 - polarDeg);
    const azimuthDeg = Number.isFinite(visualOrbitState.azimuth) ? wrapAngle(visualOrbitState.azimuth) * RAD_TO_DEG : null;
    let horizontalOffsetM = null;
    if(cameraPosition && visualScene){
      const roomCenter = {
        x: 0,
        y: visualScene.floorDepth / 2,
        z: visualScene.wallHeight / 2,
      };
      const dx = cameraPosition.x - roomCenter.x;
      horizontalOffsetM = dx / 1000;
    }
    return {
      distance: Number.isFinite(distanceM) ? `${fmt(distanceM,2)} m` : "—",
      height: Number.isFinite(heightM) ? `${fmt(heightM,2)} m` : "—",
      elevation: Number.isFinite(elevationDeg) ? `${fmt(elevationDeg,1)}°` : "—",
      azimuth: Number.isFinite(azimuthDeg) ? `${fmt(azimuthDeg,1)}°` : "—",
      horizontalOffset: Number.isFinite(horizontalOffsetM) ? `${fmt(horizontalOffsetM,2)} m` : "—",
    };
  },[visualOrbitState, visualScene]);
  const { distance: cameraDistanceLabel, height: cameraHeightLabel, elevation: cameraElevationLabel, azimuth: cameraAzimuthLabel, horizontalOffset: cameraHorizontalOffsetLabel } = visualCameraMetrics;
  const visualTelemetryRows = React.useMemo(()=>[
    { label: "Zoom", value: `${visualZoomPercent}%` },
    { label: "View distance", value: cameraDistanceLabel },
    { label: "View height", value: cameraHeightLabel },
    { label: "Angle Tilt", value: cameraElevationLabel },
    { label: "Angle Pan", value: cameraAzimuthLabel },
    { label: "Horizontal offset", value: cameraHorizontalOffsetLabel },
  ],[visualZoomPercent, cameraDistanceLabel, cameraHeightLabel, cameraElevationLabel, cameraAzimuthLabel, cameraHorizontalOffsetLabel]);

  const visualControlButtonClass = "pwlc-visual-controls__button";
  const visualControlButtonVariants = {
    zoomIn: "pwlc-visual-controls__button--zoom-in",
    panLeft: "pwlc-visual-controls__button--pan-left",
    center: null,
    panRight: "pwlc-visual-controls__button--pan-right",
    zoomOut: "pwlc-visual-controls__button--zoom-out",
  };
  const getVisualControlButtonClass = (variant)=>{
    if (!variant) {
      return visualControlButtonClass;
    }
    return `${visualControlButtonClass} ${variant}`;
  };
  const visualControlButtonPlacement = {
    zoomIn: { gridColumn: "2", gridRow: "1" },
    panLeft: { gridColumn: "1", gridRow: "2" },
    center: { gridColumn: "2", gridRow: "2" },
    panRight: { gridColumn: "3", gridRow: "2" },
    zoomOut: { gridColumn: "2", gridRow: "3" },
  };

  const visualScreenDesignContentId = "pwlc-visual-screen-design-panel";
  const visualContentDisplayContentId = "pwlc-visual-content-display-panel";

  const visualCalculatorContent=(
    <div className="space-y-4">
      <section className="rounded-2xl border bg-white shadow-sm">
        <div className="grid grid-cols-1 gap-6 p-4 xl:grid-cols-[minmax(0,260px)_1fr]">
          <div className="space-y-4 xl:max-w-[260px]">
            <div className="overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm">
              <div className="flex items-center justify-between border-b px-3 py-2">
                <h3 className="pwlc-section-heading">Screen Design</h3>
                <button
                  type="button"
                  onClick={handleVisualScreenDesignToggle}
                  aria-expanded={!visualScreenDesignCollapsed}
                  aria-controls={visualScreenDesignContentId}
                  className="pwlc-icon-button"
                >
                  <span className="pwlc-sr-only">{visualScreenDesignCollapsed ? "Expand screen design controls" : "Collapse screen design controls"}</span>
                  <span aria-hidden="true">{visualScreenDesignCollapsed ? "+" : "−"}</span>
                </button>
              </div>
              {!visualScreenDesignCollapsed && (
                <div id={visualScreenDesignContentId} className="pwlc-field-stack p-3">
                  <label className="pwlc-field">
                    <span className="pwlc-field-label">Cabinet</span>
                    <select
                      value={manualCab}
                      onChange={(e)=>setManualCab(e.target.value)}
                      className="pwlc-field-control"
                    >
                      {CABINETS.map((cab)=>(<option key={cab.id} value={cab.id}>{cab.label}</option>))}
                    </select>
                  </label>
                  <label className="pwlc-field">
                    <span className="pwlc-field-label">Pixel Pitch</span>
                    <select
                      value={manualPitch}
                      onChange={(e)=>setManualPitch(parseFloat(e.target.value))}
                      className="pwlc-field-control"
                    >
                      {manualPitchOptions.map((pitch)=>(<option key={pitch} value={pitch}>{fmtPitch(pitch)}</option>))}
                    </select>
                  </label>
                  <div>
                    <span className="pwlc-field-label">Columns</span>
                    <div className="pwlc-field-inline">
                      <button
                        type="button"
                        onClick={()=>adjustVisualCols(-1)}
                        className="pwlc-stepper-button"
                        aria-label="Remove column"
                      >
                        −
                      </button>
                      <input
                        type="number"
                        min="1"
                        step="1"
                        value={visualColsInput}
                        onChange={(e)=>setVisualColsInput(e.target.value)}
                        onBlur={handleVisualColsBlur}
                        className="pwlc-field-control pwlc-field-control--center"
                      />
                      <button
                        type="button"
                        onClick={()=>adjustVisualCols(1)}
                        className="pwlc-stepper-button"
                        aria-label="Add column"
                      >
                        +
                      </button>
                    </div>
                    {wallWidthWarning ? (
                      <p className="mt-2 text-sm text-amber-600" role="alert">{wallWidthWarning}</p>
                    ) : null}
                  </div>
                  <div>
                    <span className="pwlc-field-label">Rows</span>
                    <div className="pwlc-field-inline">
                      <button
                        type="button"
                        onClick={()=>adjustVisualRows(-1)}
                        className="pwlc-stepper-button"
                        aria-label="Remove row"
                      >
                        −
                      </button>
                      <input
                        type="number"
                        min="1"
                        step="1"
                        value={visualRowsInput}
                        onChange={(e)=>setVisualRowsInput(e.target.value)}
                        onBlur={handleVisualRowsBlur}
                        className="pwlc-field-control pwlc-field-control--center pwlc-field-control--sm"
                      />
                      <button
                        type="button"
                        onClick={()=>adjustVisualRows(1)}
                        className="pwlc-stepper-button"
                        aria-label="Add row"
                      >
                        +
                      </button>
                    </div>
                    {visualRowsError ? (
                      <p className="mt-2 text-sm text-red-600" role="alert">{visualRowsError}</p>
                    ) : null}
                  </div>
                  <label className="pwlc-field">
                    <span className="pwlc-field-label">Bottom edge above floor</span>
                    <div className="pwlc-field-inline">
                      <input
                        type="number"
                        min="0"
                        step="any"
                        value={visualFloorClearanceInput}
                        onChange={(e)=>setVisualFloorClearanceInput(e.target.value)}
                        onBlur={handleVisualFloorClearanceBlur}
                        className="pwlc-field-control pwlc-field-control--sm"
                      />
                      <select
                        value={visualFloorClearanceUnit}
                        onChange={(e)=>handleVisualFloorClearanceUnitChange(e.target.value)}
                        className="pwlc-field-control pwlc-field-control--sm w-24 sm:w-28"
                      >
                        {LENGTH_UNIT_OPTIONS.map((opt)=>(
                          <option key={opt.value} value={opt.value}>{opt.label}</option>
                        ))}
                      </select>
                    </div>
                  </label>
                  <label className="pwlc-field">
                    <span className="pwlc-field-label">Wall width</span>
                    <div className="pwlc-field-inline">
                      <input
                        type="number"
                        min="0"
                        step="any"
                        value={visualWallWidthInput}
                        onChange={(e)=>setVisualWallWidthInput(e.target.value)}
                        onBlur={handleVisualWallWidthBlur}
                        className="pwlc-field-control pwlc-field-control--sm"
                      />
                      <select
                        value={visualWallWidthUnit}
                        onChange={(e)=>handleVisualWallWidthUnitChange(e.target.value)}
                        className="pwlc-field-control pwlc-field-control--sm w-24 sm:w-28"
                      >
                        {LENGTH_UNIT_OPTIONS.map((opt)=>(
                          <option key={opt.value} value={opt.value}>{opt.label}</option>
                        ))}
                      </select>
                    </div>
                  </label>
                  <label className="pwlc-field">
                    <span className="pwlc-field-label">Wall height</span>
                    <div className="pwlc-field-inline">
                      <input
                        type="number"
                        min="0"
                        step="any"
                        value={visualWallHeightInput}
                        onChange={(e)=>setVisualWallHeightInput(e.target.value)}
                        onBlur={handleVisualWallHeightBlur}
                        className="pwlc-field-control pwlc-field-control--sm"
                      />
                      <select
                        value={visualWallHeightUnit}
                        onChange={(e)=>handleVisualWallHeightUnitChange(e.target.value)}
                        className="pwlc-field-control pwlc-field-control--sm w-24 sm:w-28"
                      >
                        {LENGTH_UNIT_OPTIONS.map((opt)=>(
                          <option key={opt.value} value={opt.value}>{opt.label}</option>
                        ))}
                      </select>
                    </div>
                    {wallHeightClearanceError ? (
                      <p className="mt-2 text-sm text-red-600" role="alert">Wall height must be greater than the space above the floor.</p>
                    ) : null}
                  </label>
                  <div className="pwlc-toggle-card">
                    <span className="pwlc-toggle-card__label">Display Reference Person</span>
                    <button
                      type="button"
                      role="switch"
                      aria-checked={visualPersonEnabled}
                      onClick={handleVisualPersonToggle}
                      className={`pwlc-toggle-switch${visualPersonEnabled ? " pwlc-toggle-switch--on" : ""}`}
                    >
                      <span className="pwlc-sr-only">Toggle Display Reference Person</span>
                      <span aria-hidden="true" className="pwlc-toggle-switch__thumb" />
                    </button>
                  </div>
                </div>
              )}
            </div>

            <div className="overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm">
              <div className="flex items-center justify-between border-b px-3 py-2">
                <h3 className="pwlc-section-heading">Content Display</h3>
                <button
                  type="button"
                  onClick={handleVisualContentCollapseToggle}
                  aria-expanded={!visualContentCollapsed}
                  aria-controls={visualContentDisplayContentId}
                  className="pwlc-icon-button"
                >
                  <span className="pwlc-sr-only">{visualContentCollapsed ? "Expand content display controls" : "Collapse content display controls"}</span>
                  <span aria-hidden="true">{visualContentCollapsed ? "+" : "−"}</span>
                </button>
              </div>
              {!visualContentCollapsed && (
                <div id={visualContentDisplayContentId} className="pwlc-field-stack p-3">
                  <div className="pwlc-toggle-card">
                    <span className="pwlc-toggle-card__label">Display Image Texture</span>
                    <button
                      type="button"
                      role="switch"
                      aria-checked={screenContentEnabled}
                      onClick={handleScreenContentToggle}
                      className={`pwlc-toggle-switch${screenContentEnabled ? " pwlc-toggle-switch--on" : ""}`}
                    >
                      <span className="pwlc-sr-only">Toggle image texture display</span>
                      <span aria-hidden="true" className="pwlc-toggle-switch__thumb" />
                    </button>
                  </div>
                  <div className="pwlc-field-stack">
                    <label className="pwlc-field">
                      <span className="pwlc-field-label">Sample Image</span>
                      <select
                        value={screenContentSampleId}
                        onChange={handleScreenContentSampleChange}
                        className="pwlc-field-control"
                      >
                        <option value="">Select a sample image</option>
                        {SAMPLE_SCREEN_CONTENT_IMAGES.map((sample)=>(
                          <option key={sample.id} value={sample.id}>{sample.label}</option>
                        ))}
                      </select>
                    </label>
                    <div className="pwlc-field-stack sm:flex-row sm:items-end">
                      <label className="pwlc-field pwlc-field--flex">
                        <span className="pwlc-field-label">Image Texture</span>
                        <input
                          ref={screenContentFileInputRef}
                          type="file"
                          accept="image/png,image/jpeg,image/gif,.png,.jpg,.jpeg,.gif"
                          onChange={handleScreenContentFileChange}
                          className="pwlc-field-control"
                        />
                        {screenContentImage ? (
                          <p className="pwlc-field-hint pwlc-field-hint--truncate">Selected: {screenContentImage.name}</p>
                        ) : (
                          <p className="pwlc-field-hint">Upload a custom PNG, JPG, or GIF.</p>
                        )}
                      </label>
                      <button
                        type="button"
                        onClick={handleScreenContentClear}
                        disabled={!screenContentImage}
                        className="pwlc-button pwlc-button--ghost pwlc-button--sm"
                      >
                        Clear
                      </button>
                    </div>
                  </div>
                  <label className="pwlc-field">
                    <span className="pwlc-field-label">Fitting Method</span>
                    <select value={screenContentFit} onChange={handleScreenContentFitChange} className="pwlc-field-control">
                      {SCREEN_CONTENT_FIT_OPTIONS.map((option)=>(
                        <option key={option.value} value={option.value}>{option.label}</option>
                      ))}
                    </select>
                  </label>
                  <div className="pwlc-toggle-card">
                    <span className="pwlc-toggle-card__label">Hide Cabinet Grid Lines</span>
                    <button
                      type="button"
                      role="switch"
                      aria-checked={hideCabinetGridLines}
                      onClick={handleHideCabinetGridLinesToggle}
                      className={`pwlc-toggle-switch${hideCabinetGridLines ? " pwlc-toggle-switch--on" : ""}`}
                    >
                      <span className="pwlc-sr-only">Toggle cabinet grid line visibility</span>
                      <span aria-hidden="true" className="pwlc-toggle-switch__thumb" />
                    </button>
                  </div>
                </div>
              )}
            </div>
          </div>
          <div className="flex flex-col">
            <div
              ref={attachVisualContainer}
              className="relative h-[500px] w-full overflow-hidden rounded-xl bg-slate-900"
              onPointerDown={handleVisualPointerDown}
              onPointerMove={handleVisualPointerMove}
              onPointerUp={handleVisualPointerEnd}
              onPointerCancel={handleVisualPointerCancel}
              onPointerLeave={handleVisualPointerCancel}
              onContextMenu={(event)=>event.preventDefault()}
              style={{ touchAction: "none" }}
            >
              {hasVisualScene ? (
                <>
                  <canvas
                    ref={attachVisualCanvas}
                    className="absolute inset-0 h-full w-full"
                    role="img"
                    aria-label="3D visualization of the cabinet layout"
                  />
                  <div className="pointer-events-none absolute inset-0 flex flex-col">
                    <div className="flex justify-end p-2 sm:p-3">
                      <div className="pointer-events-auto">
                        <div className="pwlc-visual-controls">
                          <button
                            type="button"
                            title="Zoom in"
                            aria-label="Zoom in"
                            onClick={(event)=>{
                              event.stopPropagation();
                              handleVisualZoomIn();
                            }}
                            onPointerDown={(event)=>event.stopPropagation()}
                            onPointerUp={(event)=>event.stopPropagation()}
                            onPointerMove={(event)=>event.stopPropagation()}
                            onPointerCancel={(event)=>event.stopPropagation()}
                            className={getVisualControlButtonClass(visualControlButtonVariants.zoomIn)}
                            style={visualControlButtonPlacement.zoomIn}
                          >
                            <span aria-hidden="true" className="material-symbols-outlined pwlc-visual-controls__icon">zoom_in</span>
                          </button>
                          <button
                            type="button"
                            title="Pan left"
                            aria-label="Pan left"
                            onClick={(event)=>{
                              event.stopPropagation();
                              handleVisualPanLeft();
                            }}
                            onPointerDown={(event)=>event.stopPropagation()}
                            onPointerUp={(event)=>event.stopPropagation()}
                            onPointerMove={(event)=>event.stopPropagation()}
                            onPointerCancel={(event)=>event.stopPropagation()}
                            className={getVisualControlButtonClass(visualControlButtonVariants.panLeft)}
                            style={visualControlButtonPlacement.panLeft}
                          >
                            <span aria-hidden="true" className="material-symbols-outlined pwlc-visual-controls__icon">chevron_left</span>
                          </button>
                          <button
                            type="button"
                            title="Center"
                            aria-label="Center view"
                            onClick={(event)=>{
                              event.stopPropagation();
                              handleVisualReset();
                            }}
                            onPointerDown={(event)=>event.stopPropagation()}
                            onPointerUp={(event)=>event.stopPropagation()}
                            onPointerMove={(event)=>event.stopPropagation()}
                            onPointerCancel={(event)=>event.stopPropagation()}
                            className={getVisualControlButtonClass(visualControlButtonVariants.center)}
                            style={visualControlButtonPlacement.center}
                          >
                            <span aria-hidden="true" className="material-symbols-outlined pwlc-visual-controls__icon">center_focus_weak</span>
                          </button>
                          <button
                            type="button"
                            title="Pan right"
                            aria-label="Pan right"
                            onClick={(event)=>{
                              event.stopPropagation();
                              handleVisualPanRight();
                            }}
                            onPointerDown={(event)=>event.stopPropagation()}
                            onPointerUp={(event)=>event.stopPropagation()}
                            onPointerMove={(event)=>event.stopPropagation()}
                            onPointerCancel={(event)=>event.stopPropagation()}
                            className={getVisualControlButtonClass(visualControlButtonVariants.panRight)}
                            style={visualControlButtonPlacement.panRight}
                          >
                            <span aria-hidden="true" className="material-symbols-outlined pwlc-visual-controls__icon">chevron_right</span>
                          </button>
                          <button
                            type="button"
                            title="Zoom out"
                            aria-label="Zoom out"
                            onClick={(event)=>{
                              event.stopPropagation();
                              handleVisualZoomOut();
                            }}
                            onPointerDown={(event)=>event.stopPropagation()}
                            onPointerUp={(event)=>event.stopPropagation()}
                            onPointerMove={(event)=>event.stopPropagation()}
                            onPointerCancel={(event)=>event.stopPropagation()}
                            className={getVisualControlButtonClass(visualControlButtonVariants.zoomOut)}
                            style={visualControlButtonPlacement.zoomOut}
                          >
                            <span aria-hidden="true" className="material-symbols-outlined pwlc-visual-controls__icon">zoom_out</span>
                          </button>
                        </div>
                      </div>
                    </div>
                    <div className="mt-auto flex justify-end p-3 sm:p-4">
                      <div className="pointer-events-none min-w-[150px] rounded-lg bg-slate-900/45 px-3 py-2 text-[0.7rem] font-medium text-emerald-300 shadow-[0_8px_25px_rgba(15,23,42,0.45)]">
                        <div className="space-y-0.5">
                          {visualTelemetryRows.map((row)=>(
                            <div key={row.label} className="flex justify-between gap-2 text-emerald-200/90">
                              <span className="uppercase tracking-wide text-[0.65rem] text-emerald-300/80">{row.label}</span>
                              <span className="font-semibold text-emerald-100">{row.value}</span>
                            </div>
                          ))}
                        </div>
                      </div>
                    </div>
                  </div>
                </>
              ) : (
                <div className="flex h-full items-center justify-center px-4 text-center text-sm text-white/70">
                  Adjust the cabinet, pitch, columns, and rows to preview the video wall.
                </div>
              )}
            </div>
            <div className="pwlc-field-hint pwlc-field-hint--static space-y-1">
              <div><span className="font-semibold text-gray-600">Body:</span> Fixed position, stands 1.82&nbsp;m tall, positioned 1&nbsp;m left of center and 2&nbsp;m from the screen.</div>
              <div><span className="font-semibold text-gray-600">Rotate:</span> Click and drag with the left mouse button.</div>
              <div><span className="font-semibold text-gray-600">Pan:</span> Drag with the right mouse button or use a two-finger drag.</div>
              <div><span className="font-semibold text-gray-600">Zoom:</span> Scroll with your mouse wheel or trackpad.</div>
            </div>
          </div>
        </div>
      </section>
      <section
        ref={visualSummaryRef}
        className="overflow-hidden rounded-2xl border bg-white shadow-sm pwlc-exportable"
        onMouseLeave={handleVisualSummaryHoverOut}
      >
        <table className="w-full text-base leading-6">
          <thead className="bg-gray-50">
            <tr>
              <th
                colSpan={2}
                className="relative px-4 py-3 text-left text-base font-semibold text-gray-800"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                <span>Design V1</span>
                <DesignHoverActions
                  label="Design V1"
                  show={hoveredDesign==='V1'}
                  onShare={()=>handleShareClick('V1')}
                  shareEnabled={!!visualFormatted}
                  onCopy={()=>handleDesignCopy('V1')}
                  copyEnabled={!!visualFormatted}
                  onHoverIn={handleVisualSummaryHoverIn}
                  onHoverOut={handleVisualSummaryHoverOut}
                />
              </th>
            </tr>
          </thead>
          <tbody className="bg-white">
            <tr className="border-t">
              <th className="w-1/3 px-4 py-2 text-left font-medium text-gray-700">Cabinet Layout (C×R)</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.layoutLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Total Cabinets</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.totalCabinetsLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Screen Dimension</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? (
                  <div className="space-y-1">
                    <div>{visualFormatted.dimensionLabel}</div>
                    <div className="pwlc-field-hint pwlc-field-hint--static">({visualFormatted.dimensionFeetLabel})</div>
                  </div>
                ) : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Screen Area</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.areaLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Screen Diagonal (in)</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.diagLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Pixel Pitch</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.pitchLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Screen Resolution</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.resolutionLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Aspect Ratio</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.aspectLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Total Pixels</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.totalPixelsLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Pixel Density</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.pixelDensityLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Processor Recommendation</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.processorRecommendation : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Minimum Viewing Distance</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualFormatted.minViewingDistanceLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Bottom Edge Above Floor</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted ? visualClearanceLabel : '—'}
              </td>
            </tr>
            <tr className="border-t">
              <th className="px-4 py-2 text-left font-medium text-gray-700">Overall Screen Height</th>
              <td
                className="px-4 py-2"
                onMouseEnter={handleVisualSummaryHoverIn}
                onMouseLeave={handleVisualSummaryHoverOut}
              >
                {visualFormatted && visualOverallHeightLabel ? visualOverallHeightLabel : '—'}
              </td>
            </tr>
          </tbody>
        </table>
      </section>
    </div>
  );

  const screenCalculatorContent=(
    <>
      <div className="mb-6 grid grid-cols-1 gap-4 rounded-2xl border bg-white p-4 shadow-sm md:grid-cols-3">
        <label className="pwlc-field">
          <span className="pwlc-field-label">Desired Screen Width</span>
          <div className="pwlc-field-inline">
            <input type="number" value={desiredW} onChange={(e)=>setDesiredW(parseFloat(e.target.value))} className="pwlc-field-control" />
            <select value={unit} onChange={(e)=>setUnit(e.target.value)} className="pwlc-field-control pwlc-field-control--sm w-28">
              <option value="m">m</option><option value="ft">ft</option><option value="in">in</option><option value="mm">mm</option>
            </select>
          </div>
        </label>
        <label className="pwlc-field">
          <span className="pwlc-field-label">Desired Screen Height</span>
          <div className="pwlc-field-inline">
            <input type="number" value={desiredH} onChange={(e)=>setDesiredH(parseFloat(e.target.value))} className="pwlc-field-control" />
            <span className="pwlc-static-chip w-28">{unit}</span>
          </div>
        </label>
        <label className="pwlc-field">
          <span className="pwlc-field-label">Fit Strategy</span>
          <select value={fitMode} onChange={(e)=>setFitMode(e.target.value)} className="pwlc-field-control">
            {FIT_MODES.map((m)=>(<option key={m.id} value={m.id}>{m.label}</option>))}
          </select>
        </label>
        <label className="pwlc-field">
          <span className="pwlc-field-label">Desired Screen Resolution</span>
          <select value={desiredRes} onChange={(e)=>setDesiredRes(e.target.value)} className="pwlc-field-control">
            {RES_CHOICES.map((r)=>(<option key={r.id} value={r.id}>{r.label}</option>))}
          </select>
        </label>
        <div className="md:col-span-3 grid grid-cols-1 gap-2 rounded-xl bg-gray-50 p-3 text-sm text-gray-700 md:grid-cols-3">
          <div><span className="font-medium">Desired Area:</span> <span title="Square meters">{fmt(desiredAreaM2,3)} m²</span></div>
          <div className="text-gray-500">(Computed from width × height)</div>
        </div>
      </div>

      <div ref={screenDesignRef} className="overflow-hidden rounded-2xl border bg-white shadow-sm pwlc-exportable" onMouseLeave={clearHoveredDesign}>
          <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 bg-gray-50 text-base font-semibold">
            <div className="px-3 py-2">Attribute</div>
            <HeaderCell
              label="Design A"
              idx={0}
              onShare={()=>handleShareClick('A')}
              shareDisabled={!colA}
              onCopy={()=>handleDesignCopy('A')}
              copyDisabled={!colA}
              hovered={hoveredDesign==='A'}
              onHoverIn={()=>handleColumnMouseEnter('A')}
              onHoverOut={()=>handleColumnMouseLeave('A')}
            />
            <HeaderCell
              label="Design B"
              idx={1}
              hideCls="hidden md:block"
              onShare={()=>handleShareClick('B')}
              shareDisabled={!colB}
              onCopy={()=>handleDesignCopy('B')}
              copyDisabled={!colB}
              hovered={hoveredDesign==='B'}
              onHoverIn={()=>handleColumnMouseEnter('B')}
              onHoverOut={()=>handleColumnMouseLeave('B')}
            />
            <HeaderCell
              label="Design C"
              idx={2}
              hideCls="hidden lg:block"
              onShare={()=>handleShareClick('C')}
              shareDisabled={!colC}
              onCopy={()=>handleDesignCopy('C')}
              copyDisabled={!colC}
              hovered={hoveredDesign==='C'}
              onHoverIn={()=>handleColumnMouseEnter('C')}
              onHoverOut={()=>handleColumnMouseLeave('C')}
            />
          </div>
          <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 text-base">
            {screenTableRows.map((row, idx) => (
              <React.Fragment key={row.field}>
                <div className={`border-t px-3 py-2 font-medium ${idx % 2 ? "bg-gray-50" : ""}`}>{row.label}</div>
                <div
                  className={columnCellClass(idx,0)}
                  onMouseEnter={()=>handleColumnMouseEnter('A')}
                  onMouseLeave={()=>handleColumnMouseLeave('A')}
                >
                  {row.field==="cabinetSelect" ? (<HeaderSelect value={cabA} onChange={setCabA} />) : row.field==="pitchSelect" ? (<PitchSelect value={pitchA} onChange={handlePitchAChange} options={getSupportedPitches(cabA)} />) : (cellData(colA,row.field))}
                </div>
                <div
                  className={columnCellClass(idx,1,"hidden md:block")}
                  onMouseEnter={()=>handleColumnMouseEnter('B')}
                  onMouseLeave={()=>handleColumnMouseLeave('B')}
                >
                  {row.field==="cabinetSelect" ? (<HeaderSelect value={cabB} onChange={setCabB} />) : row.field==="pitchSelect" ? (<PitchSelect value={pitchB} onChange={handlePitchBChange} options={getSupportedPitches(cabB)} />) : (cellData(colB,row.field))}
                </div>
                <div
                  className={columnCellClass(idx,2,"hidden lg:block")}
                  onMouseEnter={()=>handleColumnMouseEnter('C')}
                  onMouseLeave={()=>handleColumnMouseLeave('C')}
                >
                  {row.field==="cabinetSelect" ? (<HeaderSelect value={cabC} onChange={setCabC} />) : row.field==="pitchSelect" ? (<PitchSelect value={pitchC} onChange={handlePitchCChange} options={getSupportedPitches(cabC)} />) : (cellData(colC,row.field))}
                </div>
              </React.Fragment>
            ))}
        </div>
      </div>
      {designActionStatus && (
        <div
          className={`mt-3 text-sm ${designActionStatus.variant==='success' ? 'text-green-600' : designActionStatus.variant==='info' ? 'text-gray-600' : 'text-red-600'}`}
          role="status"
          aria-live="polite"
        >
          {designActionStatus.message}
        </div>
      )}
    </>
  );

  const manualCalculatorContent=(
    <div ref={manualDesignRef} className="rounded-2xl border bg-white p-4 shadow-sm pwlc-exportable" onMouseLeave={handleManualHoverOut}>
      <table className="w-full text-base leading-6">
        <thead>
          <tr>
            <th
              colSpan={2}
              className="relative px-3 py-3 text-left text-base font-semibold text-gray-800"
              onMouseEnter={handleManualHoverIn}
              onMouseLeave={handleManualHoverOut}
            >
              <span>Design M1</span>
              <DesignHoverActions
                label="Design M1"
                show={hoveredDesign==='M1'}
                onShare={()=>handleShareClick('M1')}
                shareEnabled={!!manualFormatted}
                onCopy={()=>handleDesignCopy('M1')}
                copyEnabled={!!manualFormatted}
                onHoverIn={handleManualHoverIn}
                onHoverOut={handleManualHoverOut}
              />
            </th>
          </tr>
        </thead>
        <tbody>
          <tr className="border-t">
            <th className="w-1/3 px-3 py-2 text-left font-medium text-gray-700">Cabinet Size</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>
              <HeaderSelect value={manualCab} onChange={setManualCab} />
            </td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Pixel Pitch</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>
              <PitchSelect value={manualPitch} onChange={setManualPitch} options={manualPitchOptions} />
            </td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Columns Count</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>
              <input
                type="number"
                min="1"
                step="1"
                value={manualColsInput}
                onChange={(e)=>setManualColsInput(e.target.value)}
                onBlur={handleManualColsBlur}
                className="pwlc-field-control w-32"
              />
            </td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Row Count</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>
              <input
                type="number"
                min="1"
                step="1"
                value={manualRowsInput}
                onChange={(e)=>setManualRowsInput(e.target.value)}
                onBlur={handleManualRowsBlur}
                className="pwlc-field-control w-32"
              />
            </td>
          </tr>
        </tbody>
        <tbody>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Cabinet Layout (C×R)</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>{manualFormatted ? manualFormatted.layoutLabel : "—"}</td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Total Cabinets</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>{manualFormatted ? manualFormatted.totalCabinetsLabel : "—"}</td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Screen Dimension</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>
              {manualFormatted ? (
                <div className="space-y-1">
                  <div>{manualFormatted.dimensionLabel}</div>
                  <div className="pwlc-field-hint pwlc-field-hint--static">({manualFormatted.dimensionFeetLabel})</div>
                </div>
              ) : "—"}
            </td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Screen Area</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>{manualFormatted ? manualFormatted.areaLabel : "—"}</td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Screen Diagonal (in)</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>{manualFormatted ? manualFormatted.diagLabel : "—"}</td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Pixel Pitch</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>
              {manualFormatted ? manualFormatted.pitchLabel : fmtPitch(manualPitch)}
            </td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Screen Resolution</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>{manualFormatted ? manualFormatted.resolutionLabel : "—"}</td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Aspect Ratio</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>{manualFormatted ? manualFormatted.aspectLabel : "—"}</td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Total Pixels</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>{manualFormatted ? manualFormatted.totalPixelsLabel : "—"}</td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Pixel Density</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>{manualFormatted ? manualFormatted.pixelDensityLabel : "—"}</td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Processor Recommendation</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>{manualFormatted ? manualFormatted.processorRecommendation : "—"}</td>
          </tr>
          <tr className="border-t">
            <th className="px-3 py-2 text-left font-medium text-gray-700">Minimum Viewing Distance</th>
            <td className="px-3 py-2" onMouseEnter={handleManualHoverIn} onMouseLeave={handleManualHoverOut}>
              {manualFormatted ? manualFormatted.minViewingDistanceLabel : "—"}
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  );

  return (
    <div className="pwlc-app font-sans mx-auto max-w-6xl p-4">
      <div className="mb-6 flex justify-center md:justify-start">
        <div className="flex w-full max-w-3xl flex-col gap-2 rounded-2xl border bg-white p-1 shadow-sm sm:flex-row sm:items-stretch">
          <div className="flex flex-col gap-2 sm:flex-1 sm:flex-row" role="tablist" aria-label="Pixel calculator mode">
            <button
              type="button"
              role="tab"
              aria-selected={activeTab==="visual"}
              className={tabButtonClass("visual")}
              onClick={()=>setActiveTab("visual")}
            >
              Visual Calculator
            </button>
            <button
              type="button"
              role="tab"
              aria-selected={activeTab==="screen"}
              className={tabButtonClass("screen")}
              onClick={()=>setActiveTab("screen")}
            >
              Screen Based Calculator
            </button>
            <button
              type="button"
              role="tab"
              aria-selected={activeTab==="manual"}
              className={tabButtonClass("manual")}
              onClick={()=>setActiveTab("manual")}
            >
              Manual Calculator
            </button>
          </div>
        </div>
      </div>

      <div className="mb-4 flex flex-col items-end gap-2 sm:flex-row sm:items-center sm:justify-end">
        {exportStatus ? (
          <div
            className={`text-right text-sm ${exportStatus.type === 'error' ? 'text-red-600' : exportStatus.type === 'success' ? 'text-green-600' : 'text-gray-600'}`}
            role="status"
            aria-live="polite"
          >
            {exportStatus.message}
          </div>
        ) : null}
        <button
          type="button"
          className={`pwlc-export-button${exportingDesign ? " pwlc-export-button--busy" : ""}`}
          onClick={handleExportDesign}
          disabled={exportingDesign}
          aria-busy={exportingDesign}
        >
          <span className="material-symbols-outlined pwlc-export-button__icon" aria-hidden="true">ios_share</span>
          <span className="pwlc-export-button__label">{exportingDesign ? "Exporting…" : "Export Design to PDF"}</span>
        </button>
      </div>

      {activeTab==="visual"
        ? visualCalculatorContent
        : activeTab==="screen"
          ? screenCalculatorContent
          : manualCalculatorContent}

      <form className="mt-6 rounded-2xl border bg-white p-4 shadow-sm" onSubmit={(e)=>{ e.preventDefault(); onSendRFQ(); }}>
        <button
          type="button"
          onClick={()=>setRfqOpen(prev=>!prev)}
          className="pwlc-accordion-trigger"
          aria-expanded={rfqOpen}
          aria-controls={rfqContentId}
        >
          <span>Request for Quote</span>
          <span className="pwlc-accordion-trigger__icon" aria-hidden="true">{rfqOpen ? '−' : '+'}</span>
        </button>
        {rfqOpen && (
          <div id={rfqContentId} className="mt-4">
            <div className="mb-3 grid grid-cols-1 gap-2 md:grid-cols-4 lg:grid-cols-5">
              <label className="pwlc-checkbox-label">
                <input type="checkbox" className="pwlc-checkbox" checked={rqSelA} onChange={(e)=>setRqSelA(e.target.checked)} />
                <span>Include Design A</span>
              </label>
              <label className="pwlc-checkbox-label">
                <input type="checkbox" className="pwlc-checkbox" checked={rqSelB} onChange={(e)=>setRqSelB(e.target.checked)} />
                <span>Include Design B</span>
              </label>
              <label className="pwlc-checkbox-label">
                <input type="checkbox" className="pwlc-checkbox" checked={rqSelC} onChange={(e)=>setRqSelC(e.target.checked)} />
                <span>Include Design C</span>
              </label>
              <label className="pwlc-checkbox-label">
                <input type="checkbox" className="pwlc-checkbox" checked={rqSelM1} onChange={(e)=>setRqSelM1(e.target.checked)} />
                <span>Include Design M1</span>
              </label>
              <label className="pwlc-checkbox-label">
                <input type="checkbox" className="pwlc-checkbox" checked={rqSelV1} onChange={(e)=>setRqSelV1(e.target.checked)} />
                <span>Include Design V1</span>
              </label>
            </div>
            <label className="mb-3 pwlc-field">
              <span className="pwlc-field-label">Your Name <span className="text-red-600">*</span></span>
              <input type="text" value={rqName} onChange={(e)=>setRqName(e.target.value)} className="pwlc-field-control" required />
            </label>
            <label className="mb-3 pwlc-field">
              <span className="pwlc-field-label">Your Email <span className="text-red-600">*</span></span>
              <input type="email" value={rqEmail} onChange={(e)=>setRqEmail(e.target.value)} className="pwlc-field-control" required />
            </label>
            <label className="mb-3 pwlc-field">
              <span className="pwlc-field-label">Your Phone <span className="text-red-600">*</span></span>
              <input type="tel" value={rqPhone} onChange={(e)=>setRqPhone(e.target.value)} className="pwlc-field-control" required />
            </label>
            <div className="mb-3 grid grid-cols-1 gap-3 md:grid-cols-2">
              <label className="pwlc-field">
                <span className="pwlc-field-label">Installation Environment <span className="text-red-600">*</span></span>
                <select value={rqInstallEnvironment} onChange={(e)=>setRqInstallEnvironment(e.target.value)} className="pwlc-field-control" required>
                  {INSTALL_ENVIRONMENT_OPTIONS.map((opt)=>(<option key={opt.value} value={opt.value}>{opt.label}</option>))}
                </select>
              </label>
              <label className="pwlc-field">
                <span className="pwlc-field-label">Service Scope <span className="text-red-600">*</span></span>
                <select value={rqServiceScope} onChange={(e)=>setRqServiceScope(e.target.value)} className="pwlc-field-control" required>
                  {SERVICE_SCOPE_OPTIONS.map((opt)=>(<option key={opt.value} value={opt.value}>{opt.label}</option>))}
                </select>
              </label>
            </div>
            <div className="mb-3 grid grid-cols-1 gap-3 md:grid-cols-2">
              <label className="pwlc-field">
                <span className="pwlc-field-label">Installation City <span className="text-red-600">*</span></span>
                <input type="text" value={rqInstallCity} onChange={(e)=>setRqInstallCity(e.target.value)} className="pwlc-field-control" required />
              </label>
              <label className="pwlc-field">
                <span className="pwlc-field-label">Installation Country <span className="text-red-600">*</span></span>
                <select value={rqInstallCountry} onChange={(e)=>setRqInstallCountry(e.target.value)} className="pwlc-field-control" required>
                  {COUNTRY_OPTIONS.map((opt)=>(<option key={opt.value || opt.label} value={opt.value}>{opt.label}</option>))}
                </select>
              </label>
            </div>
            <label className="mb-3 pwlc-field">
              <span className="pwlc-field-label">Message (design details included below — you can edit)</span>
              <textarea value={rqMsg} onChange={(e)=>setRqMsg(e.target.value)} rows={8} className="pwlc-field-control pwlc-field-control--textarea"></textarea>
              <p className="pwlc-field-hint">Selecting/unselecting designs updates the auto-filled summary between the markers. Your additional edits outside the markers are preserved.</p>
            </label>
            <div className="flex items-center justify-between gap-3">
              <div className="pwlc-field-hint pwlc-field-hint--static">Your request for quote will be sent to original LED manufactures and service integrators.</div>
              <button type="submit" disabled={rqSending} className="pwlc-button">
                {rqSending ? 'Scheduling…' : 'Send Request'}
              </button>
            </div>
            {rqError && (<div className="mt-2 rounded-md bg-red-50 px-3 py-2 text-sm text-red-700">{rqError}</div>)}
            {rqSuccessMessage && (<div className="mt-2 rounded-md bg-green-50 px-3 py-2 text-sm text-green-700">{rqSuccessMessage}</div>)}
          </div>
        )}
      </form>

      <section className="mt-6 border-t border-gray-200 pt-4 text-sm text-gray-600">
        <p>
          Pixel Calculator for LED Video Wall V{TOOL_VERSION} — Made by PixelCal.com — Designs Calculated: {designCounter.toLocaleString()} — © {new Date().getFullYear()} PixelCal.com. All rights reserved.
        </p>
      </section>

      {shareState.open && (
        <div className="fixed inset-0 z-40 flex items-center justify-center bg-slate-900/70 px-4 py-6">
          <div className="w-full max-w-xl rounded-2xl bg-white p-4 shadow-2xl sm:p-5 lg:max-w-2xl lg:p-6 max-h-[90vh] overflow-y-auto">
            <div className="mb-4 flex items-start justify-between gap-4">
              <div>
                <h2 className="text-lg font-semibold text-gray-900">Share {shareState.designLabel}</h2>
                {shareExpires && (
                  <p className="mt-2 text-sm text-gray-500">Share link available until {shareExpires.toLocaleString()}</p>
                )}
              </div>
              <button
                type="button"
                onClick={closeShare}
                className="pwlc-icon-button"
                aria-label="Close share dialog"
              >
                ×
              </button>
            </div>
            {shareState.loading && (
              <div className="rounded-lg border border-dashed border-gray-300 bg-gray-50 px-4 py-6 text-center text-sm text-gray-600">
                Preparing share link…
              </div>
            )}
            {!shareState.loading && shareState.error && (
              <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
                {shareState.error}
              </div>
            )}
            {!shareState.loading && shareState.result && (
              <div>
                {shareState.result.canvas_snapshot && (
                  <div className="mb-4">
                    <div className="overflow-hidden rounded-xl border border-gray-200 bg-slate-900">
                      <img src={shareState.result.canvas_snapshot} alt="Snapshot of the current canvas view" className="block w-full" />
                    </div>
                    <p className="pwlc-field-hint">Snapshot captured from the visualizer canvas.</p>
                  </div>
                )}
                <div className="rounded-xl border border-gray-200 bg-gray-50 p-3">
                  <h3 className="text-sm font-semibold text-gray-800">Design Summary</h3>
                  <dl className="mt-2 grid grid-cols-1 gap-x-4 gap-y-2 sm:grid-cols-2">
                    {(shareState.result.summary || []).map((row)=>(
                      <div key={`${row.label}-${row.value}`}>
                        <dt className="text-xs uppercase tracking-wide text-gray-500">{row.label}</dt>
                        <dd className="text-sm text-gray-900">{row.value}</dd>
                      </div>
                    ))}
                  </dl>
                </div>
                <div className="mt-4 space-y-4">
                  <div className="rounded-xl border border-gray-200 bg-white p-4 sm:p-5">
                    <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
                      <div>
                        <h3 className="text-sm font-semibold text-gray-900">Share via SMS</h3>
                        <p className="pwlc-field-hint">Send this design with a prefilled text message.</p>
                      </div>
                      <a
                        href={`sms:?&body=${encodeURIComponent(shareState.result.sms_body)}`}
                        className="pwlc-button pwlc-button--success pwlc-button--sm"
                      >
                        Open SMS Draft
                      </a>
                    </div>
                    <div className="mt-4 rounded-lg border border-gray-200 bg-gray-50 p-3">
                      <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
                        <div>
                          <div className="text-xs font-semibold uppercase text-gray-500">Share Link</div>
                          <div className="mt-1 break-all text-sm text-gray-900">{shareState.result.share_url}</div>
                        </div>
                        <button
                          type="button"
                          onClick={()=>handleShareCopy(shareState.result.share_url,'sms-link')}
                          className="pwlc-button pwlc-button--sm"
                        >
                          Copy Link
                        </button>
                      </div>
                    </div>
                    <div className="mt-3 rounded-lg border border-gray-200 bg-gray-50 p-3">
                      <div className="mb-2 flex items-center justify-between">
                        <div className="text-xs font-semibold uppercase text-gray-500">Suggested Message</div>
                        <button
                          type="button"
                          onClick={()=>handleShareCopy(shareState.result.sms_body,'sms-body')}
                          className="pwlc-button pwlc-button--sm"
                        >
                          Copy Message
                        </button>
                      </div>
                      <textarea className="pwlc-field-control pwlc-field-control--textarea" value={shareState.result.sms_body} readOnly />
                    </div>
                  </div>
                  <div className="rounded-xl border border-gray-200 bg-white p-4 sm:p-5">
                    <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
                      <div>
                        <h3 className="text-sm font-semibold text-gray-900">Share via Email</h3>
                        <p className="pwlc-field-hint">Copy the email content or launch your email client.</p>
                      </div>
                      <a
                        href={`mailto:?subject=${encodeURIComponent(shareState.result.email_subject)}&body=${encodeURIComponent(shareState.result.email_text + '\n\nView online: ' + shareState.result.share_url)}`}
                        className="pwlc-button pwlc-button--success pwlc-button--sm"
                      >
                        Open Email Draft
                      </a>
                    </div>
                    <div className="mt-4 rounded-lg border border-gray-200 bg-gray-50 p-3">
                      <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                        <div className="text-xs font-semibold uppercase text-gray-500">Rich Email Content</div>
                        <div className="flex gap-2">
                          <button
                            type="button"
                            onClick={()=>handleShareCopy(shareState.result.email_html,'email-html')}
                            className="pwlc-button pwlc-button--sm"
                          >
                            Copy HTML
                          </button>
                          <button
                            type="button"
                            onClick={()=>handleShareCopy(`${shareState.result.email_text}\n\nView online: ${shareState.result.share_url}`,'email-text')}
                            className="pwlc-button pwlc-button--sm"
                          >
                            Copy Plain Text
                          </button>
                        </div>
                      </div>
                      <div className="mt-3 max-h-64 overflow-auto rounded-lg border border-gray-200 bg-white p-3 text-sm text-gray-800" dangerouslySetInnerHTML={{ __html: shareState.result.email_html }} />
                    </div>
                  </div>
                </div>
              </div>
            )}
            {shareCopyStatus && (
              <div className="mt-4 text-center text-sm text-gray-600">{shareCopyStatus.message}</div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

const existingApi = window.PixelCalculatorApp || window.PixelWallCalculator || {};

existingApi.mount = function mount(rootId) {
  const el = document.getElementById(rootId);
  if (!el) return;
  const root = ReactDOM.createRoot(el);
  root.render(React.createElement(PixelCalculator));
};

window.PixelCalculatorApp = existingApi;
window.PixelWallCalculator = existingApi;
