Când CSS nu este suficient: cerințe JavaScript pentru componentele accesibile

Publicat: 2022-03-10
Rezumat rapid ↬ Alertă spoiler: sfaturi cu instrumente, modal, file, carusele și meniuri drop-down sunt câteva dintre componentele interfeței cu utilizatorul care necesită mai mult decât CSS. Pentru a asigura accesibilitatea interfeței dvs., JavaScript este un plus necesar pentru a realiza gestionarea focalizării, a răspunde la evenimentele de la tastatură și a comuta atributele ARIA.

În calitate de autor al lui ModernCSS.dev, sunt un mare susținător al soluțiilor CSS. Și îmi place să văd modurile inteligente în care oamenii folosesc CSS pentru design-uri și interactivitate cu adevărat ieșite din cutie! Cu toate acestea, am observat o tendință de promovare a componentelor „doar CSS” folosind metode precum „hack-ul casetei de selectare”. Din păcate, hack-uri ca acestea lasă un număr semnificativ de utilizatori în imposibilitatea de a vă folosi interfața.

Acest articol acoperă câteva componente comune și de ce CSS nu este suficient pentru a acoperi accesibilitatea prin detalierea cerințelor JavaScript. Aceste cerințe se bazează pe Ghidurile privind accesibilitatea conținutului web (WCAG) și pe cercetări suplimentare din partea experților în accesibilitate. Nu voi prescrie soluții JavaScript sau CSS demonstrativ, ci mai degrabă voi examina ce trebuie luat în considerare atunci când creez fiecare componentă. Un cadru JavaScript poate fi folosit cu siguranță, dar nu este necesar pentru a adăuga evenimentele și caracteristicile discutate.

Cerințele enumerate nu sunt, în general, opționale - sunt necesare pentru a asigura accesibilitatea componentelor dumneavoastră.

Dacă utilizați un cadru sau o bibliotecă de componente, puteți folosi acest articol pentru a vă ajuta să evaluați dacă componentele furnizate îndeplinesc cerințele de accesibilitate . Este important să știți că multe dintre elementele notate nu vor fi acoperite pe deplin de instrumente automate de testare a accesibilității precum aXe și, prin urmare, au nevoie de unele teste manuale. Sau puteți utiliza un cadru de testare precum Cypress pentru a crea teste pentru funcționalitatea necesară.

Rețineți că acest articol este axat pe informarea dvs. despre considerentele JavaScript pentru fiecare componentă a interfeței. Aceasta nu este o resursă cuprinzătoare pentru toate detaliile de implementare pentru crearea de componente complet accesibile, cum ar fi aria necesară sau chiar markup. Sunt incluse resurse pentru fiecare tip, pentru a vă ajuta să aflați mai multe despre considerentele mai ample pentru fiecare componentă.

Mai multe după săritură! Continuați să citiți mai jos ↓

Stabilirea dacă CSS-Only este o soluție adecvată

Iată câteva întrebări de adresat înainte de a continua cu o soluție numai CSS. Vom acoperi câțiva dintre termenii prezentați aici în mai mult context, alături de componentele lor aferente.

  • Este pentru propria dvs. plăcere?
    Apoi, intrați absolut totul în CSS, depășiți limitele și învățați ce poate face limba!
  • Funcția include afișarea și ascunderea conținutului?
    Apoi aveți nevoie de JS pentru a comuta minim aria și pentru a activa închiderea pe Esc . Pentru anumite tipuri de componente care își schimbă și starea, este posibil să fie necesar să comunicați modificări prin declanșarea actualizărilor într-o regiune live ARIA.
  • Este ordinea naturală de focalizare cea mai ideală?
    Dacă ordinea naturală pierde relația dintre un declanșator și elementul pe care l-a declanșat, sau un utilizator de tastatură nu poate accesa nici măcar conținutul prin ordinea naturală a filelor, atunci aveți nevoie de JS pentru a ajuta la gestionarea focalizării.
  • Controlul stilizat oferă informațiile corecte despre funcționalitate?
    Utilizatorii tehnologiei de asistență, cum ar fi cititoarele de ecran, primesc informații bazate pe semantică și ARIA care îi ajută să determine ce face un control. Și, utilizatorii de recunoaștere a vorbirii trebuie să fie capabili să identifice eticheta sau tipul componentei pentru a determina expresia pe care să o folosească pentru a opera comenzile. De exemplu, dacă componenta dvs. are un stil ca file, dar folosește butoane radio pentru a „funcționa” ca file, un cititor de ecran poate auzi „buton radio”, iar un utilizator de vorbire poate încerca să folosească cuvântul „filă” pentru a le opera. În aceste cazuri, veți avea nevoie de JS pentru a activa utilizarea controalelor și a semanticii adecvate pentru a obține funcționalitatea dorită.
  • Efectul se bazează pe hover și/sau focalizare?
    Atunci este posibil să aveți nevoie de JS pentru a ajuta la o soluție alternativă pentru a oferi acces egal sau acces persistent la conținut, în special pentru utilizatorii de ecran tactil și cei care folosesc un zoom desktop de 200%+ sau un software de mărire.

Sfat rapid : O altă referință atunci când creați orice tip de control personalizat este Lista de verificare pentru dezvoltare accesibilă pentru control personalizat din ghidul W3 „Utilizarea ARIA”. Aceasta menționează mai multe puncte de mai sus, cu câteva considerații suplimentare de design și semantice.

Sfaturi instrumente

Restrângerea definiției unui balon explicativ este puțin complicată, dar pentru această secțiune vorbim despre etichete de text mici care apar la trecerea mouse-ului în apropierea unui element de declanșare. Acestea se suprapun cu alt conținut, nu necesită interacțiune și dispar atunci când un utilizator elimină hoverul sau focalizarea.

Exemple de sfaturi de la GitHub, Whimsical și Notion
Exemple de sfaturi de la GitHub, Whimsical și Notion. (Previzualizare mare)

Soluția doar CSS de aici poate părea complet bună și poate fi realizată cu ceva de genul:

 <button class="tooltip-trigger">I have a tooltip</button> <span class="tooltip">Tooltip</span> .tooltip { display: none; } .tooltip-trigger:hover + .tooltip, .tooltip-trigger:focus + .tooltip { display: block; }

Cu toate acestea, acest lucru ignoră o listă de probleme de accesibilitate și exclude mulți utilizatori să acceseze conținutul descrierii.

Un grup mare de utilizatori excluși sunt cei care folosesc ecrane tactile în care :hover este posibil să nu fie declanșat, deoarece pe ecranele tactile, un eveniment :hover declanșează sincronizat cu un eveniment :focus . Aceasta înseamnă că orice acțiune asociată conectată la elementul de declanșare, cum ar fi un buton sau o legătură, se va declanșa odată cu dezvăluirea descrierii. Aceasta înseamnă că utilizatorul poate rata indicația sau nu are timp să-i citească conținutul.

În cazul în care balonul explicativ este atașat la un element interactiv fără evenimente, balonul explicativ poate apărea, dar nu poate fi respins până când un alt element capătă atenție și, între timp, poate bloca conținutul și împiedica un utilizator să realizeze o sarcină.

În plus, utilizatorii care au nevoie să folosească software de zoom sau de mărire pentru a naviga se confruntă, de asemenea, cu o barieră destul de mare în utilizarea sfaturile instrumente. Deoarece sfaturi cu instrumente sunt dezvăluite la trecerea cu mouse-ul, dacă acești utilizatori trebuie să își schimbe câmpul vizual prin deplasarea ecranului pentru a citi sfaturile cu instrumente, acesta poate face să dispară. Sfaturile cu instrumente elimină, de asemenea, controlul utilizatorului, deoarece adesea nu există nimic care să-i spună utilizatorului că va apărea din timp balonul cu instrumente. Suprapunerea conținutului îi poate împiedica să îndeplinească o sarcină. În unele circumstanțe, cum ar fi un balon explicativ legat de un câmp de formular, tastaturile mobile sau alte tastaturi de pe ecran pot ascunde conținutul balonului explicativ. Și, dacă nu sunt conectați corespunzător la elementul de declanșare, este posibil ca unii utilizatori de tehnologie de asistență să nu știe nici măcar că a apărut un sfat explicativ.

Îndrumarea pentru comportamentul sfașurilor informative provine din Criteriul de succes WCAG 1.4.13 — Conținut pe Hover sau Focus. Acest criteriu este destinat să ajute utilizatorii cu vedere slabă și cei care folosesc software de zoom și mărire. Principiile directoare pentru balonul explicativ (și alte conținuturi care apar la trecerea cu mouse-ul și focalizarea) includ:

  • Demisibil
    Indicatorul poate fi înlăturat fără a deplasa hover sau focalizare
  • Posibil
    Conținutul indicativ dezvăluit poate fi plasat fără ca acesta să dispară
  • Persistent
    Conținutul suplimentar nu dispare pe baza unui timeout, ci așteaptă ca un utilizator să elimine hoverul sau să se concentreze sau să îl îndepărteze în alt mod

Pentru a îndeplini pe deplin aceste reguli este nevoie de asistență JavaScript, în special pentru a permite închiderea conținutului.

  • Utilizatorii tehnologiei de asistență vor presupune că comportamentul de concediere este legat de tasta Esc , care necesită un ascultător JavaScript.
  • Conform cercetării lui Sarah Higley descrise în secțiunea următoare, adăugarea unui buton de „închidere” vizibil în balonul ar necesita, de asemenea, JavaScript pentru a gestiona evenimentul de închidere.
  • Este posibil ca JavaScript să fie nevoie să vă îmbunătățească soluția de stil pentru a se asigura că un utilizator poate trece cu mouse-ul peste conținutul balonului explicativ fără ca acesta să fie respins în timp ce utilizatorul își mișcă mouse-ul.

Alternative la Tooltips

Sfaturile instrumente ar trebui să fie o ultimă soluție. Sarah Higley – o expertă în accesibilitate care are o pasiune deosebită pentru a descuraja utilizarea sfaturile instrumente – oferă acest test simplu:

„De ce adaug acest text în interfața de utilizare? Unde altundeva s-ar putea duce?”

— Sarah Higley din prezentarea „Tooltips: Investigation Into Four Parts”

Pe baza cercetărilor în care Sarah a fost implicată pentru rolul său la Microsoft, o soluție alternativă este un „toggletip” dedicat. În esență, aceasta înseamnă furnizarea unui element suplimentar pentru a permite unui utilizator să declanșeze în mod intenționat afișarea și ascunderea conținutului suplimentar . Spre deosebire de sfaturi cu instrumente, sfaturi de comutare pot reține semantica elementelor din conținutul dezvăluit. De asemenea, oferă utilizatorului înapoi controlul comutării acestora și păstrează posibilitatea de a fi descoperită și operațională de către mai mulți utilizatori și în special utilizatorii de ecran tactil.

Dacă ți-ai amintit că atributul title există, trebuie doar să știi că suferă aceleași probleme pe care le-am observat din soluția noastră doar CSS. Cu alte cuvinte, nu folosiți title sub presupunerea că este o soluție acceptabilă de tip tooltip.

Pentru mai multe informații, consultați prezentarea lui Sarah pe YouTube, precum și articolul ei amplu despre sfaturi despre instrumente. Pentru a afla mai multe despre sfaturi cu instrumente versus sfaturi de comutare și câteva informații despre de ce să nu folosiți title , consultați articolul lui Heydon Pickering din Inclusive Components: Tooltips and Toggletips.

Modale

Modalurile – alias casete luminoase sau casete de dialog – sunt ferestre în pagină care apar după o acțiune de declanșare. Acestea suprapun alt conținut al paginii, pot conține informații structurate, inclusiv acțiuni suplimentare și au adesea un fundal semi-transparent pentru a ajuta la distingerea ferestrei modale de restul paginii.

Exemple de modale din GitHub și Material Design
Exemple de modale din GitHub și Material Design. (Previzualizare mare)

Am văzut câteva variante ale unui mod numai CSS (și sunt vinovat că am făcut unul pentru o versiune mai veche a portofoliului meu). Ei pot folosi „checkbox hack”, pot folosi comportamentul lui :target , sau pot încerca să-l modeleze din :focus (care este, probabil, un tooltip prea mare deghizat).

În ceea ce privește elementul de dialog HTML, rețineți că acesta nu este considerat a fi accesibil în mod cuprinzător. Deci, deși îi încurajez pe oameni să folosească HTML nativ înainte de soluții personalizate, din păcate, acesta încalcă această idee. Puteți afla mai multe despre motivul pentru care dialog HTML nu este accesibil.

Spre deosebire de sfaturi cu instrumente, modalele sunt menite să permită conținut structurat. Aceasta înseamnă potențial un titlu, un conținut de paragraf și elemente interactive precum linkuri, butoane sau chiar formulare. Pentru ca cei mai mulți utilizatori să acceseze acel conținut, aceștia trebuie să poată utiliza evenimentele de la tastatură , în special tabularea. Pentru conținutul modal mai lung, tastele săgeți ar trebui să păstreze și capacitatea de a derula. Și ca și sfaturile instrumente, acestea ar trebui să fie dezactivate cu tasta Esc - și nu există nicio modalitate de a activa acest lucru numai cu CSS.

JavaScript este necesar pentru gestionarea concentrării în cadrul modalelor. Modalurile ar trebui să capteze focalizarea, ceea ce înseamnă că, odată ce focalizarea se află în modal, un utilizator nu ar trebui să poată trece din acesta în conținutul paginii din spatele acestuia. Dar mai întâi, concentrarea trebuie să intre în modal, care necesită și JavaScript pentru o soluție modală complet accesibilă.

Iată secvența evenimentelor legate de modal care trebuie gestionate cu JavaScript:

  1. Ascultătorul de evenimente pe un buton deschide modulul
  2. Focalizarea este plasată în modal; ce element variază în funcție de conținutul modal (vezi arborele de decizie)
  3. Focalizarea este prinsă în modal până când este respinsă
  4. De preferință, un utilizator poate închide un modal cu tasta Esc în plus față de un buton de închidere dedicat sau o acțiune distructivă a butonului, cum ar fi „Anulare”, dacă este necesară confirmarea conținutului modal
    1. Dacă Esc este permis, clicurile pe fundalul modal ar trebui, de asemenea, să respingă modulul
  5. La demitere, dacă nu a avut loc nicio navigare, focalizarea este plasată din nou pe elementul butonului de declanșare

Arborele de decizie de focalizare modală

Pe baza exemplului de dialog modal WAI-ARIA Authoring Practices, iată un arbore de decizie simplificat pentru unde să plasați focalizarea odată ce un modal este deschis. Contextul va dicta întotdeauna alegerea aici și, în mod ideal, focalizarea este gestionată mai departe decât pur și simplu „primul element focalizat”. De fapt, uneori trebuie selectate elemente nefocalabile.

  • Subiectul principal al modului este o formă.
    Concentrați primul câmp de formular.
  • Conținutul modal este semnificativ în lungime și împinge acțiunile modale din vedere.
    Focalizează un titlu, dacă este prezent, sau primul paragraf.
  • Scopul modalului este procedural (de exemplu: confirmarea acțiunii) cu acțiuni multiple disponibile.
    Concentrați-vă pe acțiunea „cel mai puțin distructivă” în funcție de context (exemplu: „OK”).
  • Scopul modalului este procedural cu o singură acțiune.
    Concentrați-vă pe primul element focalizat

Sfat rapid : în cazul în care trebuie să focalizați un element care nu poate fi focalizat, cum ar fi un titlu sau un paragraf, adăugați tabindex="-1" care permite elementului să devină focalizat prin programare cu JS, dar nu îl adaugă la ordinea de file DOM .

Consultați demonstrația modală WAI-ARIA pentru mai multe informații despre alte cerințe pentru configurarea ARIA și detalii suplimentare despre cum să selectați elementul la care să adăugați focalizare. Demo include, de asemenea, JavaScript pentru a exemplifica cum să faci gestionarea concentrării.

Pentru o soluție gata de utilizare, Kitty Giraudel a creat un dialog a11y care include cerințele caracteristicilor pe care le-am discutat. Adrian Roselli a cercetat, de asemenea, gestionarea focalizării dialogurilor modale și a creat o demonstrație și a compilat informații despre modul în care diferitele combinații de browser și cititor de ecran vor comunica elementul focalizat.

Filele

Interfețele cu file implică o serie de declanșatoare care afișează panourile de conținut corespunzătoare pe rând. „Hack-urile” CSS pe care le puteți găsi pentru acestea implică utilizarea butoanelor radio stilizate sau :target , care permit ambele dezvăluirea unui singur panou la un moment dat.

Exemple de file de la Shopify Polaris și IBM Carbon
Exemple de file de la Shopify Polaris și IBM Carbon. (Previzualizare mare)

Iată caracteristicile filei care necesită JavaScript:

  1. Comutarea atributului aria-selected la true pentru fila curentă și false pentru filele neselectate
  2. Crearea unui tabindex itineran pentru a distinge selecția filelor de focalizare
  3. Mutați focalizarea între file răspunzând la evenimentele tastelor săgeți (și opțional Home și End )

Opțional, puteți face ca selectarea filei să urmeze focalizarea - adică atunci când o filă este focalizată, este și apoi selectată și arată panoul de file asociat. Practicile de creație WAI-ARIA oferă acest ghid pentru a alege dacă selecția ar trebui să urmeze focalizarea.

Indiferent dacă alegeți sau nu ca selecția să urmeze focalizarea, veți folosi și JavaScript pentru a asculta evenimentele tastelor săgeți pentru a muta focalizarea între elementele filei. Acesta este un model alternativ pentru a permite navigarea în opțiunile de file , deoarece utilizarea unui tabindex itineran (descris în continuare) modifică ordinea naturală de focalizare a filei de la tastatură.

Despre Roving tabindex

Conceptul unui tabindex itineran este că valoarea valorii tabindex este controlată programatic pentru a gestiona ordinea de focalizare a elementelor. În ceea ce privește filele, aceasta înseamnă că numai fila selectată face parte din ordinea de focalizare prin setarea tabindex="0" , iar filele neselectate sunt setate la tabindex="-1" , ceea ce le elimină din ordinea naturală de focalizare a tastaturii.

Motivul pentru aceasta este că, atunci când o filă este selectată, următoarea filă va atrage atenția utilizatorului în panoul de file asociat. Puteți alege să faceți focalizabil elementul care este panoul cu file, atribuindu-i tabindex="0" , sau acest lucru poate să nu fie necesar dacă există garanția unui element focalizat în panoul cu file . Dacă conținutul panoului de file va fi mai variabil sau mai complex, puteți lua în considerare gestionarea concentrării în funcție de arborele de decizie pe care l-am examinat pentru modali.

Exemple de modele de file

Iată câteva modele de referință pentru crearea de file:

  • Demo Tabpanel de la Universitatea Deque
  • Teste widget cu filă de la Scott O'Hara (testează mai multe modele funcționale)
  • Interfețe cu file de la Heydon Pickering's Inclusive Components , care demonstrează cum filele pot fi o îmbunătățire progresivă a unui cuprins

Carusele

Denumite și prezentări de diapozitive sau glisoare, caruselele implică o serie de panouri de conținut rotative (alias „diapozitive”) care includ mecanisme de control. Le veți găsi în multe configurații cu o gamă largă de conținut. Sunt considerate oarecum un model de design prost.

Un exemplu demo carusel creat cu bxSlider
Un exemplu demo carusel creat cu bxSlider. (Previzualizare mare)

Partea dificilă despre caruselele numai CSS este că acestea nu oferă controale sau pot folosi comenzi neașteptate pentru a manipula mișcarea caruselului. De exemplu, puteți utiliza din nou „checkbox hack” pentru a determina tranziția caruselului, dar casetele de selectare oferă utilizatorilor de tehnologie de asistență tipul greșit de informații despre interacțiune. În plus, dacă stilați etichetele casetei de selectare pentru a apărea vizual ca săgeți înainte și înapoi, este posibil să oferiți utilizatorilor software-ului de recunoaștere a vorbirii o impresie greșită a ceea ce ar trebui să spună pentru a controla caruselul.

Mai recent, a aterizat suportul CSS nativ pentru scroll snap. La început, aceasta pare soluția perfectă numai pentru CSS. Dar, chiar și verificarea automată a accesibilității le va semnala ca nenavigabile de către utilizatorii de tastatură în cazul în care nu există nicio modalitate de a le naviga prin elemente interactive. Există și alte probleme legate de accesibilitate și experiența utilizatorului cu comportamentul implicit al acestei caracteristici, dintre care unele le-am inclus în demonstrația mea de derulare pe SmolCSS.

În ciuda gamei largi de aspect al caruselelor, există câteva trăsături comune. O opțiune este să creați un carusel utilizând marcarea filelor, deoarece este efectiv aceeași interfață de bază cu o prezentare vizuală modificată. În comparație cu file, caruselele pot oferi comenzi suplimentare pentru precedentul și următorul și, de asemenea, pot întrerupe dacă caruselul se redă automat.

Următoarele sunt considerații JavaScript în funcție de caracteristicile caruselului dvs.:

  • Utilizarea controalelor paginate
    La selectarea unui articol numerotat, focalizați în mod programatic diapozitivul carusel asociat. Acest lucru va implica configurarea containerelor de diapozitive folosind tabindex itineran, astfel încât să puteți focaliza diapozitivul curent, dar să împiedicați accesul la diapozitivele din afara ecranului.
  • Folosind Redarea automată
    Includeți un control pentru pauză și, de asemenea, activați întreruperea atunci când diapozitivul este plasat cu mouse-ul sau un element interactiv din acesta este focalizat. În plus, puteți verifica prefers-reduced-motion în JavaScript pentru a încărca prezentarea într-o stare întreruptă pentru a respecta preferințele utilizatorului.
  • Utilizarea comenzilor precedente/următoare
    Includeți un element ascuns vizual marcat ca aria-live="polite" și după activarea acestor comenzi, populați regiunea live cu o indicație a poziției curente, cum ar fi „Diapozitiv 2 din 4”.

Resurse pentru construirea de carusele accesibile

  • Detalii și considerații amănunțite despre implementare, precum și un exemplu de cod complet din tutorialul de accesibilitate web W3C despre carusele
  • Exemplul Universității Deque de îmbunătățire a unei interfețe cu file într-un carusel
  • Exemplul de practici de creație WAI-ARIA de carusel de imagini cu rotire automată
  • O selecție de resurse carusel în rezumatul Smashing de componente accesibile

Meniuri derulante

Aceasta se referă la o componentă în care un buton comută deschiderea unei liste de link-uri, utilizate de obicei pentru meniurile de navigare. Implementările CSS care se opresc la afișarea meniului pe :hover sau :focus ratează doar câteva detalii importante.

Exemple de meniuri drop-down din Dribbble, căutare Google și GitHub
Exemple de meniuri drop-down din Dribbble, căutare Google și GitHub. (Previzualizare mare)

Recunosc, chiar m-am gândit că folosind noua proprietate :focus-within am putea implementa în siguranță o soluție numai CSS. Veți vedea că articolul meu despre meniurile drop-down CSS a fost modificat pentru a include note și resurse despre JavaScript-ul necesar (am păstrat titlul, astfel încât alții care caută soluția respectivă vor finaliza, de asemenea, implementarea JS). Mai exact, a te baza doar pe CSS înseamnă încălcarea Criteriului de succes WCAG 1.4.13: Conținut pe Hover sau Focus despre care am aflat cu ajutorul sfaturii.

Trebuie să adăugăm JavaScript pentru unele tehnici care ar trebui să sune familiare în acest moment:

  • Comutarea aria-expanded pe butonul de meniu între true și false , ascultând evenimentele de click
  • Închiderea unui meniu deschis la utilizarea tastei Esc și readucerea focalizării pe butonul de comutare a meniului
  • De preferință, închiderea meniurilor deschise când focalizarea este mutată în afara meniului
  • Opțional : implementați tastele săgeți, precum și tastele Home și End pentru navigarea de la tastatură între butoanele de comutare din meniu și linkurile din meniurile derulante

Sfat rapid : Asigurați-vă implementarea corectă a meniului drop-down asociind afișarea meniului cu selectorul .dropdown-toggle[aria-expanded= " true " ] + .dropdown , mai degrabă decât bazați afișarea meniului pe prezența unui JS- suplimentar. clasa adăugată ca active . Acest lucru elimină o oarecare complexitate și din soluția dvs. JS!

Acesta este, de asemenea, denumit „model de dezvăluire” și puteți găsi mai multe detalii în meniul de navigare Exemplu de dezvăluire din WAI-ARIA Authoring Practices.

Resurse suplimentare pentru crearea de componente accesibile

  • Ghidul complet al Smashing pentru componentele front-end accesibile
  • Articolul lui Carie Fisher Good, Better, Best: Untangling The Complex World Of Accessible Patterns
  • Demonstrații și informații despre modelele de design și widget-urile comune disponibile din Practicile de creație WAI-ARIA 1.2
  • Biblioteca de coduri a Universității Deque
  • Componentele accesibile Scott O'Hara
  • Componentele incluzive ale lui Heydon Pickering