מתג בין מסלול חודשי לשנתי, והמחיר לא מתחלף בקפיצה: הספרה הישנה נגללת למעלה ויוצאת, החדשה נכנסת מלמטה, וכל ספרה בעיכוב קטן משלה.
מתי משתמשים: כל סקשן מחירים עם שני מסלולים. ההתגלגלות מוכיחה שהמספר באמת השתנה, במקום שהעין תפספס את ההנחה.
הדמו כאן עיצובי-ניטרלי בכוונה. כשהמהלך נכנס לפרויקט, מיובאת רק ההתנהגות: הצבעים, הרדיוסים, הפונטים והצללים יורשים את העיצוב של אותו פרויקט.
לחודש, ללא התחייבות
חיסכון של 20%הקוד כאן עומד בפני עצמו. כל טוקן עיצוב נושא ברירת מחדל, מחלקות העזר של המאגר מוטמעות בו, ותגי הסקריפט מסודרים לפי הסדר הנכון. פרויקט שמגדיר --accent או --line משלו יורש אותם אוטומטית. הרכיב הזה מסתמך על dir="rtl".
/* מחלקות עזר של המאגר שהרכיב הזה משתמש בהן */
.stage{padding:var(--sec, clamp(120px, 12vw, 260px)) var(--gutter, clamp(24px, 5vw, 96px));position:relative}
.stage.tight{padding-block:clamp(48px,5vw,90px)}
.stage.full{padding-inline:0}
/* B20 · מחיר שמתגלגל בהחלפת מסלול */
.pr-card{max-width:420px;margin-inline:auto;background:var(--card, #fff);border:1px solid var(--line, #e4e4ee);border-radius:20px;padding:32px 28px;text-align:center}
.pr-top{display:inline-flex;align-items:center;gap:12px;font-size:14px;color:var(--muted, #6a6d85);margin-bottom:26px}
.pr-top button{background:none;border:0;font:inherit;color:var(--muted, #6a6d85);cursor:pointer;padding:4px}
.pr-top button.on{color:var(--ink, #16182b);font-weight:700}
.pr-sw{width:52px;height:30px;border-radius:999px;background:#dcdce8;position:relative;transition:background .3s;flex:none}
.pr-sw i{position:absolute;top:3px;inset-inline-start:3px;width:24px;height:24px;border-radius:50%;background:#fff /* qa-allow: white, ידית/סמן ולא משטח טקסט */;transition:transform .3s cubic-bezier(.2,.6,.2,1)}
.pr-sw.year{background:var(--accent, #4a3aff)}
.pr-sw.year i{transform:translateX(-22px)}
.pr-price{display:flex;justify-content:center;align-items:baseline;gap:4px;font-weight:800;line-height:1;font-size:clamp(52px,6vw,90px);direction:ltr}
.pr-digit{display:inline-block;overflow:hidden;height:1em;position:relative}
.pr-digit span{display:block;will-change:transform}
.pr-cur{font-size:.55em;font-weight:700;color:var(--muted, #6a6d85)}
.pr-sub{color:var(--muted, #6a6d85);font-size:14px;margin-top:14px;min-height:1.4em}
.pr-save{display:inline-block;margin-top:8px;font-size:12px;background:#e9ffd6;color:#2b6a13;border-radius:999px;padding:5px 12px}
.pr-save[hidden]{display:none}
<div class="stage tight"><div class="pr-card">
<div class="pr-top">
<button class="to-m on">חודשי</button>
<span class="pr-sw" role="button" tabindex="0" aria-label="החלפת מסלול תשלום"><i></i></span>
<button class="to-y">שנתי</button>
</div>
<div class="pr-price" aria-live="polite"><span class="pr-cur">₪</span><span class="pr-num"></span></div>
<p class="pr-sub">לחודש, ללא התחייבות</p>
<span class="pr-save" hidden>חיסכון של 20%</span>
</div></div>
(function(){
const PLANS={m:{price:"390",sub:"לחודש, ללא התחייבות",save:false},y:{price:"312",sub:"לחודש, בחיוב שנתי",save:true}};
const num=document.querySelector(".pr-num"),sub=document.querySelector(".pr-sub"),save=document.querySelector(".pr-save");
const sw=document.querySelector(".pr-sw"),bm=document.querySelector(".to-m"),by=document.querySelector(".to-y");
let plan="m",digits=[];
function build(str){
num.innerHTML="";
digits=[...str].map(c=>{
const w=document.createElement("span");w.className="pr-digit";
const inner=document.createElement("span");inner.textContent=c;
w.appendChild(inner);num.appendChild(w);return inner;
});
}
function roll(str){
if(digits.length!==str.length){build(str);return;}
[...str].forEach((c,i)=>{
const el=digits[i];
if(el.textContent===c)return;
gsap.timeline({delay:i*0.06})
.to(el,{yPercent:-110,autoAlpha:0,duration:.22,ease:"power2.in",onComplete:()=>el.textContent=c})
.fromTo(el,{yPercent:110,autoAlpha:0},{yPercent:0,autoAlpha:1,duration:.34,ease:"back.out(2)"});
});
}
function apply(next){
plan=next;const p=PLANS[plan];
roll(p.price);
sub.textContent=p.sub;save.hidden=!p.save;
sw.classList.toggle("year",plan==="y");
bm.classList.toggle("on",plan==="m");by.classList.toggle("on",plan==="y");
}
build(PLANS.m.price);
const toggle=()=>apply(plan==="m"?"y":"m");
sw.addEventListener("click",toggle);
sw.addEventListener("keydown",e=>{if(e.key===" "||e.key==="Enter"){e.preventDefault();toggle();}});
bm.addEventListener("click",()=>apply("m"));
by.addEventListener("click",()=>apply("y"));
})();