index.html 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  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>Financial Technology 20/30 — Mr. Tang</title>
  7. <!-- ===========================================
  8. FONTS
  9. Archivo Black = display headlines (heavy, tight, uppercase)
  10. Archivo = body copy
  11. Space Mono = UI labels, years, spec readouts (the "Hextech UI" voice)
  12. =========================================== -->
  13. <link rel="preconnect" href="https://fonts.googleapis.com">
  14. <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  15. <link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Archivo:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
  16. <style>
  17. /* ===========================================
  18. THEME TOKENS — change these to reskin the deck
  19. =========================================== */
  20. :root{
  21. /* Riot palette */
  22. --void: #0F141C; /* primary dark */
  23. --deep: #070A0E; /* deepest bg */
  24. --charcoal: #141A24; /* container fill */
  25. --charcoal2: #1A212D; /* raised fill */
  26. --crimson: #EB0029; /* primary accent */
  27. --gold: #C2A66C; /* secondary */
  28. --light: #F3F3F3; /* type on dark */
  29. --body: #B4BCC8; /* body copy */
  30. --muted: #7E8899; /* labels */
  31. /* Type stack */
  32. --font-display: 'Archivo Black', sans-serif;
  33. --font-body: 'Archivo', sans-serif;
  34. --font-mono: 'Space Mono', monospace;
  35. /* Motion */
  36. --ease: cubic-bezier(.16,1,.3,1);
  37. --dur: .85s;
  38. /* Stage chrome */
  39. --stage-bg: #000;
  40. --slide-bg: var(--void);
  41. }
  42. *{margin:0;padding:0;box-sizing:border-box}
  43. /* ===========================================
  44. FIXED 16:9 STAGE — MANDATORY BASE
  45. (full contents of viewport-base.css)
  46. Slides are authored at 1920x1080 and the whole
  47. stage is scaled by one transform to fit the window.
  48. =========================================== */
  49. html,body{width:100%;height:100%;margin:0;overflow:hidden;background:var(--stage-bg,#000)}
  50. .deck-viewport{position:fixed;inset:0;overflow:hidden;background:var(--stage-bg,#000)}
  51. .deck-stage{position:absolute;left:0;top:0;width:1920px;height:1080px;overflow:hidden;
  52. transform-origin:0 0;background:var(--slide-bg,#fff)}
  53. .slide{position:absolute;inset:0;width:1920px;height:1080px;overflow:hidden;display:block;
  54. visibility:hidden;opacity:0;pointer-events:none;background:var(--slide-bg,#fff)}
  55. .slide.active,.slide.visible{visibility:visible;opacity:1;pointer-events:auto;z-index:1}
  56. img,video,canvas,svg{max-width:100%;max-height:100%}
  57. .deck-controls{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);z-index:1000}
  58. @media print{
  59. html,body{width:1920px;height:auto;overflow:visible;background:#fff}
  60. .deck-viewport{position:static;overflow:visible;background:#fff}
  61. .deck-stage{position:static;width:auto;height:auto;transform:none!important;background:none}
  62. .slide{position:relative;display:block!important;visibility:visible!important;opacity:1!important;
  63. pointer-events:auto!important;width:1920px;height:1080px;break-after:page;page-break-after:always}
  64. .slide:last-child{break-after:auto;page-break-after:auto}
  65. .deck-controls{display:none!important}
  66. }
  67. @media (prefers-reduced-motion: reduce){
  68. *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.2s!important}
  69. }
  70. /* ===========================================
  71. SHARED ATMOSPHERE
  72. Every slide gets: engineering grid + directional
  73. crimson glow + heavy vignette. This is the deck's
  74. single strongest visual signature.
  75. =========================================== */
  76. .atmos{position:absolute;inset:0;pointer-events:none}
  77. .atmos .base{position:absolute;inset:0;background:var(--deep)}
  78. .atmos .grid{position:absolute;inset:0;opacity:.5;
  79. background-image:linear-gradient(rgba(243,243,243,.04) 1px,transparent 1px),
  80. linear-gradient(90deg,rgba(243,243,243,.04) 1px,transparent 1px);
  81. background-size:80px 80px}
  82. .atmos .glow{position:absolute;inset:0;
  83. background:radial-gradient(ellipse 1000px 900px at 20% 55%, rgba(235,0,41,.20), transparent 62%),
  84. radial-gradient(ellipse 800px 700px at 94% 10%, rgba(194,166,108,.09), transparent 60%)}
  85. .atmos .vig{position:absolute;inset:0;
  86. background:radial-gradient(ellipse 1400px 1000px at 45% 50%, transparent 42%, rgba(7,10,14,.84) 90%)}
  87. /* Diagonal crimson shard — the one aggressive angle in the system.
  88. Sits top-right on most slides; mirrored on the closing slide. */
  89. .shard{position:absolute;top:-120px;right:-60px;width:560px;height:560px;opacity:.26;
  90. background:linear-gradient(200deg, rgba(235,0,41,.85), rgba(235,0,41,0) 62%);
  91. clip-path:polygon(100% 0, 100% 46%, 34% 100%, 0 66%);pointer-events:none}
  92. .shard.flip{top:auto;bottom:-120px;left:-60px;right:auto;transform:rotate(180deg)}
  93. /* ===========================================
  94. SLIDE HEADER — kicker + section title
  95. Repeated on every content slide for rhythm.
  96. =========================================== */
  97. .s-head{position:absolute;left:120px;top:104px;right:120px;z-index:3}
  98. .kicker{display:flex;align-items:center;gap:16px;margin-bottom:22px}
  99. .kicker .tick{width:52px;height:3px;background:var(--crimson);flex:none}
  100. .kicker span{font-family:var(--font-mono);font-size:15px;font-weight:700;letter-spacing:.36em;
  101. text-transform:uppercase;color:var(--gold);white-space:nowrap}
  102. .s-head h2{font-family:var(--font-display);text-transform:uppercase;font-size:82px;line-height:.92;
  103. letter-spacing:-.030em;color:var(--light)}
  104. .s-head h2 .hot{color:var(--crimson);text-shadow:0 0 46px rgba(235,0,41,.45)}
  105. /* ===========================================
  106. PANEL — the core container of the design system.
  107. Sharp corners, gold hairline, crimson left edge.
  108. =========================================== */
  109. .panel{background:linear-gradient(160deg, rgba(20,26,36,.94), rgba(15,20,28,.84));
  110. border:1px solid rgba(194,166,108,.30);border-left:3px solid var(--crimson);position:absolute;z-index:3}
  111. .panel.quiet{border-left-color:rgba(194,166,108,.45)}
  112. /* Media frame: photographs always sit in a hard-edged frame,
  113. never bare. Crimson top edge + inner darkening for legibility. */
  114. .media{position:absolute;overflow:hidden;z-index:3;background:var(--charcoal);
  115. border:1px solid rgba(194,166,108,.28)}
  116. .media::before{content:"";position:absolute;left:0;right:0;top:0;height:3px;background:var(--crimson);z-index:2}
  117. .media img{width:100%;height:100%;object-fit:cover;display:block;filter:saturate(1.05) contrast(1.04)}
  118. /* Bottom scrim so overlay captions stay readable on any photo */
  119. .media .scrim{position:absolute;left:0;right:0;bottom:0;height:52%;z-index:1;
  120. background:linear-gradient(180deg,transparent,rgba(7,10,14,.92))}
  121. .media .cap{position:absolute;left:26px;right:26px;bottom:22px;z-index:2}
  122. .media .cap .l{font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.26em;
  123. text-transform:uppercase;color:var(--gold);display:block;margin-bottom:7px}
  124. .media .cap .t{font-family:var(--font-body);font-size:23px;font-weight:600;color:var(--light);line-height:1.25}
  125. .media.sm .cap{left:16px;right:16px;bottom:13px}
  126. .media.sm .cap .t{font-size:16px;font-weight:600}
  127. .media.sm .cap .l{font-size:10px;letter-spacing:.20em;margin-bottom:4px}
  128. /* ===========================================
  129. REVEAL ANIMATION
  130. Elements fade + rise when their slide becomes visible.
  131. Per-element delay via inline --d custom property.
  132. =========================================== */
  133. .reveal{opacity:0;transform:translateY(26px);
  134. transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  135. transition-delay:var(--d,0s)}
  136. .slide.visible .reveal{opacity:1;transform:none}
  137. .reveal-x{opacity:0;transform:translateX(-34px);
  138. transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  139. transition-delay:var(--d,0s)}
  140. .slide.visible .reveal-x{opacity:1;transform:none}
  141. .reveal-w{opacity:0;transform:scaleX(0);transform-origin:left;
  142. transition:opacity .5s var(--ease), transform var(--dur) var(--ease);
  143. transition-delay:var(--d,0s)}
  144. .slide.visible .reveal-w{opacity:1;transform:none}
  145. /* ===========================================
  146. SLIDE 1 — TITLE
  147. =========================================== */
  148. .t-dossier{left:120px;top:236px;width:452px;padding:44px 40px 40px}
  149. .t-dossier h3{font-family:var(--font-mono);font-size:14px;font-weight:700;letter-spacing:.34em;
  150. text-transform:uppercase;color:var(--gold);margin-bottom:30px}
  151. .t-row{padding:19px 0;border-top:1px solid rgba(243,243,243,.09)}
  152. .t-row:first-of-type{border-top:none;padding-top:0}
  153. .t-row .k{font-family:var(--font-mono);font-size:12px;letter-spacing:.24em;text-transform:uppercase;
  154. color:var(--muted);display:block;margin-bottom:9px}
  155. .t-row .v{font-family:var(--font-body);font-size:27px;font-weight:600;color:var(--light);
  156. display:flex;align-items:center;gap:12px}
  157. .t-row .v svg{width:21px;height:21px;flex:none}
  158. .t-row .v em{font-style:normal;color:var(--gold)}
  159. /* Two Instagram handles stacked under one icon: the personal
  160. account leads, the business account sits beneath it in a
  161. lighter weight so the hierarchy stays clear. */
  162. .t-row .v .handles{display:flex;flex-direction:column;gap:7px;min-width:0}
  163. .t-row .v .handles .alt{font-size:20px;font-weight:500;color:var(--body);letter-spacing:-.005em}
  164. .t-head{position:absolute;left:700px;top:262px;right:120px;z-index:3}
  165. .t-head h1{font-family:var(--font-display);text-transform:uppercase;font-size:146px;line-height:.85;
  166. letter-spacing:-.035em;color:var(--light)}
  167. .t-head h1 .accent{color:var(--crimson);text-shadow:0 0 54px rgba(235,0,41,.55);display:block}
  168. .t-head h1 .ln{display:block}
  169. .t-sub{margin-top:46px;padding-top:34px;border-top:1px solid rgba(194,166,108,.30);
  170. font-family:var(--font-body);font-size:25px;line-height:1.55;color:var(--body);max-width:830px}
  171. .t-sub strong{color:var(--light);font-weight:600}
  172. .t-tags{margin-top:40px;display:flex}
  173. .t-tag{font-family:var(--font-mono);font-size:13px;font-weight:700;letter-spacing:.22em;
  174. text-transform:uppercase;color:var(--muted);padding:12px 26px;
  175. border:1px solid rgba(243,243,243,.14);margin-right:-1px}
  176. .t-tag.hot{color:var(--light);border-color:var(--crimson);background:rgba(235,0,41,.13)}
  177. .rail{position:absolute;left:120px;right:120px;bottom:80px;height:1px;background:rgba(243,243,243,.13);z-index:3}
  178. .railtext{position:absolute;left:120px;right:120px;bottom:44px;display:flex;justify-content:space-between;
  179. font-family:var(--font-mono);font-size:13px;letter-spacing:.26em;text-transform:uppercase;
  180. color:var(--muted);z-index:3}
  181. .railtext .hot{color:var(--crimson)}
  182. /* Single-item rail: keep the marker pinned right */
  183. .railtext.right{justify-content:flex-end}
  184. /* ===========================================
  185. SLIDE 2 — BACKGROUND (timeline + thesis)
  186. =========================================== */
  187. .tl{position:absolute;left:120px;top:368px;width:830px;z-index:3}
  188. .tl-item{position:relative;padding-left:74px;padding-bottom:58px}
  189. .tl-item:last-child{padding-bottom:0}
  190. /* vertical connector rail */
  191. .tl-item::before{content:"";position:absolute;left:11px;top:26px;bottom:-4px;width:1px;
  192. background:rgba(194,166,108,.32)}
  193. .tl-item:last-child::before{display:none}
  194. /* node marker — rotated square, Hextech-ish */
  195. .tl-item::after{content:"";position:absolute;left:5px;top:9px;width:14px;height:14px;
  196. background:var(--crimson);transform:rotate(45deg);box-shadow:0 0 18px rgba(235,0,41,.75)}
  197. .tl-item.gold::after{background:var(--gold);box-shadow:0 0 16px rgba(194,166,108,.55)}
  198. .tl-item .yr{font-family:var(--font-mono);font-size:14px;font-weight:700;letter-spacing:.28em;
  199. text-transform:uppercase;color:var(--gold);display:block;margin-bottom:10px}
  200. .tl-item .ttl{font-family:var(--font-body);font-size:38px;font-weight:700;color:var(--light);
  201. line-height:1.1;letter-spacing:-.01em}
  202. .tl-item .det{font-family:var(--font-body);font-size:22px;color:var(--muted);margin-top:8px;line-height:1.45}
  203. /* Right-hand thesis statement */
  204. .thesis{position:absolute;left:1060px;top:342px;right:120px;z-index:3}
  205. .thesis .big{font-family:var(--font-display);text-transform:uppercase;font-size:82px;line-height:.94;
  206. letter-spacing:-.030em;color:var(--light)}
  207. .thesis .big .hot{color:var(--crimson);text-shadow:0 0 44px rgba(235,0,41,.45)}
  208. .thesis .note{margin-top:40px;border-left:3px solid var(--gold);padding:6px 0 6px 26px;
  209. font-family:var(--font-body);font-size:24px;line-height:1.6;color:var(--body)}
  210. /* ===========================================
  211. SLIDE 3 — WHAT I'VE BUILT (ventures + team photo)
  212. =========================================== */
  213. .vent{position:absolute;left:120px;top:316px;width:800px;z-index:3}
  214. .v-row{display:flex;align-items:baseline;gap:26px;padding:26px 0;
  215. border-top:1px solid rgba(243,243,243,.10)}
  216. .v-row:first-child{border-top:none}
  217. .v-row .yr{font-family:var(--font-mono);font-size:15px;font-weight:700;letter-spacing:.16em;
  218. color:var(--gold);width:168px;flex:none;white-space:nowrap}
  219. .v-row .nm{font-family:var(--font-body);font-size:34px;font-weight:700;color:var(--light);
  220. letter-spacing:-.005em;line-height:1.15}
  221. .v-row .nm small{display:block;font-size:20px;font-weight:400;color:var(--muted);
  222. margin-top:6px;letter-spacing:0}
  223. .v-row.now .yr{color:var(--crimson)}
  224. .v-row.now .nm{color:var(--light)}
  225. /* Live badge on the current venture */
  226. .livebadge{display:inline-flex;align-items:center;gap:8px;margin-left:14px;vertical-align:middle;
  227. font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.22em;
  228. text-transform:uppercase;color:var(--crimson);border:1px solid var(--crimson);
  229. background:rgba(235,0,41,.13);padding:5px 10px 4px}
  230. .livebadge i{width:7px;height:7px;background:var(--crimson);display:block;
  231. box-shadow:0 0 10px var(--crimson);animation:pulse 1.8s ease-in-out infinite}
  232. @keyframes pulse{0%,100%{opacity:1}50%{opacity:.25}}
  233. /* ===========================================
  234. SLIDE 4 — OFF THE CLOCK (photo mosaic)
  235. =========================================== */
  236. .mos-strip{position:absolute;left:920px;top:868px;right:120px;z-index:3;
  237. font-family:var(--font-body);font-size:21px;color:var(--muted);line-height:1.5}
  238. .mos-strip b{color:var(--light);font-weight:600}
  239. .mos-strip .dot{color:var(--crimson);padding:0 8px}
  240. /* ===========================================
  241. SLIDE 5 — THE RIG (spec panel + photo triptych)
  242. =========================================== */
  243. .spec{left:120px;top:290px;width:520px;padding:40px 36px 36px}
  244. .spec h3{font-family:var(--font-mono);font-size:13px;font-weight:700;letter-spacing:.32em;
  245. text-transform:uppercase;color:var(--gold);margin-bottom:30px}
  246. .sp-row{padding:26px 0;border-top:1px solid rgba(243,243,243,.10)}
  247. .sp-row:first-of-type{border-top:none;padding-top:0}
  248. .sp-row .k{font-family:var(--font-mono);font-size:12px;letter-spacing:.26em;text-transform:uppercase;
  249. color:var(--muted);display:block;margin-bottom:11px}
  250. .sp-row .v{font-family:var(--font-body);font-size:33px;font-weight:700;color:var(--light);
  251. letter-spacing:-.01em;line-height:1.1}
  252. .sp-row .v span{color:var(--crimson)}
  253. .sp-foot{margin-top:30px;padding-top:24px;border-top:1px solid rgba(194,166,108,.28);
  254. font-family:var(--font-body);font-size:19px;line-height:1.55;color:var(--body)}
  255. /* ===========================================
  256. SLIDE 6 — WHAT IS FINTECH (three pillars)
  257. =========================================== */
  258. /* Flex column so the bottom label pins to the panel floor,
  259. giving all three pillars a shared baseline. */
  260. .pil{top:300px;height:590px;padding:40px 34px 34px;overflow:hidden;
  261. display:flex;flex-direction:column}
  262. .pil .num{position:absolute;right:20px;top:6px;font-family:var(--font-display);font-size:132px;
  263. line-height:1;color:rgba(243,243,243,.045);letter-spacing:-.04em;pointer-events:none}
  264. .pil h3{font-family:var(--font-display);text-transform:uppercase;font-size:40px;line-height:1.0;
  265. letter-spacing:-.025em;color:var(--light);position:relative;z-index:2;min-height:80px}
  266. .pil h3 .hot{color:var(--crimson)}
  267. /* Long single-word headings get a step down so they never
  268. run into the ghost number watermark. */
  269. .pil.long h3{font-size:33px}
  270. .pil .bar{width:64px;height:3px;background:var(--gold);margin:22px 0 24px;position:relative;z-index:2}
  271. .pil p{font-family:var(--font-body);font-size:22px;line-height:1.6;color:var(--body);
  272. position:relative;z-index:2}
  273. .pil p + p{margin-top:16px}
  274. .pil .kv{margin-top:auto;font-family:var(--font-mono);font-size:12px;font-weight:700;
  275. letter-spacing:.24em;text-transform:uppercase;color:var(--gold);position:relative;z-index:2}
  276. /* Inset photo inside pillar 1 */
  277. .pil-media{position:absolute;left:34px;right:34px;bottom:34px;height:196px;overflow:hidden;
  278. border:1px solid rgba(194,166,108,.28);z-index:2}
  279. .pil-media img{width:100%;height:100%;object-fit:cover;display:block}
  280. .pil-media .scrim{position:absolute;inset:0;
  281. background:linear-gradient(180deg,rgba(7,10,14,.15),rgba(7,10,14,.90))}
  282. .pil-media .cap{position:absolute;left:16px;right:16px;bottom:12px;font-family:var(--font-mono);
  283. font-size:11px;font-weight:700;letter-spacing:.20em;text-transform:uppercase;color:var(--gold);
  284. line-height:1.5}
  285. .pil-media .cap b{display:block;font-family:var(--font-body);font-size:16px;font-weight:600;
  286. letter-spacing:0;text-transform:none;color:var(--light);margin-top:5px}
  287. /* ===========================================
  288. SLIDE 7 — CLOSING
  289. =========================================== */
  290. .close-wrap{position:absolute;left:120px;top:288px;right:120px;z-index:3}
  291. .close-wrap h1{font-family:var(--font-display);text-transform:uppercase;font-size:168px;line-height:.84;
  292. letter-spacing:-.038em;color:var(--light)}
  293. .close-wrap h1 .hot{color:var(--crimson);text-shadow:0 0 60px rgba(235,0,41,.55);display:block}
  294. .close-sub{margin-top:44px;font-family:var(--font-body);font-size:29px;line-height:1.55;
  295. color:var(--body);max-width:900px}
  296. .close-sub strong{color:var(--light);font-weight:600}
  297. .cta-grid{position:absolute;left:120px;right:120px;bottom:150px;display:flex;gap:0;z-index:3}
  298. .cta{flex:1;border:1px solid rgba(243,243,243,.14);border-left:3px solid var(--crimson);
  299. margin-right:-1px;padding:30px 32px 28px;
  300. background:linear-gradient(160deg,rgba(20,26,36,.92),rgba(15,20,28,.80))}
  301. .cta .k{font-family:var(--font-mono);font-size:12px;letter-spacing:.26em;text-transform:uppercase;
  302. color:var(--muted);display:block;margin-bottom:14px}
  303. .cta .v{font-family:var(--font-body);font-size:29px;font-weight:600;color:var(--light);
  304. display:flex;align-items:center;gap:13px;line-height:1.2}
  305. .cta .v svg{width:24px;height:24px;flex:none}
  306. .cta .v small{display:block;font-size:18px;font-weight:400;color:var(--muted);margin-top:5px}
  307. /* ===========================================
  308. DECK CHROME — outside the 1920x1080 stage
  309. =========================================== */
  310. .deck-controls{display:flex;align-items:center;gap:18px;
  311. background:rgba(7,10,14,.82);border:1px solid rgba(194,166,108,.26);padding:9px 16px;
  312. backdrop-filter:blur(8px);opacity:.28;transition:opacity .3s ease}
  313. .deck-controls:hover{opacity:1}
  314. .dots{display:flex;gap:9px}
  315. .dot{width:9px;height:9px;background:rgba(243,243,243,.22);cursor:pointer;
  316. transition:background .25s ease,transform .25s ease;border:none;padding:0}
  317. .dot:hover{background:rgba(243,243,243,.5)}
  318. .dot.on{background:var(--crimson);transform:rotate(45deg);box-shadow:0 0 12px rgba(235,0,41,.8)}
  319. .count{font-family:var(--font-mono);font-size:12px;letter-spacing:.2em;color:var(--muted);
  320. min-width:52px;text-align:right}
  321. .count b{color:var(--light);font-weight:700}
  322. /* Progress bar pinned to the very bottom of the window */
  323. .progress{position:fixed;left:0;bottom:0;height:3px;background:var(--crimson);z-index:1001;
  324. box-shadow:0 0 14px rgba(235,0,41,.9);transition:width .5s var(--ease);width:0}
  325. /* ===========================================
  326. INLINE EDIT MODE
  327. Hover the top-left hotzone, or press E.
  328. =========================================== */
  329. .edit-hotzone{position:fixed;top:0;left:0;width:80px;height:80px;z-index:10000;cursor:pointer}
  330. .edit-toggle{position:fixed;top:16px;left:16px;z-index:10001;width:44px;height:44px;
  331. background:rgba(7,10,14,.92);border:1px solid var(--crimson);color:var(--light);
  332. font-size:18px;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .3s ease}
  333. .edit-toggle.show,.edit-toggle.active{opacity:1;pointer-events:auto}
  334. .edit-toggle.active{background:var(--crimson)}
  335. body.editing [contenteditable="true"]{outline:1px dashed rgba(194,166,108,.65);
  336. outline-offset:4px;cursor:text}
  337. body.editing [contenteditable="true"]:focus{outline:2px solid var(--crimson);background:rgba(235,0,41,.07)}
  338. .edit-hint{position:fixed;top:16px;left:72px;z-index:10001;
  339. background:rgba(7,10,14,.94);border:1px solid rgba(194,166,108,.4);
  340. font-family:var(--font-mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;
  341. color:var(--gold);padding:13px 16px;display:none;white-space:nowrap}
  342. body.editing .edit-hint{display:block}
  343. </style>
  344. </head>
  345. <body>
  346. <div class="deck-viewport">
  347. <main class="deck-stage" id="deckStage">
  348. <!-- ===========================================
  349. SLIDE 1 — TITLE
  350. Asymmetric: metadata dossier left, oversized
  351. headline offset right.
  352. =========================================== -->
  353. <section class="slide title-slide active visible" aria-label="Title">
  354. <div class="atmos"><div class="base"></div><div class="grid"></div><div class="glow"></div><div class="vig"></div></div>
  355. <div class="shard"></div>
  356. <aside class="panel t-dossier reveal-x" style="--d:.40s">
  357. <h3>Course File // 10/20/30</h3>
  358. <div class="t-row"><span class="k">Instructor</span><span class="v">Mr. Tang</span></div>
  359. <div class="t-row"><span class="k">Room</span><span class="v"><em>2303</em></span></div>
  360. <div class="t-row"><span class="k">Emergencies</span><span class="v">
  361. <svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="2" y="6" width="13" height="12" rx="1.5" stroke="#EB0029" stroke-width="1.8"/><path d="M15 10.5L22 7v10l-7-3.5z" fill="#C2A66C"/></svg>
  362. Google Chat</span></div>
  363. <div class="t-row"><span class="k">Instagram</span><span class="v">
  364. <svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="4" stroke="#EB0029" stroke-width="1.8"/><circle cx="12" cy="12" r="4.2" stroke="#C2A66C" stroke-width="1.8"/><circle cx="17.4" cy="6.6" r="1.2" fill="#C2A66C"/></svg>
  365. <span class="handles">
  366. <span>_michtang</span>
  367. <span class="alt">codeninjassprucegrove</span>
  368. </span></span></div>
  369. </aside>
  370. <div class="t-head">
  371. <div class="kicker reveal" style="--d:.14s"><span class="tick"></span><span>Semester One</span></div>
  372. <h1>
  373. <span class="ln reveal" style="--d:.26s">Financial</span>
  374. <span class="accent reveal" style="--d:.38s">Technology</span>
  375. </h1>
  376. <p class="t-sub reveal" style="--d:.58s">How money works investing, entrepreneurship.</p>
  377. <div class="t-tags reveal" style="--d:.72s">
  378. <span class="t-tag hot">Financial Literacy</span>
  379. <span class="t-tag">Entrepreneurship</span>
  380. <span class="t-tag">Work Ethic</span>
  381. </div>
  382. </div>
  383. <div class="rail reveal" style="--d:.9s"></div>
  384. <div class="railtext right reveal" style="--d:.95s">
  385. <span class="hot">Day One</span>
  386. </div>
  387. </section>
  388. <!-- ===========================================
  389. SLIDE 2 — BACKGROUND
  390. Vertical timeline left, thesis statement right.
  391. No photograph: the type carries this one.
  392. =========================================== -->
  393. <section class="slide" aria-label="Background">
  394. <div class="atmos"><div class="base"></div><div class="grid"></div><div class="glow"></div><div class="vig"></div></div>
  395. <div class="shard"></div>
  396. <header class="s-head">
  397. <div class="kicker reveal" style="--d:.10s"><span class="tick"></span><span>01 &nbsp;/&nbsp; Background</span></div>
  398. <h2 class="reveal" style="--d:.18s">Where I <span class="hot">Started</span></h2>
  399. </header>
  400. <div class="tl">
  401. <div class="tl-item reveal-x" style="--d:.34s">
  402. <span class="yr">2011</span>
  403. <div class="ttl">Bachelor of Commerce</div>
  404. <div class="det">Business first &mdash; accounting, finance, how companies actually work.</div>
  405. </div>
  406. <div class="tl-item reveal-x" style="--d:.46s">
  407. <span class="yr">2013</span>
  408. <div class="ttl">Bachelor of Education</div>
  409. <div class="det">Then the decision to teach it instead of just do it.</div>
  410. </div>
  411. <div class="tl-item gold reveal-x" style="--d:.58s">
  412. <span class="yr">Classrooms</span>
  413. <div class="ttl">Grades 3, 5 and 11</div>
  414. <div class="det">Elementary to senior high &mdash; and a stretch teaching in the United Kingdom.</div>
  415. </div>
  416. </div>
  417. <div class="thesis">
  418. <div class="big reveal" style="--d:.52s">Commerce<br>first.<br><span class="hot">Classroom</span><br>second.</div>
  419. <p class="note reveal" style="--d:.70s">Which means this course isn&rsquo;t theory I read in a textbook. It&rsquo;s the stuff I had to learn the expensive way.</p>
  420. </div>
  421. </section>
  422. <!-- ===========================================
  423. SLIDE 3 — WHAT I'VE BUILT
  424. Venture list left, Code Ninjas team photo right.
  425. =========================================== -->
  426. <section class="slide" aria-label="What I have built">
  427. <div class="atmos"><div class="base"></div><div class="grid"></div><div class="glow"></div><div class="vig"></div></div>
  428. <div class="shard"></div>
  429. <header class="s-head">
  430. <div class="kicker reveal" style="--d:.10s"><span class="tick"></span><span>02 &nbsp;/&nbsp; Track Record</span></div>
  431. <h2 class="reveal" style="--d:.18s">What I&rsquo;ve <span class="hot">Built</span></h2>
  432. </header>
  433. <div class="vent">
  434. <div class="v-row reveal-x" style="--d:.34s">
  435. <span class="yr">Early</span>
  436. <div class="nm">T&#8209;Shirt Business<small>Design, inventory, margins &mdash; the first experiment.</small></div>
  437. </div>
  438. <div class="v-row reveal-x" style="--d:.44s">
  439. <span class="yr">2015-2016</span>
  440. <div class="nm">Website Development<small>Building for clients, quoting work, getting paid.</small></div>
  441. </div>
  442. <div class="v-row reveal-x" style="--d:.54s">
  443. <span class="yr">2017 &ndash; 2020</span>
  444. <div class="nm">Ethereum Mining<small>Hardware, power costs, and a very volatile asset.</small></div>
  445. </div>
  446. <div class="v-row now reveal-x" style="--d:.64s">
  447. <span class="yr">2021 &ndash; Present</span>
  448. <div class="nm">Code Ninjas<span class="livebadge"><i></i>Live</span><small>Co&#8209;owner of two locations: Spruce Grove &amp; North Edmonton.</small></div>
  449. </div>
  450. </div>
  451. <figure class="media reveal" style="left:990px;top:296px;width:810px;height:580px;--d:.48s">
  452. <img src="img/code-ninjas-team.png" alt="Code Ninjas staff team photo in front of a lime green CREATE wall">
  453. <div class="scrim"></div>
  454. <figcaption class="cap">
  455. <span class="l">Code Ninjas &middot; Spruce Grove</span>
  456. <span class="t">The team. Payroll, rent, marketing, hiring &mdash; every month, whether it&rsquo;s busy or not.</span>
  457. </figcaption>
  458. </figure>
  459. </section>
  460. <!-- ===========================================
  461. SLIDE 4 — OFF THE CLOCK
  462. Photo mosaic: one hero + two mid + a trio strip.
  463. =========================================== -->
  464. <section class="slide" aria-label="Personal interests">
  465. <div class="atmos"><div class="base"></div><div class="grid"></div><div class="glow"></div><div class="vig"></div></div>
  466. <div class="shard"></div>
  467. <header class="s-head">
  468. <div class="kicker reveal" style="--d:.10s"><span class="tick"></span><span>03 &nbsp;/&nbsp; Off the Clock</span></div>
  469. <h2 class="reveal" style="--d:.18s">When I&rsquo;m <span class="hot">Not Here</span></h2>
  470. </header>
  471. <!-- Hero: League profile -->
  472. <figure class="media reveal" style="left:120px;top:300px;width:760px;height:560px;--d:.34s">
  473. <img src="img/league-rank.png" alt="League of Legends profile showing Gold III rank, Honor Level 5, mastery score 331">
  474. </figure>
  475. <!-- Mid row -->
  476. <figure class="media sm reveal" style="left:920px;top:300px;width:425px;height:250px;--d:.44s">
  477. <img src="img/badminton.png" alt="Badminton racket and shuttlecocks on a gymnasium floor">
  478. </figure>
  479. <figure class="media sm reveal" style="left:1375px;top:300px;width:425px;height:250px;--d:.52s">
  480. <img src="img/budaejigae.png" alt="Korean army stew simmering in a metal pot">
  481. </figure>
  482. <!-- Starbucks trio strip -->
  483. <figure class="media sm reveal" style="left:920px;top:580px;width:273px;height:250px;--d:.60s">
  484. <img src="img/sbux-summer.png" alt="Starbucks summer berry refresher">
  485. </figure>
  486. <figure class="media sm reveal" style="left:1223px;top:580px;width:273px;height:250px;--d:.66s">
  487. <img src="img/sbux-mango.png" alt="Two Starbucks mango dragonfruit refreshers">
  488. </figure>
  489. <figure class="media sm reveal" style="left:1526px;top:580px;width:274px;height:250px;--d:.72s">
  490. <img src="img/sbux-cranberry.png" alt="Starbucks iced cranberry drink">
  491. </figure>
  492. <p class="mos-strip reveal" style="--d:.80s">
  493. Also: <b>running</b>, <b>weight lifting</b>, and an <b>indefensible Starbucks habit</b>
  494. <span class="dot">&mdash;</span> ask me what that one costs per year.
  495. </p>
  496. </section>
  497. <!-- ===========================================
  498. SLIDE 5 — THE RIG
  499. Spec panel + two-photo triptych.
  500. =========================================== -->
  501. <section class="slide" aria-label="The rig">
  502. <div class="atmos"><div class="base"></div><div class="grid"></div><div class="glow"></div><div class="vig"></div></div>
  503. <div class="shard"></div>
  504. <header class="s-head">
  505. <div class="kicker reveal" style="--d:.10s"><span class="tick"></span><span>04 &nbsp;/&nbsp; Hardware</span></div>
  506. <h2 class="reveal" style="--d:.18s">The <span class="hot">Rig</span></h2>
  507. </header>
  508. <aside class="panel spec reveal-x" style="--d:.34s">
  509. <h3>System Spec</h3>
  510. <div class="sp-row"><span class="k">Processor</span><span class="v">Ryzen 9 <span>3900X</span></span></div>
  511. <div class="sp-row"><span class="k">Memory</span><span class="v">64 GB <span>DDR4</span></span></div>
  512. <div class="sp-row"><span class="k">Graphics</span><span class="v">GeForce <span>RTX 3090</span></span></div>
  513. <p class="sp-foot">Built piece by piece, paid for by the businesses on the last slide. Every part was a decision about cost versus what it actually returns.</p>
  514. </aside>
  515. <figure class="media reveal" style="left:700px;top:290px;width:740px;height:600px;--d:.46s">
  516. <img src="img/gaming-setup.png" alt="Triple curved monitor gaming desk with red ambient lighting and a racing chair">
  517. </figure>
  518. <figure class="media reveal" style="left:1480px;top:290px;width:320px;height:600px;--d:.56s">
  519. <img src="img/gaming-rig.png" alt="Open PC case interior showing RGB memory, liquid cooler and RTX graphics card">
  520. <div class="scrim"></div>
  521. </section>
  522. <!-- ===========================================
  523. SLIDE 6 — WHAT IS FINTECH
  524. Three pillars. Pillar 1 carries an inset photo.
  525. =========================================== -->
  526. <section class="slide" aria-label="What is fintech">
  527. <div class="atmos"><div class="base"></div><div class="grid"></div><div class="glow"></div><div class="vig"></div></div>
  528. <div class="shard"></div>
  529. <header class="s-head">
  530. <div class="kicker reveal" style="--d:.10s"><span class="tick"></span><span>05 &nbsp;/&nbsp; The Course</span></div>
  531. <h2 class="reveal" style="--d:.18s">So What Is <span class="hot">Fintech?</span></h2>
  532. </header>
  533. <article class="panel pil reveal" style="left:120px;width:533px;--d:.34s">
  534. <span class="num">01</span>
  535. <h3>Financial<br><span class="hot">Literacy</span></h3>
  536. <div class="bar"></div>
  537. <p>How money actually works. Saving, investing, what an asset is, and why some things go up while other things quietly go to zero.</p>
  538. <div class="pil-media">
  539. <img src="img/pokemon-collection.png" alt="Graded Pokemon trading cards in protective cases">
  540. <div class="scrim"></div>
  541. <div class="cap">Condition, scarcity, demand<b>Everything is a market.</b></div>
  542. </div>
  543. </article>
  544. <article class="panel pil quiet long reveal" style="left:693px;width:533px;--d:.44s">
  545. <span class="num">02</span>
  546. <h3>Entrepre<span class="hot">neurship</span></h3>
  547. <div class="bar"></div>
  548. <p>What running a business really means &mdash; twelve months a year, not just when it&rsquo;s fun.</p>
  549. <p>Where the cash actually goes, and then planning and running your own.</p>
  550. <span class="kv">Fintech 20 &amp; 30</span>
  551. </article>
  552. <article class="panel pil quiet reveal" style="left:1266px;width:534px;--d:.54s">
  553. <span class="num">03</span>
  554. <h3>Work Ethic<br>&amp; <span class="hot">Attitude</span></h3>
  555. <div class="bar"></div>
  556. <p>The part no spreadsheet teaches. Showing up, doing the unglamorous work, and how you handle it when a plan doesn&rsquo;t survive contact with reality.</p>
  557. <p>This is the one that transfers to everything else.</p>
  558. <span class="kv">Every Job You&rsquo;ll Ever Have</span>
  559. </article>
  560. </section>
  561. <!-- ===========================================
  562. SLIDE 7 — CLOSING CTA
  563. =========================================== -->
  564. <section class="slide" aria-label="Closing">
  565. <div class="atmos"><div class="base"></div><div class="grid"></div><div class="glow"></div><div class="vig"></div></div>
  566. <div class="shard flip"></div>
  567. <div class="close-wrap">
  568. <div class="kicker reveal" style="--d:.10s"><span class="tick"></span><span>Let&rsquo;s Get Started</span></div>
  569. <h1>
  570. <span class="reveal" style="--d:.24s">See You In</span>
  571. <span class="hot reveal" style="--d:.36s">Room 2303</span>
  572. </h1>
  573. <p class="close-sub reveal" style="--d:.56s">Bring a notebook and a question about money you&rsquo;ve always wanted answered. <strong>We&rsquo;ll start there.</strong></p>
  574. </div>
  575. <div class="cta-grid">
  576. <div class="cta reveal" style="--d:.70s">
  577. <span class="k">Can&rsquo;t make class</span>
  578. <span class="v">
  579. <svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="2" y="6" width="13" height="12" rx="1.5" stroke="#EB0029" stroke-width="1.8"/><path d="M15 10.5L22 7v10l-7-3.5z" fill="#C2A66C"/></svg>
  580. <span>Google Meet<small>Link posted before each session</small></span>
  581. </span>
  582. </div>
  583. <div class="cta reveal" style="--d:.78s">
  584. <span class="k">Instagram</span>
  585. <span class="v">
  586. <svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="4" stroke="#EB0029" stroke-width="1.8"/><circle cx="12" cy="12" r="4.2" stroke="#C2A66C" stroke-width="1.8"/><circle cx="17.4" cy="6.6" r="1.2" fill="#C2A66C"/></svg>
  587. <span>@_michtang<small>Course updates and reminders</small></span>
  588. </span>
  589. </div>
  590. <div class="cta reveal" style="--d:.86s">
  591. <span class="k">Find me outside school</span>
  592. <span class="v">
  593. <svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M12 2.5L21 7v10l-9 4.5L3 17V7z" stroke="#EB0029" stroke-width="1.8"/><path d="M8.5 10.5l3.5 3.5 3.5-3.5" stroke="#C2A66C" stroke-width="1.8"/></svg>
  594. <span>Code Ninjas<small>Spruce Grove &amp; North Edmonton</small></span>
  595. </span>
  596. </div>
  597. </div>
  598. <div class="rail reveal" style="--d:.94s"></div>
  599. <div class="railtext reveal" style="--d:.98s">
  600. <span>Financial Technology 20 / 30 &nbsp;&middot;&nbsp; Mr. Tang</span>
  601. <span class="hot">Room 2303</span>
  602. </div>
  603. </section>
  604. </main>
  605. </div>
  606. <!-- ===========================================
  607. DECK CHROME (outside the scaled stage)
  608. =========================================== -->
  609. <nav class="deck-controls" aria-label="Slide navigation">
  610. <div class="dots" id="dots"></div>
  611. <div class="count"><b id="cur">1</b> / <span id="tot">7</span></div>
  612. </nav>
  613. <div class="progress" id="progress"></div>
  614. <!-- Inline editing affordance -->
  615. <div class="edit-hotzone" aria-hidden="true"></div>
  616. <button class="edit-toggle" id="editToggle" title="Edit mode (E)" aria-label="Toggle edit mode">&#9998;</button>
  617. <div class="edit-hint">Click any text to edit &nbsp;&middot;&nbsp; Ctrl+S saves &nbsp;&middot;&nbsp; E exits</div>
  618. <script>
  619. /* ===========================================
  620. SLIDE PRESENTATION CONTROLLER
  621. Handles: fixed-stage scaling, keyboard / touch /
  622. wheel navigation, progress chrome.
  623. =========================================== */
  624. class SlidePresentation {
  625. constructor(){
  626. this.slides = Array.from(document.querySelectorAll('.slide'));
  627. this.stage = document.getElementById('deckStage');
  628. this.current = 0;
  629. this.buildDots();
  630. this.setupStageScale();
  631. this.setupKeyboardNav();
  632. this.setupTouchNav();
  633. this.setupWheelNav();
  634. this.showSlide(0);
  635. }
  636. /* --- Scale the 1920x1080 stage uniformly into the window.
  637. Letterboxes / pillarboxes; never reflows content. --- */
  638. setupStageScale(){
  639. const scale = () => {
  640. const f = Math.min(window.innerWidth/1920, window.innerHeight/1080);
  641. const x = (window.innerWidth - 1920*f)/2;
  642. const y = (window.innerHeight - 1080*f)/2;
  643. this.stage.style.transform = `translate(${x}px, ${y}px) scale(${f})`;
  644. };
  645. scale();
  646. window.addEventListener('resize', scale);
  647. }
  648. buildDots(){
  649. const wrap = document.getElementById('dots');
  650. document.getElementById('tot').textContent = this.slides.length;
  651. this.slides.forEach((_, i) => {
  652. const b = document.createElement('button');
  653. b.className = 'dot';
  654. b.setAttribute('aria-label', 'Go to slide ' + (i+1));
  655. b.addEventListener('click', () => this.showSlide(i));
  656. wrap.appendChild(b);
  657. });
  658. this.dots = Array.from(wrap.children);
  659. }
  660. setupKeyboardNav(){
  661. document.addEventListener('keydown', e => {
  662. // Never hijack keys while the user is editing text
  663. if (e.target.getAttribute && e.target.getAttribute('contenteditable') === 'true') return;
  664. switch(e.key){
  665. case 'ArrowRight': case 'ArrowDown': case ' ': case 'PageDown':
  666. e.preventDefault(); this.next(); break;
  667. case 'ArrowLeft': case 'ArrowUp': case 'PageUp':
  668. e.preventDefault(); this.prev(); break;
  669. case 'Home': e.preventDefault(); this.showSlide(0); break;
  670. case 'End': e.preventDefault(); this.showSlide(this.slides.length-1); break;
  671. }
  672. });
  673. }
  674. setupTouchNav(){
  675. let sx = 0, sy = 0;
  676. document.addEventListener('touchstart', e => {
  677. sx = e.changedTouches[0].clientX; sy = e.changedTouches[0].clientY;
  678. }, {passive:true});
  679. document.addEventListener('touchend', e => {
  680. const dx = e.changedTouches[0].clientX - sx;
  681. const dy = e.changedTouches[0].clientY - sy;
  682. if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy)) {
  683. dx < 0 ? this.next() : this.prev();
  684. }
  685. }, {passive:true});
  686. }
  687. setupWheelNav(){
  688. let lock = false;
  689. document.addEventListener('wheel', e => {
  690. if (lock || document.body.classList.contains('editing')) return;
  691. if (Math.abs(e.deltaY) < 24) return;
  692. lock = true;
  693. e.deltaY > 0 ? this.next() : this.prev();
  694. setTimeout(() => lock = false, 700);
  695. }, {passive:true});
  696. }
  697. next(){ this.showSlide(this.current + 1); }
  698. prev(){ this.showSlide(this.current - 1); }
  699. showSlide(i){
  700. this.current = Math.max(0, Math.min(i, this.slides.length - 1));
  701. this.slides.forEach((s, n) => {
  702. const on = n === this.current;
  703. s.classList.toggle('active', on);
  704. s.classList.toggle('visible', on);
  705. });
  706. this.dots.forEach((d, n) => d.classList.toggle('on', n === this.current));
  707. document.getElementById('cur').textContent = this.current + 1;
  708. document.getElementById('progress').style.width =
  709. ((this.current + 1) / this.slides.length * 100) + '%';
  710. }
  711. }
  712. /* ===========================================
  713. INLINE TEXT EDITOR
  714. Press E or use the top-left hotzone. Edits persist
  715. to localStorage; Ctrl+S downloads the edited file.
  716. =========================================== */
  717. class InlineEditor {
  718. constructor(deck){
  719. this.deck = deck;
  720. this.isActive = false;
  721. // Every text-bearing element that is safe to edit
  722. this.targets = Array.from(document.querySelectorAll(
  723. '.slide h1, .slide h2, .slide h3, .slide p, .slide .ttl, .slide .det, ' +
  724. '.slide .yr, .slide .nm, .slide .v, .slide .k, .slide .t, .slide .l, ' +
  725. '.slide .kicker span:last-child, .slide .t-tag, .slide .railtext span, ' +
  726. '.slide .kv, .slide .cap'
  727. ));
  728. this.restore();
  729. this.bind();
  730. }
  731. bind(){
  732. const toggle = document.getElementById('editToggle');
  733. const hotzone = document.querySelector('.edit-hotzone');
  734. let hideTimer = null;
  735. // JS-driven hover with a grace period. A CSS `~` sibling
  736. // selector fails here because pointer-events breaks the chain.
  737. const show = () => { clearTimeout(hideTimer); toggle.classList.add('show'); };
  738. const hide = () => {
  739. hideTimer = setTimeout(() => {
  740. if (!this.isActive) toggle.classList.remove('show');
  741. }, 400);
  742. };
  743. hotzone.addEventListener('mouseenter', show);
  744. hotzone.addEventListener('mouseleave', hide);
  745. toggle.addEventListener('mouseenter', show);
  746. toggle.addEventListener('mouseleave', hide);
  747. hotzone.addEventListener('click', () => this.toggle());
  748. toggle.addEventListener('click', () => this.toggle());
  749. document.addEventListener('keydown', e => {
  750. const editingNow = e.target.getAttribute &&
  751. e.target.getAttribute('contenteditable') === 'true';
  752. if ((e.key === 'e' || e.key === 'E') && !editingNow) { this.toggle(); }
  753. if (e.key === 'Escape' && this.isActive) { this.toggle(); }
  754. if ((e.ctrlKey || e.metaKey) && e.key === 's' && this.isActive) {
  755. e.preventDefault(); this.download();
  756. }
  757. });
  758. }
  759. toggle(){
  760. this.isActive = !this.isActive;
  761. document.body.classList.toggle('editing', this.isActive);
  762. document.getElementById('editToggle').classList.toggle('active', this.isActive);
  763. this.targets.forEach((el, i) => {
  764. if (this.isActive){
  765. el.setAttribute('contenteditable', 'true');
  766. el.dataset.editKey = 'fintech-' + i;
  767. el.addEventListener('input', this.persist);
  768. } else {
  769. el.removeAttribute('contenteditable');
  770. el.removeEventListener('input', this.persist);
  771. }
  772. });
  773. }
  774. persist(){
  775. const store = JSON.parse(localStorage.getItem('fintechDeckEdits') || '{}');
  776. store[this.dataset.editKey] = this.innerHTML;
  777. localStorage.setItem('fintechDeckEdits', JSON.stringify(store));
  778. }
  779. restore(){
  780. const store = JSON.parse(localStorage.getItem('fintechDeckEdits') || '{}');
  781. this.targets.forEach((el, i) => {
  782. const saved = store['fintech-' + i];
  783. if (saved !== undefined) el.innerHTML = saved;
  784. });
  785. }
  786. // Save a copy of the edited deck to disk
  787. download(){
  788. const clone = document.documentElement.cloneNode(true);
  789. clone.querySelectorAll('[contenteditable]').forEach(n => {
  790. n.removeAttribute('contenteditable');
  791. n.removeAttribute('data-edit-key');
  792. });
  793. const blob = new Blob(['<!DOCTYPE html>\n' + clone.outerHTML], {type:'text/html'});
  794. const a = document.createElement('a');
  795. a.href = URL.createObjectURL(blob);
  796. a.download = 'fintech-2030-deck.html';
  797. a.click();
  798. URL.revokeObjectURL(a.href);
  799. }
  800. }
  801. const deck = new SlidePresentation();
  802. const editor = new InlineEditor(deck);
  803. </script>
  804. </body>
  805. </html>