fuzzyworld

bubbles / files / index.html

index.html

8.6 KB · history · blame

1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
7 <meta name="mobile-web-app-capable" content="yes" />
8 <meta name="apple-mobile-web-app-capable" content="yes" />
9 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
10 <meta name="apple-mobile-web-app-title" content="Bubbles" />
11 <meta name="theme-color" content="#0ea5e9" />
12 <meta name="description" content="Your phone is a glass of water. Tilt to slosh. Tap to drop things in." />
13 <meta property="og:title" content="Bubbles" />
14 <meta property="og:description" content="Your phone is a glass of water. Tilt to slosh. Tap to drop things in." />
15 <meta property="og:image" content="og-image.png" />
16 <meta property="og:url" content="https://apps.fuzzyworld.net/bubbles/" />
17 <meta property="og:type" content="website" />
18 <title>Bubbles</title>
19 <link rel="icon" type="image/x-icon" href="favicon.ico" />
20 <style>
21 *,
22 *::before,
23 *::after {
24 box-sizing: border-box;
25 margin: 0;
26 padding: 0;
27 }
28
29 html,
30 body {
31 width: 100%;
32 height: 100%;
33 overflow: hidden;
34 background: #0f172a;
35 font-family: system-ui, sans-serif;
36 color: #e2e8f0;
37 touch-action: none;
38 }
39
40 /* CSS fallback: if orientation lock API didn't work, rotate everything back to portrait */
41 @media screen and (orientation: landscape) {
42 #rotate-overlay {
43 display: flex !important;
44 }
45 }
46
47 #app {
48 display: flex;
49 flex-direction: column;
50 align-items: center;
51 justify-content: center;
52 width: 100%;
53 height: 100%;
54 position: relative;
55 }
56
57 canvas {
58 display: block;
59 touch-action: none;
60 }
61
62 #hint {
63 position: absolute;
64 bottom: 48px;
65 left: 50%;
66 transform: translateX(-50%);
67 font-size: 13px;
68 color: rgba(226, 232, 240, 0.45);
69 white-space: nowrap;
70 pointer-events: none;
71 user-select: none;
72 }
73
74 footer {
75 position: absolute;
76 bottom: 8px;
77 left: 50%;
78 transform: translateX(-50%);
79 font-size: 11px;
80 color: rgba(226, 232, 240, 0.3);
81 white-space: nowrap;
82 text-align: center;
83 }
84
85 footer a {
86 color: rgba(226, 232, 240, 0.45);
87 text-decoration: underline;
88 text-underline-offset: 2px;
89 }
90
91 /* ── Start screen ───────────────────────────────────────────────── */
92 #start-screen {
93 position: fixed;
94 inset: 0;
95 z-index: 100;
96 background: #0f172a;
97 display: flex;
98 flex-direction: column;
99 align-items: center;
100 justify-content: center;
101 gap: 32px;
102 padding: 40px;
103 transition: opacity 0.4s, visibility 0.4s;
104 }
105
106 #start-screen.hidden {
107 opacity: 0;
108 visibility: hidden;
109 pointer-events: none;
110 }
111
112 #start-screen .logo {
113 width: 90px;
114 height: 90px;
115 opacity: 0.85;
116 }
117
118 #start-screen h1 {
119 font-size: 28px;
120 font-weight: 700;
121 letter-spacing: 0.04em;
122 color: #e2e8f0;
123 margin: 0;
124 }
125
126 #start-screen p {
127 font-size: 15px;
128 color: rgba(226, 232, 240, 0.5);
129 text-align: center;
130 max-width: 260px;
131 line-height: 1.6;
132 margin: 0;
133 }
134
135 #start-btn {
136 margin-top: 8px;
137 padding: 16px 48px;
138 font-size: 17px;
139 font-weight: 600;
140 font-family: inherit;
141 color: #0f172a;
142 background: #38bdf8;
143 border: none;
144 border-radius: 999px;
145 cursor: pointer;
146 touch-action: manipulation;
147 -webkit-tap-highlight-color: transparent;
148 transition: background 0.15s, transform 0.1s;
149 }
150
151 #start-btn:active {
152 background: #7dd3fc;
153 transform: scale(0.97);
154 }
155
156 #reset-btn {
157 position: absolute;
158 top: 12px;
159 right: 12px;
160 width: 40px;
161 height: 40px;
162 border: none;
163 border-radius: 10px;
164 background: rgba(255, 255, 255, 0.09);
165 color: rgba(226, 232, 240, 0.6);
166 cursor: pointer;
167 display: flex;
168 align-items: center;
169 justify-content: center;
170 transition: background 0.15s, color 0.15s;
171 -webkit-tap-highlight-color: transparent;
172 touch-action: manipulation;
173 }
174
175 #reset-btn:hover,
176 #reset-btn:active {
177 background: rgba(255, 255, 255, 0.18);
178 color: rgba(226, 232, 240, 0.95);
179 }
180
181 #reset-btn svg {
182 width: 20px;
183 height: 20px;
184 fill: currentColor;
185 }
186
187 /* ── Level dot indicator ──────────────────────────────────────── */
188 #level-dots {
189 position: absolute;
190 bottom: 68px;
191 left: 50%;
192 transform: translateX(-50%);
193 display: flex;
194 gap: 8px;
195 align-items: center;
196 opacity: 0;
197 transition: opacity 0.3s;
198 pointer-events: none;
199 }
200
201 #level-dots.visible {
202 opacity: 1;
203 }
204
205 #level-dots .dot {
206 width: 6px;
207 height: 6px;
208 border-radius: 50%;
209 background: rgba(226, 232, 240, 0.25);
210 transition: background 0.2s, transform 0.2s;
211 }
212
213 #level-dots .dot.active {
214 background: rgba(56, 189, 248, 0.9);
215 transform: scale(1.4);
216 }
217 </style>
218</head>
219
220<body>
221 <div id="app">
222 <!-- Landscape blocker -->
223 <div id="rotate-overlay"
224 style="display:none;position:fixed;inset:0;z-index:9999;background:#0f172a;flex-direction:column;align-items:center;justify-content:center;gap:16px;font-size:15px;color:rgba(226,232,240,0.7);text-align:center;padding:24px">
225 <svg width="48" height="48" viewBox="0 0 24 24" fill="rgba(226,232,240,0.5)">
226 <path
227 d="M16.48 2.52c3.27 1.55 5.61 4.72 5.97 8.48h1.5C23.44 4.84 18.29 0 12 0l-.66.03 3.81 3.81 1.33-1.32zm-6.25-.77c-.59-.59-1.54-.59-2.12 0L1.75 8.11c-.59.59-.59 1.54 0 2.12l12.02 12.02c.59.59 1.54.59 2.12 0l6.36-6.36c.59-.59.59-1.54 0-2.12L10.23 1.75zm4.6 19.44L2.81 9.17l6.36-6.36 12.02 12.02-6.36 6.36zm-7.57.29C4.01 19.92 1.67 16.75 1.31 13H-.19C.56 19.16 5.71 24 12 24l.66-.03-3.81-3.81-1.33 1.32z" />
228 </svg>
229 <span>Please rotate back to portrait</span>
230 </div>
231
232 <!-- Start screen -->
233 <div id="start-screen">
234 <img class="logo" src="favicon.ico" alt="Bubbles logo" width="90" height="90" />
235 <h1>Bubbles</h1>
236 <p>Your phone is a glass of water.<br>Tilt to slosh. Tap to drop things in.</p>
237 <button id="start-btn">Start</button>
238 </div>
239
240 <canvas id="canvas"></canvas>
241 <button id="reset-btn" title="Reset" aria-label="Reset simulation">
242 <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
243 <path
244 d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" />
245 </svg>
246 </button>
247 <div id="level-dots"></div>
248 <p id="hint">tap to drop · tilt to slosh · swipe to switch</p>
249 <footer>
250 <a href="https://code.fuzzyworld.net/bubbles/" target="_blank" rel="noopener">source</a>
251 &nbsp;·&nbsp;
252 wind icon by <a href="https://www.flaticon.com/authors/icongeek26" target="_blank"
253 rel="noopener">Icongeek26</a>
254 from <a href="https://www.flaticon.com/" target="_blank" rel="noopener">Flaticon</a>
255 </footer>
256 </div>
257 <script type="module" src="/src/main.ts"></script>
258</body>
259
260</html>