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.
Files changed (2) hide show
  1. package/app/index.html +196 -19
  2. 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: #8a5a1c;
15
+ --p45: #a8702a;
16
16
  --p25: #4a3112;
17
- --glow: 0 0 6px rgba(255, 170, 60, 0.45);
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
- /* CRT overlays */
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
- .scan { background: repeating-linear-gradient(to bottom, rgba(0,0,0,0) 0, rgba(0,0,0,0) 2px, rgba(0,0,0,0.28) 3px); }
102
- .vig { background: radial-gradient(ellipse at center, rgba(0,0,0,0) 58%, rgba(0,0,0,0.55) 100%); }
103
- .roll { overflow: hidden; }
104
- .roll::after {
105
- content: ""; position: absolute; left: 0; right: 0; top: 0; height: 22%;
106
- background: linear-gradient(to bottom, rgba(255,180,80,0), rgba(255,180,80,0.035), rgba(255,180,80,0));
107
- animation: roll 11s linear infinite;
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
- .flick { background: rgba(255,170,60,0.02); animation: flick 7s steps(1) infinite; }
110
- @keyframes roll { from { transform: translateY(-120%); } to { transform: translateY(560%); } }
111
- @keyframes flick { 0% {opacity:1} 41% {opacity:.4} 42% {opacity:1} 77% {opacity:.6} 78% {opacity:1} }
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
- .roll::after, .flick, #p-alerts.hot h2 { animation: none; }
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 flick"></div>
167
- <div class="fx roll"></div>
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 vig"></div>
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.0.0",
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",