Opens in a new tab

FAQ Chat — intrebari si raspunsuri ca intr-o conversatie

Produs Codegrid: Scroll-Powered FAQ Animation (pachetul codegrid-crency-animated-faq). Pagina live: /faq-chat/. Sursa: un build Vite cu un modul ES si o foaie de stil. Nicio imagine — tot efectul e cod.

Ce face

  • Fiecare intrebare si fiecare raspuns e un balon de chat. Intrebarile stau in stanga, raspunsurile in dreapta.
  • Balonul porneste ca un cerc de 64px cu trei puncte care „scriu” (animatie CSS pura, typing-pulse) si e ascuns la scale: 0.
  • Cand urca la 85% din ecran, cercul apare. La 75%, punctele dispar, cercul se intinde la latimea reala, apoi la inaltimea reala (cele doua se suprapun 0.2s), iar randurile de text apar pe rand.
  • La derulare inapoi, balonul se strange intai inapoi in cerc si abia apoi dispare. Ordinea e tinuta de un flag, collapseWhenDone: daca balonul era deschis, cercul asteapta sfarsitul inchiderii (onReverseComplete) inainte sa se micsoreze.
  • Randul care tine balonul primeste de la inceput min-height egal cu inaltimea balonului deschis, asa ca nimic de dedesubt nu sare cand balonul se deschide.

Ce cere site-ul

  • GSAP 3.13.0 + ScrollTrigger — deja globale pe site. Sursa cerea 3.15; nu foloseste nimic ce 3.13 nu are.
  • Lenis 1.1.13 prin import() dinamic, doar pe pagina asta, pe fereastra (ca pe celelalte pagini cu Lenis). Sursa cerea 1.3.26. Lenis e optional aici: daca nu se incarca, pagina merge cu scroll nativ — animatia nu depinde de el.
  • Fonturi: Barlow Condensed (titluri) si Host Grotesk (text), ambele noi pe site, doar aici.
  • Conversie rem → px peste tot, si in CSS, si in cele trei valori din JS (2rem si 1.5rem pentru padding, 2rem pentru colturi). Tema are baza rem de 10px.

Doua reparatii fata de sursa

1. Fonturile. Sursa masoara fiecare balon imediat ce ruleaza scriptul. Daca fontul web nu a venit inca, masoara textul in fontul de rezerva si balonul se deschide la o dimensiune gresita — prea lat sau taiat jos. Aici masuratoarea asteapta document.fonts.ready.

2. Redimensionarea. Sursa masoara o singura data. Dupa o schimbare de latime (fereastra trasa, telefon rotit) baloanele se deschideau la dimensiunea veche. Aici tot ce construieste scriptul sta intr-un gsap.context(). La o schimbare de latime (nu de inaltime — bara de adresa de pe telefon schimba doar inaltimea), contextul e anulat cu ctx.revert(): dispar stilurile inline si ScrollTrigger-ele. Apoi totul e construit din nou din masuratori proaspete. Baloanele deja trecute de prag se redeschid singure, pentru ca ScrollTrigger declanseaza onEnter la creare daca pozitia e deja depasita.

Verificat intr-un browser real: la 390px baloanele au 236px latime, dupa redimensionare la 1280px au 422px / 605px / 354px, fara text taiat.

Cum e construit in Bricks

O sectiune, un element Code. Markup 3,4 KB, CSS 4 KB, JS 6,9 KB.

  • Radacina: sectiunea .cfaq, _padding 0, _width: 100%. Element Code cu noRoot, executeCode, language: html, signature => wp_hash($code).
  • Page settings: bodyClasses: cfaq, headerDisabled, footerDisabled, disableLazyLoad.
  • Sursa stilizeaza global *, h1, p, section si body. Toate sunt scopate sub .cfaq sau body.cfaq. Culorile (--base-100/200/300) stau pe body.cfaq, nu pe :root. Keyframe-ul are prefix: cfaq-typing-pulse.
  • Ascunderea barei de scroll din sursa (::-webkit-scrollbar global) e legata de pagina prin html:has(body.cfaq).
  • Ca sa adaugi o intrebare, copiezi un bloc .faq-item. Scriptul ia singur fiecare .faq-message, oricate ar fi.
  • Constantele de reglaj sunt sus in JS: BUBBLE_SIZE, START_POP, START_EXPAND, POP_DURATION, EXPAND_DURATION, PAD_X, PAD_Y, RADIUS. Ultimele trei trebuie sa se potriveasca cu CSS-ul balonului.

Capcane

  • Scroll greu, uitat la prima publicare. Tema are html { scroll-behavior: smooth }. Lenis scrie pozitia de scroll la fiecare cadru, iar cu scroll-ul neted nativ activ fiecare scriere porneste o animatie a browserului — cele doua se bat si pagina se simte greoaie. Lenis are CSS-ul lui (html.lenis { scroll-behavior: auto !important } si restul), dar nu vine cu el cand e importat ca modul ES. Trebuie pus in CSS-ul paginii, ca la LeoParpeix si Studio Monet. Verificare: getComputedStyle(document.documentElement).scrollBehavior trebuie sa dea auto.
  • minHeight ca numar nu merge. gsap.set(row, { minHeight: 77 }) nu scrie nimic cand valoarea calculata de pornire e auto — fara eroare, pur si simplu nimic. Cu '77px' (sir cu unitate) merge. Sursa scria direct style.minHeight, deci nu avea problema; a aparut cand am mutat-o in gsap.set ca sa o prinda ctx.revert().
  • letter-spacing: -1% pe h1 — procentele nu sunt valide acolo. Ignorat de browser si in sursa, si aici; pastrat cu comentariu.
  • Tabul ascuns ingheata totul. In tabul de automatizare pus in fundal, requestAnimationFrame ruleaza la 0 cadre, iar timerele sunt amanate pana la un minut. Baloanele raman cercuri si redimensionarea pare ca nu reconstruieste nimic. Verificarea s-a facut fortand progresul (onEnter() pe fiecare ScrollTrigger, apoi progress(1) pe toate timeline-urile) si, pentru redimensionare, intr-un browser headless vizibil.

Pasi de reproducere

  1. Scopeaza CSS-ul sub .cfaq; muta variabilele si fundalul de pe :root/body pe body.cfaq. Converteste rem in px (×16), inclusiv valorile din JS.
  2. Aplatizeaza script.js intr-un IIFE cu garda de DOM gata si garda de rulare dubla. gsap si ScrollTrigger din global, Lenis prin import() cu catch care lasa scroll-ul nativ.
  3. Leaga Lenis de gsap.ticker si cheama gsap.ticker.wake() — ticker.add singur nu trezeste un ticker adormit.
  4. Pune construirea baloanelor intr-o functie build() care ruleaza intr-un gsap.context(), dupa document.fonts.ready. La schimbare de latime (debounce 200ms): ctx.revert(), apoi build().
  5. Scrie arborele cu wp_slash() si signature => wp_hash($code), verifica md5 la citire, apoi regenereaza CSS-ul Bricks.
  6. Verifica: 12 ScrollTrigger-e (doua pe balon), fiecare rand cu min-height in px, dupa deschidere niciun balon cu text taiat (scrollHeight ≤ clientHeight), la derulare inapoi toate la scale 0 si 64px. Zero overflow orizontal la latime mare si la 390px.