watchfloor-tizenbrew 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/index.html +196 -19
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
--glass: #0d0905;
|
|
13
13
|
--p100: #ffb648;
|
|
14
14
|
--p70: #d08a2a;
|
|
15
|
-
--p45: #
|
|
15
|
+
--p45: #a8702a;
|
|
16
16
|
--p25: #4a3112;
|
|
17
|
-
--glow: 0 0
|
|
17
|
+
--glow: 0 0 8px rgba(255, 170, 60, 0.55);
|
|
18
18
|
}
|
|
19
19
|
html, body {
|
|
20
20
|
margin: 0; height: 100%; overflow: hidden;
|
|
@@ -28,9 +28,19 @@
|
|
|
28
28
|
transform: translate(-50%, -50%);
|
|
29
29
|
box-sizing: border-box; padding: 54px 72px 40px;
|
|
30
30
|
display: flex; flex-direction: column;
|
|
31
|
-
text-shadow: var(--glow);
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
/* Misconvergence and chroma bleed: the red and blue guns land a little
|
|
34
|
+
off the amber, worse toward the screen edges, and colour smears right.
|
|
35
|
+
text-shadow inherits as a computed value, so each region restates it. */
|
|
36
|
+
#stage, .col, .top > div {
|
|
37
|
+
text-shadow: var(--cvl) 0 0 rgba(255, 40, 30, 0.55), var(--cvr) 0 0 rgba(40, 170, 255, 0.42),
|
|
38
|
+
3px 0 5px rgba(255, 140, 40, 0.35), var(--glow);
|
|
39
|
+
}
|
|
40
|
+
#stage { --cvl: -1.5px; --cvr: 1.5px; }
|
|
41
|
+
.col-a, .station { --cvl: -3px; --cvr: 1px; }
|
|
42
|
+
.col-c, .when { --cvl: -1px; --cvr: 3px; }
|
|
43
|
+
|
|
34
44
|
/* header */
|
|
35
45
|
.top { display: flex; align-items: flex-end; border-bottom: 2px solid var(--p25); padding-bottom: 14px; }
|
|
36
46
|
.station { flex: 1; font-size: 34px; color: var(--p70); line-height: 1.15; }
|
|
@@ -96,25 +106,153 @@
|
|
|
96
106
|
|
|
97
107
|
.log { border-top: 2px solid var(--p25); padding-top: 10px; margin-top: 18px; font-size: 26px; color: var(--p45); height: 64px; overflow: hidden; text-shadow: none; line-height: 1.2; }
|
|
98
108
|
|
|
99
|
-
/*
|
|
109
|
+
/* ---------- the tube ----------
|
|
110
|
+
Every effect is a separate layer that only animates transform or
|
|
111
|
+
opacity, so the 2018 TV's compositor does the work instead of
|
|
112
|
+
repainting the page. */
|
|
113
|
+
#tube {
|
|
114
|
+
position: fixed; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden;
|
|
115
|
+
background: var(--glass);
|
|
116
|
+
/* Tired high-voltage supply: the picture breathes and sits slightly
|
|
117
|
+
rotated from the Earth's field, drifting over time. */
|
|
118
|
+
animation: breathe 9s ease-in-out infinite alternate;
|
|
119
|
+
}
|
|
120
|
+
@keyframes breathe {
|
|
121
|
+
0% { transform: scale(1.000, 1.000) rotate(0.10deg) translateX(0); }
|
|
122
|
+
40% { transform: scale(1.004, 1.007) rotate(0.16deg) translateX(1px); }
|
|
123
|
+
100% { transform: scale(0.998, 1.002) rotate(0.06deg) translateX(-1px); }
|
|
124
|
+
}
|
|
125
|
+
|
|
100
126
|
.fx { position: fixed; left: 0; top: 0; right: 0; bottom: 0; pointer-events: none; }
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
127
|
+
|
|
128
|
+
/* Static glass: heavy scanlines plus the aperture-grille phosphor stripes. */
|
|
129
|
+
.scan {
|
|
130
|
+
background:
|
|
131
|
+
repeating-linear-gradient(to bottom, rgba(0,0,0,0) 0, rgba(0,0,0,0) 2px, rgba(0,0,0,0.55) 2px, rgba(0,0,0,0.55) 4px),
|
|
132
|
+
repeating-linear-gradient(to right, rgba(255,0,0,0.07) 0, rgba(255,0,0,0.07) 1px, rgba(0,255,0,0.05) 1px, rgba(0,255,0,0.05) 2px, rgba(0,80,255,0.07) 2px, rgba(0,80,255,0.07) 3px);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Interlace: odd and even fields alternate 1px apart, so fine lines twitter. */
|
|
136
|
+
.interlace {
|
|
137
|
+
top: -2px;
|
|
138
|
+
background: repeating-linear-gradient(to bottom, rgba(0,0,0,0.16) 0, rgba(0,0,0,0.16) 1px, rgba(0,0,0,0) 1px, rgba(0,0,0,0) 2px);
|
|
139
|
+
animation: field 0.0334s steps(1) infinite;
|
|
140
|
+
}
|
|
141
|
+
@keyframes field { 50% { transform: translateY(1px); } }
|
|
142
|
+
|
|
143
|
+
/* Colour purity: a magnetised shadow mask stains the corners, and the
|
|
144
|
+
stain wanders as the field shifts. Oversized so drifting shows no edge. */
|
|
145
|
+
.purity {
|
|
146
|
+
left: -6%; top: -6%; right: -6%; bottom: -6%;
|
|
147
|
+
background:
|
|
148
|
+
radial-gradient(circle at 86% 14%, rgba(210,60,230,0.20), rgba(210,60,230,0) 30%),
|
|
149
|
+
radial-gradient(circle at 12% 88%, rgba(60,230,130,0.13), rgba(60,230,130,0) 28%),
|
|
150
|
+
radial-gradient(circle at 60% 95%, rgba(80,120,255,0.08), rgba(80,120,255,0) 25%);
|
|
151
|
+
animation: magnet 53s ease-in-out infinite alternate;
|
|
152
|
+
}
|
|
153
|
+
@keyframes magnet {
|
|
154
|
+
0% { transform: translate(0, 0) rotate(0deg); }
|
|
155
|
+
35% { transform: translate(-2.5%, 1.2%) rotate(-1deg); }
|
|
156
|
+
70% { transform: translate(1.5%, -1%) rotate(0.6deg); }
|
|
157
|
+
100% { transform: translate(-1%, 2%) rotate(0deg); }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Snow: a random tile generated at boot, jumped around at ~12 fps. */
|
|
161
|
+
.noise {
|
|
162
|
+
left: -256px; top: -256px; right: -256px; bottom: -256px;
|
|
163
|
+
opacity: 0.045; animation: snow 0.5s infinite;
|
|
164
|
+
}
|
|
165
|
+
@keyframes snow {
|
|
166
|
+
0% { transform: translate(0, 0); animation-timing-function: steps(1); }
|
|
167
|
+
17% { transform: translate(-91px, 37px); animation-timing-function: steps(1); }
|
|
168
|
+
33% { transform: translate(53px, -120px); animation-timing-function: steps(1); }
|
|
169
|
+
50% { transform: translate(-170px, -44px); animation-timing-function: steps(1); }
|
|
170
|
+
67% { transform: translate(122px, 88px); animation-timing-function: steps(1); }
|
|
171
|
+
83% { transform: translate(-33px, 171px); animation-timing-function: steps(1); }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Hum bar: a 60 Hz ground loop rolling slowly up the picture. */
|
|
175
|
+
.hum { overflow: hidden; }
|
|
176
|
+
.hum::after {
|
|
177
|
+
content: ""; position: absolute; left: 0; right: 0; top: 0; height: 34%;
|
|
178
|
+
background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.22) 45%, rgba(255,190,110,0.05) 58%, rgba(0,0,0,0));
|
|
179
|
+
animation: hum 13s linear infinite;
|
|
180
|
+
}
|
|
181
|
+
@keyframes hum { from { transform: translateY(330%); } to { transform: translateY(-120%); } }
|
|
182
|
+
|
|
183
|
+
/* Flicker from a tired supply, plus slow brightness drift as it warms. */
|
|
184
|
+
.flick { background: rgba(255,170,60,0.035); animation: flick 3.7s steps(1) infinite; }
|
|
185
|
+
@keyframes flick {
|
|
186
|
+
0% { opacity: 1; } 9% { opacity: 0.3; } 10% { opacity: 1; } 41% { opacity: 0.5; } 42% { opacity: 1; }
|
|
187
|
+
63% { opacity: 0.2; } 64% { opacity: 0.8; } 65% { opacity: 1; } 88% { opacity: 0.6; } 89% { opacity: 1; }
|
|
188
|
+
}
|
|
189
|
+
.drift { background: #000; opacity: 0; animation: drift 29s ease-in-out infinite alternate; }
|
|
190
|
+
@keyframes drift { 0% { opacity: 0; } 45% { opacity: 0.1; } 70% { opacity: 0.03; } 100% { opacity: 0.07; } }
|
|
191
|
+
|
|
192
|
+
/* Tube shape: rounded corners, darker curved edges, glare on the glass. */
|
|
193
|
+
.glass {
|
|
194
|
+
border-radius: 5% / 8%;
|
|
195
|
+
box-shadow: 0 0 0 80px #000, inset 0 0 140px 40px rgba(0,0,0,0.85), inset 0 0 22px 6px rgba(0,0,0,0.9);
|
|
196
|
+
background:
|
|
197
|
+
radial-gradient(ellipse at 28% 16%, rgba(255,255,255,0.06), rgba(255,255,255,0) 38%),
|
|
198
|
+
radial-gradient(ellipse at center, rgba(0,0,0,0) 52%, rgba(0,0,0,0.6) 100%);
|
|
108
199
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
200
|
+
|
|
201
|
+
/* Degauss: the rainbow wobble a CRT makes when it powers on. */
|
|
202
|
+
.degauss {
|
|
203
|
+
left: -10%; top: -10%; right: -10%; bottom: -10%; opacity: 0;
|
|
204
|
+
background: linear-gradient(115deg, rgba(255,0,140,0.32), rgba(0,255,170,0.26) 25%, rgba(90,120,255,0.32) 50%, rgba(255,210,0,0.26) 75%, rgba(255,0,140,0.32));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* ---------- glitches, triggered from script ---------- */
|
|
208
|
+
.power #tube { animation: poweron 1.1s ease-out; }
|
|
209
|
+
@keyframes poweron {
|
|
210
|
+
0% { transform: scale(0.4, 0.003); opacity: 1; }
|
|
211
|
+
30% { transform: scale(1, 0.003); }
|
|
212
|
+
65% { transform: scale(1, 1.05); }
|
|
213
|
+
100% { transform: scale(1, 1); }
|
|
214
|
+
}
|
|
215
|
+
.degaussing #tube { animation: wobble 1.7s ease-out; }
|
|
216
|
+
.degaussing .degauss { animation: degauss 1.7s ease-out; }
|
|
217
|
+
@keyframes wobble {
|
|
218
|
+
0% { transform: rotate(0deg) skewX(0deg); } 12% { transform: rotate(1.4deg) skewX(2deg); }
|
|
219
|
+
25% { transform: rotate(-1.1deg) skewX(-1.6deg); } 40% { transform: rotate(0.8deg) skewX(1deg); }
|
|
220
|
+
60% { transform: rotate(-0.4deg) skewX(-0.5deg); } 100% { transform: rotate(0deg) skewX(0deg); }
|
|
221
|
+
}
|
|
222
|
+
@keyframes degauss {
|
|
223
|
+
0% { opacity: 0.9; transform: rotate(0deg) scale(1); } 30% { opacity: 0.7; transform: rotate(8deg) scale(1.1); }
|
|
224
|
+
60% { opacity: 0.35; transform: rotate(-5deg) scale(1.05); } 100% { opacity: 0; transform: rotate(0deg) scale(1); }
|
|
225
|
+
}
|
|
226
|
+
/* Horizontal sync loses lock for a moment and the picture tears sideways. */
|
|
227
|
+
.hsync #tube { animation: hsync 0.42s; }
|
|
228
|
+
@keyframes hsync {
|
|
229
|
+
0% { transform: translateX(0) skewX(0deg); animation-timing-function: steps(1); }
|
|
230
|
+
15% { transform: translateX(-14px) skewX(-3deg); animation-timing-function: steps(1); }
|
|
231
|
+
35% { transform: translateX(9px) skewX(2.2deg); animation-timing-function: steps(1); }
|
|
232
|
+
55% { transform: translateX(-5px) skewX(-1deg); animation-timing-function: steps(1); }
|
|
233
|
+
75% { transform: translateX(2px) skewX(0.4deg); animation-timing-function: steps(1); }
|
|
234
|
+
100% { transform: translateX(0) skewX(0deg); }
|
|
235
|
+
}
|
|
236
|
+
/* Vertical hold slips and the picture rolls once before catching. */
|
|
237
|
+
.vroll #tube { animation: vroll 0.9s ease-in-out; }
|
|
238
|
+
@keyframes vroll {
|
|
239
|
+
0% { transform: translateY(0); } 45% { transform: translateY(55%); }
|
|
240
|
+
46% { transform: translateY(-45%); } 100% { transform: translateY(0); }
|
|
241
|
+
}
|
|
242
|
+
/* Brownout: the supply sags, the picture shrinks and dims, then recovers. */
|
|
243
|
+
.brown #tube { animation: brown 1.4s ease-in-out; }
|
|
244
|
+
@keyframes brown {
|
|
245
|
+
0% { transform: scale(1, 1); opacity: 1; } 35% { transform: scale(0.965, 0.975); opacity: 0.72; }
|
|
246
|
+
55% { transform: scale(0.975, 0.985); opacity: 0.8; } 100% { transform: scale(1, 1); opacity: 1; }
|
|
247
|
+
}
|
|
248
|
+
|
|
112
249
|
@media (prefers-reduced-motion: reduce) {
|
|
113
|
-
.
|
|
250
|
+
#tube, .interlace, .purity, .noise, .hum::after, .flick, .drift, #p-alerts.hot h2 { animation: none !important; }
|
|
114
251
|
}
|
|
115
252
|
</style>
|
|
116
253
|
</head>
|
|
117
|
-
<body>
|
|
254
|
+
<body class="power">
|
|
255
|
+
<div id="tube">
|
|
118
256
|
<div id="stage">
|
|
119
257
|
<header class="top">
|
|
120
258
|
<div class="station"><b id="st-name">WATCHFLOOR</b><span id="st-coords"></span></div>
|
|
@@ -163,10 +301,16 @@
|
|
|
163
301
|
|
|
164
302
|
<footer class="log" id="log"></footer>
|
|
165
303
|
</div>
|
|
166
|
-
<div class="fx
|
|
167
|
-
|
|
304
|
+
<div class="fx purity"></div>
|
|
305
|
+
</div>
|
|
306
|
+
<div class="fx noise" id="noise"></div>
|
|
168
307
|
<div class="fx scan"></div>
|
|
169
|
-
<div class="fx
|
|
308
|
+
<div class="fx interlace"></div>
|
|
309
|
+
<div class="fx hum"></div>
|
|
310
|
+
<div class="fx flick"></div>
|
|
311
|
+
<div class="fx drift"></div>
|
|
312
|
+
<div class="fx degauss"></div>
|
|
313
|
+
<div class="fx glass"></div>
|
|
170
314
|
|
|
171
315
|
<script>
|
|
172
316
|
/* ---------- config: edit these ---------- */
|
|
@@ -392,6 +536,36 @@ function hn() {
|
|
|
392
536
|
});
|
|
393
537
|
}
|
|
394
538
|
|
|
539
|
+
/* ---------- tube faults ---------- */
|
|
540
|
+
function makeNoise() {
|
|
541
|
+
var c = document.createElement("canvas");
|
|
542
|
+
c.width = c.height = 256;
|
|
543
|
+
var g = c.getContext("2d"), img = g.createImageData(256, 256), px = img.data;
|
|
544
|
+
for (var i = 0; i < px.length; i += 4) {
|
|
545
|
+
var v = Math.random() * 255;
|
|
546
|
+
px[i] = v; px[i + 1] = v * 0.8; px[i + 2] = v * 0.6; px[i + 3] = Math.random() < 0.5 ? 255 : 0;
|
|
547
|
+
}
|
|
548
|
+
g.putImageData(img, 0, 0);
|
|
549
|
+
$("noise").style.backgroundImage = "url(" + c.toDataURL() + ")";
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Play one glitch by putting its class on <body> for the length of its animation.
|
|
553
|
+
function fault(cls, ms, done) {
|
|
554
|
+
document.body.className = cls;
|
|
555
|
+
setTimeout(function () { document.body.className = ""; if (done) done(); }, ms);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Every 20-70 s something in the old set misbehaves.
|
|
559
|
+
function scheduleFault() {
|
|
560
|
+
setTimeout(function () {
|
|
561
|
+
var r = Math.random();
|
|
562
|
+
if (r < 0.5) fault("hsync", 450, scheduleFault);
|
|
563
|
+
else if (r < 0.75) fault("brown", 1450, scheduleFault);
|
|
564
|
+
else if (r < 0.92) fault("vroll", 950, scheduleFault);
|
|
565
|
+
else fault("degaussing", 1750, scheduleFault);
|
|
566
|
+
}, 20000 + Math.random() * 50000);
|
|
567
|
+
}
|
|
568
|
+
|
|
395
569
|
/* ---------- boot ---------- */
|
|
396
570
|
function fit() {
|
|
397
571
|
var s = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
|
|
@@ -404,6 +578,9 @@ $("st-name").textContent = CFG.name;
|
|
|
404
578
|
$("st-coords").textContent = Math.abs(CFG.lat).toFixed(2) + "°" + (CFG.lat >= 0 ? "N " : "S ") +
|
|
405
579
|
Math.abs(CFG.lon).toFixed(2) + "°" + (CFG.lon >= 0 ? "E" : "W");
|
|
406
580
|
tick(); setInterval(tick, 1000);
|
|
581
|
+
makeNoise();
|
|
582
|
+
// Power on, then degauss, then settle into random faults.
|
|
583
|
+
fault("power", 1150, function () { fault("degaussing", 1750, scheduleFault); });
|
|
407
584
|
setInterval(ages, 15000);
|
|
408
585
|
log("Terminal online");
|
|
409
586
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "watchfloor-tizenbrew",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CRT-style live status wall for Samsung TVs via TizenBrew: weather, NWS alerts, air quality, service status, earthquakes, geomagnetic activity, Hacker News.",
|
|
5
5
|
"packageType": "app",
|
|
6
6
|
"appName": "Watchfloor",
|