Membangun teka -teki tom dengan CSS – Beragampengetahuan
Selama bertahun -tahun, saya pikir drag and drop game, terutama game yang melibatkan rotasi, logika spasial dan solusi puzzle – adalah ranah eksklusif JavaScript. Sampai suatu hari, saya bertanya pada AI:
“Apakah mungkin untuk membangun game teka -teki Tangram yang sepenuhnya interaktif hanya menggunakan CSS?”
Menjawab: “Tidak- tidak benar. Anda membutuhkan JavaScript.” Itu semua motivasi yang perlu saya buktikan.
Tapi pertama -tama, mari kita ajukan pertanyaan yang jelas: Mengapa ada orang yang melakukan ini?
Dengan sangat baik …
- Ketahui seberapa jauh CSS dapat mendorong saat membuat UI interaktif.
- Untuk meningkatkan keterampilan CSS saya.
- Sangat menarik!
cukup adil?
Sekarang, tidak mengherankan: CSS tidak dibuat untuk itu. Ini bukan bahasa yang logis, dan jujur, itu juga tidak terlalu dinamis. (Tentu saja, kami sekarang memiliki variabel CSS dan beberapa fitur bawaan, Houray!)
Dalam JavaScript, kami secara alami memikirkan fungsi, loop, kondisi, objek, dan perbandingan. Kami menulis konten yang logis dan abstrak ke dalam metode dan akhirnya mengirimkan bundel yang dipahami oleh browser. Setelah dikirim? Kami jarang melihat bundel JavaScript terakhir itu – kami hanya fokus untuk menjaganya agar tetap ramping.
Sekarang tanyakan pada diri sendiri: bukankah ini efek Sass pada CSS?
Mengapa kita harus menulis garis CSS yang berulang tanpa akhir dengan tangan ketika kita dapat menggunakan mixin dan fungsi untuk menghasilkannya – bersih, efisien, dan selama kita tidak peduli tidak mengoptimalkan output lama?
Jadi kami mengujinya dan ternyata Sass dapat menggantikan JavaScript, setidaknya dalam hal logika tingkat rendah dan perilaku puzzle. Terlepas dari peta, campuran, fitur, dan banyak matematika, kami semua berhasil menghidupkan teka -teki tangram kami tanpa JavaScript.
Biarkan game (hanya CSS) dimulai! 🎉
Contents
permainan
Gim ini terdiri dari tujuh karya: setelan tangram klasik. Secara alami, potongan -potongan ini dapat diatur menjadi kotak yang sempurna (dan juga banyak bentuk lainnya). Tapi kita membutuhkan lebih dari sekadar fragmentasi statis.
Jadi inilah yang saya bangun:
- Target puzzle, ini adalah bentuk target yang harus dibuat ulang pemain.
- Tombol Mulai yang menempatkan semua bagian ke area panggung.
- Setiap pekerjaan dapat diklik dan interaktif.
- Teka -teki harus memberi tahu pengguna kapan seseorang salah dan merayakan ketika teka -teki selesai.
Struktur HTML
Saya pertama kali mengatur struktur HTML, yang bukan tugas kecil, mengingat jumlah elemen yang terlibat.
- Setiap bentuk mendapat tujuh tombol radio. Saya memilih radio di kotak centang untuk memanfaatkan eksklusivitas bawaannya. Hanya satu yang dapat dipilih dalam kelompok yang sama. Ini membuatnya lebih mudah untuk melacak bentuk dan status mana yang saat ini aktif.
- Tombol Mulai? Juga input radio. Kotak centang juga berfungsi, tetapi untuk konsistensi, saya telah mengambil root di radio secara penuh.
- Teka -teki itu sendiri hanyalah yang biasa lama
<div>sederhana dan efektif. - Untuk rotasi, kami menambahkan delapan tombol radio, masing -masing mewakili kenaikan 45 derajat: 45 °, 90 °, 135 °, sampai 360 °. Rotasi simulasi ini sepenuhnya dikontrol dalam CSS.
- Setiap lokasi bayangan potensial juga memiliki tombol siarannya sendiri. (Ya, saya tahu banyak.)
- Untuk membungkus semuanya, saya menyertakan tombol reset klasik dalam satu
<form>menggunakan<button type="reset">sehingga pemain dapat dengan mudah memulai dari awal kapan saja.
Saya menggunakan PUG untuk menghasilkan HTML lebih efisien, mengingat jumlah elemen yang diperlukan. Ini adalah opsi yang murni nyaman. Itu tidak mempengaruhi logika atau perilaku teka -teki dengan cara apa pun.
Di bawah ini adalah sampel HTML yang dikompilasi. Sekilas, mungkin tampak luar biasa (ini hanya bagian darinya!), Tetapi menggambarkan kompleksitas struktural yang terlibat. Bagian ini runtuh untuk tidak menyetujui layar, tetapi Anda dapat memperluas jika Anda ingin menjelajahinya.
Buka kode HTML
<div class="wrapper">
<div class="tanagram-box"></div>
<div class="tanagram-box"></div>
<form class="container">
<input class="hide_input start" type="checkbox" id="start" autofocus />
<button class="start-button" type="reset" id="restart">Restart</button>
<label class="start-button" for="start">Start </label>
<div class="shadow">
<input class="hide_input" type="radio" id="blueTriangle-tan" name="tan-active" />
<input class="hide_input" type="radio" id="yellowTriangle-tan" name="tan-active" />
<!-- Inputs for others tans -->
<input class="hide_input" type="radio" id="rotation-reset" name="tan-active" />
<input class="hide_input" type="radio" id="rotation-45" name="tan-rotation" />
<input class="hide_input" type="radio" id="rotation-90" name="tan-rotation" />
<!--radios for 90, 225, 315, 360 -->
<input class="hide_input" type="checkbox" id="yellowTriangle-tan-1-135" name="tan-rotation" />
<input class="hide_input" type="checkbox" id="yellowTriangle-tan-1-225" name="tan-rotation" />
<!-- radio for every possible shape shadows-->
<label class="rotation rot" for="rotation-45" id="rot45">⟲</label>
<label class="rotation rot" for="rotation-90" id="rot90">⟲</label>
<!--radios for 90, 225, 315, 360 -->
<label class="rotation" for="rotation-reset" id="rotReset">✘</label>
<label class="blueTriangle tans" for="blueTriangle-tan" id="tanblueTrianglelab"></label>
<div class="tans tan_blocked" id="tanblueTrianglelabRes"></div>
<!-- labels for every tan and disabled div -->
<label class="blueTriangle tans" for="blueTriangle-tan-1-90" id="tanblueTrianglelab-1-90"></label>
<label class="blueTriangle tans" for="blueTriangle-tan-1-225" id="tanblueTrianglelab-1-225"></label>
<!-- labels radio for every possible shape shadows-->
<div class="shape"></div>
</div>
</form>
<div class="tanagram-box"></div>
<div class="tanagram-box"></div>
<div class="tanagram-box"></div>
<div class="tanagram-box"></div>
<div class="tanagram-box"></div>
</div>
Buat peta untuk data bentuk
Sekarang kerangka HTML siap, saatnya untuk menyuntikkan kekuatan nyata ke dalamnya. Itu tempat kami Peta puing Masuk, di sinilah logika teka -teki mulai bersinar.
Catatan: Peta dalam sass berisi pasangan kunci dan nilai dan memudahkan untuk menemukan nilai dengan kunci yang sesuai. Suka objek dalam JavaScript, kamus dalam python dan peta di C ++.
Saya merencanakan semua data inti yang diperlukan untuk mengontrol setiap karya tangram (tan): warna, bentuk, posisi, dan bahkan logika interaksi. Peta ini berisi:
- ini
background-colorUntuk setiap tan, - ini
clip-pathKoordinat menentukan bentuknya, - Posisi awal setiap tan,
- Lokasi pemblokiran
div(Interaksi dinonaktifkan saat TAN dipilih), - Posisi bayangan (koordinat garis besar TAN yang ditampilkan di papan tugas),
- Informasi kisi, dan
- Kombinasi yang menang – Target yang tepat koordinat untuk setiap tan, menandai solusi yang benar.
$colors: ( blue-color: #53a0e0, yellow-color: #f7db4f, /* Colors for each tan */ );
$nth-child-grid: ( 1: (2, 3, 1, 2, ), 2: ( 3, 4, 1, 2, ), 4: ( 1, 2, 2, 3, ), /* More entries to be added */);
$bluePosiblePositions: ( 45: none, 90: ( (6.7, 11.2), ), 135: none, 180: none, /* Positions defined up to 360 degrees */);
/* Other tans */
/* Data defined for each tan */
$tansShapes: (
blueTriangle: (
color: map.get($colors, blue-color),
clip-path: ( 0 0, 50 50, 0 100, ),
rot-btn-position: ( -20, -25, ),
exit-mode-btn-position: ( -20, -33, ),
tan-position: ( -6, -37, ),
diable-lab-position: ( -12, -38, ),
poss-positions: $bluePosiblePositions,
correct-position: ((4.7, 13.5), (18.8, 13.3), ),
transform-origin: ( 4.17, 12.5,),
),
);
/* Remaining 7 combinations */
$winningCombinations: (
combo1: (
(blueTriangle, 1, 360),
(yellowTriangle, 1, 225),
(pinkTriangle, 1, 180),
(redTriangle, 4, 360),
(purpleTriangle, 2, 225),
(square, 1, 90),
(polygon, 4, 90),
),
);
Anda dapat melihat ini di codepen di mana peta ini mendorong penampilan dan perilaku yang sebenarnya dari setiap teka -teki. Pada titik ini, tidak ada perubahan yang jelas dalam pratinjau. Kami hanya menyiapkan dan menyimpan data untuk digunakan nanti.
Baca dari peta menggunakan mixin
Gagasan utamanya adalah membuat campuran yang dapat digunakan kembali yang akan membaca data dari peta saat diperlukan dan menerapkannya pada aturan CSS yang sesuai.
Tetapi sebelum itu, kami membawa hal -hal ke tingkat yang lebih tinggi dengan membuat keputusan kritis: kami tidak pernah memiliki hardcoded langsung di dalam peta. Mengganti vmin,,,,, pxdll.) Saat menggunakan nilai. Dengan begitu, kapan kita bisa menggunakan peta kita.
@function get-coordinates($data, $key, $separator, $unit) {
$coordinates: null;
// Check if the first argument is a map
@if meta.type-of($data) == "map" {
// If the map contains the specified key
@if map.has-key($data, $key) {
// Get the value associated with the key (expected to be a list of coordinates)
$coordinates: map.get($data, $key);
}
// If the first argument is a list
} @else if meta.type-of($data) == "list" {
// Ensure the key is a valid index (1-based) within the list
@if meta.type-of($key) == "number" and $key > 0 and $key <= list.length($data) {
// Retrieve the item at the specified index
$coordinates: list.nth($data, $key);
}
// If neither map nor list, throw an error
} @else {
@error "Invalid input: First argument must be a map or a list.";
}
// If no valid coordinates were found, return null
@if $coordinates == null {
@return null;
}
// Extract x and y values from the list
$x: list.nth($coordinates, 1);
$y: list.nth($coordinates, -1); // -1 gets the last item (y)
// Return the combined x and y values with units and separator
@return #{$x}#{$unit}#{$separator}#{$y}#{$unit};
}
Tentu saja, tidak ada yang muncul sejauh ini, tetapi sihir yang sebenarnya sekarang menyala.
Sekarang, mari kita lanjutkan menulis mixins. Saya akan menguraikan metode untuk campuran pertama dan sisanya akan dijelaskan dengan komentar.
Aplikasi dinamis pertama dari protein campuran grid-column Dan grid-row Aturan penempatan berdasarkan elemen nilai anak yang disimpan dalam peta. Setiap entri dalam peta sesuai dengan indeks elemen (1 hingga 8) dan berisi daftar empat nilai: [start-col, end-col, start-row, end-row].
@mixin tanagram-grid-positioning($nth-child-grid) {
// Loop through numbers 1 to 8, corresponding to the tanam pieces
@for $i from 1 through 8 {
// Check if the map contains a key for the current piece (1-8)
@if map.has-key($nth-child-grid, $i) {
// Get the grid values for this piece: [start-column, end-column, start-row, end-row]
$values: map.get($nth-child-grid, $i);
// Target the nth child (piece) and set its grid positions
&:nth-child(#{$i}) {
// Set grid-column: start and end values based on the first two items in the list
grid-column: #{list.nth($values, 1)} / #{list.nth($values, 2)};
// Set grid-row: start and end values based on the last two items in the list
grid-row: #{list.nth($values, 3)} / #{list.nth($values, 4)};
}
}
}
}
Kita dapat berharap untuk menghasilkan CSS berikut:
.tanagram-box:nth-child(1) {
grid-column: 2 / 3;
grid-row: 1 / 2;
}
.tanagram-box:nth-child(2) {
grid-column: 3 / 4;
grid-row: 1 / 2;
}
Dalam campuran ini, tujuan saya adalah untuk benar -benar menciptakan semua bentuk (tan). Saya menggunakan clip-path. Ada beberapa ide untuk menggunakan gambar SVG yang indah, tetapi proyek uji ini lebih tentang menguji logika daripada berfokus pada desain yang indah. Oleh karena itu, solusi termudah adalah memotong elemen sesuai dengan ukuran ketika mereka berada di kotak (posisi awal semua penyamakan).
Oleh karena itu, dalam hal ini, dengan perhitungan statis $tansShapes Peta telah diperbarui clip-path milik:
clip-path: (0 0, 50 50, 0 100);
Ini berisi semua titik klip tan. Pada dasarnya, campuran ini membentuk setiap tan.
@mixin set-tan-clip-path($tanName, $values) {
// Initialize an empty list to hold the final clip-path points
$clip-path-points: ();
// Extract the 'clip-path' data from the map, which contains coordinate pairs
$clip-path-key: map.get($values, clip-path);
// Get the number of coordinate pairs to loop through
$count: list.length($clip-path-key);
// Loop through each coordinate point
@for $i from 1 through $count {
// Convert each pair of numbers into a formatted coordinate string with units
$current-point: get-coordinates($clip-path-key, $i, " ", "%");
// Add the formatted coordinate to the list, separating each point with a comma
$clip-path-points: list.append($clip-path-points, #{$current-point}, comma);
}
// Style for the preview element (lab version), using the configured background color
#tan#{$tanName}lab {
background: map.get($values, color);
clip-path: polygon(#{$clip-path-points}); // Apply the full list of clip-path points
}
// Apply the same clip-path to the actual tan element
.#{$tanName} {
clip-path: polygon(#{$clip-path-points});
}
}
Output di CSS harus:
.blueTriangle {
clip-path: polygon(0% 0%, 50% 50%, 0% 100%);
}
/* other tans */
Logika startup
Nah, sekarang saya ingin mengklarifikasi apa yang harus terjadi terlebih dahulu ketika permainan dimuat.
Pertama, klik tombol Mulai dan semua Tans “pergi ke tempat mereka.” Bahkan, kami menugaskannya transform: translate() Memiliki koordinat dan rotasi spesifik.
.start:checked ~ .shadow #tanblueTrianglelab {
transform-origin: 4.17vmin 12.5vmin;
transform: translate(-6vmin,-37vmin) rotate(360deg);
cursor: pointer;
}
Karena itu, kami masih mempertahankan pola ini. Kami menggunakan transformasi dan cukup mengubah posisi atau sudut tan dan bayangannya (di peta).
Saat mengklik TAN apa pun, tombol Rotary muncul. Dengan mengkliknya, tan harus berputar di sekitar pusatnya dan kemudian lanjutkan di setiap klik berikutnya. Sebenarnya ada delapan tombol radio, dan setiap kali Anda mengklik, satu menghilang dan tombol berikutnya muncul. Ketika kita mencapai yang terakhir, mengkliknya menghilang dan yang pertama muncul kembali. Dengan cara ini kami mendapatkan kesan bahwa kami mengklik tombol yang sama (gaya yang sama tentu saja) dan dapat mengklik tanpa batas (putar tan). Ini adalah persis hibrida berikut yang diaktifkan.
@mixin set-tan-rotation-states($tanName, $values, $angles, $color) {
// This mixin dynamically applies rotation UI styles based on a tan's configuration.
// It controls the positioning and appearance of rotation buttons and visual feedback when a rotation state is active.
@each $angle in $angles{
& ~ #rot#{$angle}{ transform: translate(get-coordinates($values,rot-btn-position,',',vmin )); background: $color;}
& ~ #rotation-#{$angle}:checked{
@each $key in map.keys($tansShapes){
& ~ #tan#{$key}labRes{ visibility: visible; background:rgba(0,0,0,0.4); }
& ~ #tan#{$key}lab{ opacity:.3; }
& ~ #rotReset{ visibility: visible; }
}
}
}
}
CSS yang dihasilkan harus:
#blueTriangle-tan:checked ~ #rotation-45:checked ~ #tanblueTrianglelab {
transform: translate(-6vmin,-37vmin) rotate(45deg);
}
#blueTriangle-tan:checked ~ #rotation-45:checked ~ #tanblueTrianglelabRes {
visibility: hidden;
}
Oke, gunakan campuran berikut set-clip-path Dan set-rotation campuran. Mereka berisi semua informasi tentang tan dan perilakunya dengan klik tan dan rotasi mana yang harus dipilih dan posisinya (sebagaimana didefinisikan dalam campuran kedua).
@mixin generate-tan-shapes-and-interactions($tansShapes) {
// Applies styling logic and UI interactions for each individual tan shape from the $tansShapes map.
@each $tanName, $values in $tansShapes{
$color: color.scale(map.get($values, color), $lightness: 10%);
$angles: (45, 90, 135, 180, 225, 270, 315, 360);
@include set-tan-clip-path($tanName, $values);
##{$tanName}-tan:checked{
& ~ #tan#{$tanName}Res{ visibility:hidden; }
& ~ #tan#{$tanName}lab{opacity: 1 !important;background: #{$color};cursor:auto;}
@each $key in map.keys($tansShapes){
& ~ #tan#{$tanName}Res:checked ~ #tan#{$key}labRes{visibility: visible;}
}
& ~ #rot45{display: flex;visibility: visible;}
& ~ #rotReset{ transform: translate(get-coordinates($values, exit-mode-btn-position,',', vmin)); }
@include set-tan-rotation-states($tanName, $values, $angles, $color);
}
}
}
@mixin set-initial-tan-position($tansShapes) {
// This mixin sets the initial position and transformation for both the interactive (`lab`) and shadow (`labRes`) versions
// of each tan shape, based on coordinates provided in the $tansShapes map.
@each $tanName, $values in $tansShapes{
& ~ .shadow #tan#{$tanName}lab{
transform-origin: get-coordinates($values, transform-origin,' ' ,vmin);
transform: translate( get-coordinates($values,tan-position,',', vmin)) rotate(360deg) ;
cursor: pointer;
}
& ~ .shadow #tan#{$tanName}labRes{
visibility:hidden;
transform: translate(get-coordinates($values,diable-lab-position,',',vmin));
}
}
}
Seperti yang disebutkan sebelumnya, satu hal yang terlihat saat mengklik Tan adalah bayangannya – siluet yang muncul di papan tugas.
Posisi bayangan ini (koordinat) saat ini didefinisikan secara statis. Setiap bayangan memiliki lokasi tertentu di peta, dan mixin membaca data ini dan menerapkannya pada bayangan transform: translate().
Ketika rotasi mengklik Tan, jumlah bayangan yang terlihat pada papan tugas dapat berubah, serta sudutnya.
Tentu saja, konvensi penamaan tunduk pada perawatan khusus. Setiap elemen bayangan mendapatkan ID unik, yang diwarisi dengan nama (diwarisi dari Tan induknya) dan angka yang mewakili posisi urutannya pada sudut tertentu.
Sangat keren, kan? Dengan cara ini, kita sepenuhnya menghindari pola penamaan yang kompleks!
@mixin render-possible-tan-positions( $name, $angle, $possiblePositions, $visibility, $color, $id, $transformOrigin ) {
// This mixin generates styles for possible positions of a tan shape based on its name, rotation angle, and configuration map.
// It handles both squares and polygons, normalizing their rotation angles accordingly and applying transform styles if positions exist.}
@if $name == 'square' {
$angle: normalize-angle($angle); // Normalizujemo ugao ako je u pitanju square
} @else if $name == 'polygon'{
$angle: normalize-polygon-angle($angle);
}
@if map.has-key($possiblePositions, $angle) {
$values: map.get($possiblePositions, $angle);
@if $values != none {
$count: list.length($values);
@for $i from 1 through $count {
$position: get-coordinates($values, $i, ',', vmin);
& ~ #tan#{$name}lab-#{$i}-#{$angle} {
@if $visibility == visible {
visibility: visible;
background-color: $color;
opacity: .2;
z-index: 2;
transform-origin: #{$transformOrigin};
transform: translate(#{$position}) rotate(#{$angle}deg);
} @else if $visibility == hidden { visibility: hidden; }
&:hover{ opacity: 0.5; cursor: pointer; }
}
}
}
}
}
CSS yang dihasilkan:
#blueTriangle-tan:checked ~ #tanblueTrianglelab-1-360 {
visibility: visible;
background-color: #53a0e0;
opacity: 0.2;
z-index: 2;
transform-origin: 4.17vmin 12.5vmin;
transform: translate(4.7vmin,13.5vmin) rotate(360deg);
}
Campuran berikutnya terikat pada campuran sebelumnya dan mengelola kapan dan bagaimana warna tan ketika Tan induk diputar menggunakan tombol. Ini mendengarkan sudut rotasi saat ini dan memeriksa posisi bayangan yang ditentukan untuk sudut tertentu. Jika ada, itu akan menunjukkan kepada mereka; Jika tidak ada – tidak ada bayangan!
@mixin render-possible-positions-by-rotation {
// This mixin applies rotation to each tan shape. It loops through each tan, calculates its possible positions for each angle, and handles visibility and transformation.
// It ensures that rotation is applied correctly, including handling the transitions between various tan positions and visibility states.
@each $tanName, $values in $tansShapes{
$possiblePositions: map.get($values, poss-positions);
$possibleTansColor: map.get($values, color);
$validPosition: get-coordinates($values, correct-position,',' ,vmin);
$transformOrigin: get-coordinates($values,transform-origin,' ' ,vmin);
$rotResPosition: get-coordinates($values,exit-mode-btn-position ,',' ,vmin );
$angle: 0;
@for $i from 1 through 8{
$angle: $i * 45;
$nextAngle: if($angle + 45 > 360, 45, $angle + 45);
@include render-position-feedback-on-task($tanName,$angle, $possiblePositions,$possibleTansColor, #{$tanName}-tan, $validPosition,$transformOrigin, $rotResPosition);
##{$tanName}-tan{
@include render-possible-tan-positions($tanName,$angle, $possiblePositions,hidden, $possibleTansColor, #{$tanName}-tan,$transformOrigin)
}
##{$tanName}-tan:checked{
@include render-possible-tan-positions($tanName,360, $possiblePositions,visible, $possibleTansColor, #{$tanName}-tan,$transformOrigin);
& ~ #rotation-#{$angle}:checked {
@include render-possible-tan-positions($tanName,360, $possiblePositions,hidden, $possibleTansColor, #{$tanName}-tan,$transformOrigin);
& ~ #tan#{$tanName}lab{transform:translate( get-coordinates($values,tan-position,',', vmin)) rotate(#{$angle}deg) ;}
& ~ #tan#{$tanName}labRes{ visibility: hidden; }
& ~ #rot#{$angle}{ visibility: hidden; }
& ~ #rot#{$nextAngle}{ visibility: visible }
@include render-possible-tan-positions($tanName,$angle, $possiblePositions,visible, $possibleTansColor, #{$tanName}-tan,$transformOrigin);
}
}
}
}
}
Saat mengklik naungan tan, tan yang sesuai harus pindah ke posisi bayangan itu. Campuran berikutnya kemudian memeriksa apakah posisi baru ini adalah tempat yang tepat untuk menyelesaikan teka -teki. Jika benar, tan akan menghasilkan efek flashing singkat dan menjadi tidak dapat dibuang, mengirimkan sinyal yang ditempatkan dengan benar. Jika tidak benar, tan hanya tetap dalam posisi bayangan. Tidak ada efek, dan masih bisa diseret/diklik.
Tentu saja, setiap tan memiliki daftar semua tempat yang tepat. Karena beberapa cokelat memiliki ukuran yang sama – beberapa bahkan dapat bergabung untuk membentuk bentuk yang lebih besar – kami memiliki beberapa kombinasi efektif. Untuk misi unta ini, semua tugas ini diperhitungkan. Peta khusus dengan kombinasi ini dibuat, serta protein campuran untuk dibaca dan menerapkannya.
Di akhir permainan, ketika semua posisi kecokelatan ditempatkan di posisi yang benar, kami memicu efek “gabungan” – profil unta berubah menjadi kuning. Pada saat itu, satu -satunya tindakan yang tersisa adalah mengklik tombol restart.
Nah, itu panjang, tetapi ketika Anda memilih kesenangan (meskipun sulit dan panjang), itulah yang Anda dapatkan. Semua adalah odes untuk sihir khusus CSS!
Breaking the Boundaries: Buat teka-teki tanggram untuk CSS yang awalnya diterbitkan di beragampengetahuan, yang merupakan bagian dari keluarga Digitalocean. Anda harus menerima buletin.
Dari beragampengetahuan https://ift.tt/17pfed9
Dapatkan $ 200 seminggu
Baca selengkapnya
rencana pengembangan website
metode pengembangan website
jelaskan beberapa rencana untuk pengembangan website, proses pengembangan website, kekuatan dan kelemahan bisnis pengembangan website
, jasa pengembangan website, tahap pengembangan website, biaya pengembangan website
#Membangun #teka #teki #tom #dengan #CSS