Þegar þú tengir stjórnanda, maskarðu hnappa, hreyfir prikanna, dregur í gikkinn ... og sem þróunaraðili sérðu ekkert af því. Vafrinn tekur það upp, vissulega, en nema þú sért að skrá númer í stjórnborðinu, þá er það ósýnilegt. Það er höfuðverkurinn með Gamepad API. Það hefur verið til í mörg ár og það er í raun ansi öflugt. Þú getur lesið hnappa, prik, kveikjur, verkin. En flestir snerta það ekki. Hvers vegna? Vegna þess að það er engin endurgjöf. Engin spjaldið í þróunarverkfærum. Engin skýr leið til að vita hvort stjórnandinn er jafnvel að gera það sem þú heldur. Það er eins og að fljúga blindur. Það truflaði mig nóg til að smíða lítið tól: Gamepad Cascade Debugger. Í stað þess að glápa á úttak stjórnborðsins færðu lifandi, gagnvirkt útsýni yfir stjórnandann. Ýttu á eitthvað og það bregst á skjánum. Og með CSS Cascade Layers haldast stílarnir skipulagðir, svo það er hreinna að kemba. Í þessari færslu mun ég sýna þér hvers vegna kembiforrit er svo sársaukafullt, hvernig CSS hjálpar til við að hreinsa það upp og hvernig þú getur smíðað endurnýtanlegan sjónrænan kembiforrit fyrir eigin verkefni.
Jafnvel þó að þú getir skráð þá alla, muntu fljótt enda með ólæsilegt ruslpóst á vélinni. Til dæmis: [0,0,1,0,0,0.5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]
Geturðu sagt hvaða hnapp var ýtt á? Kannski, en aðeins eftir að hafa þvingað augun og vantað nokkur inntak. Svo, nei, villuleit kemur ekki auðveldlega þegar kemur að því að lesa inntak. Vandamál 3: Skortur á uppbyggingu Jafnvel ef þú setur saman fljótlegan sjónræna, getur stíll fljótt orðið sóðalegur. Sjálfgefin, virk og kembiforrit geta skarast og án skýrrar uppbyggingu verður CSS þinn brothætt og erfitt að framlengja. CSS Cascade Layers getur hjálpað. Þeir flokka stíla í „lög“ sem eru raðað eftir forgangi, svo þú hættir að berjast gegn sérhæfni og giska á: „Af hverju er kembiforritið mitt ekki sýnt? Þess í stað heldurðu aðskildum áhyggjum:
Grunnur: Staðall stjórnandans, upphaflegt útlit. Virkur: Hápunktar fyrir ýtt á hnappa og færðar prik. Villuleit: Yfirlög fyrir þróunaraðila (t.d. tölulegar útlestur, leiðbeiningar og svo framvegis).
Ef við myndum skilgreina lög í CSS samkvæmt þessu, þá hefðum við: /* lægsta til hæsta forgangs */ @lagsgrunnur, virkur, kembiforrit;
@lagsgrunnur { /* ... */ }
@lag virkt { /* ... */ }
@lags villuleit { /* ... */ }
Vegna þess að hvert lag staflast fyrirsjáanlega, þú veist alltaf hvaða reglur vinna. Sá fyrirsjáanleiki gerir villuleit ekki bara auðveldari heldur í raun viðráðanlegri. Við höfum fjallað um vandamálið (ósýnilegt, sóðalegt inntak) og nálgunina (sjónræn kembiforrit byggður með Cascade Layers). Nú förum við í gegnum skref-fyrir-skref ferlið til að búa til villuleit. Villuleitarhugtakið Auðveldasta leiðin til að gera falið inntak sýnilegt er að teikna það bara á skjáinn. Það er það sem þessi kembiforrit gerir. Hnappar, kveikjar og stýripinnar fá allir mynd.
Ýttu á A: Hringur kviknar. Hnúðu prikinu: Hringurinn rennur um. Ýttu í gikkinn hálfa leið: Stöng fyllist hálfa leið.
Nú ertu ekki að glápa á 0 og 1, heldur í raun að horfa á stjórnandann bregðast við í beinni. Auðvitað, þegar þú byrjar að safna ríkjum eins og sjálfgefnu, ýttu, villuleitarupplýsingar, kannski jafnvel upptökuham, byrjar CSS að verða stærri og flóknari. Það er þar sem fossalög koma sér vel. Hér er afskræmt dæmi: @lagsgrunnur { .hnappur { bakgrunnur: #222; landamæraradíus: 50%; breidd: 40px; hæð: 40px; } }
@lag virkt { .button.pressed { bakgrunnur: #0f0; /* skærgrænn */ } }
@lags villuleit { .button::eftir { innihald: attr(gagnagildi); leturstærð: 12px; litur: #fff; } }
Lagaröðin skiptir máli: grunnur → virk → kembiforrit.
stöð dregur stjórnandann. virk handföng ýtt á ástand. kembiforrit á yfirlögn.
Að brjóta það upp eins og þetta þýðir að þú ert ekki að berjast við skrýtin sértæknistríð. Hvert lag hefur sinn stað og þú veist alltaf hvað vinnur. Byggja það út Við skulum fyrst fá eitthvað á skjáinn. Það þarf ekki að líta vel út - þarf bara að vera til svo við höfum eitthvað til að vinna með.
Gamepad Cascade Debugger
Þetta eru bókstaflega bara kassar. Ekki spennandi ennþá, en það gefur okkur handtök til að grípa síðar með CSS og JavaScript. Allt í lagi, ég er að nota Cascade lög hér vegna þess að það heldur hlutum skipulagt þegar þú bætir við fleiri ríkjum. Hér er gróft framlag:
/* =================================== CASCADE LAYERS UPPSETNING Pöntun skiptir máli: grunnur → virkur → villuleit ====================================* */
/* Skilgreindu lagaröðun fyrirfram */ @lagsgrunnur, virkur, kembiforrit;
/* Lag 1: Grunnstíll - sjálfgefið útlit */ @lagsgrunnur { .hnappur { bakgrunnur: #333; landamæraradíus: 50%; breidd: 70px; hæð: 70px; sýna: beygja; réttlæta-innihald: miðja; samræma-atriði: miðju; }
.pause { breidd: 20px; hæð: 70px; bakgrunnur: #333; sýna: inline-blokk; } }
/* Layer 2: Virkar stöður - höndlar þrýsta hnappa */ @lag virkt { .button.active { bakgrunnur: #0f0; /* Ljósgrænt þegar ýtt er á */ umbreyta: mælikvarða(1.1); /* Stækkar hnappinn örlítið */ }
.pause.active { bakgrunnur: #0f0; umbreyta: scaleY(1.1); /* Teygir sig lóðrétt þegar ýtt er á */ } }
/* Lag 3: Villuleit yfirlög - upplýsingar þróunaraðila */ @lags villuleit { .button::eftir { innihald: attr(gagnagildi); /* Sýnir tölugildi */ leturstærð: 12px; litur: #fff; } }
Fegurðin við þessa nálgun er að hvert lag hefur skýran tilgang. Grunnlagið getur aldrei hnekið virku og virkt getur aldrei hnekið villuleit, óháð sérstöðu. Þetta útilokar CSS-sérhæfnistríð sem venjulega herja á villuleitarverkfæri. Nú lítur út fyrir að sumir klasar sitji á dökkum bakgrunni. Heiðarlega, ekki svo slæmt.
Bæti við JavaScript JavaScript tími. Þetta er þar sem stjórnandinn gerir í raun eitthvað. Við munum byggja þetta skref fyrir skref. Skref 1: Settu upp ríkisstjórnun Í fyrsta lagi þurfum við breytur til að fylgjast með stöðu kembiforritsins: // ================================== // RÍKISSTJÓRN // ==================================
láta hlaupa = false; // Fylgir hvort villuleitarforritið sé virkt láta rafId; // Geymir requestAnimationFrame ID fyrir afpöntun
Þessar breytur stjórna hreyfilykkjunni sem les stöðugt inntak leikjatölvunnar. Skref 2: Gríptu DOM tilvísanir Næst fáum við tilvísanir í alla HTML þætti sem við munum uppfæra: // ================================== // DOM ELEMENT TILVÍSANIR // ==================================
const btnA = document.getElementById("btn-a"); const btnB = document.getElementById("btn-b"); const btnX = document.getElementById("btn-x"); const pause1 = document.getElementById("pause1"); const pause2 = document.getElementById("pause2"); const status = document.getElementById("status");
Að geyma þessar tilvísanir fyrirfram er skilvirkara en að spyrjast fyrir um DOM endurtekið. Skref 3: Bæta við lyklaborði Til að prófa án líkamlegrar stjórnandi munum við kortleggja lyklaborðslyklana á hnappa: // ================================== // FALLBACK LYKLABORÐ (til að prófa án stjórnanda) // ==================================
const keyMap = { "a": btnA, "b": btnB, "x": btnX, "p": [pause1, pause2] // 'p' takki stjórnar báðum biðstikum };
Þetta gerir okkur kleift að prófa notendaviðmótið með því að ýta á takka á lyklaborði. Skref 4: Búðu til aðaluppfærslulykkjuna Hér er þar sem galdurinn gerist. Þessi aðgerð keyrir stöðugt og les gamepad stöðu: // ================================== // AÐAL UPPFÆRSLA GAMEPAD UPDATE LOOP // ==================================
function updateGamepad() { // Fáðu alla tengda leikjatölvur const gamepads = navigator.getGamepads(); ef (!spilaborðar) snúa aftur;
// Notaðu fyrsta tengda spilaborðið const gp = gamepads[0];
ef (gp) { // Uppfærðu stöðu hnappsins með því að skipta um "virka" flokkinn btnA.classList.toggle("virkur", gp.hnappar[0].ýttir); btnB.classList.toggle("virkur", gp.hnappar[1].ýttir); btnX.classList.toggle("virkur", gp.hnappar[2].ýttir);
// Meðhöndla hlé hnappinn (hnappur vísir 9 á flestum stýringar) const pausePressed = gp.buttons[9].pressed; pause1.classList.toggle("virk", pausePressed); pause2.classList.toggle("virk", pausePressed);
// Búðu til lista yfir hnappa sem nú er ýtt á til að sýna stöðu látið ýta = []; gp.buttons.forEach((btn, i) => { ef (btn.pressed)pressed.push("Hnappur " + i); });
// Uppfærðu stöðutexta ef ýtt er á einhvern takka if (ýtt.lengd > 0) { status.textContent = "Ýtt: " + pressed.join(", "); } }
// Haltu áfram lykkjunni ef villuleitarforritið er í gangi ef (hlaupandi) { rafId = requestAnimationFrame(updateGamepad); } }
ClassList.toggle() aðferðin bætir við eða fjarlægir virka bekkinn byggt á því hvort ýtt er á hnappinn, sem kveikir á CSS lagstílunum okkar. Skref 5: Meðhöndla lyklaborðsviðburði Þessir atburðahlustendur láta lyklaborðið virka: // ================================== // HLJÓMBORÐSVIÐBANDARAR // ==================================
document.addEventListener("keydown", (e) => { if (keyMap[e.key]) { // Meðhöndla staka eða marga þætti if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("virk")); } annað { keyMap[e.key].classList.add("virkt"); } status.textContent = "Þýtt á takkann: " + e.key.toUpperCase(); } });
document.addEventListener("keyup", (e) => { if (keyMap[e.key]) { // Fjarlægðu virkt ástand þegar lyklinum er sleppt if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("virkt")); } annað { keyMap[e.key].classList.remove("virkt"); } status.textContent = "Lykill gefinn út: " + e.key.toUpperCase(); } });
Skref 6: Bættu við Start/Stop Control Að lokum þurfum við leið til að kveikja og slökkva á kembiforritinu: // ================================== // SLÁKJA/SLÖKKTU KEYMILAGERI // ==================================
document.getElementById("toggle").addEventListener("click", () => { hlaupandi = !hlaupandi; // Snúðu hlaupandi ástandi
ef (hlaupandi) { status.textContent = "Kembiforrit í gangi..."; uppfæraGamepad(); // Byrjaðu uppfærslulykkjuna } annað { status.textContent = "Kembiforrit óvirkt"; cancelAnimationFrame(rafId); // Stöðva lykkjuna } });
Svo já, ýttu á takka og hann logar. Ýttu á stöngina og hann hreyfist. Það er það. Eitt enn: hrá gildi. Stundum vill maður bara sjá tölur, ekki ljós.
Á þessu stigi ættir þú að sjá:
Einfaldur stjórnandi á skjánum, Hnappar sem bregðast við þegar þú hefur samskipti við þá, og Valfrjáls kembiforrit sem sýnir vísitölur fyrir ýtt á hnappa.
Til að gera þetta minna óhlutbundið er hér stutt sýnishorn af skjástýringunni sem bregst við í rauntíma:
Nú, með því að ýta á Start Recording skráir allt þar til þú ýtir á Stop Recording. 2. Flytja út gögn í CSV/JSON Þegar við erum komin með annál viljum við vista hann.
Skref 1: Búðu til niðurhalshjálpina Í fyrsta lagi þurfum við hjálparaðgerð sem sér um niðurhal skráa í vafranum: // ================================== // HJÁLPAREFNI fyrir skráarniðurhal // ==================================
function downloadFile(skráarnafn, innihald, tegund = "texti/látlaus") { // Búðu til klump úr innihaldinu const blob = new Blob([efni], {tegund }); const url = URL.createObjectURL(blob);
// Búðu til tímabundið niðurhalstengil og smelltu á hann const a = document.createElement("a"); a.href = vefslóð; a.download = skráarnafn; a.smella();
// Hreinsaðu upp slóð hlutarins eftir niðurhal setTimeout(() => URL.revokeObjectURL(url), 100); }
Þessi aðgerð virkar með því að búa til Blob (tvöfaldur stóran hlut) úr gögnunum þínum, búa til tímabundna vefslóð fyrir það og smella forritunarlega á niðurhalstengil. Hreinsunin tryggir að við lekum ekki minni. Skref 2: Meðhöndla JSON útflutning JSON er fullkomið til að varðveita heildaruppbyggingu gagna:
// ================================== // FLUTTU SEM JSON // ==================================
document.getElementById("export-json").addEventListener("click", () => { // Athugaðu hvort það sé eitthvað til að flytja út ef (!rammar.lengd) { console.warn("Engin upptaka tiltæk til útflutnings."); skila; }
// Búðu til farm með lýsigögnum og ramma const farmload = { búið á: new Date().toISOString(), ramma };
// Sæktu sem sniðið JSON hlaða niður skrá( "gamepad-log.json", JSON.stringify(hleðsla, núll, 2), "app/json" ); });
JSON sniðið heldur öllu skipulögðu og auðvelt að greina, sem gerir það tilvalið til að hlaða aftur inn í þróunarverkfæri eða deila með liðsfélögum. Skref 3: Meðhöndla CSV útflutning Fyrir CSV útflutning þurfum við að fletja stigveldisgögnin í línur og dálka:
//=================================== // FLUTTU ÚT SEM CSV // ==================================
document.getElementById("export-csv").addEventListener("click", () => { // Athugaðu hvort það sé eitthvað til að flytja út ef (!rammar.lengd) { console.warn("Engin upptaka tiltæk til útflutnings."); skila; }
// Búðu til CSV hauslínu (dálkar fyrir tímastimpil, alla hnappa, alla ása) const headerButtons = rammar[0].buttons.map((_, i) => btn${i}); const headerAxes = rammar[0].axes.map((_, i) => ás${i}); const header = ["t", ...headerButtons, ...headerAxes].join(",") + "\n";
// Byggja CSV gagnalínur const rows = frames.map(f => { const btnVals = f.buttons.map(b => b.value); skila [f.t, ...btnVals, ...f.axes].join(","); }).join("\n");
// Sækja sem CSV downloadFile("gamepad-log.csv", haus + raðir, "texti/csv"); });
CSV er frábært fyrir gagnagreiningu vegna þess að það opnast beint í Excel eða Google Sheets, sem gerir þér kleift að búa til töflur, sía gögn eða koma auga á mynstur sjónrænt. Nú þegar útflutningshnapparnir eru komnir inn muntu sjá tvo nýja valkosti á spjaldinu: Flytja út JSON og Flytja út CSV. JSON er gott ef þú vilt henda hráa annálinu aftur í þróunartólin þín eða pæla í uppbyggingunni. CSV, aftur á móti, opnast beint í Excel eða Google Sheets svo þú getur grafið, síað eða borið saman inntak. Eftirfarandi mynd sýnir hvernig spjaldið lítur út með þessum aukastýringum.
3. Skyndimyndakerfi Stundum þarftu ekki fulla upptöku, bara stutt „skjáskot“ af inntaksstöðu. Það er þar sem Take Snapshot hnappur hjálpar.
Og JavaScript:
// ================================== // TAKKA SKYNDAMYND // ==================================
document.getElementById("snapshot").addEventListener("click", () => { // Fáðu alla tengda leikjatölvur const pads = navigator.getGamepads(); const activePads = [];
// Sláðu í gegnum og taktu stöðu hvers tengds leikjatölvu fyrir (const gp of pads) { ef (!gp) halda áfram; // Slepptu tómum rifum
activePads.push({ auðkenni: gp.id, // Nafn stjórnanda/líkan tímastimpill: performance.now(), hnappar: gp.buttons.map(b => ({ ýtt: b. ýtt, gildi: b.gildi })), ásar: [...gp.axes] }); }
// Athugaðu hvort einhver leikjatölva hafi fundist if (!activePads.length) { console.warn("Engir leikjatölvur tengdir fyrir skyndimynd."); alert("Enginn stjórnandi fannst!"); skila; }
// Skráðu þig og láttu notanda vita console.log("Snapshot:", activePads); alert(Skyndimynd tekin! ${activePads.length} stjórnandi(r) tekin.); });
Skyndimyndir frysta nákvæma stöðu stjórnandans á einu augnabliki í tíma. 4. Ghost Input Replay Nú fyrir það skemmtilega: endurspilun draugainntaks. Þetta tekur log og spilar það sjónrænt eins og phantom player væri að nota stjórnandann.
JavaScript fyrir endurspilun: // ================================== // ENDURSPIÐUR DRAUGA // ==================================
document.getElementById("replay").addEventListener("click", () => { // Gakktu úr skugga um að við höfum upptöku til að spila aftur ef (!rammar.lengd) { alert("Engin upptaka til að spila aftur!"); skila; }
console.log("Ræsir draugaendurspilun...");
// Lagatímasetning fyrir samstillta spilun let startTime = performance.now(); láta frameIndex = 0;
// Endurspilaðu hreyfimyndalykkju fall skref() { const now = performance.now(); const lapsed = now - startTime;
// Vinnið úr öllum ramma sem ættu að hafa komið upp núna while (frameIndex < frames.length && frames[frameIndex].t <= liðinn) { const rammi = rammar[frameIndex];
// Uppfærðu notendaviðmót með skráðum hnappastöðu btnA.classList.toggle("virkur", frame.buttons[0].pressed); btnB.classList.toggle("virkur", frame.buttons[1].pressed); btnX.classList.toggle("virkur", frame.buttons[2].pressed);
// Uppfærðu stöðuskjáinn látið ýta = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push("Button " + i); }); if (ýtt.lengd > 0) { status.textContent = "Draugur: " + pressed.join(", "); }
frameIndex++; }
// Haltu áfram lykkju ef það eru fleiri rammar if (frameIndex < frames.length) { requestAnimationFrame(skref); } annað { console.log("Endurspilunlokið."); status.textContent = "Endurspilun lokið"; } }
// Byrjaðu endurspilunina skref(); });
Til að gera kembiforritið aðeins meira praktískt bætti ég við draugaendurspilun. Þegar þú hefur tekið upp lotu geturðu ýtt á endurspilun og horft á notendaviðmótið spila það, næstum eins og draugaspilari sé að keyra púðann. Nýr Replay Ghost hnappur birtist á spjaldinu fyrir þetta.
Smelltu á Record, drullaðu aðeins með stjórnandann, hættu og spilaðu síðan aftur. HÍ endurómar bara allt sem þú gerðir, eins og draugur sem fylgir inntakinu þínu. Til hvers að vera að skipta sér af þessum aukahlutum?
Upptaka/útflutningur auðveldar prófurum að sýna nákvæmlega hvað gerðist. Skyndimyndir frjósa augnablik í tíma, mjög gagnlegar þegar þú ert að elta skrýtnar villur. Ghost replay er frábært fyrir kennsluefni, aðgengisskoðun eða bara að bera saman stjórnunaruppsetningar hlið við hlið.
Á þessum tímapunkti er þetta ekki bara sniðugt kynningu lengur, heldur eitthvað sem þú gætir í raun sett í verk. Raunveruleg notkunartilvik Núna höfum við þennan villuleitara sem getur gert mikið. Það sýnir lifandi inntak, skráir annála, flytur þá út og jafnvel endurspilar efni. En raunverulega spurningin er: hverjum er eiginlega ekki sama? Hverjum er þetta gagnlegt? Leikjahönnuðir Stýringar eru hluti af starfinu, en að kemba þá? Yfirleitt sársauki. Ímyndaðu þér að þú sért að prófa bardagaleikjasamsetningu, eins og ↓ → + kýla. Í stað þess að biðja, ýttu á það eins tvisvar, þú tekur það upp einu sinni og spilar það aftur. Búið. Eða þú skiptir um JSON logs við liðsfélaga til að athuga hvort fjölspilunarkóði þinn bregðist eins á vélinni þeirra. Það er risastórt. Aðgengissérfræðingar Þessi stendur mér nærri. Ekki allir spila með „venjulegum“ stjórnandi. Aðlögunarstýringar kasta stundum frá sér undarlegum merkjum. Með þessu tóli geturðu séð nákvæmlega hvað er að gerast. Kennarar, rannsakendur, hver sem er. Þeir geta gripið annála, borið þá saman eða endurspilað inntak hlið við hlið. Allt í einu verður ósýnilegt efni augljóst. Gæðatryggingarprófun Prófendur skrifa venjulega athugasemdir eins og „Ég maukaði hnappa hér og það brotnaði“. Ekki mjög hjálplegt. Nú? Þeir geta tekið nákvæmar pressur, flutt út annálinn og sent hann af stað. Engin ágiskun. Kennarar Ef þú ert að búa til kennsluefni eða YouTube myndbönd, þá er draugaendurspilun gulls ígildi. Þú getur bókstaflega sagt: „Hér er það sem ég gerði við stjórnandann,“ á meðan notendaviðmótið sýnir það gerast. Gerir skýringar mun skýrari. Beyond Games Og já, þetta snýst ekki bara um leiki. Fólk hefur notað stýringar fyrir vélmenni, listaverkefni og aðgengisviðmót. Sama mál í hvert skipti: hvað sér vafrinn í raun og veru? Með þessu þarftu ekki að giska. Niðurstaða Að kemba inntak stjórnanda hefur alltaf liðið eins og að fljúga í blindni. Ólíkt DOM eða CSS, þá er enginn innbyggður skoðunarmaður fyrir leikjatölvur; þetta eru bara hráar tölur í stjórnborðinu, glatast auðveldlega í hávaðanum. Með nokkur hundruð línum af HTML, CSS og JavaScript, byggðum við eitthvað annað:
Sjónræn kembiforrit sem gerir ósýnileg inntak sýnileg. Lagskipt CSS kerfi sem heldur notendaviðmótinu hreinu og villuleitanlegu. Set af endurbótum (upptaka, útflutningur, skyndimyndir, endurspilun drauga) sem lyfta því úr kynningu í þróunartól.
Þetta verkefni sýnir hversu langt þú getur náð með því að blanda saman krafti vefkerfisins og smá sköpunargáfu í CSS Cascade Layers. Tólið sem ég útskýrði bara í heild sinni er opinn uppspretta. Þú getur klónað GitHub endurhverfan og prófað það sjálfur. En mikilvægara er að þú getur gert það að þínu eigin. Bættu við þínum eigin lögum. Byggðu þína eigin endurspilunarlógík. Samþættu það með frumgerð leiksins þinnar. Eða jafnvel nota það á þann hátt sem ég hef ekki ímyndað mér. Til kennslu, aðgengis eða gagnagreiningar. Þegar öllu er á botninn hvolft snýst þetta ekki bara um villuleit á leikjatölvum. Þetta snýst um að varpa ljósi á falin inntak og veita þróunaraðilum sjálfstraust til að vinna með vélbúnað sem vefurinn nær ekki að fullu. Svo skaltu tengja stjórnandann þinn, opna ritilinn þinn og byrja að gera tilraunir. Þú gætir verið hissa á því hvað vafrinn þinn og CSS geta raunverulega áorkað.