PageSourceSearch

https://motive11.com/

html motive11.com collected 2026-10-09 23:50:48 UTC 26,124 bytes, 980 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8" />
5<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6<title>Motive11</title>
7<meta name="description" content="Motive11 is the entity. Mark is the operator. One person plus AI, building web systems and automations from Pittsburgh." />
8<meta name="theme-color" content="#1E1E1F" />
9<meta http-equiv="Content-Language" content="en" />
10<meta name="google" content="notranslate" />
11
12<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32.png" />
13<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png" />
14<link rel="manifest" href="/site.webmanifest" />
15
16<meta property="og:type" content="website" />
17<meta property="og:url" content="https://motive11.com/" />
18<meta property="og:title" content="Motive11" />
19<meta property="og:description" content="Motive11 is the entity. Mark is the operator." />
20<meta property="og:image" content="https://motive11.com/images/icon-512.png" />
21
22<meta name="twitter:card" content="summary_large_image" />
23<meta name="twitter:title" content="Motive11" />
24<meta name="twitter:description" content="Motive11 is the entity. Mark is the operator." />
25<meta name="twitter:image" content="https://motive11.com/images/icon-512.png" />
26
27<link rel="preconnect" href="https://api.fontshare.com">
28<link rel="preconnect" href="https://cdn.fontshare.com" crossorigin>
29<!-- Satoshi: distinctive geometric sans, not Inter, not General Sans -->
30<link href="https://api.fontshare.com/v2/css?f[]=satoshi@900,700,500,400&display=swap" rel="stylesheet">
31
32<!-- Fathom Analytics (motive11.com). Source of truth is AIOS site_traffic; see
33     pipelines/site-analytics. -->
34<script src="https://cdn.usefathom.com/script.js" data-excluded-domains="localhost,127.0.0.1" data-site="UYCRGPKV" defer></script>
34
35
36<style>
37  :root {
38    --bg: #1E1E1F;
39    --fg: #F4F3EF;
40    --fg-dim: rgba(244, 243, 239, 0.55);
41    --fg-faint: rgba(244, 243, 239, 0.18);
42    --orange: #d64916;
43    --sans: "Satoshi", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
44  }
45
46  * { box-sizing: border-box; margin: 0; padding: 0; }
47
48  html, body {
49    width: 100%;
50    height: 100%;
51    overflow: hidden;
52    background: var(--bg);
53    color: var(--fg);
54    font-family: var(--sans);
55    -webkit-font-smoothing: antialiased;
56    -moz-osx-font-smoothing: grayscale;
57  }
58
59  body::before {
60    content: "";
61    position: fixed;
62    inset: 0;
63    pointer-events: none;
64    z-index: 10;
65    opacity: 0.05;
66    mix-blend-mode: overlay;
67    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
68  }
69
70  body::after {
71    content: "";
72    position: fixed;
73    inset: 0;
74    pointer-events: none;
75    z-index: 9;
76    background: radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.55) 100%);
77  }
78
79  #scene {
80    position: fixed;
81    inset: 0;
82    z-index: 0;
83  }
84
85  .progress {
86    position: fixed;
87    top: 0;
88    left: 0;
89    height: 4px;
90    width: 0%;
91    background: var(--orange);
92    z-index: 20;
93    box-shadow: 0 0 12px var(--orange);
94  }
95
96  .station,
97  .footer-mark,
98  .footer-mark-right {
99    position: fixed;
100    z-index: 15;
101    font-size: 10px;
102    letter-spacing: 0.3em;
103    text-transform: uppercase;
104    color: var(--fg-dim);
105    font-weight: 500;
106  }
107  .station {
108    top: 24px;
109    left: 50%;
110    transform: translateX(-50%);
111  }
112  .footer-mark { bottom: 24px; left: 28px; color: var(--fg-faint); }
113  .footer-mark-right { bottom: 24px; right: 28px; color: var(--fg-faint); }
114
115  .footer-mark {
116    display: flex;
117    align-items: center;
118    gap: 14px;
119  }
120  .footer-mark a {
121    color: var(--fg-dim);
122    text-decoration: none;
123    transition: color 0.3s ease;
124  }
125  .footer-mark a:hover,
126  .footer-mark a:focus-visible {
127    color: var(--orange);
128  }
129
130  .pause-btn {
131    position: fixed;
132    right: 28px;
133    bottom: 56px;
134    z-index: 16;
135    width: 40px;
136    height: 40px;
137    display: flex;
138    align-items: center;
139    justify-content: center;
140    background: transparent;
141    border: 1px solid var(--fg-faint);
142    border-radius: 50%;
143    color: var(--orange);
144    cursor: pointer;
145    opacity: 0;
146    transition: opacity 1.2s ease, border-color 0.3s ease, background 0.3s ease;
147    padding: 0;
148    font-family: inherit;
149  }
150
151  .pause-btn.visible {
152    opacity: 0.7;
153  }
154
155  .pause-btn:hover,
156  .pause-btn:focus-visible {
157    opacity: 1;
158    border-color: var(--fg-dim);
159    background: rgba(214, 73, 22, 0.08);
160    outline: none;
161  }
162
163  .pause-btn svg {
164    width: 14px;
165    height: 14px;
166    display: block;
167  }
168
169  .pause-btn .icon-play { display: none; }
170  .pause-btn.paused .icon-play { display: block; }
171  .pause-btn.paused .icon-pause { display: none; }
172
173  .station a {
174    display: block;
175    line-height: 0;
176  }
177
178  .station .logo {
179    display: block;
180    height: 22px;
181    width: auto;
182    opacity: 0.85;
183    transition: opacity 0.3s ease;
184  }
185
186  .station a:hover .logo,
187  .station a:focus-visible .logo {
188    opacity: 1;
189  }
190
191  .station a:focus-visible {
192    outline: 1px solid var(--fg-faint);
193    outline-offset: 6px;
194    border-radius: 2px;
195  }
196
197  .stage {
198    position: fixed;
199    inset: 0;
200    z-index: 5;
201  }
202
203  .slide {
204    position: absolute;
205    inset: 0;
206    display: flex;
207    align-items: center;
208    justify-content: center;
209    padding: 80px 6vw;
210    opacity: 0;
211    pointer-events: none;
212    transition: opacity 0.9s cubic-bezier(0.22, 1, 0.36, 1);
213  }
214
215  .slide.active {
216    opacity: 1;
217    pointer-events: auto;
218  }
219
220  .slide-inner {
221    width: 100%;
222    max-width: 1400px;
223    text-align: left;
224  }
225
226  .big {
227    font-family: var(--sans);
228    font-weight: 500;
229    font-size: clamp(48px, 9vw, 140px);
230    line-height: 1.05;
231    letter-spacing: -0.045em;
232    color: var(--fg);
233  }
234
235  .big.heavy { font-weight: 900; line-height: 0.95; }
236  .big.light { font-weight: 400; line-height: 1.15; }
237
238  .big .orange { color: var(--orange); }
239  .big .dim { color: var(--fg-dim); }
240
241  .slide.url-slide .big {
242    white-space: nowrap;
243    font-size: clamp(36px, 9vw, 140px);
244    letter-spacing: -0.05em;
245  }
246
247  .word {
248    display: inline-block;
249    opacity: 0;
250    transform: translateY(0.4em);
251    transition: opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1),
252                transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
253    white-space: pre;
254  }
255
256  .word.in {
257    opacity: 1;
258    transform: translateY(0);
259  }
260
261  .char {
262    display: inline-block;
263    opacity: 0;
264    transform: translateY(0.3em);
265    transition: opacity 0.3s ease, transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
266    white-space: pre;
267  }
268
269  .char.in {
270    opacity: 1;
271    transform: translateY(0);
272  }
273
274  .cta-slide .big {
275    font-size: clamp(40px, 7vw, 100px);
276  }
277
278  .cta-link {
279    display: inline-block;
280    color: var(--orange);
281    text-decoration: none;
282    position: relative;
283    padding-bottom: 8px;
284  }
285
286  .cta-link::after {
287    content: "";
288    position: absolute;
289    left: 0;
290    bottom: 0;
291    width: 0;
292    height: 3px;
293    background: var(--orange);
294    transition: width 1.4s cubic-bezier(0.22, 1, 0.36, 1) 0.3s;
295  }
296
297  .cta-link.drawn::after {
298    width: 100%;
299  }
300
301  /* Greeting slide: portrait + text */
302  .slide.greeting-slide .slide-inner {
303    display: flex;
304    flex-direction: row;
305    align-items: center;
306    gap: 40px;
307    text-align: left;
308  }
309
310  .portrait {
311    flex-shrink: 0;
312    width: 140px;
313    height: 140px;
314    border-radius: 50%;
315    object-fit: cover;
316    opacity: 0;
317    transform: scale(0.94);
318    transition: opacity 0.9s cubic-bezier(0.22, 1, 0.36, 1),
319                transform 1.2s cubic-bezier(0.22, 1, 0.36, 1);
320    box-shadow: 0 0 0 1px rgba(244, 243, 239, 0.18),
321                0 0 60px rgba(0, 0, 0, 0.5);
322  }
323
324  .portrait.in {
325    opacity: 1;
326    transform: scale(1);
327  }
328
329  .greeting-text {
330    font-family: var(--sans);
331    font-weight: 500;
332    font-size: clamp(36px, 6vw, 84px);
333    line-height: 1.05;
334    letter-spacing: -0.035em;
335    color: var(--fg);
336  }
337
338  .greeting-text .line {
339    display: block;
340  }
341
342  .greeting-text .wave {
343    display: inline-block;
344    margin-right: 0.15em;
345  }
346
347  @media (max-width: 640px) {
348    .portrait { width: 96px; height: 96px; }
349    .slide.greeting-slide .slide-inner {
350      flex-direction: column;
351      align-items: flex-start;
352      gap: 20px;
353    }
354    .greeting-text { font-size: clamp(32px, 9vw, 56px); }
355    .slide { padding: 100px 24px; }
356    .big { font-size: clamp(40px, 13vw, 80px); }
357    .cta-slide .big { font-size: clamp(32px, 10vw, 60px); }
358    .footer-mark, .footer-mark-right { font-size: 9px; letter-spacing: 0.2em; }
359    .station { top: 18px; }
360    .station .logo { height: 18px; }
361    .footer-mark { bottom: 18px; left: 18px; }
362    .footer-mark-right { bottom: 18px; right: 18px; }
363  }
364
365  @media (max-width: 420px) {
366    .footer-mark-right { display: none; }
367  }
368</style>
369</head>
370<body>
371
372<canvas id="scene"></canvas>
373
374<div class="progress" id="progress"></div>
375
376<div class="station">
377  <a href="/" aria-label="Replay from the start">
378    <img class="logo" src="/images/logo-light.svg" alt="Motive11" />
379  </a>
380</div>
381<div class="footer-mark">
382  <span>Motive11, LLC</span>
383  <a href="/terms">Terms</a>
384  <a href="/ai-policy">AI Policy</a>
385</div>
386<div class="footer-mark-right">Est. 2019</div>
387<button class="pause-btn" id="pauseBtn" aria-label="Pause" type="button">
388  <svg class="icon-pause" viewBox="0 0 12 14" fill="currentColor" aria-hidden="true"><rect x="1" y="1" width="3" height="12" rx="0.5"/><rect x="8" y="1" width="3" height="12" rx="0.5"/></svg>
389  <svg class="icon-play" viewBox="0 0 12 14" fill="currentColor" aria-hidden="true"><path d="M2 1.5 L11 7 L2 12.5 Z"/></svg>
390</button>
391
392<div class="stage" id="stage"></div>
393
394<script type="importmap">
395{
396  "imports": {
397    "three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.module.js"
398  }
399}
400</script>
400
401
402<script type="module">
403import * as THREE from "three";
404
405// ============ BACKGROUND ============
406const canvas = document.getElementById("scene");
407const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
408renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
409renderer.setSize(window.innerWidth, window.innerHeight);
410
411const scene = new THREE.Scene();
412scene.fog = new THREE.FogExp2(0x1E1E1F, 0.045);
413
414const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 100);
415camera.position.z = 8;
416
417const particleCount = window.innerWidth < 640 ? 1000 : 2500;
418const positions = new Float32Array(particleCount * 3);
419const colors = new Float32Array(particleCount * 3);
420const sizes = new Float32Array(particleCount);
421const velocities = new Float32Array(particleCount * 3);
422
423const orangeColor = new THREE.Color(0xd64916);
424const whiteColor = new THREE.Color(0xF4F3EF);
425
426for (let i = 0; i < particleCount; i++) {
427  const i3 = i * 3;
428  positions[i3]     = (Math.random() - 0.5) * 45;
429  positions[i3 + 1] = (Math.random() - 0.5) * 30;
430  positions[i3 + 2] = (Math.random() - 0.5) * 35 - 8;
431
432  const isOrange = Math.random() < 0.05;
433  const c = isOrange ? orangeColor : whiteColor;
434  colors[i3]     = c.r;
435  colors[i3 + 1] = c.g;
436  colors[i3 + 2] = c.b;
437
438  sizes[i] = isOrange ? 0.05 + Math.random() * 0.03 : 0.012 + Math.random() * 0.018;
439
440  velocities[i3]     = (Math.random() - 0.5) * 0.0004;
441  velocities[i3 + 1] = (Math.random() - 0.5) * 0.0004;
442  velocities[i3 + 2] = (Math.random() - 0.5) * 0.0003;
443}
444
445const geometry = new THREE.BufferGeometry();
446geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
447geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
448geometry.setAttribute("size", new THREE.BufferAttribute(sizes, 1));
449
450const particleMaterial = new THREE.ShaderMaterial({
451  uniforms: {},
452  vertexShader: `
453    attribute float size;
454    varying vec3 vColor;
455    void main() {
456      vColor = color;
457      vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
458      gl_PointSize = size * (450.0 / -mvPosition.z);
459      gl_Position = projectionMatrix * mvPosition;
460    }
461  `,
462  fragmentShader: `
463    varying vec3 vColor;
464    void main() {
465      vec2 xy = gl_PointCoord.xy - vec2(0.5);
466      float r = length(xy);
467      if (r > 0.5) discard;
468      float alpha = smoothstep(0.5, 0.0, r);
469      gl_FragColor = vec4(vColor, alpha * 0.55);
470    }
471  `,
472  vertexColors: true,
473  transparent: true,
474  depthWrite: false,
475  blending: THREE.AdditiveBlending
476});
477
478const particles = new THREE.Points(geometry, particleMaterial);
479scene.add(particles);
480
481const glowGeom = new THREE.PlaneGeometry(70, 45);
482const glowMat = new THREE.ShaderMaterial({
483  uniforms: { time: { value: 0 } },
484  vertexShader: `
485    varying vec2 vUv;
486    void main() {
487      vUv = uv;
488      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
489    }
490  `,
491  fragmentShader: `
492    uniform float time;
493    varying vec2 vUv;
494    void main() {
495      vec2 center = vec2(0.5 + sin(time * 0.08) * 0.2, 0.5 + cos(time * 0.06) * 0.12);
496      float d = distance(vUv, center);
497      float glow = smoothstep(0.6, 0.0, d) * 0.28;
498      vec3 col = vec3(0.839, 0.286, 0.086) * glow;
499      gl_FragColor = vec4(col, glow);
500    }
501  `,
502  transparent: true,
503  depthWrite: false,
504  blending: THREE.AdditiveBlending
505});
506const glow = new THREE.Mesh(glowGeom, glowMat);
507glow.position.z = -22;
508scene.add(glow);
509
510const clock = new THREE.Clock();
511function animate() {
512  const t = clock.getElapsedTime();
513  const pos = geometry.attributes.position.array;
514
515  for (let i = 0; i < particleCount; i++) {
516    const i3 = i * 3;
517    pos[i3]     += velocities[i3];
518    pos[i3 + 1] += velocities[i3 + 1];
519    pos[i3 + 2] += velocities[i3 + 2];
520
521    if (pos[i3] > 22)  pos[i3] = -22;
522    if (pos[i3] < -22) pos[i3] = 22;
523    if (pos[i3 + 1] > 15)  pos[i3 + 1] = -15;
524    if (pos[i3 + 1] < -15) pos[i3 + 1] = 15;
525    if (pos[i3 + 2] > 18)  pos[i3 + 2] = -18;
526    if (pos[i3 + 2] < -20) pos[i3 + 2] = 12;
527  }
528  geometry.attributes.position.needsUpdate = true;
529
530  particles.rotation.y = t * 0.012;
531  particles.rotation.x = Math.sin(t * 0.06) * 0.015;
532
533  glowMat.uniforms.time.value = t;
534
535  renderer.render(scene, camera);
536  requestAnimationFrame(animate);
537}
538animate();
539
540window.addEventListener("resize", () => {
541  camera.aspect = window.innerWidth / window.innerHeight;
542  camera.updateProjectionMatrix();
543  renderer.setSize(window.innerWidth, window.innerHeight);
544});
545
546// ============ SLIDES ============
547const slides = [
548  {
549    isGreeting: true,
550    hold: 2400
551  },
552  {
553    html: `Thanks for stopping by<br>motive11.com.`,
554    weight: "light",
555    hold: 1600,
556    reveal: "word",
557    revealSpeed: 85
558  },
559  {
560    html: `I'll be quick.<br>I just wanna explain something.`,
561    weight: "light",
562    hold: 1800,
563    reveal: "word",
564    revealSpeed: 80
565  },
566  {
567    html: `Motive11 <span class="dim">is</span> my company.`,
568    weight: "heavy",
569    hold: 1600,
570    reveal: "word",
571    revealSpeed: 100
572  },
573  {
574    html: `Websites. Marketing systems.<br>AI automation. CRM builds.`,
575    weight: "light",
576    hold: 2000,
577    reveal: "word",
578    revealSpeed: 85
579  },
580  {
581    html: `It's <span class="orange">real.</span><br>It gets <span class="orange">real</span> results.`,
582    weight: "heavy",
583    hold: 2200,
584    reveal: "word",
585    revealSpeed: 110
586  },
587  {
588    html: `But I'm going to be<br>straight with you.`,
589    weight: "light",
590    hold: 1600,
591    reveal: "word",
592    revealSpeed: 90
593  },
594  {
595    html: `It's just <span class="orange">me.</span>`,
596    weight: "heavy",
597    hold: 2200,
598    reveal: "word",
599    revealSpeed: 130
600  },
601  {
602    html: `No 20-person agency.<br>No team page<br>with stock photos.`,
603    weight: "",
604    hold: 2200,
605    reveal: "word",
606    revealSpeed: 80
607  },
608  {
609    html: `One person who's spent<br>10+ years figuring out<br>how all this actually connects.`,
610    weight: "light",
611    hold: 2400,
612    reveal: "word",
613    revealSpeed: 80
614  },
615  {
616    html: `Plus <span class="orange">AI,</span> doing the work<br>of many.`,
617    weight: "heavy",
618    hold: 2200,
619    reveal: "word",
620    revealSpeed: 110
621  },
622  {
623    html: `Motive11 <span class="dim">is</span> the vehicle.`,
624    weight: "heavy",
625    hold: 1800,
626    reveal: "word",
627    revealSpeed: 110
628  },
629  {
630    html: `I'm <span class="orange">the driver.</span>`,
631    weight: "heavy",
632    hold: 2200,
633    reveal: "word",
634    revealSpeed: 120
635  },
636  {
637    html: `I run everything<br>through my personal brand.`,
638    weight: "light",
639    hold: 1800,
640    reveal: "word",
641    revealSpeed: 80
642  },
643  {
644    html: `My content. My offers.<br>My YouTube. All of it.`,
645    weight: "light",
646    hold: 2000,
647    reveal: "word",
648    revealSpeed: 80
649  },
650  {
651    html: `Rather than duplicate<br>all of it here,`,
652    weight: "light",
653    hold: 1600,
654    reveal: "word",
655    revealSpeed: 85
656  },
657  {
658    html: `I'm sending you<br>to <span class="orange">the source.</span>`,
659    weight: "heavy",
660    hold: 2200,
661    reveal: "word",
662    revealSpeed: 110
663  },
664  {
665    html: `If you're here<br>to work with me,`,
666    weight: "light",
667    hold: 1500,
668    reveal: "word",
669    revealSpeed: 75
670  },
671  {
672    html: `<span class="orange">markszymanski.co</span>`,
673    weight: "heavy",
674    hold: 1800,
675    reveal: "char",
676    revealSpeed: 42,
677    isURL: true
678  },
679  {
680    html: `<a class="cta-link" id="ctaLink" href="https://markszymanski.co/work-with-me" target="_top" rel="noopener">Find me there →</a>`,
681    weight: "",
682    hold: 999999,
683    reveal: "word",
684    revealSpeed: 140,
685    isCTA: true
686  }
687];
688
689const stage = document.getElementById("stage");
690const progressEl = document.getElementById("progress");
691const pauseBtn = document.getElementById("pauseBtn");
692
693// Fade the pause button in after the first slide has settled
694setTimeout(() => pauseBtn.classList.add("visible"), 2200);
695
696window.addEventListener("keydown", (e) => {
697  if (e.key === " " || e.code === "Space") {
698    e.preventDefault();
699    togglePause();
700  }
701});
702
703// ============ PAUSE STATE ============
704let paused = false;
705let pauseStartedAt = 0;
706const pauseWaiters = new Set();
707
708function togglePause() {
709  if (paused) resume();
710  else pause();
711}
712
713function pause() {
714  if (paused) return;
715  paused = true;
716  pauseStartedAt = performance.now();
717  pauseBtn.classList.add("paused");
718  pauseBtn.setAttribute("aria-label", "Play");
719
720  // Cancel every pending timer, keep remaining ms for resume
721  for (const w of pauseWaiters) {
722    clearTimeout(w.timerId);
723    w.timerId = null;
724    w.remaining = Math.max(0, w.fireAt - pauseStartedAt);
725  }
726}
727
728function resume() {
729  if (!paused) return;
730  const pausedFor = performance.now() - pauseStartedAt;
731  paused = false;
732  pauseBtn.classList.remove("paused");
733  pauseBtn.setAttribute("aria-label", "Pause");
734
735  // Shift progress bar's elapsed-time anchor
736  progressStart += pausedFor;
737
738  // Re-schedule every waiter with its remaining time
739  const now = performance.now();
740  for (const w of pauseWaiters) {
741    w.fireAt = now + w.remaining;
742    w.timerId = setTimeout(w.fire, w.remaining);
743  }
744
745  if (progressRunning) requestAnimationFrame(tickProgress);
746}
747
748pauseBtn.addEventListener("click", togglePause);
749
750function pausableSleep(ms) {
751  return new Promise((resolve) => {
752    const waiter = {
753      fireAt: performance.now() + ms,
754      remaining: ms,
755      timerId: null,
756      fire: () => {
757        pauseWaiters.delete(waiter);
758        resolve();
759      }
760    };
761    pauseWaiters.add(waiter);
762    if (!paused) {
763      waiter.timerId = setTimeout(waiter.fire, ms);
764    }
765    // If paused, waiter just sits in the set; resume() will schedule it.
766  });
767}
768
769function splitForReveal(html, mode) {
770  const tmp = document.createElement("div");
771  tmp.innerHTML = html;
772  const output = document.createElement("div");
773
774  function processNode(node, parent) {
775    if (node.nodeType === Node.TEXT_NODE) {
776      const text = node.nodeValue;
777      if (mode === "word") {
778        const parts = text.split(/(\s+)/);
779        for (const p of parts) {
780          if (p.length === 0) continue;
781          if (/^\s+$/.test(p)) {
782            parent.appendChild(document.createTextNode(p));
783          } else {
784            const span = document.createElement("span");
785            span.className = "word";
786            span.textContent = p;
787            parent.appendChild(span);
788          }
789        }
790      } else if (mode === "char") {
791        for (const ch of text) {
792          if (ch === " ") {
793            parent.appendChild(document.createTextNode(" "));
794          } else {
795            const span = document.createElement("span");
796            span.className = "char";
797            span.textContent = ch;
798            parent.appendChild(span);
799          }
800        }
801      }
802    } else if (node.nodeType === Node.ELEMENT_NODE) {
803      if (node.tagName === "BR") {
804        parent.appendChild(document.createElement("br"));
805      } else {
806        const clone = node.cloneNode(false);
807        parent.appendChild(clone);
808        for (const child of Array.from(node.childNodes)) {
809          processNode(child, clone);
810        }
811      }
812    }
813  }
814
815  for (const child of Array.from(tmp.childNodes)) {
816    processNode(child, output);
817  }
818
819  return output;
820}
821
822function createSlide(slide, index) {
823  const el = document.createElement("div");
824  el.className = "slide"
825    + (slide.isCTA ? " cta-slide" : "")
826    + (slide.isGreeting ? " greeting-slide" : "")
827    + (slide.isURL ? " url-slide" : "");
828
829  const inner = document.createElement("div");
830  inner.className = "slide-inner";
831
832  if (slide.isGreeting) {
833    const img = document.createElement("img");
834    img.className = "portrait";
835    img.src = "/images/mark-szymanski.webp";
836    img.alt = "Mark Szymanski";
837
838    const text = document.createElement("div");
839    text.className = "greeting-text";
840    text.innerHTML = `
841      <span class="line"><span class="word"><span class="wave">👋</span>Hey there.</span></span>
842      <span class="line"><span class="word">I'm Mark Szymanski.</span></span>
843    `;
844
845    inner.appendChild(img);
846    inner.appendChild(text);
847    el.appendChild(inner);
848    return el;
849  }
850
851  const big = document.createElement("div");
852  big.className = "big" + (slide.weight ? " " + slide.weight : "");
853
854  const revealContainer = splitForReveal(slide.html, slide.reveal);
855  while (revealContainer.firstChild) {
856    big.appendChild(revealContainer.firstChild);
857  }
858
859  inner.appendChild(big);
860  el.appendChild(inner);
861  return el;
862}
863
864async function revealSlide(slideEl, mode, speed) {
865  const units = slideEl.querySelectorAll(mode === "word" ? ".word" : ".char");
866  if (units.length === 0) return;
867  for (let i = 0; i < units.length; i++) {
868    units[i].classList.add("in");
869    if (i < units.length - 1) await pausableSleep(speed);
870  }
871  await pausableSleep(300);
872}
873
874async function revealGreeting(slideEl) {
875  const img = slideEl.querySelector(".portrait");
876  const words = slideEl.querySelectorAll(".greeting-text .word");
877  if (img) requestAnimationFrame(() => img.classList.add("in"));
878  await pausableSleep(400);
879  for (let i = 0; i < words.length; i++) {
880    words[i].classList.add("in");
881    if (i < words.length - 1) await pausableSleep(450);
882  }
883  await pausableSleep(400);
884}
885
886// All timing in the sequencer goes through pausableSleep
887const sleep = pausableSleep;
888
889function countRevealUnits(html, mode) {
890  if (!html) return 0;
891  const tmp = document.createElement("div");
892  tmp.innerHTML = html;
893  const text = tmp.textContent || "";
894  if (mode === "word") {
895    return text.trim().split(/\s+/).filter(Boolean).length;
896  }
897  return text.replace(/\s+/g, "").length;
898}
899
900function estimateSlideDuration(slide) {
901  const PRE = 300;
902  const REVEAL_BUFFER = 300;
903  const FADE_OUT = 900;
904  if (slide.isGreeting) {
905    // 400ms initial delay + 2 lines × 450ms stagger + 400ms trailing buffer
906    return PRE + 400 + 2 * 450 + 400 + slide.hold + FADE_OUT;
907  }
908  const units = countRevealUnits(slide.html, slide.reveal);
909  const revealTime = Math.max(0, units - 1) * (slide.revealSpeed || 0);
910  return PRE + revealTime + REVEAL_BUFFER + slide.hold + FADE_OUT;
911}
912
913let progressStart = 0;
914let progressTotal = 0;
915let progressRunning = false;
916
917function tickProgress() {
918  if (!progressRunning || paused) return;
919  const elapsed = performance.now() - progressStart;
920  const pct = Math.min(100, (elapsed / progressTotal) * 100);
921  progressEl.style.width = pct + "%";
922  if (pct < 100) requestAnimationFrame(tickProgress);
923}
924
925async function run() {
926  // total runtime = everything up to the moment the CTA appears
927  progressTotal = slides
928    .filter(s => !s.isCTA)
929    .reduce((sum, s) => sum + estimateSlideDuration(s), 0);
930  // add the pre-run sleep and the CTA's pre-reveal/fade-in so the bar
931  // finishes as the CTA text starts revealing
932  progressTotal += 700 + 300;
933
934  await sleep(700);
935
936  progressStart = performance.now();
937  progressRunning = true;
938  requestAnimationFrame(tickProgress);
939
940  for (let i = 0; i < slides.length; i++) {
941    const slide = slides[i];
942    const el = createSlide(slide, i);
943    stage.appendChild(el);
944
945    void el.offsetHeight;
946
947    el.classList.add("active");
948
949    await sleep(300);
950    if (slide.isGreeting) {
951      await revealGreeting(el);
952    } else {
953      await revealSlide(el, slide.reveal, slide.revealSpeed);
954    }
955
956    if (slide.isCTA) {
957      progressRunning = false;
958      progressEl.style.width = "100%";
959      const link = el.querySelector(".cta-link");
960      if (link) {
961        setTimeout(() => link.classList.add("drawn"), 400);
962      }
963    }
964
965    await sleep(slide.hold);
966
967    if (!slide.isCTA) {
968      el.classList.remove("active");
969      await sleep(900);
970      stage.removeChild(el);
971    }
972  }
973}
974
975run();
976</script>
976
977
978<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4bc70e2c01a94c73b74392e4234840661791215815920" integrity="sha512-L0ha0OXavK/8okipN9F8BtP84dg9DUhPERbBXzwI6dgTA55d2+yweo3pn5CSFYs45/r8md2+xvUPtTdvTNRfjA==" data-cf-beacon='{"version":"2024.11.0","token":"300aae7c47024b3092c1b46969bd2ba9","r":1,"spa":2}' crossorigin="anonymous"></script>
978
979</body>
980</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.