Aluode commited on
Commit
e438cc9
·
verified ·
1 Parent(s): c955bba

Upload 2 files

Browse files
Files changed (2) hide show
  1. README.md +14 -6
  2. index.html +149 -17
README.md CHANGED
@@ -1,12 +1,20 @@
1
  ---
2
- title: GhostYou
3
- emoji: 🏆
4
- colorFrom: gray
5
- colorTo: green
6
  sdk: static
 
7
  pinned: false
8
  license: mit
9
- short_description: 'Are you afraid of ghosts. '
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Ghost You
3
+ emoji: 👻
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: static
7
+ app_file: index.html
8
  pinned: false
9
  license: mit
10
+ short_description: Move and turn see-through; your motion plays a theremin.
11
  ---
12
 
13
+ # Ghost You
14
+
15
+ A Halloween webcam toy. Sit still and you are solid; move and you fade until the room shows
16
+ through you. Your motion plays a sine-wave theremin: higher on screen is a higher note, more
17
+ motion is louder.
18
+
19
+ Press **Summon the ghost** and allow the camera. For the best effect, press **Learn empty room**
20
+ and step out of view for 3 seconds. Everything runs in your browser; no video leaves your machine.
index.html CHANGED
@@ -1,19 +1,151 @@
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>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Ghost You</title>
7
+ <style>
8
+ :root { --bg:#07060b; --fg:#d9e4ff; --dim:#7d86a8; --glow:#9fd8ff; --accent:#ff8a3d; }
9
+ * { box-sizing:border-box; }
10
+ html,body { margin:0; height:100%; background:var(--bg); color:var(--fg);
11
+ font:15px/1.45 "Georgia", serif; overflow:hidden; }
12
+ #stage { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; }
13
+ canvas { width:min(100vw, 133.33vh); height:min(75vw, 100vh); image-rendering:auto;
14
+ filter:contrast(1.08) saturate(.85); }
15
+ #veil { position:fixed; inset:0; pointer-events:none;
16
+ background:radial-gradient(ellipse at center, transparent 45%, rgba(4,2,10,.85) 100%); }
17
+ #intro { position:fixed; inset:0; display:flex; flex-direction:column; align-items:center;
18
+ justify-content:center; gap:18px; text-align:center; padding:16px;
19
+ background:radial-gradient(ellipse at center, #15112a 0%, var(--bg) 70%); z-index:5; }
20
+ h1 { font-size:clamp(40px, 9vw, 92px); margin:0; letter-spacing:.04em; font-weight:normal;
21
+ color:var(--fg); text-shadow:0 0 18px var(--glow), 0 0 42px #4a6cff; animation:float 4s ease-in-out infinite; }
22
+ @keyframes float { 50% { transform:translateY(-10px); opacity:.85; } }
23
+ p { max-width:34em; color:var(--dim); margin:0; }
24
+ button { font:inherit; color:var(--bg); background:var(--accent); border:0; border-radius:999px;
25
+ padding:12px 26px; cursor:pointer; box-shadow:0 0 24px rgba(255,138,61,.45); }
26
+ button.ghost { background:transparent; color:var(--fg); border:1px solid #3a3f5c; box-shadow:none; padding:8px 16px; }
27
+ #bar { position:fixed; left:0; right:0; bottom:0; display:flex; flex-wrap:wrap; gap:8px; align-items:center;
28
+ justify-content:center; padding:10px 16px; z-index:4; font-size:13px; color:var(--dim);
29
+ background:linear-gradient(transparent, rgba(7,6,11,.9)); }
30
+ #bar label { display:flex; gap:6px; align-items:center; }
31
+ input[type=range] { width:110px; accent-color:var(--glow); }
32
+ #err { color:var(--accent); }
33
+ </style>
34
+ </head>
35
+ <body>
36
+ <div id="stage"><canvas id="c" width="320" height="240"></canvas></div>
37
+ <div id="veil"></div>
38
+
39
+ <div id="intro">
40
+ <h1>Ghost You</h1>
41
+ <p>Sit still and you are solid. Move, and you fade until the room shows through you.
42
+ Your motion plays a theremin: higher on the screen is a higher note, more motion is louder.</p>
43
+ <p>For the best haunting, step out of view for a few seconds so the room can learn what is behind you.</p>
44
+ <button id="go">Summon the ghost</button>
45
+ <span id="err"></span>
46
+ </div>
47
+
48
+ <div id="bar" hidden>
49
+ <button class="ghost" id="learn">Learn empty room (3 s)</button>
50
+ <label>ghostliness <input id="ghostly" type="range" min="0" max="100" value="85"></label>
51
+ <label>linger <input id="linger" type="range" min="0" max="98" value="88"></label>
52
+ <label>volume <input id="vol" type="range" min="0" max="100" value="55"></label>
53
+ <button class="ghost" id="mute">mute</button>
54
+ </div>
55
+
56
+ <video id="v" playsinline muted hidden></video>
57
+ <script>
58
+ const W = 320, H = 240, N = W * H;
59
+ const cv = document.getElementById('c'), ctx = cv.getContext('2d', { willReadFrequently:true });
60
+ const video = document.getElementById('v');
61
+ const bg = new Float32Array(N * 3); // the room's memory: slow average of what is behind you
62
+ const trail = new Float32Array(N); // lingering ghost mask
63
+ let haveBg = false, learnUntil = 0, audio = null, muted = false;
64
+ const $ = id => document.getElementById(id);
65
+
66
+ $('go').onclick = async () => {
67
+ try {
68
+ const stream = await navigator.mediaDevices.getUserMedia({ video:{ width:640, height:480, facingMode:'user' }, audio:false });
69
+ video.srcObject = stream; await video.play();
70
+ audio = makeTheremin();
71
+ $('intro').remove(); $('bar').hidden = false;
72
+ requestAnimationFrame(loop);
73
+ } catch (e) { $('err').textContent = 'The camera would not open: ' + (e.message || e); }
74
+ };
75
+ $('learn').onclick = () => { learnUntil = performance.now() + 3000; };
76
+ $('mute').onclick = () => { muted = !muted; $('mute').textContent = muted ? 'unmute' : 'mute'; };
77
+
78
+ function makeTheremin() {
79
+ const ac = new (window.AudioContext || window.webkitAudioContext)();
80
+ const out = ac.createGain(); out.gain.value = 0;
81
+ const osc = ac.createOscillator(); osc.type = 'sine'; osc.frequency.value = 440;
82
+ const osc2 = ac.createOscillator(); osc2.type = 'sine'; osc2.frequency.value = 441.5;
83
+ const g2 = ac.createGain(); g2.gain.value = .35;
84
+ const lfo = ac.createOscillator(); lfo.frequency.value = 5.6;
85
+ const lfoAmt = ac.createGain(); lfoAmt.gain.value = 6;
86
+ lfo.connect(lfoAmt); lfoAmt.connect(osc.frequency); lfoAmt.connect(osc2.frequency);
87
+ // a little haunted-hallway echo
88
+ const delay = ac.createDelay(1); delay.delayTime.value = .33;
89
+ const fb = ac.createGain(); fb.gain.value = .38;
90
+ const lp = ac.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = 1800;
91
+ osc.connect(out); osc2.connect(g2); g2.connect(out);
92
+ out.connect(ac.destination); out.connect(delay); delay.connect(lp); lp.connect(fb); fb.connect(delay); lp.connect(ac.destination);
93
+ osc.start(); osc2.start(); lfo.start();
94
+ return { ac, out, osc, osc2, lfoAmt };
95
+ }
96
+
97
+ let energy = 0, cy = .5, cx = .5;
98
+ function loop(t) {
99
+ // mirror the camera so it behaves like a mirror
100
+ ctx.save(); ctx.translate(W, 0); ctx.scale(-1, 1); ctx.drawImage(video, 0, 0, W, H); ctx.restore();
101
+ const img = ctx.getImageData(0, 0, W, H), d = img.data;
102
+ const learning = t < learnUntil;
103
+ if (!haveBg) { for (let i = 0; i < N; i++) { bg[3*i] = d[4*i]; bg[3*i+1] = d[4*i+1]; bg[3*i+2] = d[4*i+2]; } haveBg = true; }
104
+
105
+ const ghostly = $('ghostly').value / 100, linger = $('linger').value / 100;
106
+ let mSum = 0, ySum = 0, xSum = 0;
107
+ for (let i = 0; i < N; i++) {
108
+ const r = d[4*i], g = d[4*i+1], b = d[4*i+2];
109
+ const br = bg[3*i], bgG = bg[3*i+1], bb = bg[3*i+2];
110
+ const diff = (Math.abs(r - br) + Math.abs(g - bgG) + Math.abs(b - bb)) / 3;
111
+ // motion against the remembered room, softened to 0..1
112
+ let m = Math.min(1, Math.max(0, (diff - 18) / 40));
113
+ trail[i] = Math.max(m, trail[i] * linger);
114
+ const a = trail[i] * ghostly;
115
+
116
+ // the room learns fast while you're away or during "learn", barely at all where something moves
117
+ const rate = learning ? .25 : (m < .05 ? .03 : .0015);
118
+ bg[3*i] += rate * (r - br); bg[3*i+1] += rate * (g - bgG); bg[3*i+2] += rate * (b - bb);
119
+
120
+ // see-through: show the room where you moved, with a cold spectral edge glow
121
+ const lum = (r + g + b) / 3;
122
+ const edge = a * (1 - a) * 4; // strongest at the half-faded rim
123
+ const flick = 1 + (Math.random() - .5) * .08 * a;
124
+ d[4*i] = ((1 - a) * r + a * (br * .82 + lum * .18 * .7)) * flick + edge * 40;
125
+ d[4*i+1] = ((1 - a) * g + a * (bgG * .85 + lum * .18 * .85)) * flick + edge * 70;
126
+ d[4*i+2] = ((1 - a) * b + a * (bb * .9 + lum * .18)) * flick + edge * 110;
127
+
128
+ if (m > .2) { mSum += m; ySum += m * ((i / W) | 0); xSum += m * (i % W); }
129
+ }
130
+ ctx.putImageData(img, 0, 0);
131
+
132
+ // theremin: height of your motion is pitch, amount of motion is volume, left/right is wobble
133
+ const frac = mSum / N;
134
+ energy += (Math.min(1, frac * 12) - energy) * .12;
135
+ if (mSum > 30) { cy += (ySum / mSum / H - cy) * .15; cx += (xSum / mSum / W - cx) * .15; }
136
+ if (audio) {
137
+ const now = audio.ac.currentTime;
138
+ const f = 140 * Math.pow(2, (1 - cy) * 3.2); // about 140 Hz to 1300 Hz
139
+ audio.osc.frequency.setTargetAtTime(f, now, .06);
140
+ audio.osc2.frequency.setTargetAtTime(f * 1.0035, now, .06);
141
+ audio.lfoAmt.gain.setTargetAtTime(2 + cx * 14, now, .2);
142
+ const v = muted ? 0 : energy * $('vol').value / 100 * .5;
143
+ audio.out.gain.setTargetAtTime(v, now, .08);
144
+ }
145
+ if (learning) { ctx.fillStyle = 'rgba(217,228,255,.85)'; ctx.font = '14px Georgia';
146
+ ctx.fillText('learning the empty room… ' + Math.ceil((learnUntil - t) / 1000), 10, 20); }
147
+ requestAnimationFrame(loop);
148
+ }
149
+ </script>
150
+ </body>
151
  </html>