הגיעו עם שאלות שלא חשבנו עליהן, ובסוף קיבלנו אתר שמביא פניות כל שבוע. התהליך היה מסודר ובלי הפתעות.
רצועת ציטוטים שנגררת באצבע ובעכבר, נצמדת לכל כרטיס, ומסונכרנת עם חצים ונקודות. בנוי על גלילה נטיבית, ולכן חלק במובייל ולא נשבר.
מתי משתמשים: המלצות לקוחות, לוגואים, מקרי בוחן, מוצרים נבחרים. אחרי אקורדיון זה הרכיב הכי נפוץ שראינו, וכל עסק צריך אותו.
הדמו כאן עיצובי-ניטרלי בכוונה. כשהמהלך נכנס לפרויקט, מיובאת רק ההתנהגות: הצבעים, הרדיוסים, הפונטים והצללים יורשים את העיצוב של אותו פרויקט.
הגיעו עם שאלות שלא חשבנו עליהן, ובסוף קיבלנו אתר שמביא פניות כל שבוע. התהליך היה מסודר ובלי הפתעות.
שלושה ספקים לפניהם לא הצליחו. כאן הבינו את העסק שלי תוך פגישה אחת, וזה הורגש בכל שורה באתר.
הכי אהבתי שלא נעלמו אחרי העלייה לאוויר. כל שאלה נענתה, וגם חודשיים אחרי עדיין מלווים.
העלינו את האתר ותוך שבועיים ראינו הבדל בכמות הפניות. ההשקעה החזירה את עצמה מהר.
הקוד כאן עומד בפני עצמו. כל טוקן עיצוב נושא ברירת מחדל, מחלקות העזר של המאגר מוטמעות בו, ותגי הסקריפט מסודרים לפי הסדר הנכון. פרויקט שמגדיר --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}
.ph{display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;border-radius:var(--r, 14px)}
.ph-a{background:linear-gradient(160deg,#3b5bdb,#748ffc)}
.ph-c{background:linear-gradient(160deg,#0b7285,#3bc9db)}
.ph-d{background:linear-gradient(160deg,#2b8a3e,#69db7c)}
.ph-e{background:linear-gradient(160deg,#e8590c,#ffa94d)}
.ph-l{background:rgba(16,18,43,.72);color:#fff;padding:.18em .7em;border-radius:999px;line-height:1.3;font-size:max(.78em,12px)}
/* B31 · סליידר המלצות עם גרירה, חצים ונקודות */
.tq{position:relative;max-width:min(1100px,94vw);margin-inline:auto}
.tq-track{display:flex;gap:var(--gap, clamp(16px, 1.6vw, 36px));overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;padding-block:6px;
-webkit-overflow-scrolling:touch;cursor:grab}
.tq-track::-webkit-scrollbar{display:none}
.tq-track.drag{cursor:grabbing;scroll-snap-type:none;user-select:none}
.tq-card{flex:0 0 min(420px,82vw);scroll-snap-align:start;background:var(--card, #fff);border:1px solid var(--line, #e4e4ee);border-radius:var(--r, 14px);
padding:clamp(22px,2.4vw,34px);display:flex;flex-direction:column;gap:16px}
.tq-stars{color:#f0a500;font-size:15px;letter-spacing:2px}
.tq-card p{margin:0;font-size:clamp(15px,1.4vw,18px);line-height:1.65;color:var(--ink, #16182b)}
.tq-who{display:flex;align-items:center;gap:12px;margin-top:auto}
.tq-av{width:40px;height:40px;border-radius:50%;flex:none;font-size:14px}
.tq-who b{display:block;font-size:15px}
.tq-who span{font-size:13px;color:var(--muted, #6a6d85)}
.tq-bar{display:flex;align-items:center;gap:14px;justify-content:center;margin-top:26px}
.tq-nav{width:44px;height:44px;border-radius:50%;border:1px solid var(--line, #e4e4ee);background:var(--card, #fff);cursor:pointer;font-size:17px;color:var(--ink, #16182b);
display:grid;place-items:center;transition:background .25s,opacity .25s}
.tq-nav:hover{background:#eceaff}
.tq-nav[disabled]{opacity:.35;cursor:default}
.tq-dots{display:flex;gap:8px}
.tq-dots button{width:8px;height:8px;padding:0;border:0;border-radius:50%;background:var(--line, #e4e4ee);cursor:pointer;transition:/* qa-allow: layout, נקודה של 8px שנמתחת ל-24 */width .3s,background .3s}
.tq-dots button.on{width:24px;border-radius:999px;background:var(--accent, #4a3aff)}
<div class="stage tight"><div class="tq">
<div class="tq-track">
<article class="tq-card"><div class="tq-stars">★★★★★</div><p>הגיעו עם שאלות שלא חשבנו עליהן, ובסוף קיבלנו אתר שמביא פניות כל שבוע. התהליך היה מסודר ובלי הפתעות.</p><div class="tq-who"><span class="tq-av ph ph-a"><span class="ph-l">א</span></span><div><b>אורית לוי</b><span>מנכ"לית, חברת ייעוץ</span></div></div></article>
<article class="tq-card"><div class="tq-stars">★★★★★</div><p>שלושה ספקים לפניהם לא הצליחו. כאן הבינו את העסק שלי תוך פגישה אחת, וזה הורגש בכל שורה באתר.</p><div class="tq-who"><span class="tq-av ph ph-c"><span class="ph-l">ד</span></span><div><b>דני כהן</b><span>בעלים, מוסך</span></div></div></article>
<article class="tq-card"><div class="tq-stars">★★★★★</div><p>הכי אהבתי שלא נעלמו אחרי העלייה לאוויר. כל שאלה נענתה, וגם חודשיים אחרי עדיין מלווים.</p><div class="tq-who"><span class="tq-av ph ph-d"><span class="ph-l">מ</span></span><div><b>מיכל ברק</b><span>מנהלת שיווק</span></div></div></article>
<article class="tq-card"><div class="tq-stars">★★★★★</div><p>העלינו את האתר ותוך שבועיים ראינו הבדל בכמות הפניות. ההשקעה החזירה את עצמה מהר.</p><div class="tq-who"><span class="tq-av ph ph-e"><span class="ph-l">י</span></span><div><b>יוסי אדרי</b><span>בעלים, קליניקה</span></div></div></article>
</div>
<div class="tq-bar">
<button class="tq-nav prev" aria-label="הקודם">→</button>
<div class="tq-dots" role="tablist" aria-label="מעבר בין המלצות"></div>
<button class="tq-nav next" aria-label="הבא">←</button>
</div>
</div></div>
(function(){
const track=document.querySelector(".tq-track"),cards=[...track.children];
const dots=document.querySelector(".tq-dots"),prev=document.querySelector(".prev"),next=document.querySelector(".next");
cards.forEach((c,i)=>{
const b=document.createElement("button");
b.setAttribute("role","tab");b.setAttribute("aria-label","המלצה "+(i+1));
b.addEventListener("click",()=>c.scrollIntoView({behavior:"smooth",block:"nearest",inline:"start"}));
dots.appendChild(b);
});
const buttons=[...dots.children];
const rtl=getComputedStyle(track).direction==="rtl";
const startEdge=el=>{const r=el.getBoundingClientRect();return rtl?r.right:r.left;};
function current(){
// הכרטיס ששפת ההתחלה שלו הכי קרובה לשפת ההתחלה של המסלול.
// בעברית שפת ההתחלה היא הימנית, ולכן מודדים אותה ולא את המרכז.
const s=startEdge(track);
let best=0,d=1e9;
cards.forEach((c,i)=>{const dist=Math.abs(startEdge(c)-s);if(dist<d){d=dist;best=i;}});
return best;
}
// scrollLeft שלילי ב-RTL, ולכן כל ההשוואות על הערך המוחלט
const pos=()=>Math.abs(track.scrollLeft);
const maxScroll=()=>track.scrollWidth-track.clientWidth;
const atStart=()=>pos()<4, atEnd=()=>maxScroll()-pos()<4;
function sync(){
// בסוף המסלול מסמנים את האחרון גם אם הוא לא הגיע לשפת ההתחלה,
// כי כשמוצגים כמה כרטיסים במקביל אי אפשר להביא אותו לשם
const i=atEnd()?cards.length-1:current();
buttons.forEach((b,n)=>b.classList.toggle("on",n===i));
prev.disabled=atStart();next.disabled=atEnd();
}
track.addEventListener("scroll",sync,{passive:true});
prev.addEventListener("click",()=>cards[Math.max(0,current()-1)].scrollIntoView({behavior:"smooth",block:"nearest",inline:"start"}));
next.addEventListener("click",()=>cards[Math.min(cards.length-1,current()+1)].scrollIntoView({behavior:"smooth",block:"nearest",inline:"start"}));
// גרירה בעכבר. במגע הדפדפן כבר עושה את זה לבד ולכן לא נוגעים.
let down=false,sx=0,sl=0;
track.addEventListener("pointerdown",e=>{if(e.pointerType!=="mouse")return;down=true;sx=e.clientX;sl=track.scrollLeft;track.classList.add("drag");});
track.addEventListener("pointermove",e=>{if(!down)return;track.scrollLeft=sl-(e.clientX-sx);});
const up=()=>{if(!down)return;down=false;track.classList.remove("drag");
cards[current()].scrollIntoView({behavior:"smooth",block:"nearest",inline:"start"});};
["pointerup","pointerleave","pointercancel"].forEach(ev=>track.addEventListener(ev,up));
sync();
})();