ID221183 commited on
Commit
b5c0f10
·
verified ·
1 Parent(s): 47bc3bf

Olá! Claro, aqui está uma descrição da aplicação que criámos.

Browse files

Esta é uma **simulação de uma nebulosa de partículas 3D interativa**, construída com a biblioteca `three.js`. O objetivo é criar uma experiência visualmente imersiva e personalizável de exploração espacial.

As suas principais características são:

* **Visualização 3D:** A aplicação renderiza uma nebulosa composta por dezenas de milhares de partículas. Estas partículas estão distribuídas em várias camadas e têm cores diferentes, o que cria uma forte sensação de profundidade e volume. Um efeito de "brilho" (bloom) é aplicado para dar um aspeto mais etéreo e luminoso à cena.

* **Interatividade Múltipla:** A nebulosa reage a várias ações do utilizador em tempo real:
* **Navegação da Câmara:** Pode usar o rato para rodar, aproximar/afastar o zoom e mover a câmara, explorando a nebulosa de qualquer ângulo. As partículas reagem à proximidade da câmara, sendo suavemente afastadas.
* **Reatividade ao Áudio:** Ao clicar em "Ativar Áudio", a aplicação utiliza o microfone para analisar a sua voz ou o som ambiente. As partículas pulsam e mudam de cor em sintonia com a intensidade e as frequências graves do som.
* **Interação por Clique:** Clicar em qualquer ponto do espaço cria uma onda de choque visual que se propaga pela nebulosa, perturbando as partículas.

* **Painel de Configurações Avançado:** Ao clicar no ícone de engrenagem, abre-se um painel de controlo com várias opções, organizadas por abas:
* **Visual:** Permite ajustar a força da interação, o tamanho geral das partículas e a intensidade do efeito de brilho.
* **Áudio:** Contém um slider para ajustar a sensibilidade da nebulosa ao som.
* **Câmara:** Oferece um botão para repor a câmara na sua posição inicial.

Em suma, é uma peça de arte digital interativa que combina gráficos 3D, som e interação do utilizador para criar uma experiência cósmica relaxante e visualmente cativante.

Files changed (2) hide show
  1. README.md +9 -5
  2. index.html +806 -18
README.md CHANGED
@@ -1,10 +1,14 @@
1
  ---
2
- title: Nebulosa C Smica Interativa
3
- emoji: 🏃
4
- colorFrom: purple
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
  ---
2
+ title: Nebulosa Cósmica Interativa
3
+ colorFrom: yellow
4
+ colorTo: yellow
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
14
+
index.html CHANGED
@@ -1,19 +1,807 @@
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="pt">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Nebulosa Cósmica Interativa ✨</title>
7
+ <style>
8
+ :root {
9
+ --glow-sens: 30;
10
+ --card-bg: linear-gradient(8deg, #140426 75%, color-mix(in hsl, #140426, white 2.5%) 75.5%);
11
+ --blend: soft-light;
12
+ --glow-blend: plus-lighter;
13
+ --glow-color: 268deg 100% 76%;
14
+ --glow-boost: 0%;
15
+ --fg: white;
16
+ }
17
+
18
+ * {
19
+ margin: 0;
20
+ padding: 0;
21
+ box-sizing: border-box;
22
+ }
23
+ html, body {
24
+ width: 100%;
25
+ height: 100%;
26
+ overflow: hidden;
27
+ background-color: #020108; /* Cor de fundo para evitar piscar em branco */
28
+ font-family: 'Inter', sans-serif;
29
+ color: var(--fg);
30
+ }
31
+ #container {
32
+ position: fixed;
33
+ width: 100%;
34
+ height: 100%;
35
+ background: radial-gradient(circle at 50% 50%,
36
+ #1a0632 0%,
37
+ #140426 25%,
38
+ #0c021a 50%,
39
+ #06020e 75%,
40
+ #020108 100%
41
+ );
42
+ }
43
+ canvas {
44
+ display: block;
45
+ width: 100%;
46
+ height: 100%;
47
+ }
48
+ .glow-overlay {
49
+ position: fixed;
50
+ top: 0;
51
+ left: 0;
52
+ width: 100%;
53
+ height: 100%;
54
+ pointer-events: none;
55
+ background: radial-gradient(circle at 50% 50%,
56
+ rgba(120, 50, 255, 0.05) 0%,
57
+ rgba(80, 40, 200, 0.03) 40%,
58
+ transparent 70%);
59
+ mix-blend-mode: screen;
60
+ }
61
+ #audio-controls {
62
+ position: fixed;
63
+ top: 20px;
64
+ left: 50%;
65
+ transform: translateX(-50%);
66
+ z-index: 10;
67
+ text-align: center;
68
+ }
69
+ #startButton {
70
+ background-color: rgba(255, 255, 255, 0.1);
71
+ border: 1px solid rgba(255, 255, 255, 0.3);
72
+ color: white;
73
+ padding: 12px 24px;
74
+ font-size: 16px;
75
+ border-radius: 8px;
76
+ cursor: pointer;
77
+ transition: background-color 0.3s, transform 0.2s;
78
+ backdrop-filter: blur(5px);
79
+ font-weight: 500;
80
+ letter-spacing: 0.5px;
81
+ }
82
+ #startButton:hover {
83
+ background-color: rgba(255, 255, 255, 0.2);
84
+ }
85
+ #startButton:active {
86
+ transform: scale(0.95);
87
+ }
88
+ .title-container {
89
+ position: fixed;
90
+ top: 20px;
91
+ left: 20px;
92
+ z-index: 10;
93
+ text-align: left;
94
+ }
95
+ .main-title {
96
+ font-size: 2.5rem;
97
+ font-weight: 700;
98
+ background: linear-gradient(135deg, #c299ff 0%, #a855f7 50%, #7e22ce 100%);
99
+ -webkit-background-clip: text;
100
+ -webkit-text-fill-color: transparent;
101
+ margin-bottom: 5px;
102
+ text-shadow: 0 0 30px rgba(124, 58, 237, 0.5);
103
+ }
104
+ .subtitle {
105
+ font-size: 1rem;
106
+ color: rgba(255, 255, 255, 0.8);
107
+ font-weight: 300;
108
+ letter-spacing: 1px;
109
+ }
110
+ #settings-container {
111
+ position: fixed;
112
+ bottom: 20px;
113
+ left: 20px;
114
+ z-index: 20;
115
+ }
116
+ #settingsButton {
117
+ background-color: rgba(255, 255, 255, 0.1);
118
+ border: 1px solid rgba(255, 255, 255, 0.3);
119
+ color: white;
120
+ width: 48px;
121
+ height: 48px;
122
+ padding: 10px;
123
+ border-radius: 50%;
124
+ cursor: pointer;
125
+ transition: background-color 0.3s, transform 0.2s;
126
+ backdrop-filter: blur(5px);
127
+ display: flex;
128
+ justify-content: center;
129
+ align-items: center;
130
+ }
131
+ #settingsButton:hover {
132
+ background-color: rgba(255, 255, 255, 0.2);
133
+ }
134
+ #settingsButton:active {
135
+ transform: scale(0.95);
136
+ }
137
+ #settingsButton svg {
138
+ width: 24px;
139
+ height: 24px;
140
+ fill: currentColor;
141
+ }
142
+
143
+ /* Estilos do Popup */
144
+ .popup-overlay {
145
+ position: fixed;
146
+ inset: 0;
147
+ background-color: rgba(0, 0, 0, 0.5);
148
+ backdrop-filter: blur(10px);
149
+ z-index: 100;
150
+ display: grid;
151
+ place-items: center;
152
+ opacity: 0;
153
+ pointer-events: none;
154
+ transition: opacity 0.3s ease;
155
+ }
156
+ .popup-overlay.visible {
157
+ opacity: 1;
158
+ pointer-events: auto;
159
+ }
160
+
161
+ .card {
162
+ --pads: 20px;
163
+ --color-sens: calc(var(--glow-sens) + 20);
164
+ --pointer-°: 45deg;
165
+ position: relative;
166
+ width: clamp(320px, 90vw, 500px);
167
+ max-height: 90vh;
168
+ border-radius: 1.768em;
169
+ isolation: isolate;
170
+ transform: translate3d(0, 0, 0.01px);
171
+ display: grid;
172
+ border: 1px solid rgb(255 255 255 / 25%);
173
+ background: var(--card-bg);
174
+ background-repeat: no-repeat;
175
+ box-shadow: rgba(0, 0, 0, 0.1) 0px 32px 64px, rgba(0, 0, 0, 0.1) 0px 16px 32px, rgba(0, 0, 0, 0.1) 0px 8px 16px, rgba(0, 0, 0, 0.1) 0px 4px 8px, rgba(0, 0, 0, 0.1) 0px 2px 4px, rgba(0, 0, 0, 0.1) 0px 1px 2px;
176
+ }
177
+
178
+ .card::before, .card::after, .card > .glow {
179
+ content: "";
180
+ position: absolute;
181
+ inset: 0;
182
+ border-radius: inherit;
183
+ transition: opacity 0.25s ease-out;
184
+ z-index: -1;
185
+ }
186
+
187
+ .card:not(:hover):not(.animating) .glow,
188
+ .card:not(:hover):not(.animating)::before,
189
+ .card:not(:hover):not(.animating)::after {
190
+ opacity: 0;
191
+ transition: opacity 0.75s ease-in-out;
192
+ }
193
+
194
+ .card::before {
195
+ border: 1px solid transparent;
196
+ background: linear-gradient(var(--card-bg) 0 100%) padding-box, linear-gradient(rgb(255 255 255 / 0%) 0% 100%) border-box, radial-gradient(at 80% 55%, hsla(268,100%,76%,1) 0px, transparent 50%) border-box, radial-gradient(at 69% 34%, hsla(349,100%,74%,1) 0px, transparent 50%) border-box, radial-gradient(at 8% 6%, hsla(136,100%,78%,1) 0px, transparent 50%) border-box, radial-gradient(at 41% 38%, hsla(192,100%,64%,1) 0px, transparent 50%) border-box, radial-gradient(at 86% 85%, hsla(186,100%,74%,1) 0px, transparent 50%) border-box, radial-gradient(at 82% 18%, hsla(52,100%,65%,1) 0px, transparent 50%) border-box, radial-gradient(at 51% 4%, hsla(12,100%,72%,1) 0px, transparent 50%) border-box, linear-gradient(#c299ff 0 100%) border-box;
197
+ opacity: calc((var(--pointer-d) - var(--color-sens)) / (100 - var(--color-sens)));
198
+ mask-image: conic-gradient(from var(--pointer-°) at center, black 25%, transparent 40%, transparent 60%, black 75%);
199
+ }
200
+
201
+ .card > .glow {
202
+ --outset: 20px;
203
+ inset: calc(var(--outset) * -1);
204
+ pointer-events: none;
205
+ z-index: 1;
206
+ mask-image: conic-gradient(from var(--pointer-°) at center, black 2.5%, transparent 10%, transparent 90%, black 97.5%);
207
+ opacity: calc((var(--pointer-d) - var(--glow-sens)) / (100 - var(--glow-sens)));
208
+ mix-blend-mode: var(--glow-blend);
209
+ }
210
+
211
+ .card > .glow::before {
212
+ content: "";
213
+ position: absolute;
214
+ inset: var(--outset);
215
+ border-radius: inherit;
216
+ box-shadow: inset 0 0 0 1px hsl(var(--glow-color) / 100%), inset 0 0 1px 0 hsl(var(--glow-color) / calc(var(--glow-boost) + 60%)), inset 0 0 3px 0 hsl(var(--glow-color) / calc(var(--glow-boost) + 50%)), inset 0 0 6px 0 hsl(var(--glow-color) / calc(var(--glow-boost) + 40%)), inset 0 0 15px 0 hsl(var(--glow-color) / calc(var(--glow-boost) + 30%)), inset 0 0 25px 2px hsl(var(--glow-color) / calc(var(--glow-boost) + 20%)), inset 0 0 50px 2px hsl(var(--glow-color) / calc(var(--glow-boost) + 10%)), 0 0 1px 0 hsl(var(--glow-color) / calc(var(--glow-boost) + 60%)), 0 0 3px 0 hsl(var(--glow-color) / calc(var(--glow-boost) + 50%)), 0 0 6px 0 hsl(var(--glow-color) / calc(var(--glow-boost) + 40%)), 0 0 15px 0 hsl(var(--glow-color) / calc(var(--glow-boost) + 30%)), 0 0 25px 2px hsl(var(--glow-color) / calc(var(--glow-boost) + 20%)), 0 0 50px 2px hsl(var(--glow-color) / calc(var(--glow-boost) + 10%));
217
+ }
218
+
219
+ .card .inner {
220
+ display: flex;
221
+ flex-direction: column;
222
+ justify-content: flex-start;
223
+ position: relative;
224
+ overflow: hidden;
225
+ z-index: 2;
226
+ padding: 24px;
227
+ }
228
+
229
+ .card header {
230
+ display: flex;
231
+ justify-content: space-between;
232
+ align-items: center;
233
+ margin-bottom: 20px;
234
+ flex-shrink: 0;
235
+ }
236
+
237
+ .card h2 {
238
+ color: inherit;
239
+ font-weight: 500;
240
+ font-size: 1.25em;
241
+ }
242
+
243
+ .card .close-button {
244
+ background: none;
245
+ border: none;
246
+ color: white;
247
+ font-size: 24px;
248
+ cursor: pointer;
249
+ opacity: 0.7;
250
+ transition: opacity 0.2s;
251
+ }
252
+ .card .close-button:hover {
253
+ opacity: 1;
254
+ }
255
+
256
+ .tabs {
257
+ display: flex;
258
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
259
+ margin-bottom: 20px;
260
+ }
261
+
262
+ .tabs button {
263
+ background: none;
264
+ border: none;
265
+ color: rgba(255, 255, 255, 0.6);
266
+ padding: 10px 15px;
267
+ cursor: pointer;
268
+ font-size: 14px;
269
+ border-bottom: 2px solid transparent;
270
+ transition: color 0.2s, border-color 0.2s;
271
+ }
272
+ .tabs button:hover {
273
+ color: white;
274
+ }
275
+ .tabs button.active {
276
+ color: white;
277
+ border-bottom-color: #c299ff;
278
+ }
279
+
280
+ .panels > .panel {
281
+ display: none;
282
+ }
283
+ .panels > .panel.active {
284
+ display: block;
285
+ }
286
+
287
+ .panel {
288
+ padding-block: 10px;
289
+ width: 100%;
290
+ }
291
+
292
+ .panel label {
293
+ display: block;
294
+ margin-bottom: 5px;
295
+ font-size: 14px;
296
+ }
297
+
298
+ .panel div {
299
+ margin-bottom: 15px;
300
+ }
301
+
302
+ .panel input[type="range"] {
303
+ width: 100%;
304
+ cursor: pointer;
305
+ }
306
+
307
+ .panel button {
308
+ background-color: rgba(255, 255, 255, 0.1);
309
+ border: 1px solid rgba(255, 255, 255, 0.3);
310
+ color: white;
311
+ padding: 8px 16px;
312
+ font-size: 14px;
313
+ border-radius: 6px;
314
+ cursor: pointer;
315
+ transition: background-color 0.3s;
316
+ }
317
+ .panel button:hover {
318
+ background-color: rgba(255, 255, 255, 0.2);
319
+ }
320
+
321
+ </style>
322
+ </head>
323
+ <body>
324
+ <!-- O contêiner para a renderização do Three.js -->
325
+ <div id="container"></div>
326
+ <!-- Uma sobreposição para um efeito de brilho sutil -->
327
+ <div class="glow-overlay"></div>
328
+ <!-- Título e Subtítulo -->
329
+ <div class="title-container">
330
+ <h1 class="main-title">Nebulosa Cósmica</h1>
331
+ <p class="subtitle">Uma Experiência Visual Interativa</p>
332
+ </div>
333
+
334
+ <!-- Controles de áudio -->
335
+ <div id="audio-controls">
336
+ <button id="startButton">🎤 Ativar Áudio</button>
337
+ </div>
338
+ <!-- Botão de Configurações -->
339
+ <div id="settings-container">
340
+ <button id="settingsButton" aria-label="Configurações">
341
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px">
342
+ <path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/>
343
+ </svg>
344
+ </button>
345
+ </div>
346
+
347
+ <!-- Popup/Modal de Configurações -->
348
+ <div id="settings-popup" class="popup-overlay">
349
+ <div class="card">
350
+ <span class="glow"></span>
351
+ <div class="inner">
352
+ <header>
353
+ <h2>Configurações</h2>
354
+ <button class="close-button" id="close-popup-button">&times;</button>
355
+ </header>
356
+ <div class="tabs">
357
+ <button data-tab="visuals-panel" class="active">🎨 Visual</button>
358
+ <button data-tab="audio-panel">🎵 Áudio</button>
359
+ <button data-tab="camera-panel">📷 Câmara</button>
360
+ </div>
361
+ <div class="panels">
362
+ <div id="visuals-panel" class="panel active">
363
+ <div>
364
+ <label for="interactionStrength">🌟 Força da Interação</label>
365
+ <input type="range" id="interactionStrength" min="0" max="0.5" step="0.01" value="0.1">
366
+ </div>
367
+ <div>
368
+ <label for="particleSize">⚡ Tamanho das Partículas</input>
369
+ <input type="range" id="particleSize" min="0.1" max="1.0" step="0.05" value="0.3">
370
+ </div>
371
+ <div>
372
+ <label for="bloomStrength">✨ Intensidade do Brilho</label>
373
+ <input type="range" id="bloomStrength" min="0" max="3" step="0.1" value="1.2">
374
+ </div>
375
+ </div>
376
+ <div id="audio-panel" class="panel">
377
+ <div>
378
+ <label for="audioSensitivity">🎚️ Sensibilidade do Áudio</label>
379
+ <input type="range" id="audioSensitivity" min="0.1" max="2.0" step="0.1" value="1.0">
380
+ </div>
381
+ <div>
382
+ <label for="bassBoost">🔊 Reforço de Graves</label>
383
+ <input type="range" id="bassBoost" min="0.5" max="3.0" step="0.1" value="1.0">
384
+ </div>
385
+ </div>
386
+ <div id="camera-panel" class="panel">
387
+ <div>
388
+ <label for="resetCameraButton">🎯 Controlo da Câmara</label>
389
+ <button id="resetCameraButton">🔄 Repor Posição</button>
390
+ </div>
391
+ <div>
392
+ <label for="cameraSensitivity">🎮 Sensibilidade da Câmara</label>
393
+ <input type="range" id="cameraSensitivity" min="0.1" max="2.0" step="0.1" value="1.0">
394
+ </div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+ </div>
399
+ </div>
400
+
401
+
402
+ <!-- Import Map para gerenciar as dependências do Three.js -->
403
+ <script type="importmap">
404
+ {
405
+ "imports": {
406
+ "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
407
+ "three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
408
+ }
409
+ }
410
+ </script>
411
+
412
+ <!-- O script principal da aplicação -->
413
+ <script type="module">
414
+ //================================================================================
415
+ // SECÇÃO 1: IMPORTAÇÕES E VARIÁVEIS GLOBAIS
416
+ //================================================================================
417
+
418
+ // Importa os módulos necessários do Three.js
419
+ import * as THREE from 'three';
420
+ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
421
+ import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
422
+ import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
423
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
424
+
425
+ // --- Variáveis da Cena Principal ---
426
+ let scene, camera, renderer, composer, controls, bloomPass;
427
+ let particleLayers = []; // Array para armazenar as camadas de partículas
428
+ let time = 0; // Contador de tempo para animações
429
+ let ripples = []; // Array para armazenar os efeitos de ondulação do clique
430
+ const interactionRadius = 40; // Raio de interação da câmara com as partículas
431
+ // --- Variáveis de Configuração (Controladas pelo Painel) ---
432
+ let interactionStrength = 0.1;
433
+ let baseParticleSize = 0.3;
434
+ let audioSensitivity = 1.0;
435
+ let bassBoost = 1.0;
436
+ let cameraSensitivity = 1.0;
437
+ // --- Variáveis de Áudio ---
438
+ let audioContext, analyser, dataArray;
439
+ let audioInitialized = false; // Flag para verificar se o áudio foi iniciado
440
+ // --- Configuração das Camadas de Partículas ---
441
+ // Define as propriedades de cada camada da nebulosa
442
+ const layersConfig = [
443
+ { count: 20000, baseSize: 0.3, colorRange: { hue: [0.75, 0.9], sat: [0.7, 1], light: [0.5, 0.7] }, rotationSpeed: 0.001 },
444
+ { count: 25000, baseSize: 0.2, colorRange: { hue: [0.45, 0.6], sat: [0.6, 0.8], light: [0.4, 0.6] }, rotationSpeed: 0.0005 }
445
+ ];
446
+ //================================================================================
447
+ // SECÇÃO 2: FUNÇÕES DE CRIAÇÃO E INICIALIZAÇÃO
448
+ //================================================================================
449
+
450
+ /**
451
+ * Cria um sistema de partículas (uma camada da nebulosa).
452
+ * @param {object} config - Objeto de configuração para a camada de partículas.
453
+ * @returns {THREE.Points} - O objeto THREE.Points que representa o sistema de partículas.
454
+ */
455
+ function createParticleSystem(config) {
456
+ const geometry = new THREE.BufferGeometry();
457
+ const positions = new Float32Array(config.count * 3);
458
+ const colors = new Float32Array(config.count * 3);
459
+ const basePositions = new Float32Array(config.count * 3);
460
+ const baseColors = new Float32Array(config.count * 3);
461
+
462
+ for (let i = 0; i < config.count; i++) {
463
+ const i3 = i * 3;
464
+ // Posiciona as partículas numa distribuição esférica
465
+ const radius = 25 + Math.random() * 30;
466
+ const theta = Math.random() * Math.PI * 2;
467
+ const phi = Math.acos(2 * Math.random() - 1);
468
+ const x = radius * Math.sin(phi) * Math.cos(theta);
469
+ const y = radius * Math.sin(phi) * Math.sin(theta);
470
+ const z = radius * Math.cos(phi);
471
+ positions[i3] = x; positions[i3 + 1] = y; positions[i3 + 2] = z;
472
+ basePositions[i3] = x; basePositions[i3 + 1] = y; basePositions[i3 + 2] = z;
473
+
474
+ // Define a cor com base na distância ao centro
475
+ const dist = Math.sqrt(x * x + y * y + z * z) / 55;
476
+ const hue = THREE.MathUtils.lerp(config.colorRange.hue[0], config.colorRange.hue[1], dist);
477
+ const sat = THREE.MathUtils.lerp(config.colorRange.sat[0], config.colorRange.sat[1], dist);
478
+ const light = THREE.MathUtils.lerp(config.colorRange.light[0], config.colorRange.light[1], dist);
479
+ const color = new THREE.Color().setHSL(hue, sat, light);
480
+ colors[i3] = color.r; colors[i3 + 1] = color.g; colors[i3 + 2] = color.b;
481
+ baseColors[i3] = color.r; baseColors[i3 + 1] = color.g; baseColors[i3 + 2] = color.b;
482
+ }
483
+
484
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
485
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
486
+
487
+ // Define o material das partículas
488
+ const material = new THREE.PointsMaterial({
489
+ size: config.baseSize,
490
+ vertexColors: true,
491
+ transparent: true,
492
+ opacity: 0.8,
493
+ blending: THREE.AdditiveBlending,
494
+ depthWrite: false,
495
+ sizeAttenuation: true
496
+ });
497
+ const points = new THREE.Points(geometry, material);
498
+ // Armazena dados adicionais para a animação
499
+ points.userData = {
500
+ velocities: new Float32Array(config.count * 3).fill(0),
501
+ basePositions,
502
+ baseColors,
503
+ colorVelocities: new Float32Array(config.count * 3).fill(0),
504
+ rotationSpeed: config.rotationSpeed,
505
+ baseSize: config.baseSize
506
+ };
507
+ return points;
508
+ }
509
+
510
+ /**
511
+ * Cria um efeito de ondulação (ripple) na posição especificada, usado no clique.
512
+ * @param {number} x - A coordenada x da ondulação.
513
+ * @param {number} y - A coordenada y da ondulação.
514
+ */
515
+ function createRipple(x, y) {
516
+ ripples.push({ x, y, radius: 0, strength: 2.5, maxRadius: interactionRadius * 4, speed: 4, color: new THREE.Color(0xffffff) });
517
+ }
518
+
519
+ /**
520
+ * Inicializa o Web Audio API para capturar o áudio do microfone.
521
+ */
522
+ function initAudio() {
523
+ const startButton = document.getElementById('startButton');
524
+ navigator.mediaDevices.getUserMedia({ audio: true })
525
+ .then(function(stream) {
526
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
527
+ analyser = audioContext.createAnalyser();
528
+ const source = audioContext.createMediaStreamSource(stream);
529
+ source.connect(analyser);
530
+ analyser.fftSize = 256;
531
+ dataArray = new Uint8Array(analyser.frequencyBinCount);
532
+ audioInitialized = true;
533
+ startButton.textContent = '🎤 Áudio Ativo';
534
+ startButton.style.backgroundColor = 'rgba(40, 200, 120, 0.3)';
535
+ startButton.disabled = true;
536
+ }).catch(err => {
537
+ console.error('Erro ao acessar o microfone', err);
538
+ startButton.textContent = '❌ Erro de Áudio';
539
+ });
540
+ }
541
+
542
+ /**
543
+ * Função principal de inicialização da cena, controlos e eventos.
544
+ */
545
+ function init() {
546
+ // Configuração básica da cena Three.js
547
+ const container = document.getElementById('container');
548
+ scene = new THREE.Scene();
549
+ scene.fog = new THREE.FogExp2(0x020108, 0.008);
550
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
551
+ camera.position.z = 100;
552
+ renderer = new THREE.WebGLRenderer({ antialias: true });
553
+ renderer.setPixelRatio(window.devicePixelRatio);
554
+ renderer.setSize(window.innerWidth, window.innerHeight);
555
+ renderer.setClearColor(0x020108);
556
+ container.appendChild(renderer.domElement);
557
+ // Configuração dos controlos de órbita
558
+ controls = new OrbitControls(camera, renderer.domElement);
559
+ controls.enableDamping = true;
560
+ controls.dampingFactor = 0.05;
561
+ controls.screenSpacePanning = false;
562
+ controls.minDistance = 20;
563
+ controls.maxDistance = 150;
564
+ // Configuração do pós-processamento (efeito de brilho)
565
+ const renderScene = new RenderPass(scene, camera);
566
+ bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
567
+ bloomPass.threshold = 0;
568
+ bloomPass.strength = 1.2;
569
+ bloomPass.radius = 0.5;
570
+ composer = new EffectComposer(renderer);
571
+ composer.addPass(renderScene);
572
+ composer.addPass(bloomPass);
573
+
574
+ // Cria e adiciona as camadas de partículas à cena
575
+ layersConfig.forEach(config => {
576
+ const particles = createParticleSystem(config);
577
+ particleLayers.push(particles);
578
+ scene.add(particles);
579
+ });
580
+
581
+ // --- Configuração dos Event Listeners ---
582
+ document.getElementById('startButton').addEventListener('click', initAudio);
583
+ document.addEventListener('click', onClick);
584
+ window.addEventListener('resize', onWindowResize);
585
+
586
+ // Lógica do Popup e das Abas
587
+ setupSettingsPanel();
588
+ }
589
+
590
+ /**
591
+ * Configura toda a lógica do painel de configurações, incluindo popup, abas e sliders.
592
+ */
593
+ function setupSettingsPanel(){
594
+ const settingsButton = document.getElementById('settingsButton');
595
+ const settingsPopup = document.getElementById('settings-popup');
596
+ const closePopupButton = document.getElementById('close-popup-button');
597
+ const card = settingsPopup.querySelector('.card');
598
+ const tabs = settingsPopup.querySelectorAll('.tabs button');
599
+ const panels = settingsPopup.querySelectorAll('.panels .panel');
600
+
601
+ // Abrir e fechar o popup
602
+ settingsButton.addEventListener('click', () => settingsPopup.classList.add('visible'));
603
+ closePopupButton.addEventListener('click', () => settingsPopup.classList.remove('visible'));
604
+ settingsPopup.addEventListener('click', (e) => {
605
+ if (e.target === settingsPopup) settingsPopup.classList.remove('visible');
606
+ });
607
+
608
+ // Lógica de navegação das abas
609
+ tabs.forEach(tab => {
610
+ tab.addEventListener('click', () => {
611
+ tabs.forEach(item => item.classList.remove('active'));
612
+ panels.forEach(panel => panel.classList.remove('active'));
613
+ tab.classList.add('active');
614
+ document.getElementById(tab.dataset.tab).classList.add('active');
615
+ });
616
+ });
617
+
618
+ // Lógica do brilho do cartão (efeito visual)
619
+ card.addEventListener("pointermove", (e) => {
620
+ const rect = card.getBoundingClientRect();
621
+ const x = e.clientX - rect.left, y = e.clientY - rect.top;
622
+ const width = card.offsetWidth, height = card.offsetHeight;
623
+ const px = (x / width) * 100, py = (y / height) * 100;
624
+ const dx = x - width / 2, dy = y - height / 2;
625
+ const angle = Math.atan2(dy, dx) * (180 / Math.PI) + 90;
626
+ let k_x = dx !== 0 ? (width / 2) / Math.abs(dx) : Infinity;
627
+ let k_y = dy !== 0 ? (height / 2) / Math.abs(dy) : Infinity;
628
+ const edge = Math.min(Math.max(1 / Math.min(k_x, k_y), 0), 1) * 100;
629
+ card.style.setProperty('--pointer-°', `${angle.toFixed(3)}deg`);
630
+ card.style.setProperty('--pointer-d', `${edge.toFixed(3)}`);
631
+ });
632
+
633
+ // Listeners dos Sliders e Botões de controlo
634
+ document.getElementById('interactionStrength').addEventListener('input', e => interactionStrength = parseFloat(e.target.value));
635
+ document.getElementById('particleSize').addEventListener('input', e => {
636
+ baseParticleSize = parseFloat(e.target.value);
637
+ particleLayers.forEach(layer => layer.material.size = layer.userData.baseSize * baseParticleSize);
638
+ });
639
+ document.getElementById('bloomStrength').addEventListener('input', e => bloomPass.strength = parseFloat(e.target.value));
640
+ document.getElementById('audioSensitivity').addEventListener('input', e => audioSensitivity = parseFloat(e.target.value));
641
+ document.getElementById('bassBoost').addEventListener('input', e => bassBoost = parseFloat(e.target.value));
642
+ document.getElementById('cameraSensitivity').addEventListener('input', e => {
643
+ cameraSensitivity = parseFloat(e.target.value);
644
+ controls.rotateSpeed = 1.0 * cameraSensitivity;
645
+ controls.panSpeed = 1.0 * cameraSensitivity;
646
+ });
647
+ document.getElementById('resetCameraButton').addEventListener('click', () => {
648
+ controls.reset();
649
+ camera.position.z = 100;
650
+ });
651
+ }
652
+
653
+ //================================================================================
654
+ // SECÇÃO 3: LÓGICA DE ANIMAÇÃO E ATUALIZAÇÃO
655
+ //================================================================================
656
+
657
+ /**
658
+ * Atualiza a posição e cor de cada partícula em cada frame.
659
+ */
660
+ function updateParticles() {
661
+ let overallVolume = 0, bassLevel = 0;
662
+ // Analisa o áudio se estiver inicializado
663
+ if (audioInitialized) {
664
+ analyser.getByteFrequencyData(dataArray);
665
+ let total = 0;
666
+ for (let i = 0; i < dataArray.length; i++) total += dataArray[i];
667
+ overallVolume = total / dataArray.length;
668
+ let bassTotal = 0;
669
+ const bassEnd = dataArray.length * 0.2; // Frequências graves
670
+ for (let i = 0; i < bassEnd; i++) bassTotal += dataArray[i];
671
+ bassLevel = (bassTotal / bassEnd) / 255;
672
+ }
673
+
674
+ // Atualiza as ondulações ativas
675
+ ripples = ripples.filter(r => (r.radius += r.speed) < r.maxRadius && (r.strength *= 0.96));
676
+
677
+ // Itera sobre cada camada de partículas
678
+ particleLayers.forEach(layer => {
679
+ const { position, color } = layer.geometry.attributes;
680
+ const { velocities, basePositions, baseColors, colorVelocities, baseSize } = layer.userData;
681
+ // Atualiza o tamanho das partículas com base no grave do áudio
682
+ layer.material.size = (baseSize * baseParticleSize) + (bassLevel * 0.5 * audioSensitivity * bassBoost);
683
+ // Itera sobre cada partícula na camada
684
+ for (let i = 0; i < position.count; i++) {
685
+ const i3 = i * 3;
686
+ const currentPos = new THREE.Vector3(position.array[i3], position.array[i3 + 1], position.array[i3 + 2]);
687
+ let totalForce = new THREE.Vector3();
688
+ let colorShift = new THREE.Vector3();
689
+
690
+ // 1. Força de interação com a câmara
691
+ const cameraDist = camera.position.distanceTo(currentPos);
692
+ if (cameraDist < interactionRadius) {
693
+ const forceStrength = (1 - cameraDist / interactionRadius) * interactionStrength;
694
+ const forceDirection = currentPos.clone().sub(camera.position).normalize();
695
+ totalForce.add(forceDirection.multiplyScalar(forceStrength));
696
+ const colorIntensity = (1 - cameraDist / interactionRadius) * 0.8;
697
+ colorShift.set(colorIntensity, colorIntensity, colorIntensity);
698
+ }
699
+ // 2. Força de interação com o áudio
700
+ if (audioInitialized && overallVolume > 1) {
701
+ const audioForceStrength = (overallVolume / 255.0) * 0.4 * audioSensitivity;
702
+ totalForce.add(currentPos.clone().normalize().multiplyScalar(audioForceStrength));
703
+ if (bassLevel > 0.1) colorShift.x += bassLevel * 0.5 * audioSensitivity * bassBoost;
704
+ }
705
+ // 3. Força de interação com as ondulações do clique
706
+ ripples.forEach(ripple => {
707
+ const rippleDist = Math.hypot(ripple.x - currentPos.x, ripple.y - currentPos.y);
708
+ const rippleWidth = 15;
709
+ if (Math.abs(rippleDist - ripple.radius) < rippleWidth) {
710
+ const falloff = 1 - Math.abs(rippleDist - ripple.radius) / rippleWidth;
711
+ const rippleForce = ripple.strength * falloff * 0.1;
712
+ const forceDirection = currentPos.clone().sub(new THREE.Vector3(ripple.x, ripple.y, currentPos.z)).normalize();
713
+ totalForce.add(forceDirection.multiplyScalar(rippleForce));
714
+ colorShift.add(new THREE.Vector3(ripple.color.r, ripple.color.g, ripple.color.b).multiplyScalar(falloff * ripple.strength));
715
+ }
716
+ });
717
+
718
+ // 4. Força de retorno à posição original e amortecimento (damping)
719
+ velocities[i3] += totalForce.x + (basePositions[i3] - currentPos.x) * 0.02;
720
+ velocities[i3 + 1] += totalForce.y + (basePositions[i3 + 1] - currentPos.y) * 0.02;
721
+ velocities[i3 + 2] += totalForce.z + (basePositions[i3 + 2] - currentPos.z) * 0.02;
722
+ velocities[i3] *= 0.94; velocities[i3 + 1] *= 0.94; velocities[i3 + 2] *= 0.94;
723
+
724
+ // Atualiza a posição
725
+ position.array[i3] += velocities[i3];
726
+ position.array[i3 + 1] += velocities[i3 + 1];
727
+ position.array[i3 + 2] += velocities[i3 + 2];
728
+
729
+ // Atualiza a cor (lógica semelhante à da posição)
730
+ colorVelocities[i3] += colorShift.x + (baseColors[i3] - color.array[i3]) * 0.05;
731
+ colorVelocities[i3 + 1] += colorShift.y + (baseColors[i3 + 1] - color.array[i3 + 1]) * 0.05;
732
+ colorVelocities[i3 + 2] += colorShift.z + (baseColors[i3 + 2] - color.array[i3 + 2]) * 0.05;
733
+ colorVelocities[i3] *= 0.9; colorVelocities[i3 + 1] *= 0.9; colorVelocities[i3 + 2] *= 0.9;
734
+
735
+ color.array[i3] += colorVelocities[i3];
736
+ color.array[i3 + 1] += colorVelocities[i3 + 1];
737
+ color.array[i3 + 2] += colorVelocities[i3 + 2];
738
+ }
739
+
740
+ // Informa o Three.js que os atributos foram atualizados
741
+ position.needsUpdate = true;
742
+ color.needsUpdate = true;
743
+ });
744
+ }
745
+
746
+ /**
747
+ * O loop principal de animação, chamado a cada frame.
748
+ */
749
+ function animate() {
750
+ requestAnimationFrame(animate);
751
+ time += 0.01;
752
+ controls.update(); // Atualiza os controlos de órbita
753
+ updateParticles(); // Atualiza as partículas
754
+
755
+ // Animação de rotação base das camadas
756
+ particleLayers.forEach(layer => {
757
+ layer.rotation.y += layer.userData.rotationSpeed;
758
+ layer.rotation.x = Math.sin(time * 0.1) * 0.05;
759
+ });
760
+
761
+ // Renderiza a cena através do composer para aplicar o pós-processamento
762
+ composer.render();
763
+ }
764
+
765
+ //================================================================================
766
+ // SECÇÃO 4: FUNÇÕES DE EVENTOS DE UTILIZADOR
767
+ //================================================================================
768
+
769
+ /**
770
+ * Adiciona um efeito de ondulação ao clicar no ecrã.
771
+ * @param {MouseEvent} event - O objeto do evento de clique.
772
+ */
773
+ function onClick(event) {
774
+ // Ignora cliques em botões ou no painel de configurações
775
+ if (event.target.closest('button') || event.target.closest('.card')) return;
776
+
777
+ // Converte as coordenadas do clique do ecrã para coordenadas 3D do mundo
778
+ const clickMouse = new THREE.Vector2((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
779
+ const vector = new THREE.Vector3(clickMouse.x, clickMouse.y, 0.5);
780
+ vector.unproject(camera);
781
+ const dir = vector.sub(camera.position).normalize();
782
+ const distance = -camera.position.z / dir.z;
783
+ const pos = camera.position.clone().add(dir.multiplyScalar(distance));
784
+ createRipple(pos.x, pos.y);
785
+ }
786
+
787
+ /**
788
+ * Lida com o redimensionamento da janela do navegador.
789
+ */
790
+ function onWindowResize() {
791
+ camera.aspect = window.innerWidth / window.innerHeight;
792
+ camera.updateProjectionMatrix();
793
+ renderer.setSize(window.innerWidth, window.innerHeight);
794
+ composer.setSize(window.innerWidth, window.innerHeight);
795
+ }
796
+
797
+ //================================================================================
798
+ // SECÇÃO 5: INICIALIZAÇÃO DA APLICAÇÃO
799
+ //================================================================================
800
+ init();
801
+ animate();
802
+ </script>
803
+ <script src="https://deepsite.hf.co/deepsite-badge.js"></script>
804
+ </body>
805
  </html>
806
+
807
+