{*
  ===========================================================================
  Vanity order-form panel  (PUQ Web-Hosting module)
  ===========================================================================
  Rendered into the shopping-cart "Configure product" page for VANITY-mode
  products by the `ClientAreaFooterOutput` hook (modules/servers/puqWebHosting/
  hooks.php). It builds the "Choose your name & domain" card, MOVES the real
  WHMCS inputs (the vanity_name custom field + the domain / website-quota /
  mailbox-quota configurable-option selects) into it, live-checks name
  availability, and previews the resulting  name.domain  /  name@domain.

  ---------------------------------------------------------------------------
  CUSTOMISING THIS TEMPLATE
  ---------------------------------------------------------------------------
  * This file is plain text (NOT ionCube-encoded) — edit it freely.
  * To survive module updates, copy it to:
        templates/order_form_vanity.custom.tpl
    The hook loads the `.custom.tpl` automatically when present and never
    overwrites it; the shipped `order_form_vanity.tpl` may be replaced on
    upgrade.
  * Restyle via the CSS in the <style> block below (all classes are prefixed
    `puqv-`). Re-arrange the markup in the "SKELETON" section. The behaviour
    (JS) section can be left untouched — it only fills text, moves the real
    inputs into the `*-slot` elements, and toggles the result classes.
  * The wording (title / labels / availability messages) is translatable via
    the module language files (keys `order_*`) — it is injected at runtime,
    not hard-coded here.
  ===========================================================================
*}
<script type="text/javascript">window.PUQV_CFG = {$puqv_cfg nofilter};</script>

{literal}
<style>
/* ---- Vanity order-form panel — safe to restyle ------------------------- */
.puqv-panel{ margin-top:12px; }
.puqv-panel .puqv-name-row{ display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.puqv-panel .puqv-name-slot{ flex:1 1 160px; min-width:140px; max-width:280px; }
.puqv-panel .puqv-name-slot > .form-control{ width:100%; }
.puqv-panel .puqv-dot{ font-size:20px; color:#999; line-height:1; }
.puqv-panel .puqv-domain-slot > .form-control{ width:auto; min-width:160px; }
.puqv-panel .puqv-nodom{ color:#a94442; }
.puqv-panel .puqv-help{ margin:8px 0 0; }
.puqv-panel .puqv-quota-row{ display:flex; align-items:center; gap:8px; margin-top:10px; flex-wrap:wrap; }
.puqv-panel .puqv-quota-label{ margin:0; min-width:120px; font-weight:600; }
.puqv-panel .puqv-quota-slot > .form-control{ width:auto; min-width:210px; }
/* availability "pill" — colour/border/background are set per-state by JS */
.puqv-panel .puqv-pill{ display:none; margin-top:12px; padding:8px 16px; border:1px solid #ccc; border-radius:22px; font-size:16px; font-weight:600; line-height:1.25; }
/* spinning glyph shown while the name is being checked */
.puqv-panel .puqv-spin{ display:inline-block; animation:puqv-spin 0.9s linear infinite; }
@keyframes puqv-spin{ from{ transform:rotate(0deg); } to{ transform:rotate(360deg); } }
/* what-you-buy preview — plain framed card, one line per item */
.puqv-panel .puqv-preview{ display:none; margin-top:12px; padding:12px 16px; border:1px solid #ddd; border-radius:8px; background:#fff; font-size:15px; line-height:1.6; word-break:break-all; overflow-wrap:anywhere; }
.puqv-panel .puqv-preview .puqv-pv-row{ display:flex; gap:10px; align-items:baseline; margin:3px 0; }
.puqv-panel .puqv-preview .puqv-pv-label{ min-width:80px; color:#888; font-weight:600; }
.puqv-panel .puqv-preview code{ font-size:15px; background:transparent; padding:0; color:#333; }
</style>

<!-- ============================ SKELETON ============================ -->
<!-- Hidden until the JS has relocated it next to the cart fields and moved
     the real WHMCS inputs into the *-slot containers. -->
<div id="puqv-panel" class="puqv-panel panel panel-default" style="display:none;">
  <div class="panel-heading puqv-heading"><strong class="puqv-title"></strong></div>
  <div class="panel-body puqv-body">

    <div class="puqv-name-row">
      <span class="puqv-name-slot"></span>
      <span class="puqv-dot">.</span>
      <span class="puqv-domain-slot"></span>
    </div>

    <div class="puqv-pill" id="puqv-st"></div>

    <div class="help-block puqv-help"></div>

    <div class="puqv-quota-row puqv-site-row" style="display:none;">
      <label class="puqv-quota-label puqv-site-label"></label>
      <span class="puqv-quota-slot puqv-site-slot"></span>
    </div>
    <div class="puqv-quota-row puqv-mail-row" style="display:none;">
      <label class="puqv-quota-label puqv-mail-label"></label>
      <span class="puqv-quota-slot puqv-mail-slot"></span>
    </div>

    <div class="puqv-preview" id="puqv-pv"></div>

  </div>
</div>

<!-- ============================ BEHAVIOUR =========================== -->
<!-- Usually no need to touch this. It reads window.PUQV_CFG (printed above),
     wires the live availability check, fills text from the translations,
     moves the real inputs into the slots, and previews name.domain/name@domain. -->
<script type="text/javascript">
(function(){
  var CFG = window.PUQV_CFG || {};
  var cfId=CFG.cfId, domId=CFG.domId, siteId=CFG.siteId, mailId=CFG.mailId, pid=CFG.pid;
  var nameMax=CFG.nameMax||0, T=CFG.T||{}, coLabel=CFG.coLabel||'', checkUrl=CFG.checkUrl||'';
  var nameRe=/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
  function ready(fn){ if(document.readyState!=='loading') fn(); else document.addEventListener('DOMContentLoaded',fn); }
  function esc(s){ var d=document.createElement('div'); d.textContent=(s==null?'':String(s)); return d.innerHTML; }

  ready(function(){
    var realName=document.querySelector('[name="customfield['+cfId+']"]')||document.getElementById('customfield'+cfId);
    var panel=document.getElementById('puqv-panel');
    if(!realName||!panel||realName.getAttribute('data-puqv')) return;
    realName.setAttribute('data-puqv','1');
    function sel(id){ return id?(document.querySelector('[name="configoption['+id+']"]')||document.getElementById('configoption'+id)):null; }
    var realDom=sel(domId), realSite=sel(siteId), realMail=sel(mailId);
    var form=realName.form||realName.closest('form');

    // Smallest field-level wrapper. Standard WHMCS themes use .form-group;
    // Bootstrap-5-rebuilt themes drop it for .mb-3 / .field-container AND wrap
    // the ENTIRE order form (config fields + Order Summary + Continue button)
    // in a single .row. We must therefore NEVER fall back to .closest('.row').
    function box(el){ return el?(el.closest('.form-group')||el.closest('.mb-3')||el.closest('.field-container')||el.parentNode):null; }
    function labelOf(el){ var c=box(el); if(!c) return ''; var l=c.querySelector('label'); return l?(l.textContent||'').replace(/[:*\s]+$/,'').trim():''; }
    var nameC=box(realName), domC=box(realDom), siteC=box(realSite), mailC=box(realMail);
    var siteLbl=labelOf(realSite), mailLbl=labelOf(realMail);

    // Relocate the card to where the name field was, then move the real WHMCS
    // inputs into its slots (keeps submission + WHMCS price recalc intact).
    var anchor=nameC||realName.parentNode;
    anchor.parentNode.insertBefore(panel,anchor);

    var titleEl=panel.querySelector('.puqv-title'); if(titleEl) titleEl.textContent=T.title||'';
    var helpEl=panel.querySelector('.puqv-help'); if(helpEl) helpEl.textContent=T.help||'';

    realName.className='form-control';
    realName.setAttribute('placeholder',T.ph||'');
    realName.setAttribute('autocomplete','off');
    realName.setAttribute('autocapitalize','off');
    if(nameMax>0) realName.setAttribute('maxlength',nameMax);
    panel.querySelector('.puqv-name-slot').appendChild(realName);

    // Clone-and-sync the WHMCS config-option <select>s into the panel instead
    // of MOVING them. Moving the real select out of #productConfigurableOptions
    // breaks WHMCS's order-summary recalc (it's delegated from that container),
    // so changing Website/Mailbox quota wouldn't update the cart total or the
    // selected option's price. We keep each real select in place (hidden — so
    // submission + recalc keep working) and put a visible proxy in the panel
    // that mirrors the value and re-fires a bubbling change on the real select.
    function cloneSync(real, slotEl){
      var proxy=real.cloneNode(true);
      proxy.removeAttribute('name'); proxy.removeAttribute('id');
      proxy.className='form-control'; proxy.value=real.value;
      proxy.addEventListener('change', function(){
        real.value=proxy.value;
        // Fire from the real select's ORIGINAL position so WHMCS's recalc
        // (and the name-availability re-check on the domain) both run.
        real.dispatchEvent(new Event('change', {bubbles:true}));
      });
      real.style.display='none';
      slotEl.appendChild(proxy);
      return proxy;
    }

    var domSlot=panel.querySelector('.puqv-domain-slot');
    if(realDom){ cloneSync(realDom, domSlot); }
    else { var nd=document.createElement('span'); nd.className='puqv-nodom'; nd.textContent=T.nodom||''; domSlot.appendChild(nd); }

    function fillQuota(s,lbl,rowSel,labelSel,slotSel){
      if(!s) return;
      var rowEl=panel.querySelector(rowSel); if(!rowEl) return;
      var laEl=panel.querySelector(labelSel); if(laEl) laEl.textContent=lbl||'';
      cloneSync(s, panel.querySelector(slotSel));
      rowEl.style.display='';
    }
    fillQuota(realSite,siteLbl,'.puqv-site-row','.puqv-site-label','.puqv-site-slot');
    fillQuota(realMail,mailLbl,'.puqv-mail-row','.puqv-mail-label','.puqv-mail-slot');

    var status=panel.querySelector('#puqv-st');
    var pv=panel.querySelector('#puqv-pv');
    panel.style.display='';

    // Hide each emptied wrapper — but NEVER one that also contains the submit
    // button / Order Summary (defensive guard for shared-ancestor themes).
    function safeHide(c){
      if(!c||!c.style) return;
      if(c.querySelector&&c.querySelector('button[type=submit],input[type=submit],#btnCompleteProductConfig,.order-summary,#orderSummary,#producttotal,.secondary-cart-sidebar')) return;
      c.style.display='none';
    }
    [nameC,domC,siteC,mailC].forEach(safeHide);

    // The moved selects WERE the only "Configurable Options" entries, so that
    // cart section is now empty — drop it (container + heading) unless some
    // OTHER visible option remains.
    (function(){
      var co=document.getElementById('productConfigurableOptions');
      if(!co) return;
      var fields=co.querySelectorAll('input:not([type=hidden]), select, textarea'), vis=false;
      for(var i=0;i<fields.length;i++){ if(fields[i].offsetParent!==null){ vis=true; break; } }
      if(vis) return;
      co.style.display='none';
      // Hide the section heading that precedes the now-empty container. Themes
      // vary wildly: a bare <h*> (BS5 custom), a wrapper holding an <h*>
      // (twenty-one), or a <div class="sub-heading"><span>…</span></div>. We
      // walk back a few siblings and hide the first heading-like node — one
      // that has NO interactive content (no field / link / button) and is
      // either a heading tag, a heading-ish class, contains a heading, matches
      // the localized label text, or is just a short label block. Stop the
      // moment we hit real fields so we never touch the wrong block.
      var norm=function(s){ return (s==null?'':String(s)).replace(/\s+/g,' ').trim().toLowerCase(); };
      var want=norm(coLabel);
      function looksLikeHeading(el){
        if(!el) return false;
        if(el.querySelector&&el.querySelector('input,select,textarea,a,button')) return false;
        if(/^H[1-6]$/.test(el.tagName)) return true;
        if(/section-title|configurable|sub-?heading|subheading|cart-heading|precheckout|panel-heading|card-header/i.test(el.className||'')) return true;
        if(el.querySelector&&el.querySelector('h1,h2,h3,h4,h5,h6,.puq-auth-section-title')) return true;
        var t=norm(el.textContent);
        if(want&&t===want) return true;
        return t.length>0 && t.length<=64;
      }
      var p=co.previousElementSibling, steps=0;
      while(p && steps++<3){
        if(p.querySelector&&p.querySelector('input,select,textarea')) break;
        if(looksLikeHeading(p)){ p.style.display='none'; break; }
        p=p.previousElementSibling;
      }
    })();

    function curDom(){ if(!realDom) return ''; var o=realDom.options[realDom.selectedIndex]; if(!o) return ''; return (o.text||o.value||'').split('|')[0].trim().split(/\s/)[0].toLowerCase(); }
    function pvRow(lbl,val){ return '<div class="puqv-pv-row"><strong class="puqv-pv-label">'+esc(lbl)+'</strong><code>'+esc(val)+'</code></div>'; }
    function preview(n,d){ if(n&&d){ pv.innerHTML=pvRow(T.website,n+'.'+d)+pvRow(T.email,n+'@'+d); pv.style.display='block'; } else pv.style.display='none'; }
    function tint(hex){ if(!hex||hex.charAt(0)!=='#'||hex.length<7) return 'transparent'; var r=parseInt(hex.substr(1,2),16),g=parseInt(hex.substr(3,2),16),b=parseInt(hex.substr(5,2),16); return 'rgba('+r+','+g+','+b+',0.12)'; }
    function badge(h,c){ if(!h){ status.style.display='none'; status.innerHTML=''; return; } status.innerHTML=h; status.style.display='inline-block'; status.style.color=c||'#333'; status.style.borderColor=c||'#ccc'; status.style.background=tint(c); }
    function setSubmit(ok){ if(!form) return; var b=form.querySelectorAll('button[type=submit], input[type=submit], #btnCompleteProductConfig'); for(var i=0;i<b.length;i++){ b[i].disabled=!ok; } }

    // Show the chosen "Website: name.domain / Email: name@domain" INSIDE the
    // WHMCS Order Summary box so the customer clearly sees WHAT they're buying,
    // not just the quota/domain config rows. WHMCS rewrites the summary on every
    // recalc, so re-apply via a disconnect/reconnect MutationObserver (no loop).
    function summaryBox(){
      return document.querySelector('#orderSummary,.order-summary,#producttotal,.secondary-cart-sidebar,.summary-container,.order-summary-container');
    }
    var sumObs=null;
    function syncSummarySlot(){
      if(sumObs) sumObs.disconnect();
      var box=summaryBox();
      if(box){
        var n=(realName.value||'').trim().toLowerCase(), d=curDom();
        var el=box.querySelector('.puqv-sum-slot');
        if(n&&d){
          if(!el){
            el=document.createElement('div');
            el.className='puqv-sum-slot';
            el.style.cssText='padding:8px 10px;margin:0 0 8px;border-left:3px solid #4caf50;background:rgba(76,175,80,.10);font-size:13px;line-height:1.5;border-radius:4px;';
            box.insertBefore(el, box.firstChild);
          }
          el.innerHTML='<div><strong>'+esc(T.website||'Website')+':</strong> <code>'+esc(n+'.'+d)+'</code></div>'+
                       '<div><strong>'+esc(T.email||'Email')+':</strong> <code>'+esc(n+'@'+d)+'</code></div>';
        } else if(el&&el.parentNode){ el.parentNode.removeChild(el); }
        if(window.MutationObserver){
          if(!sumObs) sumObs=new MutationObserver(function(){ syncSummarySlot(); });
          sumObs.observe(box,{childList:true,subtree:true});
        }
      }
    }

    var timer=null, seq=0;
    function run(){
      var n=(realName.value||'').trim().toLowerCase(); realName.value=n;
      var d=curDom();
      preview(n,d);
      syncSummarySlot();
      if(!n){ badge('',''); setSubmit(true); return; }
      if(nameMax>0 && n.length>nameMax){ badge('✗ '+esc(T.toolong)+' ('+nameMax+')','#a94442'); setSubmit(false); return; }
      if(!nameRe.test(n)){ badge('✗ '+esc(T.invalid),'#a94442'); setSubmit(false); return; }
      if(!d){ badge('',''); setSubmit(false); return; }
      badge('<span class="puqv-spin">↻</span> '+esc(T.checking),'#888'); setSubmit(false);
      var my=++seq;
      var x=new XMLHttpRequest();
      x.open('GET',checkUrl+'?pid='+pid+'&domain='+encodeURIComponent(d)+'&name='+encodeURIComponent(n),true);
      x.onreadystatechange=function(){
        if(x.readyState!==4||my!==seq) return;
        var r=null; try{ r=JSON.parse(x.responseText); }catch(e){}
        if(!r||r.status!=='success'){ badge(esc(T.nocheck),'#888'); setSubmit(true); return; }
        if(r.available){ badge('✓ '+esc(T.avail),'#3c763d'); setSubmit(true); }
        else { badge('✗ '+esc(r.state==='taken'?T.taken:(r.reason||T.taken)),'#a94442'); setSubmit(false); }
      };
      x.send();
    }
    function deb(){ clearTimeout(timer); timer=setTimeout(run,400); }
    realName.addEventListener('input',deb);
    realName.addEventListener('change',deb);
    if(realDom) realDom.addEventListener('change',run);
    run();
  });
})();
</script>
{/literal}
