David Glivar

Open the pod bay doors, HAL

Rediscovering a Canvas experiment from 2012.

Back in 2012, I was fresh into my new career as a web developer and wanted to show future employers what I was capable of. There was a momentous shift in the industry as ActionScript was on its way out and Canvas animations with JavaScript were taking over the micro-site niche.

In addition, I was (and remain) a fan of the movie 2001: A Space Odyssey , with HAL often staring back at me from my desktop background. It seemed a natural fit for me to attempt a portrait of the iconic character. Using the Canvas API , I carefully layered shapes, blurs and shadows to reproduce a scene from the film.

The original experiment had a few issues and was written in a style that was common at the time but differs wildly to that of today. I've only done the bare minimum to get it working again, see it here:

HAL: 9000 - Canvas Portrait from 2012

This is the reference frame from the movie that I used to create the portrait.

HAL 9000 reference
Scene with HAL 9000

And here's my attempt at recreating it with the Canvas API .

HAL 9000 canvas interpretation
HAL: 9000 - Canvas Portrait from 2012
Here's the source code for the original experiment, blemishes and all:
1
var HAL = function (canvas) {
2
        "use strict";
3
        var hal = {},
4
        c = canvas.getContext("2d"),
5
        vc, hc; // verticalcenter, horizontalcenter
6
    
7
        /*
8
         * Begin helpers
9
         */
10
        function saveAndBegin() {
11
            c.save();
12
            c.beginPath();
13
        }
14
    
15
        function closeAndRestore() {
16
            c.closePath();
17
            c.restore();
18
        }
19
    
20
        function drawShape(shape) {
21
            var len = shape.length,
22
                i = 1;
23
            c.moveTo(shape[0].x, shape[0].y);
24
            while (i < len) {
25
                if (("cp1x" in shape[i]) && ("cp2x" in shape[i])) {
26
                    c.bezierCurveTo(shape[i].cp1x, shape[i].cp1y, shape[i].cp2x, shape[i].cp2y, shape[i].x, shape[i].y);
27
                } else if (("cp1x" in shape[i]) && !("cp2x" in shape[i])) {
28
                    c.quadraticCurveTo(shape[i].cp1x, shape[i].cp1y, shape[i].x, shape[i].y);
29
                } else {
30
                    c.lineTo(shape[i].x, shape[i].y); 
31
                }
32
                i += 1;
33
            }
34
        }
35
    
36
        /*
37
         * Begin Hal
38
         */
39
        function iris() {
40
            var x = hc - 30,
41
                y = vc,
42
                pc = vc - 30, // pupil center
43
                grad;
44
    
45
            saveAndBegin();
46
            grad = c.createRadialGradient(hc, pc, 480, hc, pc, 1);
47
            grad.addColorStop(0, "rgba(37, 1, 1, 0.7)");
48
            grad.addColorStop(1, "rgba(254, 31, 0, 0)");
49
            c.fillStyle = grad;
50
            c.arc(hc, pc, 530, 0, 2 * Math.PI, true);
51
            c.fill();
52
            closeAndRestore();
53
    
54
            saveAndBegin();
55
            grad = c.createRadialGradient(x, y, 480, x + 30, pc, 100);
56
            grad.addColorStop(0,    "rgba(0, 0, 0, 0.6)");
57
            grad.addColorStop(0.06, "rgba(29, 0, 2, 0.6)");
58
            grad.addColorStop(0.09, "rgba(50, 3, 10, 0.8)");
59
            grad.addColorStop(0.1,  "rgba(50, 3, 10, 0.8)");
60
            grad.addColorStop(0.13, "rgba(70, 1, 2, 0.8)");
61
            grad.addColorStop(0.2,  "rgba(80, 14, 10, 0.7)");
62
            grad.addColorStop(1,    "rgba(80,14,10,0)");
63
            c.fillStyle = grad;
64
            c.arc(hc, vc, 650, 0, 2 * Math.PI, true);
65
            var i = 2;
66
            while (i--) {
67
                c.fill();
68
            }
69
            closeAndRestore();
70
    
71
            saveAndBegin();
72
            c.globalCompositeOperation = "xor";
73
            x = hc;
74
            grad = c.createRadialGradient(x + 20, y - 50, 500, x, pc, 100);
75
            grad.addColorStop(0,    "rgba(80, 14, 10, 0)");
76
            grad.addColorStop(0.45, "rgba(70, 1, 2, 0.6)");
77
            grad.addColorStop(0.52, "rgba(70, 1, 2, 0.4)");
78
            grad.addColorStop(0.54, "rgba(120, 1, 30, 0.9)");
79
            grad.addColorStop(0.62, "rgba(120, 1, 30, 0.5)");
80
            grad.addColorStop(1,    "rgba(255, 18, 46, 0)");
81
            c.fillStyle = grad;
82
            c.arc(hc, vc, 540, 0, 2 * Math.PI, true);
83
            var i = 5;
84
            while (i--) {
85
                c.fill();
86
            }
87
            closeAndRestore();
88
    
89
            saveAndBegin();
90
            c.globalCompositeOperation = "xor";
91
            x = hc - 50;
92
            y = vc + 20;
93
            grad = c.createRadialGradient(x, y, 300, hc, pc, 50);
94
            grad.addColorStop(0,    "rgba(39, 14, 10, 0)");
95
            grad.addColorStop(0.52, "rgba(42, 1, 2, 0.4)");
96
            grad.addColorStop(0.54, "rgba(63, 1, 2, 0.5)");
97
            grad.addColorStop(0.58, "rgba(63, 1, 2, 0.9)");
98
            grad.addColorStop(1,    "rgba(100, 1, 2, 0.3)");
99
            c.fillStyle = grad;
100
            c.arc(hc - 50, vc + 80, 500, 0, 2 * Math.PI, true);
101
            c.fill();
102
            closeAndRestore();
103
    
104
            saveAndBegin();
105
            c.globalCompositeOperation = "xor";
106
            x = hc - 30;
107
            y = vc;
108
            grad = c.createRadialGradient(x, y, 400, hc, pc, 50);
109
            grad.addColorStop(0,    "rgba(80, 14, 10, 0.1)");
110
            grad.addColorStop(0.52, "rgba(70, 1, 2, 0.4)");
111
            grad.addColorStop(0.54, "rgba(120, 1, 30, 0.9)");
112
            grad.addColorStop(0.58, "rgba(120, 1, 30, 0.2)");
113
            grad.addColorStop(1,    "rgba(195, 11, 0, 0.5)");
114
            c.fillStyle = grad;
115
            c.arc(hc - 50, vc + 80, 400, 0, 2 * Math.PI, true);
116
            closeAndRestore();
117
    
118
            saveAndBegin();
119
            c.globalCompositeOperation = "xor";
120
            x = hc;
121
            y = vc - 40;
122
            grad = c.createRadialGradient(x, y, 300, hc, pc, 50);
123
            grad.addColorStop(0,    "rgba(80, 14, 10, 0.1)");
124
            grad.addColorStop(0.52, "rgba(70, 1, 2, 0.4)");
125
            grad.addColorStop(0.54, "rgba(120, 1, 2, 0.9)");
126
            grad.addColorStop(0.58, "rgba(120, 1, 2, 0.2)");
127
            grad.addColorStop(1,    "rgba(195, 11, 0, 0.5)");
128
            c.fillStyle = grad;
129
            c.arc(x, y, 400, 0, 2 * Math.PI, true);
130
            var i = 5;
131
            while (i--) {
132
                c.fill();
133
            }
134
            closeAndRestore();
135
    
136
            saveAndBegin();
137
            x = hc;
138
            y = vc - 40;
139
            grad = c.createRadialGradient(x, y, 200, x, pc, 50);
140
            grad.addColorStop(0,    "rgba(39, 14, 10, 0.1)");
141
            grad.addColorStop(0.58, "rgba(63, 1, 2, 0.2)");
142
            grad.addColorStop(1,    "rgba(195, 11, 0, 0.5)");
143
            c.fillStyle = grad;
144
            c.arc(x, y, 300, 0, 2 * Math.PI, true);
145
            c.fill();
146
            closeAndRestore();
147
    
148
            saveAndBegin();
149
            c.globalCompositeOperation = "darker";
150
            x = hc;
151
            y = vc - 80;
152
            grad = c.createRadialGradient(x, y, 160, x, pc, 50);
153
            grad.addColorStop(0,    "rgba(39, 14, 10, 0.1)");
154
            grad.addColorStop(0.58, "rgba(63, 1, 2, 0.2)");
155
            grad.addColorStop(1,    "rgba(195, 11, 0, 0.5)");
156
            c.fillStyle = grad;
157
            c.arc(x, y, 250, 0, 2 * Math.PI, true);
158
            c.fill();
159
            closeAndRestore();
160
    
161
            saveAndBegin();
162
            x = hc - 10;
163
            y = vc - 10;
164
            grad = c.createRadialGradient(x, y, 100, x, pc, 50);
165
            grad.addColorStop(0,   "rgba(254, 10, 2, 0)");
166
            grad.addColorStop(0.2, "rgba(254, 10, 2, 0.5)");
167
            grad.addColorStop(1,   "rgba(255, 49, 2, 0.5)");
168
            c.fillStyle = grad;
169
            c.arc(x, y, 250, 0, 2 * Math.PI, true);
170
            c.fill();
171
            closeAndRestore();
172
    
173
            saveAndBegin();
174
            x = hc;
175
            y = vc - 30;
176
            grad = c.createRadialGradient(x, y, 100, x, pc, 50);
177
            grad.addColorStop(0,   "rgba(254, 10, 2, 0)");
178
            grad.addColorStop(0.1, "rgba(255, 10, 2, 0.8)");
179
            grad.addColorStop(1,   "rgba(255, 49, 2, 1)");
180
            c.fillStyle = grad;
181
            c.arc(x, y, 250, 0, 2 * Math.PI, true);
182
            c.fill();
183
            closeAndRestore();
184
        }
185
    
186
        function lens() {
187
            var grad,
188
                len,
189
                mod = 364,
190
                pc = vc - 30,
191
                shape;
192
    
193
            /*
194
             * Left
195
             */
196
            saveAndBegin();
197
            c.translate(0, -1000);
198
            c.globalCompositeOperation = "lighter";
199
            c.shadowColor = "rgb(41, 96, 152)";
200
            c.shadowBlur = 10;
201
            c.shadowOffsetY = 1000;
202
            c.shadowOffsetX = -3;
203
            var i = 15;
204
            while (i--) {
205
                c.beginPath();
206
                grad = c.createLinearGradient(0, 0, 0, 200);
207
                grad.addColorStop(0, "rgba(157, 242, 249, 0.5)");
208
                grad.addColorStop(1 - (i / 15), "rgba(157, 242, 249, 0)");
209
                c.strokeStyle = grad;
210
                c.lineWidth = i;
211
                c.arc(hc, pc, 485, 1.08 * Math.PI, 1.3 * Math.PI, false);
212
                c.stroke();
213
                c.closePath();
214
            }
215
            closeAndRestore();
216
    
217
            saveAndBegin();
218
            c.translate(0, -1000);
219
            c.globalCompositeOperation = "lighter";
220
            grad = c.createLinearGradient(0, 0, 0, 150);
221
            grad.addColorStop(0, "rgba(132, 153, 172, 0.1)");
222
            grad.addColorStop(0.2, "rgba(132, 153, 172, 0.8)");
223
            grad.addColorStop(1, "rgba(132, 153, 172, 0)");
224
            c.strokeStyle = grad;
225
            c.shadowBlur = 5;
226
            c.shadowColor = "rgb(41, 96, 152)";
227
            c.shadowOffsetX = -3;
228
            c.shadowOffsetY = 1000;
229
            c.arc(hc, pc, 455, 1.1 * Math.PI, 1.3 * Math.PI, false);
230
            var i = 3;
231
            while (i--) {
232
                c.lineWidth = i * 2;
233
                c.stroke();
234
            }
235
            closeAndRestore();
236
    
237
            /*
238
             * Center
239
             */
240
            saveAndBegin();
241
            c.translate(0, -1000);
242
            c.globalCompositeOperation = "lighter";
243
            c.globalAlpha = 0.4;
244
            grad = c.createLinearGradient(hc - 230, 0, hc + 230, 0);
245
            grad.addColorStop(0, "rgba(0, 0, 0, 0)");
246
            grad.addColorStop(0.4, "rgba(0, 0, 0, 0.5)");
247
            grad.addColorStop(0.7, "rgba(0, 0, 0, 0)");
248
            c.strokeStyle = grad;
249
            c.shadowBlur = 10;
250
            c.shadowColor = "rgb(41, 96, 152)";
251
            c.shadowOffsetY = 1000;
252
            c.lineWidth = 15;
253
            c.arc(hc, pc, 230, 1.2 * Math.PI, 2 * Math.PI, false);
254
            c.stroke();
255
            closeAndRestore();
256
    
257
            /*
258
             * Right
259
             */
260
            saveAndBegin();
261
            c.translate(0, -1000);
262
            c.globalCompositeOperation = "lighter";
263
            grad = c.createLinearGradient(0, 0, 0, 200);
264
            grad.addColorStop(0, "rgba(93, 136, 126, 0.5)");
265
            grad.addColorStop(0.25, "rgba(74, 79, 72, 0.5)");
266
            grad.addColorStop(0.6, "rgba(93, 136, 126, 0.2)");
267
            grad.addColorStop(1, "rgba(74, 79, 72, 0)");
268
            c.lineWidth = 4;
269
            c.strokeStyle = grad;
270
            c.shadowBlur = 5;
271
            c.shadowOffsetX = 3;
272
            c.shadowOffsetY = 1000;
273
            c.shadowColor = "rgb(41, 96, 152)";
274
            c.arc(hc, pc, 455, 1.7 * Math.PI, 2 * Math.PI, false);
275
            var i = 4;
276
            while (i--) {
277
                c.stroke();
278
            }
279
            closeAndRestore();
280
    
281
            /*
282
             * Ceiling lights
283
             */
284
    
285
            // Perspective 1
286
            var offY = 1000,
287
                mult = 3;
288
            c.globalCompositeOperation = "lighter";
289
            c.translate(0, -offY);
290
            c.shadowBlur = 40;
291
            c.shadowColor = "rgb(85, 138, 188)";
292
            c.shadowOffsetY = offY;
293
            c.fillStyle = "rgb(97, 138, 160)";
294
    
295
            // Left
296
            shape = [
297
                { x: 427, y: 18 },
298
                { x: 313, y: 106, cp1x: 324, cp1y: 30 },
299
                { x: 427, y: 21, cp1x: 450, cp1y: 80 }
300
            ];
301
            saveAndBegin();
302
            drawShape(shape);
303
            while (mult--) {
304
                c.fill();
305
            }
306
            closeAndRestore();
307
    
308
            shape = [
309
                { x: 581, y: 240 },
310
                { x: 583, y: 228 },
311
                { x: 563, y: 238 },
312
                { x: 552, y: 253 },
313
                { x: 581, y: 240, cp1x: 570, cp1y: 240 }
314
            ];
315
            saveAndBegin();
316
            drawShape(shape);
317
            mult = 10;
318
            c.shadowBlur = 50;
319
            while (mult--) {
320
                if (mult === 2) {
321
                    c.globalAlpha = 0.6;
322
                    c.shadowBlur = 10;
323
                }
324
                c.fill();
325
            }
326
            closeAndRestore();
327
    
328
            shape = [
329
                { x: 580, y: 282 },
330
                { x: 604, y: 269, cp1x: 595, cp1y: 267 },
331
                { x: 604, y: 256 },
332
                { x: 588, y: 267 },
333
                { x: 580, y: 282 }
334
            ];
335
            saveAndBegin();
336
            drawShape(shape);
337
            mult = 13;
338
            c.shadowBlur = 80;
339
            while (mult--) {
340
                if (mult === 3) {
341
                    c.globalAlpha = 0.6;
342
                    c.shadowBlur = 10;
343
                }
344
                c.fill();
345
            }
346
            closeAndRestore();
347
    
348
            // Center
349
            shape = [
350
                { x: 504, y: 31 },
351
                { x: 524, y: 73 },
352
                { x: 615, y: 28, cp1x: 600, cp1y: 67 },
353
                { x: 777, y: 30, cp1x: 696, cp1y: 15 },
354
                { x: 781, y: 25, cp1x: 785, cp1y: 27 },
355
                { x: 594, y: 11, cp1x: 687, cp1y: 3 },
356
                { x: 504, y: 31 }
357
            ];
358
            saveAndBegin();
359
            drawShape(shape);
360
            mult = 6;
361
            c.shadowBlur = 70;
362
            while (mult--) {
363
                if (mult === 1) {
364
                    c.globalAlpha = 0.2;
365
                    c.shadowBlur = 10;
366
                }
367
                c.fill();
368
            }
369
            closeAndRestore();
370
            
371
            shape = [
372
                { x: 615, y: 222 },
373
                { x: 622, y: 234 },
374
                { x: 650, y: 217, cp1x: 652, cp1y: 225 },
375
                { x: 704, y: 219, cp1x: 675, cp1y: 212 },
376
                { x: 704, y: 217 },
377
                { x: 647, y: 214, cp1x: 675, cp1y: 210 },
378
                { x: 615, y: 222 }
379
            ];
380
            saveAndBegin();
381
            drawShape(shape);
382
            mult = 11;
383
            c.shadowBlur = 60;
384
            while (mult--) {
385
                if (mult === 4) {
386
                    c.globalAlpha = 0.4;
387
                    c.shadowBlur = 7;
388
                }
389
                c.fill();
390
            }
391
            closeAndRestore();
392
    
393
            shape = [
394
                { x: 629, y: 250 },
395
                { x: 634, y: 260 },
396
                { x: 655, y: 246, cp1x: 652, cp1y: 259 },
397
                { x: 698, y: 245, cp1x: 678, cp1y: 240 },
398
                { x: 698, y: 243 },
399
                { x: 661, y: 243, cp1x: 676, cp1y: 240 },
400
                { x: 629, y: 250 }
401
            ];
402
            saveAndBegin();
403
            drawShape(shape);
404
            mult = 11;
405
            c.shadowBlur = 60;
406
            while (mult--) {
407
                if (mult === 4) {
408
                    c.globalAlpha = 0.4;
409
                    c.shadowBlur = 6;
410
                }
411
                c.fill();
412
            }
413
            closeAndRestore();
414
    
415
            // Right
416
            shape = [
417
                { x: 864, y: 12 },
418
                { x: 993, y: 90, cp1x: 960, cp1y: 20 },
419
                { x: 864, y: 12, cp1x: 840, cp1y: 75 }
420
            ];
421
            saveAndBegin();
422
            drawShape(shape);
423
            mult = 5;
424
            c.shadowBlur = 60;
425
            while (mult--) {
426
                if (mult === 2) {
427
                    c.globalAlpha = 0.2;
428
                    c.shadowBlur = 20;
429
                }
430
                c.fill();
431
            }
432
            closeAndRestore();
433
    
434
            shape = [
435
                { x: 779, y: 245 },
436
                { x: 749, y: 236, cp1x: 760, cp1y: 235 },
437
                { x: 749, y: 226 },
438
                { x: 764, y: 230 },
439
                { x: 779, y: 245 }
440
            ];
441
            saveAndBegin();
442
            drawShape(shape);
443
            mult = 10;
444
            c.shadowBlur = 50;
445
            while (mult--) {
446
                if (mult === 3) {
447
                    c.globalAlpha = 0.6;
448
                    c.shadowBlur = 7;
449
                }
450
                c.fill();
451
            }
452
            closeAndRestore();
453
    
454
            shape = [
455
                { x: 756, y: 264 },
456
                { x: 731, y: 256, cp1x: 740, cp1y: 255 },
457
                { x: 732, y: 248 },
458
                { x: 748, y: 253 },
459
                { x: 756, y: 264 }
460
            ];
461
            saveAndBegin();
462
            drawShape(shape);
463
            mult = 10;
464
            c.shadowBlur = 70;
465
            while (mult--) {
466
                if (mult === 3) {
467
                    c.globalAlpha = 0.6;
468
                    c.shadowBlur = 9;
469
                }
470
                c.fill();
471
            }
472
            closeAndRestore();
473
    
474
            // Perspective 2
475
            // Left
476
            shape = [
477
                { x: 382, y: 165 },
478
                { x: 382, y: 172 },
479
                { x: 445, y: 170 },
480
                { x: 445, y: 163 },
481
                { x: 382, y: 165 }
482
            ];
483
            saveAndBegin();
484
            drawShape(shape);
485
            mult = 5;
486
            c.shadowBlur = 50;
487
            while (mult--) {
488
                if (mult === 1) {
489
                    c.globalAlpha = 0.2;
490
                    c.shadowBlur = 7;
491
                }
492
                c.fill();
493
            }
494
            closeAndRestore();
495
    
496
            shape = [
497
                { x: 622, y: 287 },
498
                { x: 605, y: 287 },
499
                { x: 605, y: 289 },
500
                { x: 622, y: 289 },
501
                { x: 622, y: 287 }
502
            ];
503
            saveAndBegin();
504
            drawShape(shape);
505
            mult = 5;
506
            c.shadowBlur = 30;
507
            while (mult--) {
508
                if (mult === 2) {
509
                    c.globalAlpha = 0.7;
510
                    c.shadowBlur = 6;
511
                }
512
                c.fill();
513
            }
514
            closeAndRestore();
515
    
516
            // Right
517
            shape = [
518
                { x: 968, y: 165 },
519
                { x: 968, y: 172 },
520
                { x: 895, y: 170 },
521
                { x: 895, y: 163 },
522
                { x: 968, y: 165 }
523
            ];
524
            saveAndBegin();
525
            drawShape(shape);
526
            mult = 5;
527
            c.shadowBlur = 50;
528
            while (mult--) {
529
                if (mult === 1) {
530
                    c.globalAlpha = 0.2;
531
                    c.shadowBlur = 7;
532
                }
533
                c.fill();
534
            }
535
            closeAndRestore();
536
    
537
            shape = [
538
                { x: 740, y: 269 },
539
                { x: 740, y: 272 },
540
                { x: 760, y: 272 },
541
                { x: 760, y: 269 },
542
                { x: 740, y: 269 }
543
            ];
544
            saveAndBegin();
545
            drawShape(shape);
546
            mult = 5;
547
            c.shadowBlur = 30;
548
            while (mult--) {
549
                if (mult === 1) {
550
                    c.globalAlpha = 0.6;
551
                    c.shadowBlur = 6;
552
                }
553
                c.fill();
554
            }
555
            closeAndRestore();
556
    
557
            shape = [
558
                { x: 728, y: 287 },
559
                { x: 728, y: 290 },
560
                { x: 745, y: 290 },
561
                { x: 745, y: 287 },
562
                { x: 728, y: 287 }
563
            ];
564
            len = shape.length;
565
            saveAndBegin();
566
            drawShape(shape);
567
            mult = 5;
568
            c.shadowBlur = 30;
569
            while (mult--) {
570
                if (mult === 1) {
571
                    c.globalAlpha = 0.7;
572
                    c.shadowBlur = 6;
573
                }
574
                c.fill();
575
            }
576
            closeAndRestore();
577
        }
578
    
579
        function pupil() {
580
            saveAndBegin();
581
            var grad = c.createRadialGradient(hc, vc - 30, 30, hc, vc - 30, 1),
582
                x = hc,
583
                y = vc - 30;
584
            grad.addColorStop(0, "#ffee6b");
585
            grad.addColorStop(0.2, "#fdfc25");
586
            grad.addColorStop(0.4, "#fefe60");
587
            grad.addColorStop(1, "white");
588
            c.shadowColor = "#ffee6b";
589
            c.shadowBlur = 1;
590
            c.fillStyle = grad;
591
            c.globalCompositeOperation = "lighter";
592
            var i = 0;
593
            while (i < 15) {
594
                c.shadowBlur = c.shadowBlur + (i * 0.2);
595
                c.globalAlpha = 0.1 * (i * 0.2);
596
                c.arc(Math.floor(Math.random() * 3) + x, Math.floor(Math.random() * 3) + y, 30, 0, 2 * Math.PI, true); 
597
                c.fill();
598
                i += 1;
599
            }
600
            c.globalCompositeOperation = "source-over";
601
            c.globalAlpha = 0.7;
602
            c.arc(x, y, 30, 0, 2 * Math.PI, true); 
603
            c.fill();
604
            closeAndRestore();
605
        }
606
    
607
        function trim() {
608
            var rad = 600,
609
                grad;
610
    
611
            /*
612
             * Left Trim
613
             */
614
            // thin top highlight
615
            saveAndBegin();
616
            grad = c.createLinearGradient(0, 0, 0, 200);
617
            grad.addColorStop(0, "rgba(36, 64, 68, 0.6)");
618
            grad.addColorStop(1, "rgba(36, 64, 68, 0)");
619
            c.strokeStyle = grad;
620
            var i = 10;
621
            while (i--) {
622
                var x = Math.floor(Math.random() * 3) + hc + 50,
623
                    y = vc - 30;
624
                c.lineWidth = Math.floor(Math.random() * 5) + 1;
625
                c.beginPath();
626
                c.arc(x, y, rad - 10, Math.PI, 1.3 * Math.PI, false); 
627
                c.stroke();
628
                c.closePath();
629
            }
630
            closeAndRestore();
631
    
632
            // thick bottom highlight
633
            saveAndBegin();
634
            grad = c.createLinearGradient(0, vc + 80, 0, canvas.height - 50);
635
            grad.addColorStop(0, "rgba(38, 38, 48, 0)");
636
            grad.addColorStop(0.5, "rgba(42, 52, 54, 0.5)");
637
            grad.addColorStop(1, "rgba(29, 25, 22, 0)");
638
            c.lineWidth = 15;
639
            c.strokeStyle = grad;
640
            c.shadowBlur = 5;
641
            c.shadowColor = "rgb(42, 52, 54)";
642
            c.shadowOffsetX = -3;
643
            var i = 5;
644
            while (i--) {
645
                c.beginPath();
646
                c.arc(hc + 73, vc - 30, rad, 0.97 * Math.PI, 0.82 * Math.PI, true);
647
                c.stroke();
648
                c.closePath();
649
            }
650
            closeAndRestore();
651
    
652
    
653
            // thin inner highlight
654
            saveAndBegin();
655
            grad = c.createLinearGradient(0, 0, 0, canvas.height);
656
            grad.addColorStop(0, "rgba(51, 95, 104, 0.5)");
657
            grad.addColorStop(1, "rgba(38, 37, 45, 0.1)");
658
            c.lineWidth = 2;
659
            c.strokeStyle = grad;
660
            c.shadowBlur = 5;
661
            c.shadowColor = "rgb(51, 95, 104)";
662
            c.shadowOffsetX = -3;
663
            c.shadowOffsetY = -3;
664
            var i = 5;
665
            while (i--) {
666
                c.beginPath();
667
                c.arc(hc + 46 + i, vc - 30, rad - 30, 0.5 * Math.PI, 1.5 * Math.PI, false);
668
                c.stroke();
669
                c.closePath();
670
            }
671
            closeAndRestore();
672
    
673
            // dark highlight overlay
674
            saveAndBegin();
675
            grad = c.createLinearGradient(0, 0, 0, canvas.height);
676
            grad.addColorStop(0, "rgba(24, 53, 67, 0.5)");
677
            grad.addColorStop(1, "rgba(31, 31, 33, 0.5)");
678
            c.lineWidth = 55;
679
            c.strokeStyle = grad;
680
            c.shadowBlur = 8;
681
            c.shadowColor = "rgb(31, 30, 35)";
682
            c.shadowOffsetY = -8;
683
            c.arc(hc + 45, vc - 30, rad - 5, 0.5 * Math.PI, 1.5 * Math.PI, false);
684
            c.stroke();
685
            closeAndRestore();
686
    
687
            // thick outer
688
            saveAndBegin();
689
            grad = c.createLinearGradient(0, 0, 0, canvas.height);
690
            grad.addColorStop(0, "rgba(74, 166, 203, 0.5)");
691
            grad.addColorStop(0.4, "rgba(55, 105, 130, 0.5)");
692
            grad.addColorStop(0.5, "rgba(55, 105, 130, 0.5)");
693
            grad.addColorStop(0.8, "rgba(54, 90, 106, 0.5)");
694
            grad.addColorStop(1, "rgba(32, 64, 87, 0.2)");
695
            c.shadowColor = "rgb(55, 88, 119)";
696
            c.shadowOffsetX = -5;
697
            c.shadowOffsetY = -5;
698
            c.shadowBlur = 15,
699
            c.strokeStyle = grad;
700
            c.lineWidth = 30;
701
            var i = 4;
702
            while (i--) {
703
                c.beginPath();
704
                c.arc(hc + 40, vc - 30, rad + i, 0.5 * Math.PI, 1.5 * Math.PI, false);
705
                c.stroke();
706
                c.closePath();
707
            }
708
            c.beginPath();
709
            grad = c.createLinearGradient(0, 0, 0, canvas.height);
710
            grad.addColorStop(0, "rgba(74, 166, 203, 0.5)");
711
            grad.addColorStop(1, "rgba(32, 64, 87, 0)");
712
            c.strokeStyle = grad;
713
            c.lineWidth = 7;
714
            c.shadowBlur = 10;
715
            c.shadowOffsetX = 5;
716
            c.arc(hc + 25, vc - 30, rad - 30, 0.5 * Math.PI, 1.5 * Math.PI, false);
717
            c.stroke();
718
            closeAndRestore();
719
    
720
            /*
721
             * Right Trim
722
             */
723
            // thick outer
724
            saveAndBegin();
725
            grad = c.createLinearGradient(0, 0, 0, canvas.height);
726
            grad.addColorStop(0, "rgba(75, 155, 188, 0.5)");
727
            grad.addColorStop(0.6, "rgba(31, 26, 32, 0.2)");
728
            grad.addColorStop(1, "rgba(17, 19, 14, 0.1)");
729
            c.shadowColor = "rgb(55, 88, 119)";
730
            c.shadowOffsetX = 3;
731
            c.shadowOffsetY = -7;
732
            c.shadowBlur = 15,
733
            c.strokeStyle = grad;
734
            c.lineWidth = 30;
735
            var i = 4;
736
            while (i--) {
737
                c.beginPath();
738
                c.arc(hc - 40, vc - 30, rad - 10 + i, 1.5 * Math.PI, 0.5 * Math.PI, false);
739
                c.stroke();
740
                c.closePath();
741
            }
742
            c.beginPath();
743
            c.lineWidth = 7;
744
            c.shadowBlur = 10,
745
            c.shadowOffsetX = -5;
746
            c.arc(hc - 35, vc - 30, rad - 30, 1.5 * Math.PI, 0.5 * Math.PI, false);
747
            c.stroke();
748
            closeAndRestore();
749
        }
750
    
751
        hal.draw = function () {
752
            iris();
753
            trim();
754
            pupil();
755
            lens();
756
        };
757
    
758
        // Let's get HAL in the right spot
759
        hal.init = function () {
760
            var w = 1350,
761
                h = 700,
762
                top = Math.round((window.innerHeight - h) * 0.5),
763
                left = Math.round((window.innerWidth - w) * 0.5);
764
            canvas.width = w;
765
            canvas.height = h;
766
            canvas.style.marginTop = top + "px";
767
            canvas.style.marginLeft = left + "px";
768
            vc = h * 0.5;
769
            hc = w * 0.5;
770
            hal.draw();
771
            canvas.onclick = function (e) {
772
                console.log(e.offsetX, e.offsetY);
773
            }
774
        };
775
    
776
        return hal;
777
    };
778
    
779
    window.addEventListener("DOMContentLoaded", function () {
780
        var hal = HAL(document.getElementById("hal"));
781
        hal.init();
782
    }, false);
783
      

It's kind of a mess in there! But it still holds a special place in my heart from a time when I was rapidly learning new skills and pushing myself to create something.

Next

I've been meaning to revisit the project for a while now to improve the code and add some interactivity, so I've started a new version of the experiment here. It's a work in progress and isn't any different from the original as of the time of this writing. Now at least I have a place to work on it occasionally and share it with others.

What do you think? Email me with your questions and comments or share this with others if you found it worthwhile.