-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
859 lines (769 loc) · 39.4 KB
/
Copy pathindex.html
File metadata and controls
859 lines (769 loc) · 39.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shawn Anston</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;900&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Solar system cursor -->
<canvas id="solarCursor"></canvas>
<!-- Navigation -->
<nav id="nav">
<div class="nav-inner">
<a href="#hero" class="nav-logo">
<img src="bw.png" alt="Shawn Anston logo" class="nav-logo-img">
</a>
<ul class="nav-links" id="navLinks">
<li><a href="#about" data-text="About">About</a></li>
<li><a href="#experience" data-text="Experience">Experience</a></li>
<li><a href="#blog" data-text="Blog">Blog</a></li>
<li><a href="#contact" data-text="Contact">Contact</a></li>
<li><a href="assets/SHAWN_ANSTON_RESUME.pdf" target="_blank" rel="noopener" class="nav-resume" aria-label="Resume (opens in new tab)">Resume ↗</a></li>
</ul>
<button class="nav-toggle" id="navToggle" aria-label="Toggle menu" aria-expanded="false">
<span></span><span></span><span></span>
</button>
</div>
</nav>
<!-- ─── HERO ─── -->
<section id="hero">
<canvas id="particleCanvas"></canvas>
<div class="hero-content">
<div class="hero-tag">
<span class="tag-dot"></span>
Open to new opportunities · H1B · Carrollton, TX
</div>
<h1 class="hero-title">
<span class="title-line">Hi, I'm</span>
<span class="title-name">Shawn<br>Anston.</span>
</h1>
<p class="hero-sub">
I bridge <span class="typed-text" id="typedText"></span><span class="typed-cursor">|</span>
</p>
<div class="hero-actions">
<a href="#experience" class="btn-glow">See My Experience</a>
<a href="#contact" class="btn-ghost">Let's Talk</a>
</div>
</div>
<div class="hero-scroll" aria-hidden="true">
<div class="scroll-line"></div>
<span>scroll</span>
</div>
<div class="hero-grid-overlay" aria-hidden="true"></div>
</section>
<!-- ─── ABOUT ─── -->
<section id="about">
<div class="container">
<div class="section-label">
<span>About</span>
</div>
<div class="about-layout">
<div class="about-left">
<h2 class="section-heading">A bit about me</h2>
<p>
I'm a Mainframe Systems Analyst with 9+ years of experience bridging legacy infrastructure
and modern cloud architecture. I specialize in COBOL, DB2, CICS, and IMS, with proven
success delivering cost-saving Kaizen initiatives and leading cross-functional teams across
onsite and offshore models.
</p>
<p>
From automating CA7 job monitoring with REXX to migrating monolithic apps to AWS microservices,
I'm driven by the challenge of making complex systems simpler, faster, and future-proof.
AWS Certified Cloud Practitioner. Based in Carrollton, Texas.
</p>
<p>
Outside of work, I'm an avid gamer — RPGs and strategy games are my go-to, but I'll pick up
just about anything. I also love football, though it's been a while since I've been on the
field myself. Reading is another big passion of mine; I'll dive into pretty much any topic.
Whether the conversation turns to video games, quantum mechanics, or anime, I'm always up for it —
the one exception might be live sports, since I don't follow them too closely.
</p>
<div class="about-stats">
<div class="stat">
<div class="stat-row"><span class="stat-number" data-target="9">0</span><span class="stat-plus">+</span></div>
<span class="stat-label">Years experience</span>
</div>
<div class="stat">
<div class="stat-row"><span class="stat-number" data-target="97">0</span><span class="stat-plus">%</span></div>
<span class="stat-label">Efficiency gained</span>
</div>
<div class="stat">
<div class="stat-row"><span class="stat-number" data-target="99">0</span><span class="stat-plus">%+</span></div>
<span class="stat-label">SLA compliance</span>
</div>
</div>
</div>
<div class="about-right">
<div class="skills-cloud">
<span class="skill" style="--d:0s">COBOL</span>
<span class="skill" style="--d:.07s">JCL</span>
<span class="skill" style="--d:.14s">DB2 SQL</span>
<span class="skill" style="--d:.21s">CICS</span>
<span class="skill" style="--d:.28s">IMS DB</span>
<span class="skill" style="--d:.35s">REXX</span>
<span class="skill" style="--d:.42s">VSAM</span>
<span class="skill" style="--d:.49s">IBM Z/OS</span>
<span class="skill" style="--d:.56s">AWS</span>
<span class="skill" style="--d:.63s">Microservices</span>
<span class="skill" style="--d:.70s">ServiceNow</span>
<span class="skill" style="--d:.77s">Jira</span>
<span class="skill" style="--d:.84s">Agile/Scrum</span>
<span class="skill" style="--d:.91s">Kaizen</span>
</div>
</div>
</div>
</div>
</section>
<!-- ─── EXPERIENCE ─── -->
<section id="experience">
<div class="container">
<div class="section-label">
<span>Experience</span>
</div>
<h2 class="section-heading">Career journey</h2>
<!-- Key achievements banner -->
<div class="achievements-banner">
<div class="ach-item">
<span class="ach-num">97%</span>
<span class="ach-desc">efficiency gain via REXX automation</span>
</div>
<div class="ach-item">
<span class="ach-num">9+</span>
<span class="ach-desc">years Mainframe expertise</span>
</div>
<div class="ach-item">
<span class="ach-num">$M+</span>
<span class="ach-desc">saved via Kaizen CPU optimization</span>
</div>
</div>
<!-- Experience timeline -->
<div class="exp-timeline">
<article class="exp-card">
<div class="exp-header">
<div>
<h3 class="exp-role">System Analyst</h3>
<p class="exp-company">Tech Mahindra · Automotive Industry Client</p>
</div>
<span class="exp-date">May 2022 – Jan 2026</span>
</div>
<p class="exp-location">United States</p>
<p class="exp-summary">
Led requirements gathering and analysis for Order Management and Shipment Tracking Systems serving global operations.
Acted as primary technical liaison between onsite stakeholders and offshore Mainframe teams.
Coordinated cross-functional Mainframe, Java, and Angular developers for integrated solution delivery.
</p>
<ul class="exp-achievements">
<li>Enabled team to leverage generative AI for day-to-day Mainframe codebase activities, enhancing workflow efficiency</li>
<li>Led cloud migration of multiple on-premise applications to AWS infrastructure</li>
<li>Designed and documented migration strategy converting monolithic Java APIs into microservices architecture</li>
<li>Enhanced Shipment Tracking System to support consigned inventory tracking at external warehouse locations</li>
</ul>
<ul class="fp-stack">
<li>COBOL</li><li>JCL</li><li>AWS</li><li>Java Microservices</li><li>Jira</li><li>Agile</li>
</ul>
</article>
<article class="exp-card">
<div class="exp-header">
<div>
<h3 class="exp-role">Software Engineer</h3>
<p class="exp-company">Tech Mahindra · Automotive Industry Client</p>
</div>
<span class="exp-date">Oct 2017 – May 2022</span>
</div>
<p class="exp-location">Chennai Area, India · 4 yrs 8 mos.</p>
<p class="exp-summary">
Provided production support for critical Inventory Control System supporting manufacturing operations.
Managed incident resolution via ServiceNow achieving 99%+ SLA compliance.
Led Kaizen continuous improvement initiatives optimizing JCL batch processes and SQL programs.
</p>
<ul class="exp-achievements">
<li>Spearheaded Kaizen initiatives reducing CPU utilization — saving millions in annual operational costs via JCL & SQL optimization</li>
<li>Developed REXX automation module for CA7 job scheduler monitoring, cutting manual tracking from 30 minutes to under 1 minute (97% improvement)</li>
<li>Led Endevor to ISPW migration with comprehensive testing, documentation, and zero production incidents</li>
<li>Enhanced Shipment Tracking System integrating Angular frontend with Mainframe COBOL backend and stored procedures</li>
</ul>
<ul class="fp-stack">
<li>COBOL</li><li>DB2</li><li>REXX</li><li>JCL</li><li>ServiceNow</li><li>CA7</li>
</ul>
</article>
<article class="exp-card">
<div class="exp-header">
<div>
<h3 class="exp-role">Associate Software Engineer</h3>
<p class="exp-company">Tech Mahindra · Automotive Industry Client</p>
</div>
<span class="exp-date">Aug 2016 – Oct 2017</span>
</div>
<p class="exp-location">Chennai Area, India · 1 yr 3 mos.</p>
<p class="exp-summary">
Completed comprehensive Mainframe development training in COBOL, JCL, DB2, CICS, and IMS DB.
Executed multi-year IMS to DB2 database migration converting IMS DB calls to DB2 SQL.
Developed COBOL programs and stored procedures for the new Shipment Tracking System.
</p>
<ul class="exp-achievements">
<li>Successfully converted multiple IMS databases to DB2, improving system performance and maintainability</li>
<li>Documented and decommissioned obsolete legacy components, reducing technical debt across the codebase</li>
<li>Contributed to Shipment Tracking System connecting Mainframe backend with Angular frontend</li>
</ul>
<ul class="fp-stack">
<li>COBOL</li><li>IMS DB</li><li>DB2</li><li>CICS</li><li>TSO/ISPF</li><li>Endevor</li>
</ul>
</article>
</div>
</div>
</section>
<!-- ─── BLOG ─── -->
<section id="blog">
<div class="container">
<div class="blog-header">
<div class="section-label"><span>Blog</span></div>
<div class="blog-toggle-row">
<h2 class="section-heading">Thoughts from the void</h2>
<label class="blog-toggle-label" title="Toggle blog visibility">
<input type="checkbox" id="blogToggle" checked>
<span class="toggle-track"></span>
<span id="blogToggleText">Enabled</span>
</label>
</div>
</div>
<div id="blogPlanets" class="blog-planets-wrapper" aria-live="polite">
<!-- Planets rendered by JavaScript -->
</div>
</div>
</section>
<!-- Blog post modal -->
<div id="blogModal" class="blog-modal" role="dialog" aria-modal="true" aria-labelledby="blogModalTitle" tabindex="-1">
<div class="blog-modal-inner">
<button class="blog-modal-close" id="blogModalClose" aria-label="Close post">×</button>
<div id="blogModalContent"></div>
</div>
</div>
<!-- ─── CONTACT ─── -->
<section id="contact">
<div class="container">
<div class="section-label">
<span>Contact</span>
</div>
<div class="contact-layout">
<div class="contact-left">
<h2 class="section-heading">Let's connect<br>and build together.</h2>
<p>
Open to new opportunities in Mainframe systems, cloud migration, or technical leadership.
Whether you have a role in mind or just want to connect — I'd love to hear from you.
</p>
<a href="mailto:shawn.anston@gmail.com" class="btn-glow contact-btn" target="_blank" rel="noopener noreferrer">
Send a message ↗
</a>
</div>
<div class="contact-right">
<div class="contact-card">
<h3>Find me on</h3>
<ul class="contact-list">
<li>
<a href="https://github.com/Anston" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" width="20" fill="currentColor" aria-hidden="true"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.385-1.335-1.755-1.335-1.755-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
<span>GitHub</span>
<span class="link-arrow">↗</span>
</a>
</li>
<li>
<a href="https://www.linkedin.com/in/shawn-anston-a-2725a365/" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" width="20" fill="currentColor" aria-hidden="true"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
<span>LinkedIn</span>
<span class="link-arrow">↗</span>
</a>
</li>
<li>
<a href="mailto:shawn.anston@gmail.com" target="_blank" rel="noopener noreferrer">
<svg viewBox="0 0 24 24" width="20" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 7 10 7 10-7"/></svg>
<span>shawn.anston@gmail.com</span>
<span class="link-arrow">↗</span>
</a>
</li>
<li>
<a href="assets/SHAWN_ANSTON_RESUME.pdf" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" width="20" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
<span>Resume / CV</span>
<span class="link-arrow">↗</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer>
<span class="footer-dot">·</span>
<span id="footerYear"></span>
</footer>
<script>
(() => {
'use strict';
/* ══════════════════════════════════════════════════
BLOG MASTER SWITCH — set to false to hide the entire
blog section without removing any code.
══════════════════════════════════════════════════ */
const BLOG_SECTION_ENABLED = true;
/* ── Typed text ── */
const phrases = [
'legacy & cloud together.',
'Mainframe into the future.',
'COBOL to AWS solutions.',
'complex systems simply.',
];
const el = document.getElementById('typedText');
let pi = 0, ci = 0, deleting = false;
function type() {
const phrase = phrases[pi];
el.textContent = deleting ? phrase.slice(0, ci--) : phrase.slice(0, ci++);
if (!deleting && ci > phrase.length) { deleting = true; setTimeout(type, 1800); return; }
if (deleting && ci < 0) { deleting = false; pi = (pi + 1) % phrases.length; ci = 0; }
setTimeout(type, deleting ? 45 : 80);
}
type();
/* ── Particle canvas ── */
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
const MOUSE_OUT = -9999;
let W, H, particles = [], mouse = { x: MOUSE_OUT, y: MOUSE_OUT };
const N = 90, CONNECT = 130;
function resize() {
W = canvas.width = canvas.offsetWidth;
H = canvas.height = canvas.offsetHeight;
}
resize();
window.addEventListener('resize', resize);
for (let i = 0; i < N; i++) particles.push({
x: Math.random() * W,
y: Math.random() * H,
vx: (Math.random() - 0.5) * 0.4,
vy: (Math.random() - 0.5) * 0.4,
r: Math.random() * 2 + 1,
});
document.addEventListener('mousemove', e => {
const r = canvas.getBoundingClientRect();
if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) {
mouse.x = e.clientX - r.left;
mouse.y = e.clientY - r.top;
} else {
mouse.x = MOUSE_OUT;
mouse.y = MOUSE_OUT;
}
});
function drawParticles() {
ctx.clearRect(0, 0, W, H);
particles.forEach(p => {
p.x += p.vx; p.y += p.vy;
if (p.x < 0) p.x = W; if (p.x > W) p.x = 0;
if (p.y < 0) p.y = H; if (p.y > H) p.y = 0;
// mouse repulsion
const dx = p.x - mouse.x, dy = p.y - mouse.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100) { p.x += dx / dist * 1.5; p.y += dy / dist * 1.5; }
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(120,200,255,0.6)';
ctx.fill();
});
for (let i = 0; i < N; i++) {
for (let j = i + 1; j < N; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < CONNECT) {
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = `rgba(100,180,255,${0.18 * (1 - d / CONNECT)})`;
ctx.lineWidth = 0.8;
ctx.stroke();
}
}
}
requestAnimationFrame(drawParticles);
}
drawParticles();
/* ── Solar system cursor (desktop only) ── */
const solarCanvas = document.getElementById('solarCursor');
const sctx = solarCanvas.getContext('2d');
function resizeSolar() {
solarCanvas.width = window.innerWidth;
solarCanvas.height = window.innerHeight;
}
resizeSolar();
window.addEventListener('resize', resizeSolar);
let stx = window.innerWidth / 2, sty = window.innerHeight / 2;
let scx = stx, scy = sty;
let earthAngle = 0;
const ORBIT_R = 24; // earth orbit radius px
const ORBIT_SPD = 0.06; // radians per frame
const SUN_R = 9;
const EARTH_R = 5;
let isHover = false;
if (window.matchMedia('(pointer: fine)').matches) {
document.addEventListener('mousemove', e => { stx = e.clientX; sty = e.clientY; });
document.querySelectorAll('a, button, [tabindex]').forEach(el => {
el.addEventListener('mouseenter', () => { isHover = true; });
el.addEventListener('mouseleave', () => { isHover = false; });
});
(function animSolar() {
// lerp sun toward mouse – same 0.12 physics as old trail
scx += (stx - scx) * 0.12;
scy += (sty - scy) * 0.12;
// advance earth orbit
earthAngle += isHover ? ORBIT_SPD * 2 : ORBIT_SPD;
sctx.clearRect(0, 0, solarCanvas.width, solarCanvas.height);
// ── orbit ring ──
sctx.beginPath();
sctx.arc(scx, scy, ORBIT_R, 0, Math.PI * 2);
sctx.strokeStyle = 'rgba(255,220,80,0.18)';
sctx.lineWidth = 1;
sctx.stroke();
// ── sun glow ──
const sunGlow = sctx.createRadialGradient(scx, scy, 0, scx, scy, SUN_R * 2.5);
sunGlow.addColorStop(0, 'rgba(255,240,100,0.9)');
sunGlow.addColorStop(0.5, 'rgba(255,160,30,0.6)');
sunGlow.addColorStop(1, 'rgba(255,100,0,0)');
sctx.beginPath();
sctx.arc(scx, scy, SUN_R * 2.5, 0, Math.PI * 2);
sctx.fillStyle = sunGlow;
sctx.fill();
// ── sun core ──
sctx.beginPath();
sctx.arc(scx, scy, SUN_R, 0, Math.PI * 2);
sctx.fillStyle = '#ffd040';
sctx.fill();
// ── earth position ──
const ex = scx + Math.cos(earthAngle) * ORBIT_R;
const ey = scy + Math.sin(earthAngle) * ORBIT_R;
// ── earth glow ──
const earthGlow = sctx.createRadialGradient(ex, ey, 0, ex, ey, EARTH_R * 2.5);
earthGlow.addColorStop(0, 'rgba(80,200,255,0.8)');
earthGlow.addColorStop(1, 'rgba(30,100,255,0)');
sctx.beginPath();
sctx.arc(ex, ey, EARTH_R * 2.5, 0, Math.PI * 2);
sctx.fillStyle = earthGlow;
sctx.fill();
// ── earth core ──
sctx.beginPath();
sctx.arc(ex, ey, EARTH_R, 0, Math.PI * 2);
sctx.fillStyle = '#55bbff';
sctx.fill();
requestAnimationFrame(animSolar);
})();
} else {
solarCanvas.style.display = 'none';
}
/* ── Scroll-reveal ── */
const reveals = document.querySelectorAll('.about-layout, .achievements-banner, .exp-card, .contact-layout, .stat');
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) { e.target.classList.add('visible'); io.unobserve(e.target); }
});
}, { threshold: 0.12 });
reveals.forEach(el => { el.classList.add('reveal'); io.observe(el); });
/* ── Animated counters ── */
const counters = document.querySelectorAll('.stat-number');
const cio = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (!e.isIntersecting) return;
const target = +e.target.dataset.target;
let current = 0;
const step = Math.ceil(target / 40);
const timer = setInterval(() => {
current = Math.min(current + step, target);
e.target.textContent = current;
if (current >= target) clearInterval(timer);
}, 35);
cio.unobserve(e.target);
});
}, { threshold: 0.5 });
counters.forEach(c => cio.observe(c));
/* ── Sticky nav ── */
const nav = document.getElementById('nav');
window.addEventListener('scroll', () => {
nav.classList.toggle('solid', window.scrollY > 40);
}, { passive: true });
/* ── Hamburger ── */
const toggle = document.getElementById('navToggle');
const links = document.getElementById('navLinks');
function toggleNav() {
const open = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', String(!open));
links.classList.toggle('open');
}
toggle.addEventListener('click', toggleNav);
toggle.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleNav(); } });
links.querySelectorAll('a').forEach(a => a.addEventListener('click', () => {
links.classList.remove('open');
toggle.setAttribute('aria-expanded', 'false');
}));
/* ── Footer year ── */
document.getElementById('footerYear').textContent = new Date().getFullYear();
/* ══════════════════════════════════════════════════
BLOG — Planet-style blog section
Master switch is at the top of this script.
Each post has its own `enabled` flag for per-post control.
══════════════════════════════════════════════════ */
const BLOG_POSTS = [
{
id: 'mainframe-to-cloud',
enabled: false,
title: 'From Mainframe to Cloud: Hard-Won Lessons from the Migration Trenches',
date: '2026-04-09',
content: `
<p>After nearly a decade working in the mainframe world and then spending several years leading cloud migration projects, I've accumulated a collection of lessons that nobody tells you before you start. This post is the one I wish someone had handed me before my first migration kickoff meeting.</p>
<h2>The Mainframe Isn't the Enemy</h2>
<p>The first misconception I encounter on almost every migration engagement is the idea that the mainframe is old, fragile, and must be replaced as quickly as possible. In reality, the mainframe is often the most reliable component in the entire ecosystem. It has been running 24/7 for decades. It has survived hardware failures, power outages, and organizational reorgs. Before you move anything off of it, you need to understand <em>why</em> it still works.</p>
<p>When I started the IMS-to-DB2 migration at my organization, we assumed the IMS database structures were arbitrary legacy decisions. Three months in, we discovered that some of those hierarchical structures encoded critical business logic about inventory ownership that had no documentation anywhere else. If we had moved fast without deep analysis, we would have broken a system that had been working perfectly for over fifteen years.</p>
<h2>The Business Logic Is Hidden in the COBOL</h2>
<p>Modern cloud migration tools love to tell you they can "automatically lift and shift" your workloads. For COBOL, this is almost never the whole story. COBOL programs written in the 1990s and 2000s contain decades of undocumented business rules. These rules live in:</p>
<ul>
<li>Conditional branches that haven't been triggered in years but are still valid edge cases</li>
<li>Hardcoded table values that encode business categories your analysts no longer remember</li>
<li>File layout assumptions baked into <code>REDEFINES</code> clauses</li>
<li>Numeric precision behaviors that differ between COBOL and Java or Python</li>
</ul>
<p>My recommendation: before migrating any batch program, run it in parallel with the new system for at least one full fiscal quarter. You will catch discrepancies you never could have anticipated during design.</p>
<h2>JCL Is Your Dependency Map</h2>
<p>One of the most underrated migration tools available to any mainframe engineer is the existing JCL. When I was planning our migration of batch workloads to AWS Batch, I started by mapping every JCL job stream using CA7. What I found was a complex dependency web that the cloud architecture team had no visibility into. Jobs that looked independent were actually chained through GDG datasets and step condition codes in ways that weren't obvious.</p>
<p>I built a simple REXX script that parsed JCLLIB members and output a dependency graph. That graph became the foundational document for our migration sequencing. Cloud architects finally understood why certain workloads could only move after others.</p>
<h2>Performance Baselines Are Non-Negotiable</h2>
<p>Before you cut over any process, you need a clear, signed-off performance baseline for the mainframe version. Elapsed time, CPU consumption, I/O counts, record volumes — capture all of it. After migration, every stakeholder will have a different memory of how fast the old system was. The baseline is your only source of truth.</p>
<p>During our Order Management migration, we had a DB2 stored procedure that ran in under two seconds on the mainframe. The initial Java microservices implementation took twelve seconds due to N+1 query patterns that weren't visible until load testing. Without that baseline, we might have shipped it and nobody would have had the numbers to push back.</p>
<h2>The Human Side Is Harder Than the Technical Side</h2>
<p>The hardest part of every migration I've led wasn't the code. It was getting the offshore Mainframe team, the onsite cloud architects, and the business stakeholders to agree on what "done" meant. Each group had a different definition of success.</p>
<p>What worked for us was establishing a shared Definition of Done document early, with acceptance criteria that all three groups signed off on. It sounds simple. It is simple. But most teams skip it and pay for it later in scope disputes and go-live delays.</p>
<h2>Don't Abandon the Mainframe Mindset</h2>
<p>The Mainframe gave us something valuable: an obsession with reliability, data integrity, and operational rigor. ACID transactions, detailed audit trails, structured error handling — these weren't features, they were foundations. When designing cloud replacements, bring that mindset with you. The cloud gives you elasticity and speed; the mainframe gives you discipline. The best cloud systems combine both.</p>
<h2>Final Thought</h2>
<p>Cloud migration from mainframe is not a technology problem. It is a knowledge preservation problem. The challenge is capturing thirty years of business logic, operational knowledge, and hard-won engineering decisions before they walk out the door with the last mainframe developer. Do that well, and the technical migration becomes straightforward. Skip it, and no amount of cloud tooling will save you.</p>
<blockquote>The goal isn't to replace the mainframe. The goal is to carry everything the mainframe taught you into whatever comes next.</blockquote>
`
}
];
/* ── Planet rendering utilities ── */
function countWords(htmlStr) {
return htmlStr.replace(/<[^>]+>/g, ' ').trim().split(/\s+/).filter(Boolean).length;
}
/* Pre-compute and cache word counts on each post object */
BLOG_POSTS.forEach(post => { post._wordCount = countWords(post.content); });
function planetSize(wordCount) {
const MIN_SIZE = 80, MAX_SIZE = 165, MIN_W = 80, MAX_W = 900;
const clamped = Math.max(MIN_W, Math.min(wordCount, MAX_W));
return Math.round(MIN_SIZE + (clamped - MIN_W) / (MAX_W - MIN_W) * (MAX_SIZE - MIN_SIZE));
}
/* Seeded pseudo-random (mulberry32) for consistent planet appearance */
function seededRng(seed) {
let s = seed >>> 0;
return function() {
s += 0x6d2b79f5;
let t = s;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
function strHash(str) {
let h = 0x811c9dc5;
for (let i = 0; i < str.length; i++) {
h ^= str.charCodeAt(i);
h = (h * 0x01000193) >>> 0;
}
return h;
}
/* Draw a planet onto a canvas using a seeded random */
function drawPlanet(canvas, postId, diameter) {
const ctx = canvas.getContext('2d');
const R = diameter / 2;
const cx = R, cy = R;
const rng = seededRng(strHash(postId));
/* ── Palette themes ── */
const themes = [
{ base: ['#2a6fbf', '#1a3d80', '#0d2450'], bands: ['#3d8fd6', '#1e57a8', '#4ab0f5', '#0a1e4a'] }, // blue gas giant
{ base: ['#c4622a', '#8c3515', '#5a1e08'], bands: ['#e07840', '#a84020', '#d44a14', '#7a2808'] }, // rust/rocky
{ base: ['#3d9e6e', '#1e5c40', '#0c3024'], bands: ['#50c484', '#278054', '#6cd896', '#154530'] }, // teal/ocean
{ base: ['#9b4fbd', '#5e2478', '#3a0f4f'], bands: ['#c070e0', '#7030a0', '#d090f0', '#4a1860'] }, // purple nebula
{ base: ['#c8a832', '#8a6810', '#5a4208'], bands: ['#e8c840', '#a88820', '#f0d060', '#786010'] }, // golden/sandy
{ base: ['#3a7abf', '#1c4a80', '#0a2850'], bands: ['#6080e0', '#2040a0', '#8090f0', '#102060'] }, // deep blue ice
{ base: ['#bf4a3a', '#801c18', '#500c08'], bands: ['#e06050', '#a03028', '#d04040', '#702018'] }, // volcanic red
{ base: ['#4abfbf', '#1a7878', '#084040'], bands: ['#60e0e0', '#2090a0', '#80d0d8', '#105060'] }, // cyan/teal
];
const theme = themes[Math.floor(rng() * themes.length)];
/* Base planet gradient */
const baseGrad = ctx.createRadialGradient(cx - R * 0.3, cy - R * 0.3, R * 0.05, cx, cy, R);
baseGrad.addColorStop(0, theme.base[0]);
baseGrad.addColorStop(0.5, theme.base[1]);
baseGrad.addColorStop(1, theme.base[2]);
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, R - 1, 0, Math.PI * 2);
ctx.clip();
ctx.fillStyle = baseGrad;
ctx.fill();
/* Atmospheric bands */
const numBands = 4 + Math.floor(rng() * 5);
for (let i = 0; i < numBands; i++) {
const y = rng() * diameter;
const h = (rng() * 0.12 + 0.03) * diameter;
const col = theme.bands[Math.floor(rng() * theme.bands.length)];
const alpha = rng() * 0.35 + 0.1;
ctx.beginPath();
ctx.ellipse(cx, y, R, h, 0, 0, Math.PI * 2);
ctx.fillStyle = col + Math.round(alpha * 255).toString(16).padStart(2, '0');
ctx.fill();
}
/* Spots / storms */
const numSpots = Math.floor(rng() * 4);
for (let i = 0; i < numSpots; i++) {
const sx = (rng() * 0.7 + 0.15) * diameter;
const sy = (rng() * 0.7 + 0.15) * diameter;
const sr = (rng() * 0.07 + 0.04) * diameter;
const col = theme.bands[Math.floor(rng() * theme.bands.length)];
const sGrad = ctx.createRadialGradient(sx, sy, 0, sx, sy, sr);
sGrad.addColorStop(0, col + 'cc');
sGrad.addColorStop(1, col + '00');
ctx.beginPath();
ctx.ellipse(sx, sy, sr, sr * 0.65, rng() * Math.PI, 0, Math.PI * 2);
ctx.fillStyle = sGrad;
ctx.fill();
}
/* Specular highlight */
const hiGrad = ctx.createRadialGradient(cx - R * 0.35, cy - R * 0.35, 0, cx - R * 0.15, cy - R * 0.15, R * 0.55);
hiGrad.addColorStop(0, 'rgba(255,255,255,0.22)');
hiGrad.addColorStop(0.5, 'rgba(255,255,255,0.04)');
hiGrad.addColorStop(1, 'rgba(255,255,255,0)');
ctx.beginPath();
ctx.arc(cx, cy, R - 1, 0, Math.PI * 2);
ctx.fillStyle = hiGrad;
ctx.fill();
ctx.restore();
/* Optional ring system (30% chance) */
const hasRing = rng() < 0.3;
if (hasRing) {
const ringRng = seededRng(strHash(postId + '_ring'));
const ringCol = theme.bands[Math.floor(ringRng() * theme.bands.length)];
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(Math.PI * 0.18);
ctx.scale(1, 0.28);
ctx.beginPath();
ctx.arc(0, 0, R * 1.62, 0, Math.PI * 2);
ctx.strokeStyle = ringCol + '55';
ctx.lineWidth = R * 0.22;
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, R * 1.35, 0, Math.PI * 2);
ctx.strokeStyle = ringCol + '33';
ctx.lineWidth = R * 0.12;
ctx.stroke();
ctx.restore();
}
}
/* ── Render blog planets ── */
function hideBlogEverywhere() {
document.getElementById('blog').style.display = 'none';
const blogNavLink = document.querySelector('#navLinks a[href="#blog"]');
if (blogNavLink) blogNavLink.closest('li').style.display = 'none';
}
function renderBlog() {
if (!BLOG_SECTION_ENABLED) {
hideBlogEverywhere();
return;
}
const wrapper = document.getElementById('blogPlanets');
wrapper.innerHTML = '';
const activePosts = BLOG_POSTS.filter(p => p.enabled);
if (activePosts.length === 0) {
hideBlogEverywhere();
return;
}
activePosts.forEach(post => {
const wordCount = post._wordCount;
const diam = planetSize(wordCount);
const date = new Date(post.date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
const item = document.createElement('div');
item.className = 'blog-planet-item reveal';
const btn = document.createElement('button');
btn.className = 'blog-planet-btn';
btn.setAttribute('aria-label', `Read: ${post.title}`);
btn.dataset.postId = post.id;
const cvs = document.createElement('canvas');
cvs.className = 'blog-planet-canvas';
cvs.width = diam;
cvs.height = diam;
cvs.style.width = diam + 'px';
cvs.style.height = diam + 'px';
btn.appendChild(cvs);
drawPlanet(cvs, post.id, diam);
const meta = document.createElement('div');
meta.className = 'blog-planet-meta';
meta.innerHTML = `
<div class="blog-planet-title">${post.title}</div>
<div class="blog-planet-date">${date}</div>
<div class="blog-planet-words">${wordCount} words</div>
`;
item.appendChild(btn);
item.appendChild(meta);
wrapper.appendChild(item);
btn.addEventListener('click', () => openPost(post));
});
/* re-run reveal on new elements */
wrapper.querySelectorAll('.reveal').forEach(el => {
el.classList.remove('reveal');
void el.offsetWidth; // force reflow to restart CSS animation
el.classList.add('reveal');
io.observe(el);
});
}
/* ── Modal ── */
const modal = document.getElementById('blogModal');
const modalClose = document.getElementById('blogModalClose');
const modalContent = document.getElementById('blogModalContent');
function openPost(post) {
const wordCount = post._wordCount;
const date = new Date(post.date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
modalContent.innerHTML = `
<div class="blog-post-header">
<span class="blog-post-label">Blog Post</span>
<h1 class="blog-post-title" id="blogModalTitle">${post.title}</h1>
<div class="blog-post-meta">
<span>${date}</span>
<span>${wordCount} words</span>
</div>
</div>
<div class="blog-post-body">${post.content}</div>
`;
modal.classList.add('open');
modal.focus();
document.body.style.overflow = 'hidden';
}
function closeModal() {
modal.classList.remove('open');
document.body.style.overflow = '';
}
modalClose.addEventListener('click', closeModal);
modal.addEventListener('click', e => { if (e.target === modal) closeModal(); });
document.addEventListener('keydown', e => { if (e.key === 'Escape' && modal.classList.contains('open')) closeModal(); });
/* ── Blog toggle switch ── */
const blogToggle = document.getElementById('blogToggle');
const blogToggleText = document.getElementById('blogToggleText');
const blogSection = document.getElementById('blog');
blogToggle.addEventListener('change', () => {
const enabled = blogToggle.checked;
blogToggleText.textContent = enabled ? 'Enabled' : 'Disabled';
blogSection.classList.toggle('blog-hidden', !enabled);
});
/* ── Init blog ── */
renderBlog();
})();
</script>
</body>
</html>