mirror of
https://gitlab.com/arnekeller/tunnel-racer.git
synced 2024-11-22 06:54:57 +00:00
Cleanups + comments
This commit is contained in:
parent
8c2a8b6272
commit
711bc739ec
@ -4,7 +4,7 @@
|
|||||||
<title>three.js webgl - tunnel racer</title>
|
<title>three.js webgl - tunnel racer</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||||
<link type="text/css" rel="stylesheet" href="main.css">
|
<link type="text/css" rel="stylesheet" href="./main.css">
|
||||||
<link rel="icon" href="./favicon.png">
|
<link rel="icon" href="./favicon.png">
|
||||||
<script src="./gyronorm.complete.min.js"></script>
|
<script src="./gyronorm.complete.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
@ -18,6 +18,6 @@
|
|||||||
<button id="start" disabled>Start</button>
|
<button id="start" disabled>Start</button>
|
||||||
|
|
||||||
<script src="./three.min.js"></script>
|
<script src="./three.min.js"></script>
|
||||||
<script src="./game.js"></script>
|
<script src="./game.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
111
game.js
111
game.js
@ -1,3 +1,9 @@
|
|||||||
|
// ----------------------
|
||||||
|
// | setup. of three.js |
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
// shaders for sphere outline
|
||||||
|
// https://stemkoski.github.io/Three.js/Shader-Glow.html
|
||||||
const vertexShaderGlow = `
|
const vertexShaderGlow = `
|
||||||
uniform vec3 viewVector;
|
uniform vec3 viewVector;
|
||||||
uniform float c;
|
uniform float c;
|
||||||
@ -17,7 +23,7 @@ void main()
|
|||||||
vec3 vNormel = normalize( normalMatrix * viewVector );
|
vec3 vNormel = normalize( normalMatrix * viewVector );
|
||||||
intensity = pow( c - dot(vNormal, vNormel), p );
|
intensity = pow( c - dot(vNormal, vNormel), p );
|
||||||
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||||
vColor = hsv2rgb(vec3(fract(gl_Position[2] / 270.0), 0.7, 0.7));
|
vColor = hsv2rgb(vec3(fract(gl_Position[2] / 270.0), 0.7, 0.7));
|
||||||
//vColor = vec3((sin(position[2] / 360.0) + 1.0) * 0.5, (sin(position[2] / 1000.0) + 1.0) * 0.5, (sin(position[2] / 70.0) + 1.0) * 0.5);
|
//vColor = vec3((sin(position[2] / 360.0) + 1.0) * 0.5, (sin(position[2] / 1000.0) + 1.0) * 0.5, (sin(position[2] / 70.0) + 1.0) * 0.5);
|
||||||
//vColor = vec3(0.5, 0, 0.5);
|
//vColor = vec3(0.5, 0, 0.5);
|
||||||
@ -34,6 +40,7 @@ void main()
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// shaders for tunnel border
|
||||||
const fragmentShader = `
|
const fragmentShader = `
|
||||||
#include <common>
|
#include <common>
|
||||||
|
|
||||||
@ -80,20 +87,33 @@ const vertexShader = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const diameterOfTunnel = 100;
|
// size of the tunnel
|
||||||
|
const tunnelRadius = 100;
|
||||||
|
// z interval of new obstacles
|
||||||
const spawnInterval = 40;
|
const spawnInterval = 40;
|
||||||
|
// coordinate of newest obstacle
|
||||||
let lastSpawned = 0;
|
let lastSpawned = 0;
|
||||||
|
|
||||||
|
// gyroscope variables
|
||||||
var gn;
|
var gn;
|
||||||
let headSet = false;
|
let headSet = false;
|
||||||
let gammaReference = 0.0;
|
let gammaReference = 0.0;
|
||||||
let leftRightMove = 0.0;
|
let leftRightMove = 0.0;
|
||||||
let upDownMove = 0.0;
|
let upDownMove = 0.0;
|
||||||
|
|
||||||
var speed = 5.0;
|
// current player speed
|
||||||
|
const initialSpeed = 5.0;
|
||||||
|
let speed = initialSpeed;
|
||||||
|
// score points
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
// amount of sphere objects removed
|
||||||
let removed = 0;
|
let removed = 0;
|
||||||
|
// player is actively racing
|
||||||
let running = false;
|
let running = false;
|
||||||
|
// obstacles
|
||||||
|
const spheres = [];
|
||||||
|
// wall segments
|
||||||
|
const borders = [];
|
||||||
let frameCount = 0;
|
let frameCount = 0;
|
||||||
|
|
||||||
function logger(text) {
|
function logger(text) {
|
||||||
@ -101,14 +121,14 @@ function logger(text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init_gn() {
|
function init_gn() {
|
||||||
var args = {
|
const args = {
|
||||||
logger: logger
|
logger: logger
|
||||||
};
|
};
|
||||||
|
|
||||||
gn = new GyroNorm();
|
gn = new GyroNorm();
|
||||||
|
|
||||||
gn.init(args).then(function() {
|
gn.init(args).then(function() {
|
||||||
var isAvailable = gn.isAvailable();
|
const isAvailable = gn.isAvailable();
|
||||||
if (!isAvailable.deviceOrientationAvailable) {
|
if (!isAvailable.deviceOrientationAvailable) {
|
||||||
console.log({
|
console.log({
|
||||||
message: 'Device orientation is not available.'
|
message: 'Device orientation is not available.'
|
||||||
@ -196,11 +216,13 @@ document.getElementById("set_head").addEventListener("click", () => {
|
|||||||
|
|
||||||
init_gn();
|
init_gn();
|
||||||
|
|
||||||
|
// setup three.js: materials, geometries, scene, lighting and camera
|
||||||
let scene, renderer;
|
let scene, renderer;
|
||||||
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 10000);
|
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 10000);
|
||||||
|
|
||||||
|
// obstacle geometry and material
|
||||||
const geometrySphere = new THREE.SphereGeometry(11, 32, 32);
|
const geometrySphere = new THREE.SphereGeometry(11, 32, 32);
|
||||||
const materialCube = new THREE.MeshBasicMaterial();
|
const materialSphere = new THREE.MeshBasicMaterial();
|
||||||
const customMaterial = new THREE.ShaderMaterial({
|
const customMaterial = new THREE.ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
"c": {
|
"c": {
|
||||||
@ -217,14 +239,13 @@ const customMaterial = new THREE.ShaderMaterial({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
vertexShader: vertexShaderGlow,
|
vertexShader: vertexShaderGlow,
|
||||||
//vertexColors: true,
|
|
||||||
fragmentShader: fragmentShaderGlow,
|
fragmentShader: fragmentShaderGlow,
|
||||||
side: THREE.FrontSide,
|
side: THREE.FrontSide,
|
||||||
blending: THREE.AdditiveBlending,
|
blending: THREE.AdditiveBlending,
|
||||||
transparent: true
|
transparent: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// wall texture
|
||||||
const loader = new THREE.TextureLoader();
|
const loader = new THREE.TextureLoader();
|
||||||
const texture = loader.load('./bayer.png');
|
const texture = loader.load('./bayer.png');
|
||||||
texture.minFilter = THREE.NearestFilter;
|
texture.minFilter = THREE.NearestFilter;
|
||||||
@ -249,9 +270,6 @@ const material = new THREE.ShaderMaterial({
|
|||||||
});
|
});
|
||||||
const borderGeometry = new THREE.PlaneGeometry(1, 1);
|
const borderGeometry = new THREE.PlaneGeometry(1, 1);
|
||||||
|
|
||||||
const cubes = [];
|
|
||||||
const borders = [];
|
|
||||||
|
|
||||||
init();
|
init();
|
||||||
animate();
|
animate();
|
||||||
|
|
||||||
@ -264,12 +282,13 @@ document.getElementById("start").onclick = () => {
|
|||||||
}
|
}
|
||||||
document.getElementById("start").style.zIndex = -10;
|
document.getElementById("start").style.zIndex = -10;
|
||||||
document.getElementById("start").style.visibility = "hidden";
|
document.getElementById("start").style.visibility = "hidden";
|
||||||
speed = 5.0;
|
// reset variables
|
||||||
|
speed = initialSpeed;
|
||||||
score = 0;
|
score = 0;
|
||||||
for (let i = 0; i < cubes.length; i++) {
|
for (let i = 0; i < spheres.length; i++) {
|
||||||
scene.remove(cubes[i]);
|
scene.remove(spheres[i]);
|
||||||
}
|
}
|
||||||
cubes.length = 0;
|
spheres.length = 0;
|
||||||
removed = 0;
|
removed = 0;
|
||||||
running = true;
|
running = true;
|
||||||
}
|
}
|
||||||
@ -284,8 +303,6 @@ function gameOver() {
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
scene = new THREE.Scene();
|
scene = new THREE.Scene();
|
||||||
|
|
||||||
camera.position.set(0, 10, 25);
|
camera.position.set(0, 10, 25);
|
||||||
@ -318,6 +335,7 @@ function onWindowResize() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// main render/update function called once per frame
|
||||||
function animate() {
|
function animate() {
|
||||||
|
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
@ -327,18 +345,19 @@ function animate() {
|
|||||||
|
|
||||||
if (running) {
|
if (running) {
|
||||||
|
|
||||||
if (Math.abs(camera.position.x) > diameterOfTunnel || Math.abs(camera.position.y) > diameterOfTunnel) {
|
if (Math.abs(camera.position.x) > tunnelRadius || Math.abs(camera.position.y) > tunnelRadius) {
|
||||||
// out of bounds
|
// out of bounds
|
||||||
gameOver();
|
gameOver();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < cubes.length; i++) {
|
// collision checks
|
||||||
if (cubes[i].material !== materialCube) {
|
for (let i = 0; i < spheres.length; i++) {
|
||||||
|
if (spheres[i].material !== materialSphere) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let x1 = cubes[i].position.x;
|
let x1 = spheres[i].position.x;
|
||||||
let y1 = cubes[i].position.y;
|
let y1 = spheres[i].position.y;
|
||||||
let z1 = cubes[i].position.z;
|
let z1 = spheres[i].position.z;
|
||||||
let x2 = camera.position.x;
|
let x2 = camera.position.x;
|
||||||
let y2 = camera.position.y;
|
let y2 = camera.position.y;
|
||||||
let z2 = camera.position.z;
|
let z2 = camera.position.z;
|
||||||
@ -348,7 +367,7 @@ function animate() {
|
|||||||
z2 = Math.max(z1, z2 - speed);
|
z2 = Math.max(z1, z2 - speed);
|
||||||
}
|
}
|
||||||
let dist_squared = (x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2;
|
let dist_squared = (x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2;
|
||||||
if (dist_squared <= (cubes[i].geometry.parameters.radius * cubes[i].scale.x) ** 2) {
|
if (dist_squared <= (spheres[i].geometry.parameters.radius * spheres[i].scale.x) ** 2) {
|
||||||
gameOver();
|
gameOver();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -357,6 +376,7 @@ function animate() {
|
|||||||
|
|
||||||
if (running) {
|
if (running) {
|
||||||
|
|
||||||
|
// advance player position, increase speed, handle movement input
|
||||||
camera.position.z -= 0.5 * speed;
|
camera.position.z -= 0.5 * speed;
|
||||||
speed += 0.01;
|
speed += 0.01;
|
||||||
if (!Number.isNaN(leftRightMove)) {
|
if (!Number.isNaN(leftRightMove)) {
|
||||||
@ -366,16 +386,17 @@ function animate() {
|
|||||||
camera.position.y += 0.1 * upDownMove;
|
camera.position.y += 0.1 * upDownMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create new obstacles as needed
|
||||||
while (camera.position.z < lastSpawned - spawnInterval) {
|
while (camera.position.z < lastSpawned - spawnInterval) {
|
||||||
lastSpawned -= spawnInterval;
|
lastSpawned -= spawnInterval;
|
||||||
|
|
||||||
// randomly spawn large spheres
|
// randomly spawn large spheres
|
||||||
let scale = Math.random() < 0.1 ? 5.0 : 1.0;
|
let scale = Math.random() < 0.1 ? 5.0 : 1.0;
|
||||||
|
|
||||||
const meshCube = new THREE.Mesh(geometrySphere, materialCube);
|
const meshCube = new THREE.Mesh(geometrySphere, materialSphere);
|
||||||
meshCube.position.z = camera.position.z - speed * 210;
|
meshCube.position.z = camera.position.z - speed * 210;
|
||||||
meshCube.position.x = (2 * Math.random() - 1.0) * diameterOfTunnel;
|
meshCube.position.x = (2 * Math.random() - 1.0) * tunnelRadius;
|
||||||
meshCube.position.y = (2 * Math.random() - 1.0) * 0.9 * diameterOfTunnel;
|
meshCube.position.y = (2 * Math.random() - 1.0) * 0.9 * tunnelRadius;
|
||||||
meshCube.scale.multiplyScalar(scale);
|
meshCube.scale.multiplyScalar(scale);
|
||||||
|
|
||||||
const outlineMesh = new THREE.Mesh(geometrySphere, customMaterial);
|
const outlineMesh = new THREE.Mesh(geometrySphere, customMaterial);
|
||||||
@ -385,19 +406,20 @@ function animate() {
|
|||||||
outlineMesh.position.y = meshCube.position.y;
|
outlineMesh.position.y = meshCube.position.y;
|
||||||
meshCube.add(outlineMesh);
|
meshCube.add(outlineMesh);
|
||||||
scene.add(outlineMesh);
|
scene.add(outlineMesh);
|
||||||
cubes.push(outlineMesh);
|
spheres.push(outlineMesh);
|
||||||
|
|
||||||
scene.add(meshCube);
|
scene.add(meshCube);
|
||||||
cubes.push(meshCube);
|
spheres.push(meshCube);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < cubes.length; i++) {
|
// clean up game elements behind the camera
|
||||||
if (cubes[i].position.z > camera.position.z + cubes[i].geometry.parameters.radius * cubes[i].scale.x + 20) {
|
for (let i = 0; i < spheres.length; i++) {
|
||||||
scene.remove(cubes[i]);
|
if (spheres[i].position.z > camera.position.z + spheres[i].geometry.parameters.radius * spheres[i].scale.x + 20) {
|
||||||
cubes.splice(i, 1);
|
scene.remove(spheres[i]);
|
||||||
|
spheres.splice(i, 1);
|
||||||
i--;
|
i--;
|
||||||
removed++;
|
removed++;
|
||||||
}
|
}
|
||||||
@ -407,44 +429,47 @@ function animate() {
|
|||||||
removed = 0;
|
removed = 0;
|
||||||
document.getElementById("score").innerText = score;
|
document.getElementById("score").innerText = score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create new wall segments on demand
|
||||||
if (borders.length == 0 || borders[borders.length - 1].position.z - camera.position.z > -1200) {
|
if (borders.length == 0 || borders[borders.length - 1].position.z - camera.position.z > -1200) {
|
||||||
const newZ = borders.length == 0 ? -200 : borders[borders.length - 1].position.z - diameterOfTunnel * 2;
|
const newZ = borders.length == 0 ? -200 : borders[borders.length - 1].position.z - tunnelRadius * 2;
|
||||||
|
|
||||||
let border = new THREE.Mesh(borderGeometry, material);
|
let border = new THREE.Mesh(borderGeometry, material);
|
||||||
//border.position.x = -70;
|
//border.position.x = -70;
|
||||||
border.position.y = -diameterOfTunnel;
|
border.position.y = -tunnelRadius;
|
||||||
border.rotation.x = -Math.PI / 2;
|
border.rotation.x = -Math.PI / 2;
|
||||||
border.position.z = newZ;
|
border.position.z = newZ;
|
||||||
border.scale.multiplyScalar(diameterOfTunnel * 2);
|
border.scale.multiplyScalar(tunnelRadius * 2);
|
||||||
scene.add(border);
|
scene.add(border);
|
||||||
borders.push(border);
|
borders.push(border);
|
||||||
|
|
||||||
border = new THREE.Mesh(borderGeometry, material);
|
border = new THREE.Mesh(borderGeometry, material);
|
||||||
border.position.y = diameterOfTunnel;
|
border.position.y = tunnelRadius;
|
||||||
border.rotation.x = Math.PI / 2;
|
border.rotation.x = Math.PI / 2;
|
||||||
border.position.z = newZ;
|
border.position.z = newZ;
|
||||||
border.scale.multiplyScalar(diameterOfTunnel * 2);
|
border.scale.multiplyScalar(tunnelRadius * 2);
|
||||||
scene.add(border);
|
scene.add(border);
|
||||||
borders.push(border);
|
borders.push(border);
|
||||||
|
|
||||||
border = new THREE.Mesh(borderGeometry, material);
|
border = new THREE.Mesh(borderGeometry, material);
|
||||||
border.position.x = -diameterOfTunnel;
|
border.position.x = -tunnelRadius;
|
||||||
border.rotation.y = Math.PI / 2;
|
border.rotation.y = Math.PI / 2;
|
||||||
border.position.z = newZ;
|
border.position.z = newZ;
|
||||||
border.scale.multiplyScalar(diameterOfTunnel * 2);
|
border.scale.multiplyScalar(tunnelRadius * 2);
|
||||||
scene.add(border);
|
scene.add(border);
|
||||||
borders.push(border);
|
borders.push(border);
|
||||||
|
|
||||||
border = new THREE.Mesh(borderGeometry, material);
|
border = new THREE.Mesh(borderGeometry, material);
|
||||||
border.position.x = diameterOfTunnel;
|
border.position.x = tunnelRadius;
|
||||||
border.rotation.y = -Math.PI / 2;
|
border.rotation.y = -Math.PI / 2;
|
||||||
border.position.z = newZ;
|
border.position.z = newZ;
|
||||||
border.scale.multiplyScalar(diameterOfTunnel * 2);
|
border.scale.multiplyScalar(tunnelRadius * 2);
|
||||||
scene.add(border);
|
scene.add(border);
|
||||||
borders.push(border);
|
borders.push(border);
|
||||||
}
|
}
|
||||||
|
// clean up wall segments behind the camera
|
||||||
for (let i = 0; i < borders.length; i++) {
|
for (let i = 0; i < borders.length; i++) {
|
||||||
if (borders[i].position.z > camera.position.z + diameterOfTunnel + 50) {
|
if (borders[i].position.z > camera.position.z + tunnelRadius + 50) {
|
||||||
scene.remove(borders[i]);
|
scene.remove(borders[i]);
|
||||||
borders.splice(i, 1);
|
borders.splice(i, 1);
|
||||||
i--;
|
i--;
|
||||||
|
67
main.css
67
main.css
@ -1,7 +1,7 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
font-family: Monospace;
|
font-family: monospace;
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
|
|
||||||
font-size: xxx-large;
|
font-size: xxx-large;
|
||||||
@ -12,23 +12,17 @@ body {
|
|||||||
-0.07em 0 black,
|
-0.07em 0 black,
|
||||||
0 -0.07em black;
|
0 -0.07em black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#start {
|
#start {
|
||||||
width: 50vw;
|
width: 50vw;
|
||||||
height: 20vh;
|
height: 20vh;
|
||||||
|
font-size: 15vh;
|
||||||
|
|
||||||
|
/* center button */
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50vh;
|
top: 50vh;
|
||||||
left: 50vw;
|
left: 50vw;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
font-size: 15vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #ff0;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@ -43,56 +37,7 @@ button {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
-moz-user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 1; /* TODO Solve this in HTML */
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
a, button, input, select {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dg.ac {
|
|
||||||
-moz-user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
z-index: 2 !important; /* TODO Solve this in HTML */
|
|
||||||
}
|
|
||||||
|
|
||||||
#overlay {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 16px;
|
|
||||||
z-index: 2;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
background: rgba(0,0,0,0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
#overlay button {
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
border: 1px solid rgb(255, 255, 255);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 12px 18px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#notSupported {
|
|
||||||
width: 50%;
|
|
||||||
margin: auto;
|
|
||||||
background-color: #f00;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user