Pricing Cards with Sub Tabs

Description

  • Pricing Cards with sub tabs
  • view demo – password: abc
  • my friend code this, you can send her a coffee if if is useful for you

01.26c19v3

#1. Install Code

#1.1. First you need to add Section

01.26c19v3

Choose Team > Choose section with (i) icon. You need to add 3 Team sections

01.26c19v3

Make sure use Simple List

01.26c19v3

#1.2. Next, enable these options

01.26c19v3

at Content Tab, add your content

01.26c19v3

#1.3. at Title tab of First Team Section, add content like this

01.26c19v3

at Title tab of Second Team Section, add this

01.26c19v3

at Title tab of Third Team Section, add this

01.26c19v3

#1.4. Next, edit Items (repeat similar for second, third section)

01.26c19v3

at Title, enter text like this

01.26c19v3

01.26c19v3

at Description, use format like this

01.26c19v3

(after using code, we will have result like this)

 

01.26c19v3

#1.5. Hover on top right of each section > click Edit Section

01.26c19v3

at Anchor Link, enter word: pricecard-subtabs-01

pricecard-subtabs-01

01.26c19v3

at Anchor Link of second team section, enter word: pricecard-subtabs-02

pricecard-subtabs-02

01.26c19v3

at Anchor Link of third team section, enter word: pricecard-subtabs-03

pricecard-subtabs-03

01.26c19v3

#1.6. Hover on page where you added team sections > Click Gear icon

01.26c19v3

Click Advanced > Paste this code

<!-- @tuanphan - Pricing Cards Tabs -->
<style>
  body.sqs-edit-mode .tp-price-sync-wrap{display:none!important;}

  body.tp-price-sync-ready:not(.sqs-edit-mode) section[id^="pricecard-subtabs-"] .user-items-list{
    display:none!important;
  }

  .tp-price-sync-wrap{
    max-width:1200px;
    margin:0 auto;
    padding:40px 20px;
    box-sizing:border-box;
    padding-top:clamp(150px,30vh,180px);
  }

  .tp-price-sync-nav{
    text-align:center;
    margin-bottom:30px;
    border-bottom:1px solid #eee;
  }
  .tp-price-sync-nav .tp-tab-btn{
    background:transparent;
    color:#333;
    border:0;
    padding:15px 20px;
    cursor:pointer;
    margin:0 5px -1px 5px;
    font-size:18px;
    font-weight:600;
    transition:all .3s;
    border-bottom:3px solid transparent;
  }
  .tp-price-sync-nav .tp-tab-btn.is-active{
    color:#0d6efd;
    border-bottom-color:#0d6efd;
  }

  .tp-tab-panel{display:none;}
  .tp-tab-panel.is-active{display:block;}

  .tp-price-grid{
    display:grid;
    grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
    gap:25px;
    margin-top:20px;
  }

  .tp-card{
    background:#0052ff;
    color:#fff;
    padding:40px 30px;
    border-radius:16px;
    text-align:center;
    display:flex;
    flex-direction:column;
    min-height:420px;
    box-sizing:border-box;
  }
  .tp-card h3,.tp-card p,.tp-card .tp-price{color:#fff;}

  .tp-card h3{
    font-size:22px;
    font-weight:500;
    opacity:.9;
    margin:0 0 15px;
  }

  .tp-card .tp-price{
    font-size:72px;
    font-weight:700;
    margin:0;
    line-height:1;
    min-height:86px;
  }

  .tp-card .tp-note{
    font-size:16px;
    opacity:.9;
    line-height:1.5;
    margin:14px 0 0;
    flex-grow:1;
  }

  .tp-card .tp-btnwrap{
    padding-top:12px;
    margin-top:auto;
  }

  .tp-card .tp-btn{
    background:#fff;
    color:#0052ff;
    padding:18px 25px;
    border-radius:8px;
    text-decoration:none;
    font-weight:700;
    font-size:16px;
    display:block;
    width:100%;
    transition:transform .2s;
    box-sizing:border-box;
    text-align:center;
  }
  .tp-card .tp-btn:hover{transform:scale(1.02);}

  .tp-subnav{
    display:flex;
    justify-content:center;
    background:rgba(255,255,255,.1);
    border-radius:8px;
    padding:4px;
    margin:8px 0 18px;
    gap:4px;
  }
  .tp-subnav .tp-subbtn{
    background:transparent;
    border:0;
    color:#fff;
    padding:8px 12px;
    cursor:pointer;
    border-radius:6px;
    font-size:14px;
    font-weight:600;
    flex:1 1 auto;
    transition:background-color .3s,color .3s;
    white-space:nowrap;
  }
  .tp-subnav .tp-subbtn.is-active{
    background:rgba(255,255,255,.9);
    color:#0052ff;
  }

  .tp-subpanel{display:none;}
  .tp-subpanel.is-active{display:block;}
</style>

<script>
(function(){
  const SELS=[
    'section[id*="pricecard-subtabs-01"]',
    'section[id*="pricecard-subtabs-02"]',
    'section[id*="pricecard-subtabs-03"]'
  ];

  const clean=(s)=>String(s||"").replace(/\s+/g," ").trim();
  const esc=(s)=>String(s??"").replace(/[&<>"']/g,m=>({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;" }[m]));
  const slug=(s)=>clean(s).toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"");

  const normalizeUrl=(u)=>{
    const t=clean(u);
    if(!t) return "";
    if(/^https?:\/\//i.test(t)) return t;
    if(/^www\./i.test(t)) return "https://"+t;
    if(/^[\w.-]+\.[a-z]{2,}(\/.*)?$/i.test(t)) return "https://"+t;
    return t;
  };

  const getSectionTitle=(sec)=>{
    const p=sec.querySelector('.list-section-title p');
    return clean(p ? p.textContent : sec.id || "Tab");
  };

  const getDescLines=(li)=>{
    const el=li.querySelector('.list-item-content__description');
    if(!el) return [];
    const t=(el.innerText || el.textContent || "");
    return t.split(/\r?\n/).map(x=>clean(x)).filter(Boolean);
  };

  const parseBtnLine=(line)=>{
    if(!line) return null;
    const raw=String(line).trim();
    if(!raw) return null;

    const parts=raw.split(",").map(x=>x.trim()).filter(Boolean);
    if(parts.length<2) return null;

    const text=parts[0];
    const urlCandidate=parts.slice(1).join(",");

    const u=clean(urlCandidate);
    const looksLikeUrl=/^(https?:\/\/|www\.)/i.test(u) || /^[\w.-]+\.[a-z]{2,}(\/.*)?$/i.test(u);
    if(!looksLikeUrl) return null;

    const href=normalizeUrl(u);
    if(!text || !href) return null;

    return { text, href, target:"_blank" };
  };

  const parseVariantsFromLines=(lines)=>{
    const out=[];
    let cur=null;

    const pushCur=()=>{
      if(!cur) return;
      cur.note=clean((cur.noteParts||[]).join(" "));
      delete cur.noteParts;
      out.push(cur);
      cur=null;
    };

    for(const line of lines){
      if(/^##\s*/.test(line)){
        pushCur();
        cur={ label: clean(line.replace(/^##\s*/,"")), price:"", noteParts:[], btn:null };
        continue;
      }
      if(!cur) continue;

      const maybeBtn=parseBtnLine(line);
      if(maybeBtn){
        cur.btn=maybeBtn;
        continue;
      }

      if(!cur.price){
        cur.price=line;
      }else{
        cur.noteParts.push(line);
      }
    }
    pushCur();
    return out.filter(v=>v.label || v.price || v.note);
  };

  const parseItemToCard=(li)=>{
    const title=clean(li.querySelector('.list-item-content__title')?.textContent || "");
    const lines=getDescLines(li);
    const variants=parseVariantsFromLines(lines);

    const a=li.querySelector('a.list-item-content__button');
    const fallbackBtn={
      text: clean(a?.textContent || ""),
      href: a?.getAttribute('href') || "",
      target: a?.getAttribute('target') || ""
    };

    return { title, variants, fallbackBtn };
  };

  const ready=()=>{
    const secs=SELS.map(s=>document.querySelector(s)).filter(Boolean);
    const anchor=secs[0];
    if(!anchor) return false;

    if(document.querySelector('.tp-price-sync-wrap[data-tp-price-sync="1"]')) return true;

    const host=document.createElement('div');
    host.className='tp-price-sync-wrap';
    host.setAttribute('data-tp-price-sync','1');
    anchor.parentNode.insertBefore(host, anchor);

    const nav=document.createElement('div');
    nav.className='tp-price-sync-nav';
    host.appendChild(nav);

    const panels=document.createElement('div');
    panels.className='tp-price-sync-panels';
    host.appendChild(panels);

    const uid=Math.random().toString(36).slice(2,9);

    const buildCard=(card, tabKey)=>{
      const vars=(card.variants||[]).slice().sort((a,b)=>{
        const A=(a.label||"").toLowerCase(), B=(b.label||"").toLowerCase();
        const o=(x)=>x==="adult"?1:x==="student"?2:x==="child"?3:9;
        return o(A)-o(B);
      });

      const base=`tp_${uid}_${tabKey}_${slug(card.title||"card")}`;

      const subBtns=vars.map((v,i)=>{
        return `<button class="tp-subbtn${i===0?' is-active':''}" data-sub="${esc(base+'_'+i)}">${esc(v.label||('Option '+(i+1)))}</button>`;
      }).join("");

      const subPanels=vars.map((v,i)=>{
        const id=base+'_'+i;
        const btn=v.btn || null;
        const btxt=btn?.text||"";
        const bhref=btn?.href||"";
        const btar=btn?.target||"";
        return `<div class="tp-subpanel${i===0?' is-active':''}" id="${esc(id)}" data-note="${esc(v.note||"")}" data-btxt="${esc(btxt)}" data-bhref="${esc(bhref)}" data-btar="${esc(btar)}"><div class="tp-price">${esc(v.price||"")}</div></div>`;
      }).join("");

      const first=vars[0]||{};
      const firstBtn=first.btn || (card.fallbackBtn?.text ? card.fallbackBtn : null);

      const btnHtml=firstBtn?.text
        ? `<div class="tp-btnwrap"><a class="tp-btn" href="${esc(firstBtn.href||'#')}"${firstBtn.target?` target="${esc(firstBtn.target)}"`:""}>${esc(firstBtn.text)}</a></div>`
        : `<div class="tp-btnwrap"></div>`;

      return `<div class="tp-card">
        <h3>${esc(card.title||"")}</h3>
        <div class="tp-subnav">${subBtns}</div>
        ${subPanels}
        <p class="tp-note">${esc(first.note||"")}</p>
        ${btnHtml}
      </div>`;
    };

    const tabs=secs.map((sec,i)=>{
      const name=getSectionTitle(sec);
      const key=`tab_${uid}_${i}`;
      const items=[...sec.querySelectorAll('ul.user-items-list-item-container > li.list-item')].map(parseItemToCard);
      return {
        name,
        key,
        html:`<div class="tp-tab-panel${i===0?' is-active':''}" data-tabpanel="${esc(key)}">
          <div class="tp-price-grid">
            ${items.map(c=>buildCard(c,key)).join("")}
          </div>
        </div>`
      };
    });

    nav.innerHTML=tabs.map((t,i)=>`<button class="tp-tab-btn${i===0?' is-active':''}" data-tab="${esc(t.key)}">${esc(t.name)}</button>`).join("");
    panels.innerHTML=tabs.map(t=>t.html).join("");

    const openTab=(key)=>{
      nav.querySelectorAll('.tp-tab-btn').forEach(b=>b.classList.toggle('is-active', b.getAttribute('data-tab')===key));
      panels.querySelectorAll('.tp-tab-panel').forEach(p=>p.classList.toggle('is-active', p.getAttribute('data-tabpanel')===key));
    };

    const syncBtn=(cardEl)=>{
      const activePanel=cardEl.querySelector('.tp-subpanel.is-active');
      const note=activePanel ? (activePanel.getAttribute('data-note')||"") : "";
      const noteEl=cardEl.querySelector('.tp-note');
      if(noteEl) noteEl.textContent=note;

      const wrap=cardEl.querySelector('.tp-btnwrap');
      if(!wrap) return;

      const txt=activePanel?.getAttribute('data-btxt')||"";
      const href=activePanel?.getAttribute('data-bhref')||"";
      const tar=activePanel?.getAttribute('data-btar')||"";

      wrap.innerHTML=(txt && href)
        ? `<a class="tp-btn" href="${esc(href)}"${tar?` target="${esc(tar)}"`:""}>${esc(txt)}</a>`
        : ``;
    };

    const openSub=(btn)=>{
      const card=btn.closest('.tp-card');
      if(!card) return;

      const targetId=btn.getAttribute('data-sub');

      card.querySelectorAll('.tp-subbtn').forEach(b=>b.classList.toggle('is-active', b===btn));
      card.querySelectorAll('.tp-subpanel').forEach(p=>p.classList.toggle('is-active', p.id===targetId));

      syncBtn(card);
    };

    panels.querySelectorAll('.tp-card').forEach(syncBtn);

    nav.addEventListener('click',(e)=>{
      const b=e.target.closest('.tp-tab-btn');
      if(!b) return;
      openTab(b.getAttribute('data-tab'));
    });

    panels.addEventListener('click',(e)=>{
      const b=e.target.closest('.tp-subbtn');
      if(!b) return;
      openSub(b);
    });

    document.body.classList.add('tp-price-sync-ready');
    return true;
  };

  const boot=()=>{
    if(ready()) return;
    let tries=0;
    const t=setInterval(()=>{
      tries++;
      if(ready() || tries>120) clearInterval(t);
    },250);
  };

  if(document.readyState==='loading') document.addEventListener('DOMContentLoaded', boot);
  else boot();

  document.addEventListener('mercury:load', boot);
})();
</script>

01.26c19v3

#2. Customize

#2.1. To change tab titles color

01.26c19v3

Find these lines (active tab)

.tp-price-sync-nav .tp-tab-btn.is-active {
    color: #0d6efd;
    border-bottom-color: #0d6efd;
}

and these lines

.tp-price-sync-nav .tp-tab-btn {
    background: transparent;
    color: #333;
    border: 0;
    padding: 15px 20px;
    cursor: pointer;
    margin: 0 5px -1px 5px;
    font-size: 18px;
    font-weight: 600;
    transition: all .3s;
    border-bottom: 3px solid transparent;
}

#2.2. To change card background + white text color

01.26c19v3

Find these lines

.tp-card {
    background: #0052ff;
    color: #fff;
    padding: 40px 30px;
    border-radius: 16px;
    text-align: center;
    display: flex;
    flex-direction: column;
    min-height: 420px;
    box-sizing: border-box;
}

#2.3. To change card title size

01.26c19v3

Find these lines

.tp-card h3 {
    font-size: 22px;
    font-weight: 500;
    opacity: .9;
    margin: 0 0 15px;
}

#2.4. To change sub tabs style

01.26c19v3

Find these lines (active tab)

.tp-subnav .tp-subbtn.is-active {
    background: rgba(255, 255, 255, .9);
    color: #0052ff;
}

and these

.tp-subnav .tp-subbtn {
    background: transparent;
    border: 0;
    color: #fff;
    padding: 8px 12px;
    cursor: pointer;
    border-radius: 6px;
    font-size: 14px;
    font-weight: 600;
    flex: 1 1 auto;
    transition: background-color .3s, color .3s;
    white-space: nowrap;
}

and these

.tp-subnav {
    display: flex;
    justify-content: center;
    background: rgba(255, 255, 255, .1);
    border-radius: 8px;
    padding: 4px;
    margin: 8px 0 18px;
    gap: 4px;
}

#2.5. To change Price style

01.26c19v3

Find these lines

.tp-card .tp-price {
    font-size: 72px;
    font-weight: 700;
    margin: 0;
    line-height: 1;
    min-height: 86px;
}

and these

.tp-card h3, .tp-card p, .tp-card .tp-price {
    color: #fff;
}

#2.6. To change description under price

01.26c19v3

Find these lines

.tp-card .tp-note {
    font-size: 16px;
    opacity: .9;
    line-height: 1.5;
    margin: 14px 0 0;
    flex-grow: 1;
}

#2.7. To change button style

01.26c19v3

Find these lines

.tp-card .tp-btn {
    background: #fff;
    color: #0052ff;
    padding: 18px 25px;
    border-radius: 8px;
    text-decoration: none;
    font-weight: 700;
    font-size: 16px;
    display: block;
    width: 100%;
    transition: transform .2s;
    box-sizing: border-box;
    text-align: center;
}

 

Buy me a coffee