Kiam vi enŝovas regilon, vi pikas butonojn, movas la bastonojn, tiras la ellasilon... kaj kiel programisto, vi vidas nenion el ĝi. La retumilo reprenas ĝin, certe, sed krom se vi registras numerojn en la konzolo, ĝi estas nevidebla. Tio estas la kapdoloro kun la Gamepad API. Ĝi ekzistas de jaroj, kaj ĝi fakte estas sufiĉe potenca. Vi povas legi butonojn, bastonojn, ellasilon, la verkojn. Sed plej multaj homoj ne tuŝas ĝin. Kial? Ĉar ne estas sugestoj. Neniu panelo en programiloj. Neniu klara maniero scii ĉu la regilo eĉ faras tion, kion vi pensas. Ĝi sentas kiel flugi blinde. Tio sufiĉe ĝenis min por konstrui etan ilon: Gamepad Cascade Debugger. Anstataŭ rigardi konzolan eligon, vi ricevas vivan, interagan vidon de la regilo. Premu ion kaj ĝi reagas sur la ekrano. Kaj kun CSS Cascade Layers, la stiloj restas fakorganizitaj, do estas pli pure sencimigi. En ĉi tiu afiŝo, mi montros al vi kial sencimigaj regiloj estas tia doloro, kiel CSS helpas purigi ĝin kaj kiel vi povas konstrui reuzeblan vidan erarseĉilon por viaj propraj projektoj.

Eĉ se vi povas ensaluti ilin ĉiujn, vi rapide finos kun nelegebla konzola spamo. Ekzemple: [0,0,1,0,0,0.5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]

Ĉu vi povas diri, kiun butonon estis premita? Eble, sed nur post streĉado de viaj okuloj kaj mankanta kelkaj enigaĵoj. Do, ne, senararigado ne facile okazas kiam temas pri legado de enigaĵoj. Problemo 3: Manko De Strukturo Eĉ se vi kunigas rapidan bildilon, stiloj povas rapide malordiĝi. Defaŭltaj, aktivaj kaj sencimigitaj ŝtatoj povas interkovri, kaj sen klara strukturo, via CSS fariĝas fragila kaj malfacile etendebla. CSS Kaskadaj Tavoloj povas helpi. Ili grupigas stilojn en "tavolojn", kiuj estas ordigitaj laŭ prioritato, do vi ĉesas batali kontraŭ specifeco kaj divenas, "Kial mia sencimstilo ne montriĝas?" Anstataŭe, vi konservas apartajn zorgojn:

Bazo: la norma, komenca aspekto de la regilo. Aktiva: Elstaraĵoj por premitaj butonoj kaj movitaj bastonoj. Sencimigi: Supermetaĵoj por programistoj (ekz., nombraj legaĵoj, gvidiloj, ktp).

Se ni difinus tavolojn en CSS laŭ ĉi tio, ni havus: /* plej malalta al plej alta prioritato */ @tavola bazo, aktiva, sencimigi;

@tavola bazo { /* ... */ }

@tavolo aktiva { /* ... */ }

@tavola sencimigo { /* ... */ }

Ĉar ĉiu tavolo stakiĝas antaŭvideble, vi ĉiam scias, kiuj reguloj venkas. Tiu antaŭvidebleco faras sencimigon ne nur pli facila, sed efektive regebla. Ni kovris la problemon (nevidebla, senorda enigo) kaj la aliron (vida erarserĉilo konstruita kun Cascade Layers). Nun ni trairos la paŝon post paŝo por konstrui la erarserĉilon. La Senĉimiga Koncepto La plej facila maniero fari kaŝitan enigaĵon videbla estas simple desegni ĝin sur la ekrano. Tion faras ĉi tiu erarserĉilo. Butonoj, ellasiloj kaj stirstangoj ĉiuj ricevas vidaĵon.

Premu A: Rondo lumiĝas. Nuŝu la bastonon: La cirklo glitas ĉirkaŭe. Tiru ellasilon duonvoje: Trinkejo plenigas duonvoje.

Nun vi ne rigardas 0ojn kaj 1ojn, sed efektive rigardas la regilon reagi vive. Kompreneble, post kiam vi komencas amasigi statojn kiel defaŭlta, premita, sencimiga informo, eble eĉ registra reĝimo, la CSS komencas pligrandiĝi kaj pli kompleksa. Tie estas kie kaskadaj tavoloj utilas. Jen nudigita ekzemplo: @tavola bazo { .butono { fono: #222; limo-radiuso: 50%; larĝo: 40px; alteco: 40px; } }

@tavolo aktiva { .butono.premita { fono: #0f0; /* hele verda */ } }

@tavola sencimigo { .button::post { enhavo: attr(datumo-valoro); tiparo-grandeco: 12px; koloro: #fff; } }

Gravas la tavola ordo: bazo → aktiva → sencimigi.

bazo tiras la regilon. aktivaj teniloj premitaj statoj. debug ĵetas sur supermetaĵoj.

Disrompi ĝin tiel signifas, ke vi ne batalas strangajn specifajn militojn. Ĉiu tavolo havas sian lokon, kaj vi ĉiam scias, kio gajnas. Konstruante Ĝin Ni unue aperigu ion sur ekrano. Ĝi ne bezonas aspekti bone - nur bezonas ekzisti, por ke ni havu ion por labori.

Gamepad Cascade Sencimigilo

A
B
X

Senĉimilo neaktiva

Tio estas laŭvorte nur skatoloj. Ankoraŭ ne ekscita, sed ĝi donas al ni tenilojn por kapti poste per CSS kaj JavaScript. Bone, mi uzas kaskadajn tavolojn ĉi tie ĉar ĝi konservas aferojn organizitajn post kiam vi aldonas pliajn ŝtatojn. Jen malglata paŝo:

/* ===================================== KASCADAJ TAVILOJ AJRO Ordo gravas: bazo → aktiva → sencimigi ==================================== */

/* Difini tavolordon antaŭe */ @tavola bazo, aktiva, sencimigi;

/* Tavolo 1: Bazaj stiloj - defaŭlta aspekto */ @tavola bazo { .butono { fono: #333; limo-radiuso: 50%; larĝo: 70px; alteco: 70px; montri: fleksi; justify-content: centro; vic-elementoj: centro; }

.paŭzo { larĝo: 20px; alteco: 70px; fono: #333; montriĝo: enlinia bloko; } }

/* Tavolo 2: Aktivaj ŝtatoj - pritraktas premitajn butonojn */ @tavolo aktiva { .butono.aktiva { fono: #0f0; /* Brile verda kiam premata */ transformi: skalo(1.1); /* Iomete pligrandigas la butonon */ }

.pause.aktiva { fono: #0f0; transformi: skaloY(1.1); /* Etendiĝas vertikale kiam premate */ } }

/* Tavolo 3: Sencimigi superkovraĵojn - programinformoj */ @tavola sencimigo { .button::post { enhavo: attr(datumo-valoro); /* Montras la numeran valoron */ tiparo-grandeco: 12px; koloro: #fff; } }

La beleco de ĉi tiu aliro estas, ke ĉiu tavolo havas klaran celon. La baztavolo neniam povas superregi aktivan, kaj aktiva neniam povas superregi sencimigon, sendepende de specifeco. Ĉi tio forigas la CSS-specifajn militojn, kiuj kutime turmentas sencimigajn ilojn. Nun ŝajnas, ke kelkaj aretoj sidas sur malhela fono. Sincere, ne tro malbone.

Aldonante la JavaScript JavaScript tempo. Ĉi tie la regilo efektive faras ion. Ni konstruos ĉi tiun paŝon post paŝo. Paŝo 1: Agordu Ŝtatan Administradon Unue, ni bezonas variablojn por spuri la staton de la erarserĉilo: // ===================================== // STATA ADMINISTRO // =====================================

lasu kuri = false; // Spuras ĉu la erarserĉilo estas aktiva lasu rafId; // Stokas la petonAnimationFrame-ID por nuligo

Ĉi tiuj variabloj kontrolas la animacian buklon, kiu senĉese legas ludpad-enigaĵon. Paŝo 2: Prenu DOM-Referencojn Poste, ni ricevas referencojn al ĉiuj HTML-elementoj, kiujn ni ĝisdatigos: // ===================================== // DOM ELEMENTA REFERENCO // =====================================

const btnA = document.getElementById("btn-a"); const btnB = document.getElementById ("btn-b"); const btnX = document.getElementById ("btn-x"); const paŭzo1 = document.getElementById ("paŭzo1"); const pause2 = document.getElementById ("pause2"); const status = document.getElementById ("stato");

Stoki ĉi tiujn referencojn antaŭe estas pli efika ol pridemandi la DOM plurfoje. Paŝo 3: Aldonu Klavaron Fallback Por testado sen fizika regilo, ni mapos klavarajn klavojn al butonoj: // ===================================== // KLAVARA FALLBACK (por testado sen regilo) // =====================================

konst klavmapo = { "a": btnA, "b": btnB, "x": btnX, "p": [paŭzo1, paŭzo2] // klavo 'p' kontrolas ambaŭ paŭzostrekojn };

Ĉi tio ebligas al ni testi la UI premante klavojn sur klavaro. Paŝo 4: Kreu La Ĉefan Ĝisdatigitan Buklon Jen kie la magio okazas. Ĉi tiu funkcio funkcias senĉese kaj legas staton de ludpad: // ===================================== // ĈEFA LUDODOJ ĜISDATIGA BUCĈO // =====================================

funkcio ĝisdatigiGamepad () { // Akiru ĉiujn konektitajn ludpadojn const gamepads = navigator.getGamepads (); se (!gamepads) revenas;

// Uzu la unuan konektitan ludmanieron const gp = ludpads[0];

se (gp) { // Ĝisdatigu butonŝtatojn per ŝanĝado de la "aktiva" klaso btnA.classList.toggle("aktiva", gp.butonoj[0].premita); btnB.classList.toggle("aktiva", gp.butonoj[1].premita); btnX.classList.toggle("aktiva", gp.butonoj[2].premita);

// Pritraktu paŭzbutonon (butonindekso 9 ĉe plej multaj regiloj) const paŭzoPressed = gp.butonoj[9].premita; pause1.classList.toggle("aktiva", pausePressed); pause2.classList.toggle("aktiva", pausePressed);

// Konstruu liston de aktuale premitaj butonoj por stato-montrado lasu premita = []; gp.buttons.forEach((btn, i) => { se (btn.premita)pressed.push ("Butono " + i); });

// Ĝisdatigu statusan tekston se iuj butonoj estas premitaj if (premita.longo > 0) { status.textContent = "Premitata: " + pressed.join(", "); } }

// Daŭrigu la buklon se erarserĉilo funkcias se (kurante) { rafId = petoAnimationFrame(ĝisdatigiGamepad); } }

La metodo classList.toggle() aldonas aŭ forigas la aktivan klason laŭ tio, ĉu la butono estas premata, kio ekigas niajn CSS-tavolstilojn. Paŝo 5: Pritraktu Klavarajn Eventojn Ĉi tiuj evento-aŭskultantoj igas la klavaran falofunkcion: // ===================================== // KLAVARAJ EVENTAJ MANIPULOJ // =====================================

document.addEventListener ("keydown", (e) => { se (klavoMapo[e.ŝlosilo]) { // Pritraktu unuopajn aŭ plurajn elementojn se (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("aktiva")); } alia { keyMap[e.key].classList.add("aktiva"); } status.textContent = "Ŝlosilo premita: " + e.key.toUpperCase (); } });

document.addEventListener ("keyup", (e) => { se (klavoMapo[e.ŝlosilo]) { // Forigu aktivan staton kiam klavo estas liberigita se (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("aktiva")); } alia { keyMap[e.key].classList.remove("aktiva"); } status.textContent = "Ŝlosilo liberigita: " + e.key.toUpperCase (); } });

Paŝo 6: Aldonu Komencu / Ĉesu Kontrolon Fine, ni bezonas manieron ŝalti kaj malŝalti la erarserĉilon: // ===================================== // ŝaltu/malŝalti // =====================================

document.getElementById("basku").addEventListener("klaku", () => { kurante = !kurante; // Turnu la kurantan staton

se (kurante) { status.textContent = "Elĉerpilo funkcias..."; ĝisdatigiGamepad (); // Komencu la ĝisdatigon } alia { status.textContent = "Senĉimilo neaktiva"; nuligiAnimationFrame(rafId); // Haltigu la buklon } });

Do jes, premu butonon kaj ĝi brilas. Premu la bastonon kaj ĝi moviĝas. Jen ĝi. Ankoraŭ unu afero: krudaj valoroj. Kelkfoje vi volas nur vidi nombrojn, ne lumojn.

En ĉi tiu etapo, vi devus vidi:

Simpla surekrana regilo, Butonoj kiuj reagas dum vi interagas kun ili, kaj Laŭvola sencimiga legado montranta premitajn butonindeksojn.

Por fari tion malpli abstrakta, jen rapida pruvo de la surekrana regilo reaganta en reala tempo:

Nun, premante Komencu Registradon registras ĉion ĝis vi trafas Ĉesu Registradon. 2. Eksportante Datumojn al CSV/JSON Post kiam ni havas protokolon, ni volos konservi ĝin.

Paŝo 1: Kreu La Elŝutan Helpilon Unue, ni bezonas helpan funkcion, kiu pritraktas dosierojn elŝutojn en la retumilo: // ===================================== // DOSIERO ELĈU HELPERON // =====================================

funkcio elŝutuDosiero(dosiernomo, enhavo, tipo = "teksto/plain") { // Kreu blob el la enhavo const blob = new Blob ([enhavo], { tipo }); const url = URL.createObjectURL (blob);

// Kreu provizoran elŝutan ligon kaj alklaku ĝin const a = document.createElement("a"); a.href = url; a.download = dosiernomo; a.klako();

// Purigu la objektan URL post elŝuto setTimeout(() => URL.revokeObjectURL(url), 100); }

Ĉi tiu funkcio funkcias kreante Blob (binara granda objekto) el viaj datumoj, generante provizoran URL por ĝi kaj programe klakante elŝutan ligilon. La purigado certigas, ke ni ne fukas memoron. Paŝo 2: Pritraktu JSON-Eksporton JSON estas perfekta por konservi la kompletan datumstrukturon:

// ===================================== // ESPORTI KIEL JSON // =====================================

document.getElementById("export-json").addEventListener("klaku", () => { // Kontrolu ĉu estas io por eksporti if (!kadroj.longo) { console.warn("Neniu registrado disponebla por eksporti."); reveni; }

// Kreu utilan ŝarĝon kun metadatenoj kaj kadroj konstante utila ŝarĝo = { createJe: nova Dato().toISOString(), kadroj };

// Elŝutu kiel formatita JSON elŝutu dosieron ( "gamepad-log.json", JSON.stringify (utila ŝarĝo, nulo, 2), "aplikaĵo/json" ); });

La JSON-formato tenas ĉion strukturita kaj facile analizebla, igante ĝin ideala por reŝarĝi en dev-iloj aŭ kunhavigi kun samteamanoj. Paŝo 3: Pritraktu CSV-Eksporton Por CSV-eksportoj, ni devas platigi la hierarkiajn datumojn en vicojn kaj kolumnojn:

//==================================== // ESPORTI AS CSV // =====================================

document.getElementById ("export-csv").addEventListener ("klaku", () => { // Kontrolu ĉu estas io por eksporti if (!kadroj.longo) { console.warn("Neniu registrado disponebla por eksporti."); reveni; }

// Konstruu CSV-kaplinion (kolumnoj por tempostampo, ĉiuj butonoj, ĉiuj aksoj) const headerButtons = kadroj[0].buttons.map((_, i) => btn${i}); const headerAxes = frames[0].axes.map((_, i) => akso${i}); const header = ["t", ...headerButtons, ...headerAxes].join(",") + "\n";

// Konstruu CSV-datumajn vicojn konst vicoj = frames.map (f => { const btnVals = f.buttons.map (b => b.valoro); return [f.t, ...btnVals, ...f.axes].join(","); }).join ("\n");

// Elŝutu kiel CSV downloadFile("gamepad-log.csv", kaplinio + vicoj, "text/csv"); });

CSV estas brila por analizo de datumoj ĉar ĝi malfermiĝas rekte en Excel aŭ Google Sheets, ebligante vin krei diagramojn, filtri datumojn aŭ vidi ŝablonojn vide. Nun kiam la eksportbutonoj estas enigitaj, vi vidos du novajn opciojn sur la panelo: Eksporti JSON kaj Eksporti CSV. JSON estas agrabla se vi volas reĵeti la krudan protokolon en viajn dev ilojn aŭ trafosi la strukturon. CSV, aliflanke, malfermiĝas rekte en Excel aŭ Google Sheets por ke vi povu diagrami, filtri aŭ kompari enigojn. La sekva figuro montras kiel aspektas la panelo kun tiuj kromaj kontroloj.

3. Momenta Sistemo Kelkfoje vi ne bezonas plenan registradon, nur rapidan "ekrankopio" de enigaj statoj. Tie helpas butono Preni Snapshot.

Kaj la JavaScript:

// ===================================== // PRI MOMENTOJN // =====================================

document.getElementById("fotofoto").addEventListener("klako", () => { // Akiru ĉiujn konektitajn ludpadojn const pads = navigilo.getGamepads (); const activePads = [];

// Trapasu kaj kaptu la staton de ĉiu konektita ludpad por (konst gp de kusenetoj) { se (!gp) daŭrigi; // Preterpasi malplenajn fendojn

activePads.push({ id: gp.id, // Nomo/modelo de regilo tempomarko: rendimento.nun(), butonoj: gp.buttons.map(b => ({ premita: b.premita, valoro: b.valoro })), aksoj: [...gp.akses] }); }

// Kontrolu ĉu iuj ludpadiloj estis trovitaj if (!activePads.length) { console.warn ("Neniu ludpadiloj konektitaj por momentfoto."); alert("Neniu regilo detektita!"); reveni; }

// Ensalutu kaj sciigu uzanton console.log ("Momentfoto:", activePads); atentigo(Momentfoto farita! Kaptita(j)j ${activePads.length} regilo(j).); });

Momentfotoj frostigas la precizan staton de via regilo samtempe. 4. Fantoma Eniga Ripeto Nun por la amuza: fantoma eniga ripeto. Ĉi tio prenas protokolon kaj reludas ĝin vide kvazaŭ fantoma ludanto uzus la regilon.

JavaScript por ripeto: // ===================================== // FANTOMA RIPETUO // =====================================

document.getElementById("reludi").addEventListener("klako", () => { // Certigu, ke ni havas registradon por reludi if (!kadroj.longo) { alert("Neniu registrado por reludi!"); reveni; }

console.log("Komencante fantoman ripeton...");

// Spura tempo por sinkronigita reproduktado let startTime = rendimento.nun (); lasu kadroIndekso = 0;

// Ripeti animacian buklon funkcio paŝo () { const nun = rendimento.nun(); const elapsed = nun - startTime;

// Procesu ĉiujn kadrojn kiuj devus esti okazinta ĝis nun dum (frameIndex < frames.length && frames[frameIndex].t <= pasis) { const frame = kadroj[frameIndex];

// Ĝisdatigu UI kun la registritaj butonŝtatoj btnA.classList.toggle("aktiva", kadro.butonoj[0].premita); btnB.classList.toggle("aktiva", kadro.butonoj[1].premita); btnX.classList.toggle("aktiva", kadro.butonoj[2].premita);

// Ĝisdatigu statusan ekranon lasu premita = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push ("Butono " + i); }); if (premita.longo > 0) { status.textContent = "Fantomo: " + pressed.join(", "); }

frameIndex++; }

// Daŭrigu buklon se estas pli da kadroj if (frameIndex < frames.length) { petoAnimationFrame(paŝo); } alia { console.log ("Reludifinita."); status.textContent = "Reludo kompleta"; } }

// Komencu la ripeton paŝo(); });

Por ke la senararigado estas iom pli praktika, mi aldonis fantoman ripeton. Post kiam vi registris sesion, vi povas trafi ripeton kaj rigardi la UI agi ĝin, preskaŭ kvazaŭ fantoma ludanto kuras la kuseneton. Nova Replay Ghost-butono aperas en la panelo por tio.

Premu Rekordon, iom fuŝu per la regilo, haltu, poste reludu. La UI nur eĥas ĉion, kion vi faris, kiel fantomo sekvanta viajn enigojn. Kial ĝeni ĉi tiujn kromaĵojn?

Registrado/eksportado faciligas por testantoj montri precize kio okazis. Momentaj fotoj frostiĝas, tre utilaj kiam vi postkuras strangajn cimojn. Fantoma ripeto estas bonega por lerniloj, alireblaj kontroloj aŭ simple kompari kontrolajn agordojn unu apud la alia.

Je ĉi tiu punkto, ĝi ne plu estas nur bona demo, sed io, kion vi povus efektive funkcii. Real-Mondaj Uzkazoj Nun ni havas ĉi tiun erarserĉilon kiu povas fari multon. Ĝi montras vivan enigon, registras protokolojn, eksportas ilin kaj eĉ reludas aferojn. Sed la vera demando estas: kiu efektive zorgas? Por kiu ĉi tio utilas? Ludaj Programistoj Regiloj estas parto de la laboro, sed sencimigi ilin? Kutime doloro. Imagu, ke vi provas batalludan kombon, kiel ↓ → + pugno. Anstataŭ preĝi, vi premis ĝin same dufoje, vi registras ĝin unufoje, kaj reludas ĝin. Farita. Aŭ vi interŝanĝas JSON-programojn kun samteamano por kontroli ĉu via plurludanta kodo reagas same sur ilia maŝino. Tio estas grandega. Alirebleco Praktikistoj Ĉi tiu estas proksima al mia koro. Ne ĉiuj ludas per "norma" regilo. Adaptiĝaj regiloj foje elĵetas strangajn signalojn. Kun ĉi tiu ilo, vi povas vidi ĝuste kio okazas. Instruistoj, esploristoj, kiu ajn. Ili povas kapti protokolojn, kompari ilin aŭ reludi enigaĵojn flank-al-flanke. Subite, nevideblaj aferoj iĝas evidentaj. Testado pri Kvalito Testistoj kutime skribas notojn kiel "Mi purigis butonojn ĉi tie kaj ĝi rompiĝis." Ne tre helpema. Nun? Ili povas kapti la precizajn gazetarojn, eksporti la protokolon kaj sendi ĝin. Neniu diveno. Edukistoj Se vi faras lernilojn aŭ YouTube-vidaĵojn, fantoma ripeto estas ora. Vi povas laŭvorte diri, "Jen kion mi faris kun la regilo", dum la UI montras, ke ĝi okazas. Faras klarigojn multe pli klaraj. Preter Ludoj Kaj jes, ĉi tio ne temas nur pri ludoj. Homoj uzis regilojn por robotoj, artaj projektoj kaj alireblaj interfacoj. Saman problemon ĉiufoje: kion la retumilo fakte vidas? Kun ĉi tio, vi ne devas diveni. Konkludo Sencimigi enigaĵon de regilo ĉiam sentis flugi blinde. Male al la DOM aŭ CSS, ne ekzistas enkonstruita inspektisto por ludpadoj; ĝi estas nur krudaj nombroj en la konzolo, facile perdeblaj en la bruo. Kun kelkaj centoj da linioj de HTML, CSS kaj JavaScript, ni konstruis ion malsaman:

Vida erarserĉilo kiu igas nevideblajn enigaĵojn videblaj. Tavoligita CSS-sistemo, kiu tenas la UI pura kaj sencimebla. Aro da plibonigoj (registrado, eksportado, momentfotoj, fantoma ripeto) kiuj levas ĝin de demonstraĵo al programilo.

Ĉi tiu projekto montras kiom malproksimen vi povas iri miksante la potencon de la Reta Platformo kun iom da kreemo en CSS Cascade Layers. La ilo, kiun mi ĵus klarigis en ĝia tuteco, estas malfermfonta. Vi povas kloni la GitHub-repozon kaj provi ĝin mem. Sed pli grave, vi povas fari ĝin via propra. Aldonu viajn proprajn tavolojn. Konstruu vian propran reludlogikon. Integri ĝin kun via ludprototipo. Aŭ eĉ uzi ĝin laŭ manieroj, kiujn mi ne imagis. Por instruado, alirebleco aŭ analizo de datumoj. Fine de la tago, ĉi tio ne temas nur pri senararigado de ludpadoj. Temas pri lumigi kaŝitajn enigojn, kaj doni al programistoj la konfidon labori kun aparataro, kiun la reto ankoraŭ ne plene ampleksas. Do, konektu vian regilon, malfermu vian redaktilon kaj komencu eksperimenti. Vi eble surpriziĝos pri tio, kion via retumilo kaj via CSS vere povas plenumi.

You May Also Like

Enjoyed This Article?

Get weekly tips on growing your audience and monetizing your content — straight to your inbox.

No spam. Join 138,000+ creators. Unsubscribe anytime.

Create Your Free Bio Page

Join 138,000+ creators on Seemless.

Get Started Free