heyiam 0.2.29 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +45 -0
  2. package/dist/config.js +10 -1
  3. package/dist/db.js +1 -2
  4. package/dist/export.js +40 -25
  5. package/dist/format-utils.js +5 -0
  6. package/dist/index.js +168 -0
  7. package/dist/mount.js +300 -102
  8. package/dist/parsers/claude.js +2 -28
  9. package/dist/parsers/codex.js +2 -26
  10. package/dist/parsers/cursor.js +2 -26
  11. package/dist/parsers/duration.js +35 -0
  12. package/dist/parsers/gemini.js +2 -20
  13. package/dist/parsers/types.js +0 -1
  14. package/dist/public/assets/index-BZ65TU_Y.js +40 -0
  15. package/dist/public/assets/index-CqCaW2cb.css +1 -0
  16. package/dist/public/index.html +2 -2
  17. package/dist/redact.js +4 -104
  18. package/dist/render/build-render-data.js +9 -2
  19. package/dist/render/index.js +32 -5
  20. package/dist/render/liquid.js +147 -7
  21. package/dist/render/mock-data.js +303 -0
  22. package/dist/render/templates/aurora/portfolio.liquid +204 -0
  23. package/dist/render/templates/aurora/project.liquid +260 -0
  24. package/dist/render/templates/aurora/session.liquid +223 -0
  25. package/dist/render/templates/aurora/styles.css +1178 -0
  26. package/dist/render/templates/bauhaus/portfolio.liquid +179 -0
  27. package/dist/render/templates/bauhaus/project.liquid +300 -0
  28. package/dist/render/templates/bauhaus/session.liquid +333 -0
  29. package/dist/render/templates/bauhaus/styles.css +1641 -0
  30. package/dist/render/templates/blueprint/portfolio.liquid +167 -0
  31. package/dist/render/templates/blueprint/project.liquid +286 -0
  32. package/dist/render/templates/blueprint/session.liquid +248 -0
  33. package/dist/render/templates/blueprint/styles.css +1285 -0
  34. package/dist/render/templates/canvas/portfolio.liquid +215 -0
  35. package/dist/render/templates/canvas/project.liquid +235 -0
  36. package/dist/render/templates/canvas/session.liquid +223 -0
  37. package/dist/render/templates/canvas/styles.css +1436 -0
  38. package/dist/render/templates/carbon/portfolio.liquid +170 -0
  39. package/dist/render/templates/carbon/project.liquid +249 -0
  40. package/dist/render/templates/carbon/session.liquid +190 -0
  41. package/dist/render/templates/carbon/styles.css +1091 -0
  42. package/dist/render/templates/chalk/portfolio.liquid +199 -0
  43. package/dist/render/templates/chalk/project.liquid +245 -0
  44. package/dist/render/templates/chalk/session.liquid +215 -0
  45. package/dist/render/templates/chalk/styles.css +1157 -0
  46. package/dist/render/templates/circuit/portfolio.liquid +162 -0
  47. package/dist/render/templates/circuit/project.liquid +247 -0
  48. package/dist/render/templates/circuit/session.liquid +205 -0
  49. package/dist/render/templates/circuit/styles.css +1403 -0
  50. package/dist/render/templates/cosmos/portfolio.liquid +232 -0
  51. package/dist/render/templates/cosmos/project.liquid +327 -0
  52. package/dist/render/templates/cosmos/session.liquid +239 -0
  53. package/dist/render/templates/cosmos/styles.css +1151 -0
  54. package/dist/render/templates/daylight/portfolio.liquid +217 -0
  55. package/dist/render/templates/daylight/project.liquid +229 -0
  56. package/dist/render/templates/daylight/session.liquid +219 -0
  57. package/dist/render/templates/daylight/styles.css +1311 -0
  58. package/dist/render/templates/editorial/portfolio.liquid +126 -0
  59. package/dist/render/templates/editorial/project.liquid +202 -0
  60. package/dist/render/templates/editorial/session.liquid +171 -0
  61. package/dist/render/templates/editorial/styles.css +822 -0
  62. package/dist/render/templates/ember/portfolio.liquid +318 -0
  63. package/dist/render/templates/ember/project.liquid +232 -0
  64. package/dist/render/templates/ember/session.liquid +202 -0
  65. package/dist/render/templates/ember/styles.css +1283 -0
  66. package/dist/render/templates/glacier/portfolio.liquid +271 -0
  67. package/dist/render/templates/glacier/project.liquid +288 -0
  68. package/dist/render/templates/glacier/session.liquid +217 -0
  69. package/dist/render/templates/glacier/styles.css +1200 -0
  70. package/dist/render/templates/grid/portfolio.liquid +265 -0
  71. package/dist/render/templates/grid/project.liquid +306 -0
  72. package/dist/render/templates/grid/session.liquid +260 -0
  73. package/dist/render/templates/grid/styles.css +1441 -0
  74. package/dist/render/templates/kinetic/portfolio.liquid +170 -0
  75. package/dist/render/templates/kinetic/project.liquid +242 -0
  76. package/dist/render/templates/kinetic/session.liquid +228 -0
  77. package/dist/render/templates/kinetic/styles.css +944 -0
  78. package/dist/render/templates/meridian/portfolio.liquid +255 -0
  79. package/dist/render/templates/meridian/project.liquid +376 -0
  80. package/dist/render/templates/meridian/session.liquid +298 -0
  81. package/dist/render/templates/meridian/styles.css +1369 -0
  82. package/dist/render/templates/minimal/portfolio.liquid +71 -0
  83. package/dist/render/templates/minimal/project.liquid +154 -0
  84. package/dist/render/templates/minimal/session.liquid +140 -0
  85. package/dist/render/templates/minimal/styles.css +525 -0
  86. package/dist/render/templates/mono/portfolio.liquid +291 -0
  87. package/dist/render/templates/mono/project.liquid +275 -0
  88. package/dist/render/templates/mono/session.liquid +276 -0
  89. package/dist/render/templates/mono/styles.css +1016 -0
  90. package/dist/render/templates/neon/portfolio.liquid +217 -0
  91. package/dist/render/templates/neon/project.liquid +225 -0
  92. package/dist/render/templates/neon/session.liquid +195 -0
  93. package/dist/render/templates/neon/styles.css +1265 -0
  94. package/dist/render/templates/noir/portfolio.liquid +137 -0
  95. package/dist/render/templates/noir/project.liquid +220 -0
  96. package/dist/render/templates/noir/session.liquid +241 -0
  97. package/dist/render/templates/noir/styles.css +1223 -0
  98. package/dist/render/templates/obsidian/portfolio.liquid +257 -0
  99. package/dist/render/templates/obsidian/project.liquid +280 -0
  100. package/dist/render/templates/obsidian/session.liquid +241 -0
  101. package/dist/render/templates/obsidian/styles.css +1401 -0
  102. package/dist/render/templates/paper/portfolio.liquid +267 -0
  103. package/dist/render/templates/paper/project.liquid +235 -0
  104. package/dist/render/templates/paper/session.liquid +271 -0
  105. package/dist/render/templates/paper/styles.css +1509 -0
  106. package/dist/render/templates/parallax/portfolio.liquid +305 -0
  107. package/dist/render/templates/parallax/project.liquid +275 -0
  108. package/dist/render/templates/parallax/session.liquid +295 -0
  109. package/dist/render/templates/parallax/styles.css +1874 -0
  110. package/dist/render/templates/parchment/portfolio.liquid +290 -0
  111. package/dist/render/templates/parchment/project.liquid +289 -0
  112. package/dist/render/templates/parchment/session.liquid +346 -0
  113. package/dist/render/templates/parchment/styles.css +1397 -0
  114. package/dist/render/templates/partials/_beats.liquid +16 -0
  115. package/dist/render/templates/partials/_breadcrumb.liquid +9 -0
  116. package/dist/render/templates/partials/_footer.liquid +7 -0
  117. package/dist/render/templates/partials/_growth-chart.liquid +7 -0
  118. package/dist/render/templates/partials/_key-decisions.liquid +20 -0
  119. package/dist/render/templates/partials/_links.liquid +16 -0
  120. package/dist/render/templates/partials/_narrative.liquid +8 -0
  121. package/dist/render/templates/partials/_phases.liquid +20 -0
  122. package/dist/render/templates/partials/_portfolio-header.liquid +20 -0
  123. package/dist/render/templates/partials/_portfolio-projects.liquid +16 -0
  124. package/dist/render/templates/partials/_portfolio-stats.liquid +19 -0
  125. package/dist/render/templates/partials/_qa.liquid +13 -0
  126. package/dist/render/templates/partials/_screenshot.liquid +15 -0
  127. package/dist/render/templates/partials/_session-cards.liquid +30 -0
  128. package/dist/render/templates/partials/_session-header.liquid +39 -0
  129. package/dist/render/templates/partials/_session-sidebar.liquid +30 -0
  130. package/dist/render/templates/partials/_skills.liquid +12 -0
  131. package/dist/render/templates/partials/_source-breakdown.liquid +22 -0
  132. package/dist/render/templates/partials/_stats.liquid +38 -0
  133. package/dist/render/templates/partials/_work-timeline.liquid +7 -0
  134. package/dist/render/templates/project.liquid +7 -4
  135. package/dist/render/templates/radar/portfolio.liquid +233 -0
  136. package/dist/render/templates/radar/project.liquid +278 -0
  137. package/dist/render/templates/radar/session.liquid +300 -0
  138. package/dist/render/templates/radar/styles.css +1049 -0
  139. package/dist/render/templates/showcase/portfolio.liquid +231 -0
  140. package/dist/render/templates/showcase/project.liquid +237 -0
  141. package/dist/render/templates/showcase/session.liquid +210 -0
  142. package/dist/render/templates/showcase/styles.css +1279 -0
  143. package/dist/render/templates/signal/portfolio.liquid +227 -0
  144. package/dist/render/templates/signal/project.liquid +278 -0
  145. package/dist/render/templates/signal/session.liquid +282 -0
  146. package/dist/render/templates/signal/styles.css +1395 -0
  147. package/dist/render/templates/strata/portfolio.liquid +192 -0
  148. package/dist/render/templates/strata/project.liquid +282 -0
  149. package/dist/render/templates/strata/session.liquid +261 -0
  150. package/dist/render/templates/strata/styles.css +1350 -0
  151. package/dist/render/templates/styles.css +1190 -0
  152. package/dist/render/templates/terminal/portfolio.liquid +118 -0
  153. package/dist/render/templates/terminal/project.liquid +161 -0
  154. package/dist/render/templates/terminal/session.liquid +145 -0
  155. package/dist/render/templates/terminal/styles.css +492 -0
  156. package/dist/render/templates/verdant/portfolio.liquid +333 -0
  157. package/dist/render/templates/verdant/project.liquid +309 -0
  158. package/dist/render/templates/verdant/session.liquid +237 -0
  159. package/dist/render/templates/verdant/styles.css +1257 -0
  160. package/dist/render/templates/zen/portfolio.liquid +136 -0
  161. package/dist/render/templates/zen/project.liquid +187 -0
  162. package/dist/render/templates/zen/session.liquid +203 -0
  163. package/dist/render/templates/zen/styles.css +1207 -0
  164. package/dist/render/templates.js +90 -0
  165. package/dist/routes/context.js +15 -10
  166. package/dist/routes/enhance.js +17 -40
  167. package/dist/routes/export.js +14 -4
  168. package/dist/routes/preview.js +480 -108
  169. package/dist/routes/projects.js +11 -19
  170. package/dist/routes/publish.js +15 -17
  171. package/dist/routes/settings.js +94 -1
  172. package/dist/routes/sse.js +9 -0
  173. package/dist/server.js +8 -2
  174. package/dist/settings.js +17 -9
  175. package/package.json +2 -4
  176. package/dist/public/assets/index-CC9G8EF1.js +0 -21
  177. package/dist/public/assets/index-Dalqz2mC.css +0 -1
@@ -0,0 +1,305 @@
1
+ <div class="heyiam-portfolio parallax" data-render-version="2" data-template="parallax" data-username="{{ user.username }}">
2
+
3
+ <a href="#main-content" class="skip-link">Skip to content</a>
4
+
5
+ <!-- Background Shapes -->
6
+ <div class="bg-shapes" aria-hidden="true">
7
+ <div class="bg-shape bg-shape--1" data-bg-drift="0.15"></div>
8
+ <div class="bg-shape bg-shape--2" data-bg-drift="0.25"></div>
9
+ <div class="bg-shape bg-shape--3" data-bg-drift="0.1"></div>
10
+ <div class="bg-shape bg-shape--4" data-bg-drift="0.2"></div>
11
+ <div class="bg-shape bg-shape--5" data-bg-drift="0.35"></div>
12
+ <div class="bg-shape bg-shape--6" data-bg-drift="0.12"></div>
13
+ <div class="bg-shape bg-shape--7" data-bg-drift="0.3"></div>
14
+ <div class="bg-shape bg-shape--8" data-bg-drift="0.18"></div>
15
+ </div>
16
+
17
+ {% if hasProfile %}
18
+ {% if user.photoUrl %}
19
+ <!-- Fixed Photo Layer -->
20
+ <div class="photo-layer" aria-hidden="true">
21
+ <img
22
+ class="photo-fixed"
23
+ src="{{ user.photoUrl }}"
24
+ alt=""
25
+ width="420"
26
+ height="420"
27
+ >
28
+ </div>
29
+ {% endif %}
30
+ {% endif %}
31
+
32
+ <!-- Scrollable Content Layer -->
33
+ <div class="content-layer">
34
+ <main id="main-content">
35
+
36
+ {% if hasProfile %}
37
+ <!-- Hero -->
38
+ <section class="hero" aria-label="Introduction">
39
+ {% if user.photoUrl %}
40
+ <span class="sr-only">{{ user.displayName }}, profile photo</span>
41
+ {% endif %}
42
+
43
+ {% if user.displayName != blank %}
44
+ <h1 class="hero__name">{{ user.displayName }}</h1>
45
+ {% endif %}
46
+
47
+ {% if user.location != blank %}
48
+ <p class="hero__location">{{ user.location }}</p>
49
+ {% endif %}
50
+
51
+ {% if user.bio != blank %}
52
+ <p class="hero__bio">{{ user.bio }}</p>
53
+ {% endif %}
54
+
55
+ {% if user.email != blank or user.linkedinUrl != blank or user.githubUrl != blank or user.websiteUrl != blank or user.twitterHandle != blank %}
56
+ <div class="hero__contact">
57
+ {% if user.email != blank %}
58
+ <a href="mailto:{{ user.email }}" class="hero__contact-link">{{ user.email }}</a>
59
+ {% endif %}
60
+ {% if user.linkedinUrl != blank %}
61
+ {% if user.email != blank %}<span class="hero__contact-sep" aria-hidden="true">&middot;</span>{% endif %}
62
+ <a href="{{ user.linkedinUrl }}" class="hero__contact-link" rel="noopener noreferrer" target="_blank">LinkedIn</a>
63
+ {% endif %}
64
+ {% if user.githubUrl != blank %}
65
+ <span class="hero__contact-sep" aria-hidden="true">&middot;</span>
66
+ <a href="{{ user.githubUrl }}" class="hero__contact-link" rel="noopener noreferrer" target="_blank">GitHub</a>
67
+ {% endif %}
68
+ {% if user.websiteUrl != blank %}
69
+ <span class="hero__contact-sep" aria-hidden="true">&middot;</span>
70
+ <a href="{{ user.websiteUrl }}" class="hero__contact-link" rel="noopener noreferrer" target="_blank">{{ user.websiteUrl | stripProtocol }}</a>
71
+ {% endif %}
72
+ {% if user.twitterHandle != blank %}
73
+ <span class="hero__contact-sep" aria-hidden="true">&middot;</span>
74
+ <a href="https://x.com/{{ user.twitterHandle }}" class="hero__contact-link" rel="noopener noreferrer" target="_blank">@{{ user.twitterHandle }}</a>
75
+ {% endif %}
76
+ </div>
77
+ {% endif %}
78
+
79
+ <div class="stats-row" aria-label="Aggregate statistics">
80
+ <div class="stat-card">
81
+ <div class="stat-card__value">{{ projects.size }}</div>
82
+ <div class="stat-card__label">Projects</div>
83
+ </div>
84
+ <div class="stat-card">
85
+ <div class="stat-card__value">{{ totalSessions | localeNumber }}</div>
86
+ <div class="stat-card__label">Sessions</div>
87
+ </div>
88
+ <div class="stat-card">
89
+ <div class="stat-card__value">{{ totalLoc | formatLoc }}</div>
90
+ <div class="stat-card__label">Lines Changed</div>
91
+ </div>
92
+ </div>
93
+
94
+ {% if efficiencyMultiplier %}
95
+ <div class="px-leverage" role="figure" aria-label="{{ totalDurationMinutes | formatDuration }} you, {{ totalAgentDurationMinutes | formatDuration }} agents, {{ efficiencyMultiplier }} leverage">
96
+ <span class="px-leverage__multi">{{ efficiencyMultiplier }}&times;</span>
97
+ <div class="px-leverage__line" aria-hidden="true"></div>
98
+ <div class="px-leverage__values">
99
+ <span>{{ totalDurationMinutes | formatDuration }} <small>you</small></span>
100
+ <span style="color:var(--px-accent-blue)">{{ totalAgentDurationMinutes | formatDuration }} <small>agents</small></span>
101
+ </div>
102
+ </div>
103
+ {% endif %}
104
+ </section>
105
+
106
+ {% if user.photoUrl %}
107
+ <!-- Transition spacer — photo shows through this gap -->
108
+ <div class="transition-spacer" aria-hidden="true"></div>
109
+ {% endif %}
110
+ {% endif %}
111
+
112
+ <!-- Skills Overview -->
113
+ {% assign all_skills = "" %}
114
+ {% for p in projects %}
115
+ {% for s in p.skills %}
116
+ {% unless all_skills contains s %}
117
+ {% if all_skills != "" %}{% assign all_skills = all_skills | append: "|||" %}{% endif %}
118
+ {% assign all_skills = all_skills | append: s %}
119
+ {% endunless %}
120
+ {% endfor %}
121
+ {% endfor %}
122
+ {% assign skills_arr = all_skills | split: "|||" %}
123
+ {% if skills_arr.size > 0 %}
124
+ <div class="skills-overview section--opaque">
125
+ <h2 class="skills-overview__title reveal">Technology Expertise</h2>
126
+ <div class="skills-cloud reveal-stagger">
127
+ {% for skill in skills_arr %}
128
+ <span class="skills-cloud__chip reveal">{{ skill }}</span>
129
+ {% endfor %}
130
+ </div>
131
+ </div>
132
+ {% endif %}
133
+
134
+ <!-- Projects -->
135
+ {% if projects.size > 0 %}
136
+ <section class="section section--opaque" aria-label="Projects">
137
+ <h2 class="section__title reveal">Projects</h2>
138
+ <div class="project-grid reveal-stagger">
139
+ {% for p in projects %}
140
+ <a href="/{{ user.username }}/{{ p.slug }}" class="project-card reveal" data-float="0.08">
141
+ <h3 class="project-card__name">{{ p.title }}</h3>
142
+ {% if p.narrative != blank %}
143
+ <p class="project-card__narrative">{{ p.narrative }}</p>
144
+ {% endif %}
145
+ {% if p.skills.size > 0 %}
146
+ <div class="project-card__skills">
147
+ {% for skill in p.skills %}
148
+ <span class="skill-chip">{{ skill }}</span>
149
+ {% endfor %}
150
+ </div>
151
+ {% endif %}
152
+ <div class="project-card__stats">
153
+ <span>{{ p.totalSessions }} sessions</span>
154
+ <span>{{ p.totalDurationMinutes | formatDuration }}</span>
155
+ <span>{{ p.totalLoc | formatLoc }} LOC</span>
156
+ </div>
157
+ {% if p.sourceCounts.size > 0 %}
158
+ {% assign firstSrc = p.sourceCounts | first %}
159
+ {% assign firstPct = firstSrc.count | times: 100 | divided_by: p.totalSessions %}
160
+ <div class="source-bar" aria-label="Source mix: {{ firstSrc.tool }} {{ firstPct }}%">
161
+ <div class="source-bar__fill" style="width: {{ firstPct }}%"></div>
162
+ </div>
163
+ <div class="project-card__source">
164
+ {% for sc in p.sourceCounts %}
165
+ {% assign pct = sc.count | times: 100 | divided_by: p.totalSessions %}
166
+ <span>{{ sc.tool }} {{ pct }}%</span>
167
+ {% endfor %}
168
+ </div>
169
+ {% endif %}
170
+ </a>
171
+ {% endfor %}
172
+ </div>
173
+ </section>
174
+ {% endif %}
175
+
176
+ <!-- Approach Section -->
177
+ <section class="section section--opaque" aria-label="Development approach">
178
+ <h2 class="section__title reveal">How I Work</h2>
179
+
180
+ <div class="approach-grid reveal-stagger">
181
+ <div class="approach-card reveal" data-float="0.06">
182
+ <div class="approach-card__icon" aria-hidden="true">
183
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
184
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
185
+ <path d="M2 17l10 5 10-5"/>
186
+ <path d="M2 12l10 5 10-5"/>
187
+ </svg>
188
+ </div>
189
+ <h3 class="approach-card__title">Schema First</h3>
190
+ <p class="approach-card__desc">
191
+ Data models before UI. If the schema is right, the rest follows.
192
+ Discriminated unions over nullable columns.
193
+ </p>
194
+ </div>
195
+
196
+ <div class="approach-card reveal" data-float="0.09">
197
+ <div class="approach-card__icon" aria-hidden="true">
198
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
199
+ <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
200
+ </svg>
201
+ </div>
202
+ <h3 class="approach-card__title">Performance Budget</h3>
203
+ <p class="approach-card__desc">
204
+ Every query has a target. Partial indexes, connection pooling,
205
+ and profiling before shipping.
206
+ </p>
207
+ </div>
208
+
209
+ <div class="approach-card reveal" data-float="0.07">
210
+ <div class="approach-card__icon" aria-hidden="true">
211
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
212
+ <circle cx="12" cy="12" r="10"/>
213
+ <path d="M12 6v6l4 2"/>
214
+ </svg>
215
+ </div>
216
+ <h3 class="approach-card__title">AI as Multiplier</h3>
217
+ <p class="approach-card__desc">
218
+ AI handles boilerplate so I can focus on architecture decisions.
219
+ Every session is recorded to show the real process.
220
+ </p>
221
+ </div>
222
+ </div>
223
+ </section>
224
+
225
+ <!-- Recent Activity Timeline -->
226
+ {% assign allSessionEntries = "" %}
227
+ {% for p in projects %}
228
+ {% for s in p.sessions %}
229
+ {% if allSessionEntries != "" %}{% assign allSessionEntries = allSessionEntries | append: "|||" %}{% endif %}
230
+ {% assign entry = s.date | append: ":::" | append: p.title | append: ":::" | append: s.durationMinutes | append: ":::" | append: s.loc %}
231
+ {% assign allSessionEntries = allSessionEntries | append: entry %}
232
+ {% endfor %}
233
+ {% endfor %}
234
+ {% assign sessionList = allSessionEntries | split: "|||" | sort | reverse %}
235
+ {% if sessionList.size > 0 %}
236
+ <section class="section section--opaque" aria-label="Recent activity">
237
+ <h2 class="section__title reveal">Recent Activity</h2>
238
+ <div class="timeline reveal">
239
+ {% for entry in sessionList limit: 8 %}
240
+ {% assign parts = entry | split: ":::" %}
241
+ {% assign entryDate = parts[0] %}
242
+ {% assign entryProject = parts[1] %}
243
+ {% assign entryDuration = parts[2] %}
244
+ {% assign entryLoc = parts[3] %}
245
+ <div class="timeline__item" data-float="0.0{{ forloop.index | modulo: 4 | plus: 5 }}">
246
+ <div class="timeline__dot"></div>
247
+ <div class="timeline__date">{{ entryDate | formatDate }}</div>
248
+ <div class="timeline__meta">{{ entryProject }} &middot; {{ entryDuration | times: 1 | formatDuration }} &middot; {{ entryLoc }} LOC</div>
249
+ </div>
250
+ {% endfor %}
251
+ </div>
252
+ </section>
253
+ {% endif %}
254
+
255
+ <!-- About / Connect -->
256
+ {% if user.email != blank or user.linkedinUrl != blank or user.githubUrl != blank or user.websiteUrl != blank or user.twitterHandle != blank or user.resumeUrl != blank %}
257
+ <section class="about-section about-section--opaque" aria-label="About and links">
258
+ <div class="about-grid">
259
+ <div class="about-card reveal" data-float="0.06">
260
+ <h2 class="about-card__title">Connect</h2>
261
+ {% if user.email != blank %}
262
+ <a href="mailto:{{ user.email }}" class="about-card__link">{{ user.email }}</a>
263
+ {% endif %}
264
+ {% if user.linkedinUrl != blank %}
265
+ <a href="{{ user.linkedinUrl }}" class="about-card__link" rel="noopener noreferrer" target="_blank">{{ user.linkedinUrl | stripProtocol }}</a>
266
+ {% endif %}
267
+ {% if user.githubUrl != blank %}
268
+ <a href="{{ user.githubUrl }}" class="about-card__link" rel="noopener noreferrer" target="_blank">{{ user.githubUrl | stripProtocol }}</a>
269
+ {% endif %}
270
+ {% if user.websiteUrl != blank %}
271
+ <a href="{{ user.websiteUrl }}" class="about-card__link" rel="noopener noreferrer" target="_blank">{{ user.websiteUrl | stripProtocol }}</a>
272
+ {% endif %}
273
+ {% if user.twitterHandle != blank %}
274
+ <a href="https://x.com/{{ user.twitterHandle }}" class="about-card__link" rel="noopener noreferrer" target="_blank">@{{ user.twitterHandle }}</a>
275
+ {% endif %}
276
+ </div>
277
+ {% if user.resumeUrl != blank %}
278
+ <div class="about-card reveal" data-float="0.09">
279
+ <h2 class="about-card__title">Resume</h2>
280
+ <p style="font-size: 0.875rem; color: var(--px-text-secondary); margin-block-end: 1rem;">
281
+ Download my full resume for a deeper look at experience and education.
282
+ </p>
283
+ <a href="{{ user.resumeUrl }}" class="resume-btn" download>
284
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
285
+ <path d="M8 1v10m0 0L4.5 7.5M8 11l3.5-3.5M2 14h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
286
+ </svg>
287
+ Download Resume
288
+ </a>
289
+ </div>
290
+ {% endif %}
291
+ </div>
292
+ </section>
293
+ {% endif %}
294
+
295
+ </main>
296
+
297
+ <!-- Footer -->
298
+ <footer class="site-footer footer--opaque">
299
+ <p>Built with <a href="https://heyi.am">heyi.am</a> &mdash; signal over noise.</p>
300
+ </footer>
301
+ </div>
302
+
303
+ <!-- JavaScript: Parallax, Reveals, Counters -->
304
+
305
+ </div>
@@ -0,0 +1,275 @@
1
+ <div class="heyiam-project parallax" data-render-version="2" data-template="parallax"{% if sessionBaseUrl %} data-session-base-url="{{ sessionBaseUrl }}"{% endif %} data-username="{{ user.username }}" data-project-slug="{{ project.slug }}">
2
+
3
+ <a href="#main-content" class="skip-link">Skip to content</a>
4
+
5
+ <!-- Background Shapes -->
6
+ <div class="bg-shapes" aria-hidden="true">
7
+ <div class="bg-shape bg-shape--1"></div>
8
+ <div class="bg-shape bg-shape--2"></div>
9
+ <div class="bg-shape bg-shape--3"></div>
10
+ <div class="bg-shape bg-shape--4"></div>
11
+ <div class="bg-shape bg-shape--5"></div>
12
+ <div class="bg-shape bg-shape--6"></div>
13
+ </div>
14
+
15
+ <!-- Main Content -->
16
+ <main id="main-content">
17
+
18
+ <!-- Project Hero -->
19
+ <section class="project-hero" aria-label="Project overview">
20
+ {% if project.screenshotUrl != blank %}
21
+ <div class="project-hero__screenshot" data-parallax="0.06" role="img" aria-label="{{ project.title }} screenshot">
22
+ <div class="browser-frame" data-editable="screenshot">
23
+ <div class="browser-frame__bar">
24
+ <div class="browser-frame__dot"></div>
25
+ <div class="browser-frame__dot"></div>
26
+ <div class="browser-frame__dot"></div>
27
+ <div class="browser-frame__url">Preview</div>
28
+ </div>
29
+ <div class="browser-frame__viewport">
30
+ <img src="{{ project.screenshotUrl }}" alt="{{ project.title }} screenshot">
31
+ </div>
32
+ </div>
33
+ </div>
34
+ {% else %}
35
+ <div class="project-hero__screenshot" data-parallax="0.06" role="img" aria-label="{{ project.title }} placeholder"></div>
36
+ {% endif %}
37
+
38
+ <h1 class="project-hero__title">{{ project.title }}</h1>
39
+
40
+ {% if project.repoUrl != blank or project.projectUrl != blank %}
41
+ <div class="project-hero__links">
42
+ {% if project.repoUrl != blank %}
43
+ <a href="{{ project.repoUrl }}" class="project-hero__link" rel="noopener noreferrer" target="_blank">{{ project.repoUrl | stripProtocol }}</a>
44
+ {% endif %}
45
+ {% if project.repoUrl != blank and project.projectUrl != blank %}
46
+ <span style="color: var(--px-text-tertiary);" aria-hidden="true">&middot;</span>
47
+ {% endif %}
48
+ {% if project.projectUrl != blank %}
49
+ <a href="{{ project.projectUrl }}" class="project-hero__link" rel="noopener noreferrer" target="_blank">{{ project.projectUrl | stripProtocol }}</a>
50
+ {% endif %}
51
+ </div>
52
+ {% endif %}
53
+ </section>
54
+
55
+ <!-- Narrative -->
56
+ {% if project.narrative != blank %}
57
+ <section class="section" aria-label="Project narrative">
58
+ <h2 class="section__label reveal">Narrative</h2>
59
+ <div class="narrative reveal">
60
+ <p>{{ project.narrative }}</p>
61
+ </div>
62
+ </section>
63
+ {% endif %}
64
+
65
+ <!-- Stats -->
66
+ <section class="section" aria-label="Project statistics">
67
+ <div class="stats-bar" data-stats-fly>
68
+ <div class="stats-bar__item fly-left">
69
+ <div class="stats-bar__value">{{ project.totalSessions }}</div>
70
+ <div class="stats-bar__label">Sessions</div>
71
+ </div>
72
+ <div class="stats-bar__item fly-right">
73
+ <div class="stats-bar__value">{{ project.totalLoc | formatLoc }}</div>
74
+ <div class="stats-bar__label">LOC</div>
75
+ </div>
76
+ <div class="stats-bar__item fly-right">
77
+ <div class="stats-bar__value">{{ project.totalFilesChanged | localeNumber }}</div>
78
+ <div class="stats-bar__label">Files</div>
79
+ </div>
80
+ {% if project.totalTokens %}
81
+ <div class="stats-bar__item fly-left">
82
+ <div class="stats-bar__value">{{ project.totalTokens | formatTokens }}</div>
83
+ <div class="stats-bar__label">Tokens</div>
84
+ </div>
85
+ {% endif %}
86
+ </div>
87
+
88
+ {% if efficiencyMultiplier %}
89
+ <div class="px-leverage" role="figure" aria-label="{{ project.totalDurationMinutes | formatDuration }} you, {{ project.totalAgentDurationMinutes | formatDuration }} agents, {{ efficiencyMultiplier }} leverage">
90
+ <span class="px-leverage__multi">{{ efficiencyMultiplier }}&times;</span>
91
+ <div class="px-leverage__line" aria-hidden="true"></div>
92
+ <div class="px-leverage__values">
93
+ <span>{{ project.totalDurationMinutes | formatDuration }} <small>you</small></span>
94
+ <span style="color:var(--px-accent-blue)">{{ project.totalAgentDurationMinutes | formatDuration }} <small>agents</small></span>
95
+ </div>
96
+ </div>
97
+ {% endif %}
98
+ </section>
99
+
100
+ <!-- Work Timeline -->
101
+ {% if featuredSessions.size > 0 %}
102
+ {% assign maxLoc = 1 %}
103
+ {% for s in featuredSessions %}
104
+ {% if s.locChanged > maxLoc %}{% assign maxLoc = s.locChanged %}{% endif %}
105
+ {% endfor %}
106
+ <section class="section" aria-label="Work timeline">
107
+ <h2 class="section__label reveal">Work Timeline</h2>
108
+ <div class="timeline-chart reveal" role="table" aria-label="Session work timeline">
109
+ {% for s in featuredSessions %}
110
+ <div class="timeline-chart__row" role="row">
111
+ <span class="timeline-chart__num" role="cell">{{ forloop.index }}</span>
112
+ <div class="timeline-chart__bar-wrap" role="cell">
113
+ {% assign barPct = s.locChanged | times: 100 | divided_by: maxLoc | round %}
114
+ <div class="timeline-chart__bar bar--claude" data-bar-width="{{ barPct }}" style="width: 0%;">
115
+ <span class="timeline-chart__bar-label">{{ s.title }}</span>
116
+ </div>
117
+ </div>
118
+ <span class="timeline-chart__loc" role="cell">{{ s.locChanged | formatLoc }} LOC</span>
119
+ </div>
120
+ {% endfor %}
121
+ </div>
122
+ </section>
123
+ {% endif %}
124
+
125
+ <!-- Growth Chart -->
126
+ {% if featuredSessions.size > 1 %}
127
+ {% assign cumulativeLoc = 0 %}
128
+ {% assign maxCumLoc = 0 %}
129
+ {% for s in featuredSessions %}
130
+ {% assign cumulativeLoc = cumulativeLoc | plus: s.locChanged %}
131
+ {% if cumulativeLoc > maxCumLoc %}{% assign maxCumLoc = cumulativeLoc %}{% endif %}
132
+ {% endfor %}
133
+ <section class="section" aria-label="Cumulative LOC growth">
134
+ <h2 class="section__label reveal">Growth</h2>
135
+ <div class="growth-chart reveal" aria-label="Line chart showing cumulative lines of code over {{ featuredSessions.size }} sessions">
136
+ <svg viewBox="0 0 600 200" preserveAspectRatio="xMidYMid meet" role="img" aria-hidden="true">
137
+ <defs>
138
+ <linearGradient id="blueGradient" x1="0" y1="0" x2="0" y2="1">
139
+ <stop offset="0%" stop-color="rgba(96, 165, 250, 0.2)" />
140
+ <stop offset="100%" stop-color="rgba(96, 165, 250, 0)" />
141
+ </linearGradient>
142
+ </defs>
143
+ <!-- Grid lines -->
144
+ <line x1="60" y1="170" x2="570" y2="170" stroke="rgba(255,255,255,0.05)" stroke-width="1" />
145
+ <line x1="60" y1="127" x2="570" y2="127" stroke="rgba(255,255,255,0.05)" stroke-width="1" />
146
+ <line x1="60" y1="85" x2="570" y2="85" stroke="rgba(255,255,255,0.05)" stroke-width="1" />
147
+ <line x1="60" y1="42" x2="570" y2="42" stroke="rgba(255,255,255,0.05)" stroke-width="1" />
148
+ <!-- Area + Line rendered by script from data points -->
149
+ <polygon class="growth-area" points="" />
150
+ <polyline class="growth-line" points="" />
151
+ {% assign runningLoc = 0 %}
152
+ {% assign stepX = 510 | divided_by: featuredSessions.size %}
153
+ {% for s in featuredSessions %}
154
+ {% assign runningLoc = runningLoc | plus: s.locChanged %}
155
+ {% assign cx = forloop.index0 | times: stepX | plus: 60 %}
156
+ {% assign yRatio = runningLoc | times: 128 | divided_by: maxCumLoc %}
157
+ {% assign cy = 170 | minus: yRatio %}
158
+ <circle class="growth-dot" cx="{{ cx }}" cy="{{ cy }}" data-loc="{{ runningLoc }}" />
159
+ {% endfor %}
160
+ </svg>
161
+ <div class="growth-chart__labels">
162
+ {% for s in featuredSessions %}
163
+ <span>S{{ forloop.index }}</span>
164
+ {% endfor %}
165
+ </div>
166
+ </div>
167
+ </section>
168
+ {% endif %}
169
+
170
+ <!-- Phases -->
171
+ {% if arc.size > 0 %}
172
+ <section class="section" aria-label="Project phases">
173
+ <h2 class="section__label reveal">Phases</h2>
174
+ <div class="phase-timeline">
175
+ {% for phase in arc %}
176
+ <article class="phase-item" data-phase-index="{{ forloop.index0 }}">
177
+ <div class="phase-item__dot" aria-hidden="true"></div>
178
+ <div class="phase-item__num">Phase {{ phase.phase }}</div>
179
+ <h3 class="phase-item__title">{{ phase.title }}</h3>
180
+ {% if phase.dates != blank %}
181
+ <div class="phase-item__dates">{{ phase.dates }}</div>
182
+ {% endif %}
183
+ {% if phase.description != blank %}
184
+ <p class="phase-item__desc">{{ phase.description }}</p>
185
+ {% endif %}
186
+ </article>
187
+ {% endfor %}
188
+ </div>
189
+ </section>
190
+ {% endif %}
191
+
192
+ <!-- Skills -->
193
+ {% if project.skills.size > 0 %}
194
+ <section class="section" aria-label="Skills used">
195
+ <h2 class="section__label reveal">Skills</h2>
196
+ <div class="skills-wrap reveal">
197
+ {% for skill in project.skills %}
198
+ <span class="skill-tag">{{ skill }}</span>
199
+ {% endfor %}
200
+ </div>
201
+ </section>
202
+ {% endif %}
203
+
204
+ <!-- Key Decisions -->
205
+ {% if arc.size > 0 %}
206
+ <section class="section" aria-label="Key decisions">
207
+ <h2 class="section__label reveal">Key Decisions</h2>
208
+ <ol class="decisions-list reveal">
209
+ {% for item in arc %}
210
+ <li>{{ item.description }}</li>
211
+ {% endfor %}
212
+ </ol>
213
+ </section>
214
+ {% endif %}
215
+
216
+ <!-- Source Breakdown -->
217
+ {% if sourceCounts.size > 0 %}
218
+ <section class="section" aria-label="Source breakdown">
219
+ <h2 class="section__label reveal">Source Breakdown</h2>
220
+ <div class="source-breakdown reveal">
221
+ <svg class="source-ring" viewBox="0 0 80 80" aria-hidden="true">
222
+ <circle class="source-ring__bg" cx="40" cy="40" r="31.4" />
223
+ {% for src in sourceCounts %}
224
+ <circle class="source-ring__segment" cx="40" cy="40" r="31.4"
225
+ style="stroke: var(--px-accent-blue); stroke-dasharray: 197; stroke-dashoffset: {{ 197 | times: src.count | divided_by: project.totalSessions | times: -1 | plus: 197 }}; transform: rotate(-90deg); transform-origin: center;" />
226
+ {% endfor %}
227
+ </svg>
228
+ <div class="source-legend">
229
+ {% for src in sourceCounts %}
230
+ <div class="source-legend__item">
231
+ <div class="source-legend__dot" style="background: var(--px-accent-blue);"></div>
232
+ <span>{{ src.tool }} &mdash; {{ src.count | times: 100.0 | divided_by: project.totalSessions | round }}% ({{ src.count }} sessions)</span>
233
+ </div>
234
+ {% endfor %}
235
+ </div>
236
+ </div>
237
+ </section>
238
+ {% endif %}
239
+
240
+ <!-- Featured Sessions -->
241
+ {% if featuredSessions.size > 0 %}
242
+ <section class="section" aria-label="Featured sessions">
243
+ <h2 class="section__label reveal">Sessions</h2>
244
+ <div class="session-grid" data-session-grid>
245
+ {% for s in featuredSessions %}
246
+ <a href="{{ sessionBaseUrl }}/{{ s.slug }}{{ sessionSuffix }}" class="session-card">
247
+ <div class="session-card__num">Session {{ forloop.index }}</div>
248
+ <h3 class="session-card__title">{{ s.title }}</h3>
249
+ {% if s.devTake != blank %}
250
+ <p class="session-card__take">{{ s.devTake }}</p>
251
+ {% endif %}
252
+ <div class="session-card__meta">
253
+ <span>{{ s.durationMinutes | formatDuration }}</span>
254
+ <span>{{ s.locChanged | formatLoc }} LOC</span>
255
+ <span>{{ s.filesChanged }} files</span>
256
+ </div>
257
+ {% if s.skills.size > 0 %}
258
+ <span class="session-card__tag">{{ s.skills | first }}</span>
259
+ {% endif %}
260
+ </a>
261
+ {% endfor %}
262
+ </div>
263
+ </section>
264
+ {% endif %}
265
+
266
+ </main>
267
+
268
+ <!-- Footer -->
269
+ <footer class="site-footer">
270
+ <p>Built with <a href="https://heyi.am">heyi.am</a> &mdash; signal over noise.</p>
271
+ </footer>
272
+
273
+ <!-- JavaScript: Parallax, Entrance Animations -->
274
+
275
+ </div>