Vivien commited on
Commit
0b12ad4
1 Parent(s): e59481a

Add project

Browse files
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Trompeloeil
3
  emoji: 🚀
4
  colorFrom: gray
5
  colorTo: green
 
1
  ---
2
+ title: Trompe-l'œil
3
  emoji: 🚀
4
  colorFrom: gray
5
  colorTo: green
_config.yml ADDED
@@ -0,0 +1 @@
 
 
1
+ baseurl: /trompeloeil
index.html CHANGED
@@ -1,24 +1,53 @@
1
  <!DOCTYPE html>
2
  <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>
13
- You can modify this app directly by editing <i>index.html</i> in the
14
- Files and versions tab.
15
- </p>
16
- <p>
17
- Also don't forget to check the
18
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank"
19
- >Spaces documentation</a
20
- >.
21
- </p>
22
- </div>
23
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  </html>
 
 
1
  <!DOCTYPE html>
2
  <html>
3
+
4
+ <head>
5
+ <!-- Global site tag (gtag.js) - Google Analytics -->
6
+ <script async src="https://www.googletagmanager.com/gtag/js?id=G-C0Q8V7CMP9"></script>
7
+ <script>
8
+ window.dataLayer = window.dataLayer || [];
9
+
10
+ function gtag() {
11
+ dataLayer.push(arguments);
12
+ }
13
+ gtag('js', new Date());
14
+
15
+ gtag('config', 'G-C0Q8V7CMP9');
16
+ </script>
17
+ <title>3D In Your Browser</title>
18
+
19
+ <meta name="viewport" content="width=device-width, initial-scale=1">
20
+ <meta charset="UTF-8" />
21
+ <link href="./styles/main.css" rel="stylesheet" type="text/css">
22
+ </head>
23
+
24
+ <body>
25
+ <div id="scene-container">
26
+ <!-- Our <canvas> will be inserted here -->
27
+ </div>
28
+ <div id="controls" style="visibility:hidden">
29
+ <div style="z-index: 1; color:white; position: absolute; top: 20px; width: 100%; text-align: center; font-family: sans-serif">A 3D image with just a browser and a webcam<br>Press "C" to only keep the 3D image</div>
30
+ <div id="video-container" style="position:absolute; left:0; bottom:0;">
31
+ <video autoplay="true" style="width:200px; margin:10px;" id="video">
32
+ </video>
33
+ </div>
34
+ <div style="z-index: 1; position:absolute; top:0; right: 0; margin: 20px;">
35
+ <select id="object">
36
+ <option value="0">Spaceship</option>
37
+ <option value="1">Parrot</option>
38
+ <option value="2">Van Gogh</option>
39
+ </select>
40
+ </div>
41
+ </div>
42
+ </body>
43
+
44
+ <script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-core.js"></script>
45
+ <script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-converter.js"></script>
46
+ <script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-backend-webgl.js"></script>
47
+ <script src="https://unpkg.com/@tensorflow-models/[email protected]/dist/face-landmarks-detection.js">
48
+ </script>
49
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/blazeface"></script>
50
+ <script type="module" src="./src/main.js"></script>
51
+
52
  </html>
53
+ <!-- Cf. https://github.com/vivien000/trompeloeil for a write-up and the credits -->
src/World/World.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ loadObjects
3
+ } from './components/objects/objects.js';
4
+ import {
5
+ createCamera
6
+ } from './components/camera.js';
7
+ import {
8
+ createLights
9
+ } from './components/lights.js';
10
+ import {
11
+ createScene
12
+ } from './components/scene.js';
13
+
14
+ import {
15
+ FaceTracker
16
+ } from './components/geometry/geometry.js';
17
+
18
+ import {
19
+ createRenderer
20
+ } from './systems/renderer.js';
21
+ import {
22
+ Resizer
23
+ } from './systems/Resizer.js';
24
+ import {
25
+ Loop
26
+ } from './systems/Loop.js';
27
+
28
+ let camera;
29
+ let renderer;
30
+ let scene;
31
+ let loop;
32
+
33
+ class World {
34
+ constructor(container) {
35
+ camera = createCamera(0, 1);
36
+ renderer = createRenderer();
37
+ scene = createScene();
38
+ container.append(renderer.domElement);
39
+
40
+ const {
41
+ ambientLight,
42
+ mainLight
43
+ } = createLights();
44
+
45
+ scene.add(ambientLight, mainLight);
46
+ loop = new Loop(camera, scene, renderer, new FaceTracker());
47
+ const resizer = new Resizer(container, camera, renderer, loop);
48
+ }
49
+
50
+ async init() {
51
+ const object = await loadObjects();
52
+
53
+ if (typeof (object.tick) == "function") {
54
+ loop.updatables.push(object);
55
+ }
56
+ scene.add(object);
57
+ await loop.init();
58
+ }
59
+
60
+ render() {
61
+ renderer.render(scene, camera);
62
+ }
63
+
64
+ start() {
65
+ loop.start();
66
+ }
67
+
68
+ stop() {
69
+ loop.stop();
70
+ }
71
+ }
72
+
73
+ export {
74
+ World
75
+ };
src/World/components/camera.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PerspectiveCamera } from 'https://unpkg.com/[email protected]/build/three.module.js';
2
+
3
+ function createCamera(cameraPosition, fov, aspectRatio) {
4
+ const camera = new PerspectiveCamera(fov, aspectRatio, 0.1, 100);
5
+ camera.position.set(cameraPosition[0], cameraPosition[1], cameraPosition[2]);
6
+ camera.lookAt(0, 0, 0);
7
+ return camera;
8
+ }
9
+
10
+ export { createCamera };
src/World/components/geometry/geometry.js ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const MAX_WIDTH = 1280;
2
+ const FOCAL_LENGTH = 1430;
3
+ const DFOV = Math.PI / 3;
4
+
5
+ const parameter = (location.search.split('distanceMethod=')[1] || '').split('&')[0]
6
+ const distanceMethod = parameter ? parameter : 0;
7
+ const DEFAULT_DISTANCE = 0.5;
8
+
9
+ const IRIS_SIZE = 0.0117;
10
+ const NUM_KEYPOINTS = 468;
11
+ const NUM_IRIS_KEYPOINTS = 5;
12
+ const SMOOTHING = 0;
13
+
14
+ const HALF_DIAGONAL = 0.2;
15
+
16
+ const parameter2 = (location.search.split('blaze=')[1] || '').split('&')[0]
17
+ const BLAZE = (parameter2.length > 0);
18
+
19
+ let model, HFOV, VFOV;
20
+ let canvas, video;
21
+ let width, height;
22
+
23
+ function distance(a, b) {
24
+ return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2));
25
+ }
26
+
27
+ function fov(dfov, w, h) {
28
+ const hypothenuse = Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2));
29
+ const tan_dfov = Math.tan(dfov / 2);
30
+ return [2 * Math.atan(w * tan_dfov / hypothenuse), 2 * Math.atan(h * tan_dfov / hypothenuse)];
31
+ }
32
+
33
+ async function getLocation(ctx, width, height) {
34
+ let predictions;
35
+ if (BLAZE) {
36
+ predictions = await model.estimateFaces(ctx.getImageData(0, 0, width, height), false);
37
+ } else {
38
+ predictions = await model.estimateFaces({
39
+ input: ctx.getImageData(0, 0, width, height),
40
+ predictIrises: (distanceMethod == 1),
41
+ flipHorizontal: false
42
+ });
43
+ }
44
+ let centerX, centerY, keypoints, foreheadX, foreheadY;
45
+ if (predictions.length > 0) {
46
+ if (BLAZE) {
47
+ centerX = (predictions[0].landmarks[0][0] + predictions[0].landmarks[1][0])/2;
48
+ centerY = (predictions[0].landmarks[0][1] + predictions[0].landmarks[1][1])/2;
49
+ } else {
50
+ keypoints = predictions[0].scaledMesh;
51
+ centerX = keypoints[168][0];
52
+ centerY = keypoints[168][1];
53
+
54
+ foreheadX = keypoints[10][0];
55
+ foreheadY = keypoints[10][1];
56
+ }
57
+ let d;
58
+ if (distanceMethod == 0) {
59
+ d = DEFAULT_DISTANCE;
60
+ } else if (distanceMethod == 2) {
61
+ d = 0.06 * FOCAL_LENGTH * canvas.width / MAX_WIDTH / Math.sqrt(Math.pow(centerX - foreheadX, 2) + Math.pow(centerY - foreheadY, 2));
62
+ } else if (keypoints.length > NUM_KEYPOINTS) {
63
+ const leftDiameterY = distance(
64
+ keypoints[NUM_KEYPOINTS + 4],
65
+ keypoints[NUM_KEYPOINTS + 2]);
66
+ const leftDiameterX = distance(
67
+ keypoints[NUM_KEYPOINTS + 3],
68
+ keypoints[NUM_KEYPOINTS + 1]);
69
+ let diameter = Math.max(leftDiameterX, leftDiameterY);
70
+
71
+ if (keypoints.length > NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS) {
72
+ const rightDiameterY = distance(
73
+ keypoints[NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS + 2],
74
+ keypoints[NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS + 4]);
75
+ const rightDiameterX = distance(
76
+ keypoints[NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS + 3],
77
+ keypoints[NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS + 1]);
78
+ diameter = Math.max(diameter, rightDiameterX, rightDiameterY);
79
+ }
80
+ d = IRIS_SIZE * FOCAL_LENGTH * canvas.width / MAX_WIDTH / diameter;
81
+ }
82
+ return [Math.atan(2 * (centerX - width / 2) / width * Math.tan(HFOV / 2)),
83
+ Math.atan(2 * (centerY - height / 2) / height * Math.tan(VFOV / 2)),
84
+ d
85
+ ]
86
+
87
+ }
88
+ return null;
89
+ }
90
+
91
+ class FaceTracker {
92
+ constructor(container) {
93
+ this.angle1 = 0;
94
+ this.angle2 = 0;
95
+ this.distance = 0;
96
+ this.step = 1;
97
+ }
98
+
99
+ async init() {
100
+ await tf.setBackend('webgl');
101
+ if (BLAZE) {
102
+ model = await blazeface.load();
103
+ } else {
104
+ model = await faceLandmarksDetection.load(
105
+ faceLandmarksDetection.SupportedPackages.mediapipeFacemesh, {
106
+ shouldLoadIrisModel: (distanceMethod > 0),
107
+ maxFaces: 1
108
+ });
109
+ }
110
+ if (navigator.mediaDevices.getUserMedia) {
111
+ video = document.querySelector('#video');
112
+ await navigator.mediaDevices.getUserMedia({
113
+ video : { facingMode : 'user' }
114
+ })
115
+ .then(function (stream) {
116
+ video.srcObject = stream;
117
+ width = stream.getTracks()[0].getSettings().width;
118
+ height = stream.getTracks()[0].getSettings().height;
119
+ [HFOV, VFOV] = fov(DFOV, width, height);
120
+ })
121
+ // permission denied:
122
+ .catch(function (error) {
123
+ document.body.textContent = 'Could not access the camera. Error: ' + error.name;
124
+ });
125
+ }
126
+ canvas = document.createElement('canvas');
127
+ canvas.width = width;
128
+ canvas.height = height;
129
+ video.addEventListener('click', this.locateFace);
130
+ }
131
+
132
+ async getCameraParameters(outputWidth, outputHeight) {
133
+ let context = canvas.getContext('2d');
134
+ context.drawImage(video, 0, 0, width, height);
135
+ const result = await getLocation(context, width, height);
136
+ if (result !== null) {
137
+ const [angle1, angle2, distance] = result;
138
+ this.angle1 = angle1;
139
+ this.angle2 = angle2;
140
+ this.distance = (1 - SMOOTHING) * distance + SMOOTHING * this.distance;
141
+ if (this.step <= 10) {
142
+ this.distance = this.distance / (1 - Math.pow(SMOOTHING, this.step))
143
+ }
144
+ this.step = this.step + 1;
145
+ } else {
146
+ return null;
147
+ }
148
+ let d = this.distance;
149
+ const tan1 = -Math.tan(this.angle1);
150
+ const tan2 = -Math.tan(this.angle2);
151
+ const z = Math.sqrt(d * d / (1 + tan1 * tan1 + tan2 * tan2))
152
+ const cameraPosition = [z * tan1, z * tan2, z];
153
+ const fov = 180 / Math.PI * 2 * Math.atan(HALF_DIAGONAL / d);
154
+ return [cameraPosition, fov];
155
+ }
156
+ }
157
+
158
+ export {
159
+ FaceTracker
160
+ };
src/World/components/lights.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DirectionalLight, HemisphereLight } from 'https://unpkg.com/[email protected]/build/three.module.js';
2
+
3
+ function createLights() {
4
+ const ambientLight = new HemisphereLight(
5
+ 'white',
6
+ 'darkslategrey',
7
+ 5,
8
+ );
9
+
10
+ const mainLight = new DirectionalLight('white', 4);
11
+ mainLight.position.set(10, 10, 10);
12
+
13
+ return { ambientLight, mainLight };
14
+ }
15
+
16
+ export { createLights };
src/World/components/objects/objects.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { GLTFLoader } from 'https://unpkg.com/[email protected]/examples/jsm/loaders/GLTFLoader.js';
2
+
3
+ import { setupModel } from './setupModel.js';
4
+
5
+ async function loadObjects() {
6
+ const objectID = parseInt(document.querySelector('#object').value);
7
+ const loader = new GLTFLoader();
8
+ let object;
9
+
10
+ if (objectID == 0) {
11
+ const data = await loader.loadAsync('https://vivien000.github.io/trompeloeil/models/tie.glb');
12
+ object = setupModel(data);
13
+ object.scale.set(0.00002, 0.00002, 0.00002);
14
+ object.position.set(0, 0, -0.05);
15
+ } else if (objectID == 1) {
16
+ const data = await loader.loadAsync('https://vivien000.github.io/trompeloeil/models/parrot.glb');
17
+ object = setupModel(data);
18
+ object.scale.set(0.15, 0.15, 0.15);
19
+ object.position.set(0, 0, -0.02);
20
+ } else {
21
+ const data = await loader.loadAsync('https://vivien000.github.io/trompeloeil/models/vangogh.glb');
22
+ object = setupModel(data);
23
+ object.scale.set(0.2, 0.2, 0.2);
24
+ object.position.set(0, -0.3, -0.5);
25
+ }
26
+
27
+ return object;
28
+ }
29
+
30
+ export { loadObjects };
src/World/components/objects/setupModel.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AnimationMixer } from 'https://unpkg.com/[email protected]/build/three.module.js';
2
+
3
+ function setupModel(data) {
4
+ const model = data.scene.children[0];
5
+ if (data.animations.length >= 1) {
6
+ const clip = data.animations[0];
7
+ const mixer = new AnimationMixer(model);
8
+ const action = mixer.clipAction(clip);
9
+ action.play();
10
+ model.tick = (delta) => mixer.update(delta);
11
+ }
12
+
13
+ return model;
14
+ }
15
+
16
+ export { setupModel };
src/World/components/scene.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Color, Scene } from 'https://unpkg.com/[email protected]/build/three.module.js';
2
+
3
+ function createScene() {
4
+ const scene = new Scene();
5
+
6
+ scene.background = new Color('#282828');
7
+
8
+ return scene;
9
+ }
10
+
11
+ export { createScene };
src/World/systems/Loop.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Clock
3
+ } from 'https://unpkg.com/[email protected]/build/three.module.js';
4
+ import {
5
+ createCamera
6
+ } from '../components/camera.js';
7
+
8
+ const clock = new Clock();
9
+
10
+ class Loop {
11
+ constructor(camera, scene, renderer, faceTracker) {
12
+ this.camera = camera;
13
+ this.cameraPosition = [0, 0, 0.5];
14
+ this.fov = 43.6;
15
+ this.aspectRatio = 1;
16
+ this.scene = scene;
17
+ this.lastUpdate = 0;
18
+ this.renderer = renderer;
19
+ this.faceTracker = faceTracker;
20
+ this.canvas = document.createElement('canvas')
21
+ this.updatables = [];
22
+ }
23
+
24
+ async init() {
25
+ await this.faceTracker.init();
26
+ }
27
+
28
+ async updateCameraParameters() {
29
+ const timestamp = Date.now();
30
+ if (timestamp - this.lastUpdate > 30) {
31
+ const result = await this.faceTracker.getCameraParameters();
32
+ if (result !== null) {
33
+ const [cameraPosition, fov] = result;
34
+ this.cameraPosition = cameraPosition;
35
+ this.fov = fov;
36
+ this.lastUpdate = Date.now();
37
+ }
38
+ }
39
+ }
40
+
41
+ setAspectRatio(ar) {
42
+ this.aspectRatio = ar;
43
+ }
44
+
45
+ start() {
46
+ this.renderer.setAnimationLoop(() => {
47
+ this.updateCameraParameters().then(() => {
48
+ this.camera = createCamera(this.cameraPosition, this.fov, this.aspectRatio);
49
+ this.tick();
50
+ this.renderer.render(this.scene, this.camera);
51
+ });
52
+ });
53
+ }
54
+
55
+ stop() {
56
+ this.renderer.setAnimationLoop(null);
57
+ }
58
+
59
+ tick() {
60
+ const delta = clock.getDelta();
61
+ for (const object of this.updatables) {
62
+ object.tick(delta);
63
+ }
64
+ }
65
+ }
66
+
67
+ export {
68
+ Loop
69
+ };
src/World/systems/Resizer.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const setSize = (container, camera, renderer, loop) => {
2
+ camera.aspect = container.clientWidth / container.clientHeight;
3
+ camera.updateProjectionMatrix();
4
+ loop.setAspectRatio(container.clientWidth / container.clientHeight);
5
+ renderer.setSize(container.clientWidth, container.clientHeight);
6
+ renderer.setPixelRatio(window.devicePixelRatio);
7
+ };
8
+
9
+ class Resizer {
10
+ constructor(container, camera, renderer, loop) {
11
+ setSize(container, camera, renderer, loop);
12
+ window.addEventListener('resize', () => {
13
+ setSize(container, camera, renderer, loop);
14
+ this.onResize();
15
+ });
16
+ }
17
+
18
+ onResize() {}
19
+ }
20
+
21
+ export { Resizer };
src/World/systems/renderer.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { WebGLRenderer } from 'https://unpkg.com/[email protected]/build/three.module.js';
2
+
3
+ function createRenderer() {
4
+ const renderer = new WebGLRenderer({ antialias: true });
5
+
6
+ renderer.physicallyCorrectLights = true;
7
+
8
+ return renderer;
9
+ }
10
+
11
+ export { createRenderer };
src/main.js ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ World
3
+ } from './World/World.js';
4
+
5
+ const parameter = (location.search.split('object=')[1] || '').split('&')[0]
6
+ const objectID = parameter ? parameter : 0;
7
+ const parameter2 = (location.search.split('controls=')[1] || '').split('&')[0]
8
+ const displayControls = parameter2 ? parameter2 : 1;
9
+
10
+ async function main() {
11
+
12
+ const selector = document.querySelector('#object');
13
+ selector.value = objectID;
14
+
15
+ if (displayControls != 0) {
16
+ document.querySelector('#controls').style.visibility = "visible";
17
+ }
18
+
19
+ document.onkeypress = function (e) {
20
+ var evt = window.event || e;
21
+ switch (evt.keyCode) {
22
+ case 99:
23
+ if (document.querySelector('#controls').style.visibility == "visible") {
24
+ document.querySelector('#controls').style.visibility = "hidden";
25
+ } else {
26
+ document.querySelector('#controls').style.visibility = "visible";
27
+ }
28
+ break;
29
+ }
30
+ }
31
+ // Get a reference to the container element
32
+ const container = document.querySelector('#scene-container');
33
+
34
+ // create a new world
35
+ let world = new World(container);
36
+
37
+ // complete async tasks
38
+ await world.init();
39
+
40
+ //await world.getAngle();
41
+
42
+ // start the animation loop
43
+ world.start();
44
+
45
+ selector.addEventListener('change', async (event) => {
46
+ world.stop();
47
+ while (container.firstChild) {
48
+ container.removeChild(container.firstChild);
49
+ }
50
+ world = new World(container);
51
+ await world.init();
52
+ world.start();
53
+ console.log("new object");
54
+ });
55
+ }
56
+
57
+ main().catch((err) => {
58
+ console.error(err);
59
+ });
style.css DELETED
@@ -1,28 +0,0 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
styles/main.css ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ /* remove margins and scroll bars */
3
+ margin: 0;
4
+ overflow: hidden;
5
+ }
6
+
7
+ #scene-container {
8
+ /* tell our scene container to take up the full page */
9
+ position: absolute;
10
+ width: 100%;
11
+ height: 100%;
12
+
13
+ /*
14
+ Set the container's background color to the same as the scene's
15
+ background to prevent flashing on load
16
+ */
17
+ background-color: #282828;
18
+ }