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,255 @@
1
+ <div class="heyiam-portfolio meridian" data-render-version="2" data-template="meridian" data-username="{{ user.username }}">
2
+
3
+ {%- comment -%} Topographic contour background {%- endcomment -%}
4
+ <div class="topo-bg" aria-hidden="true">
5
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 900" preserveAspectRatio="xMidYMid slice">
6
+ <g class="topo-contours" fill="none" stroke-width="0.75">
7
+ <ellipse cx="350" cy="300" rx="280" ry="180" stroke="var(--mer-contour)" />
8
+ <ellipse cx="355" cy="305" rx="240" ry="150" stroke="var(--mer-contour)" />
9
+ <ellipse cx="360" cy="310" rx="200" ry="120" stroke="var(--mer-contour-strong)" />
10
+ <ellipse cx="365" cy="315" rx="160" ry="90" stroke="var(--mer-contour)" />
11
+ <ellipse cx="370" cy="320" rx="120" ry="65" stroke="var(--mer-contour-strong)" />
12
+ <ellipse cx="375" cy="325" rx="80" ry="40" stroke="var(--mer-contour)" />
13
+ <ellipse cx="850" cy="550" rx="300" ry="200" stroke="var(--mer-contour)" />
14
+ <ellipse cx="845" cy="545" rx="250" ry="165" stroke="var(--mer-contour)" />
15
+ <ellipse cx="840" cy="540" rx="200" ry="130" stroke="var(--mer-contour-strong)" />
16
+ <ellipse cx="835" cy="535" rx="150" ry="95" stroke="var(--mer-contour)" />
17
+ <ellipse cx="830" cy="530" rx="100" ry="60" stroke="var(--mer-contour-strong)" />
18
+ <ellipse cx="825" cy="525" rx="55" ry="30" stroke="var(--mer-contour)" />
19
+ <ellipse cx="950" cy="150" rx="220" ry="140" stroke="var(--mer-contour)" />
20
+ <ellipse cx="955" cy="155" rx="170" ry="105" stroke="var(--mer-contour)" />
21
+ <ellipse cx="960" cy="160" rx="120" ry="70" stroke="var(--mer-contour-strong)" />
22
+ <ellipse cx="965" cy="165" rx="70" ry="38" stroke="var(--mer-contour)" />
23
+ <ellipse cx="150" cy="700" rx="250" ry="160" stroke="var(--mer-contour)" />
24
+ <ellipse cx="155" cy="695" rx="200" ry="125" stroke="var(--mer-contour-strong)" />
25
+ <ellipse cx="160" cy="690" rx="150" ry="90" stroke="var(--mer-contour)" />
26
+ <ellipse cx="165" cy="685" rx="100" ry="55" stroke="var(--mer-contour)" />
27
+ </g>
28
+ </svg>
29
+ </div>
30
+
31
+ <a class="skip-link" href="#main-content">Skip to content</a>
32
+
33
+ <div class="mer-page">
34
+ <main id="main-content">
35
+
36
+ {%- comment -%} Hero {%- endcomment -%}
37
+ {% if hasProfile %}
38
+ <section class="hero section-reveal" aria-label="Profile">
39
+ <div class="section-label">
40
+ <span>&#167;01 &mdash; survey point</span>
41
+ </div>
42
+ <div class="hero__profile">
43
+ {% if user.photoUrl %}
44
+ <img src="{{ user.photoUrl }}" alt="{{ user.displayName }}" class="hero__photo" width="120" height="150">
45
+ {% endif %}
46
+ <div class="hero__info">
47
+ {% if user.displayName != blank %}
48
+ <h1 class="hero__name">{{ user.displayName }}</h1>
49
+ {% endif %}
50
+ <p class="hero__handle">@{{ user.username }}</p>
51
+ {% if user.bio != blank %}
52
+ <p class="hero__bio">{{ user.bio }}</p>
53
+ {% endif %}
54
+ {% if user.location != blank %}
55
+ <p class="hero__location">
56
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="9" r="2.5"/></svg>
57
+ {{ user.location }}
58
+ </p>
59
+ {% endif %}
60
+ {% if user.email or user.linkedinUrl or user.githubUrl or user.twitterHandle or user.websiteUrl %}
61
+ <ul class="hero__contact" aria-label="Contact and social links">
62
+ {% if user.email %}
63
+ <li><a href="mailto:{{ user.email }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 4 10 8 10-8"/></svg>{{ user.email }}</a></li>
64
+ {% endif %}
65
+ {% if user.phone %}
66
+ <li><a href="tel:{{ user.phone }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>{{ user.phone }}</a></li>
67
+ {% endif %}
68
+ {% if user.linkedinUrl %}
69
+ <li><a href="{{ user.linkedinUrl }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M20.5 2h-17A1.5 1.5 0 002 3.5v17A1.5 1.5 0 003.5 22h17a1.5 1.5 0 001.5-1.5v-17A1.5 1.5 0 0020.5 2zM8 19H5v-9h3zM6.5 8.25A1.75 1.75 0 118.3 6.5a1.78 1.78 0 01-1.8 1.75zM19 19h-3v-4.74c0-1.42-.6-1.93-1.38-1.93A1.74 1.74 0 0013 14.19V19h-3v-9h2.9v1.3a3.11 3.11 0 012.7-1.4c1.55 0 3.36.86 3.36 3.66z"/></svg>LinkedIn</a></li>
70
+ {% endif %}
71
+ {% if user.githubUrl %}
72
+ <li><a href="{{ user.githubUrl }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>GitHub</a></li>
73
+ {% endif %}
74
+ {% if user.twitterHandle %}
75
+ <li><a href="https://x.com/{{ user.twitterHandle }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>@{{ user.twitterHandle }}</a></li>
76
+ {% endif %}
77
+ {% if user.websiteUrl %}
78
+ <li><a href="{{ user.websiteUrl }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>{{ user.websiteUrl | stripProtocol }}</a></li>
79
+ {% endif %}
80
+ </ul>
81
+ {% endif %}
82
+ {% if user.resumeUrl %}
83
+ <a href="{{ user.resumeUrl }}" class="hero__resume-btn"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><polyline points="9 15 12 18 15 15"/></svg>Download Resume</a>
84
+ {% endif %}
85
+ </div>
86
+ </div>
87
+ </section>
88
+ {% endif %}
89
+
90
+ {%- comment -%} Stats {%- endcomment -%}
91
+ <section class="section-reveal" aria-label="Aggregate statistics">
92
+ <div class="section-label">
93
+ <span>&#167;02 &mdash; elevation data</span>
94
+ </div>
95
+ <div class="stats-grid">
96
+ <div class="stat-cell stat-counter" role="group" aria-label="Projects count">
97
+ <div class="stat-cell__coord">projects</div>
98
+ <div class="stat-cell__value">{{ projects.size }}</div>
99
+ <div class="stat-cell__label">projects</div>
100
+ </div>
101
+ <div class="stat-cell stat-counter" role="group" aria-label="Sessions count">
102
+ <div class="stat-cell__coord">sessions</div>
103
+ <div class="stat-cell__value">{{ totalSessions }}</div>
104
+ <div class="stat-cell__label">sessions</div>
105
+ </div>
106
+ {% if efficiencyMultiplier %}
107
+ <div class="stat-cell stat-cell--leverage stat-counter" role="group" aria-label="Leverage">
108
+ <div class="stat-cell__coord">leverage</div>
109
+ <div class="mer-leverage-nums">
110
+ <span><span class="mlev-val">{{ totalDurationMinutes | formatDuration }}</span> <span class="mlev-lbl">you</span></span>
111
+ <span><span class="mlev-val mlev-val--agent">{{ totalAgentDurationMinutes | formatDuration }}</span> <span class="mlev-lbl">agents</span></span>
112
+ <span><span class="mlev-val mlev-val--multi">{{ efficiencyMultiplier }}</span></span>
113
+ </div>
114
+ {% assign totalCombined = totalDurationMinutes | plus: totalAgentDurationMinutes %}
115
+ {% if totalCombined > 0 %}
116
+ {% assign humanPct = totalDurationMinutes | times: 100 | divided_by: totalCombined %}
117
+ {% assign agentPct = 100 | minus: humanPct %}
118
+ <div class="mer-leverage-bar">
119
+ <div class="mer-leverage-bar__human" style="width: {{ humanPct }}%"></div>
120
+ <div class="mer-leverage-bar__agent" style="width: {{ agentPct }}%"></div>
121
+ </div>
122
+ {% endif %}
123
+ </div>
124
+ {% else %}
125
+ <div class="stat-cell stat-counter" role="group" aria-label="Duration">
126
+ <div class="stat-cell__coord">time</div>
127
+ <div class="stat-cell__value">{{ totalDurationMinutes | formatDuration }}</div>
128
+ <div class="stat-cell__label">total</div>
129
+ </div>
130
+ {% endif %}
131
+ <div class="stat-cell stat-counter" role="group" aria-label="Lines of code">
132
+ <div class="stat-cell__coord">datum</div>
133
+ <div class="stat-cell__value">{{ totalLoc | localeNumber }}</div>
134
+ <div class="stat-cell__label">lines changed</div>
135
+ </div>
136
+ </div>
137
+ </section>
138
+
139
+ {%- comment -%} Projects {%- endcomment -%}
140
+ {% if projects.size > 0 %}
141
+ <section class="projects-section section-reveal" aria-label="Projects">
142
+ <div class="section-label">
143
+ <span>&#167;03 &mdash; terrain features</span>
144
+ </div>
145
+ <h2 class="section-heading">Projects</h2>
146
+
147
+ {% for p in projects %}
148
+ <article class="project-card">
149
+ <div class="project-card__header">
150
+ <h3 class="project-card__title">
151
+ <a href="/{{ user.username }}/{{ p.slug }}">{{ p.title }}</a>
152
+ </h3>
153
+ {% if p.publishedCount %}
154
+ <span class="project-card__source">{{ p.publishedCount }} session{% if p.publishedCount != 1 %}s{% endif %} published</span>
155
+ {% endif %}
156
+ </div>
157
+ {% if p.narrative != blank %}
158
+ <p class="project-card__narrative">{{ p.narrative }}</p>
159
+ {% endif %}
160
+ <div class="project-card__stats">
161
+ <span><strong>{{ p.totalSessions }}</strong> sessions</span>
162
+ <span><strong>{{ p.totalDurationMinutes | formatDuration }}</strong></span>
163
+ <span><strong>{{ p.totalLoc | localeNumber }}</strong> LOC</span>
164
+ </div>
165
+ {% if p.skills.size > 0 %}
166
+ <div class="project-card__skills">
167
+ {% for skill in p.skills %}
168
+ <span class="skill-chip">{{ skill }}</span>
169
+ {% endfor %}
170
+ </div>
171
+ {% endif %}
172
+ {% if p.sourceCounts.size > 0 %}
173
+ {% assign pTotalSrc = 0 %}
174
+ {% for src in p.sourceCounts %}{% assign pTotalSrc = pTotalSrc | plus: src.count %}{% endfor %}
175
+ {% if pTotalSrc > 0 %}
176
+ <div class="source-bar" role="img" aria-label="Source breakdown">
177
+ {% for src in p.sourceCounts %}
178
+ {% assign pSrcPct = src.count | times: 100.0 | divided_by: pTotalSrc | round %}
179
+ <div class="source-bar__segment source-bar__segment--{% if forloop.first %}claude{% else %}cursor{% endif %}" style="width: {{ pSrcPct }}%"></div>
180
+ {% endfor %}
181
+ </div>
182
+ <div class="source-bar__legend">
183
+ {% for src in p.sourceCounts %}
184
+ <span><span class="source-bar__legend-dot" style="background: {% if forloop.first %}var(--mer-accent){% else %}var(--mer-text-muted){% endif %}"></span>{{ src.tool }}</span>
185
+ {% endfor %}
186
+ </div>
187
+ {% endif %}
188
+ {% endif %}
189
+ </article>
190
+ {% endfor %}
191
+ </section>
192
+ {% endif %}
193
+
194
+ {%- comment -%} Skills Overview {%- endcomment -%}
195
+ {% if allSkills.size > 0 %}
196
+ <section class="section-reveal" aria-label="Skills overview">
197
+ <div class="section-label">
198
+ <span>&#167;04 &mdash; instrument readings</span>
199
+ </div>
200
+ <h2 class="section-heading">Skills Across Projects</h2>
201
+ <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
202
+ {% for skill in allSkills %}
203
+ <span class="skill-chip">{{ skill.name }}</span>
204
+ {% endfor %}
205
+ </div>
206
+ </section>
207
+ {% endif %}
208
+
209
+ {%- comment -%} Source Overview {%- endcomment -%}
210
+ {% if sourceCounts.size > 0 %}
211
+ <section class="section-reveal" aria-label="Source overview">
212
+ <div class="section-label">
213
+ <span>&#167;05 &mdash; map legend</span>
214
+ </div>
215
+ <h2 class="section-heading">Source Overview</h2>
216
+ <div style="background: var(--mer-surface); border: 1px solid var(--mer-border-subtle); border-radius: var(--radius); padding: 1.5rem;">
217
+ <div style="display: grid; grid-template-columns: repeat({{ sourceCounts.size }}, 1fr); gap: 1.5rem;">
218
+ {% for src in sourceCounts %}
219
+ {% if totalSourceSessions > 0 %}
220
+ {% assign srcPct = src.count | times: 100 | divided_by: totalSourceSessions %}
221
+ {% else %}
222
+ {% assign srcPct = 0 %}
223
+ {% endif %}
224
+ <div>
225
+ <div style="font-family: var(--font-mono); font-size: 0.6875rem; color: var(--mer-text-muted); letter-spacing: 0.06em; text-transform: uppercase; margin-block-end: 0.375rem;">{% if forloop.first %}Primary{% else %}Secondary{% endif %} Source</div>
226
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
227
+ <span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background: {% if forloop.first %}var(--mer-accent){% else %}var(--mer-text-muted){% endif %};"></span>
228
+ <span style="font-family: var(--font-mono); font-size: 0.875rem; color: var(--mer-white);">{{ src.tool }}</span>
229
+ </div>
230
+ <div style="font-family: var(--font-mono); font-size: 0.8125rem; color: var(--mer-text-muted); margin-block-start: 0.25rem;">{{ src.count }} sessions &middot; {{ srcPct }}%</div>
231
+ </div>
232
+ {% endfor %}
233
+ </div>
234
+ {% if totalSourceSessions > 0 %}
235
+ <div style="margin-block-start: 1.25rem;">
236
+ <div style="display: flex; height: 8px; border-radius: 4px; overflow: hidden;">
237
+ {% for src in sourceCounts %}
238
+ {% assign srcPct = src.count | times: 100 | divided_by: totalSourceSessions %}
239
+ <div style="width: {{ srcPct }}%; height: 100%; background: {% if forloop.first %}var(--mer-accent){% else %}var(--mer-text-muted){% endif %};"></div>
240
+ {% endfor %}
241
+ </div>
242
+ </div>
243
+ {% endif %}
244
+ </div>
245
+ </section>
246
+ {% endif %}
247
+ </main>
248
+
249
+ <footer class="mer-footer">
250
+ <p class="mer-footer__text">surveyed with heyi.am &middot; meridian template</p>
251
+ </footer>
252
+ </div>
253
+
254
+
255
+ </div>
@@ -0,0 +1,376 @@
1
+ <div class="heyiam-project meridian" data-render-version="2" data-template="meridian"{% if sessionBaseUrl %} data-session-base-url="{{ sessionBaseUrl }}"{% endif %} data-username="{{ user.username }}" data-project-slug="{{ project.slug }}">
2
+
3
+ {%- comment -%} Topographic contour background {%- endcomment -%}
4
+ <div class="topo-bg" aria-hidden="true">
5
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 900" preserveAspectRatio="xMidYMid slice">
6
+ <g class="topo-contours" fill="none" stroke-width="0.75">
7
+ <ellipse cx="350" cy="300" rx="280" ry="180" stroke="var(--mer-contour)" />
8
+ <ellipse cx="355" cy="305" rx="240" ry="150" stroke="var(--mer-contour)" />
9
+ <ellipse cx="360" cy="310" rx="200" ry="120" stroke="var(--mer-contour-strong)" />
10
+ <ellipse cx="365" cy="315" rx="160" ry="90" stroke="var(--mer-contour)" />
11
+ <ellipse cx="370" cy="320" rx="120" ry="65" stroke="var(--mer-contour-strong)" />
12
+ <ellipse cx="850" cy="550" rx="300" ry="200" stroke="var(--mer-contour)" />
13
+ <ellipse cx="845" cy="545" rx="250" ry="165" stroke="var(--mer-contour)" />
14
+ <ellipse cx="840" cy="540" rx="200" ry="130" stroke="var(--mer-contour-strong)" />
15
+ <ellipse cx="835" cy="535" rx="150" ry="95" stroke="var(--mer-contour)" />
16
+ <ellipse cx="950" cy="150" rx="220" ry="140" stroke="var(--mer-contour)" />
17
+ <ellipse cx="955" cy="155" rx="170" ry="105" stroke="var(--mer-contour-strong)" />
18
+ <ellipse cx="960" cy="160" rx="120" ry="70" stroke="var(--mer-contour)" />
19
+ <ellipse cx="150" cy="700" rx="250" ry="160" stroke="var(--mer-contour)" />
20
+ <ellipse cx="155" cy="695" rx="200" ry="125" stroke="var(--mer-contour-strong)" />
21
+ <ellipse cx="160" cy="690" rx="150" ry="90" stroke="var(--mer-contour)" />
22
+ </g>
23
+ </svg>
24
+ </div>
25
+
26
+ <a class="skip-link" href="#main-content">Skip to content</a>
27
+
28
+ <div class="mer-page">
29
+ <main id="main-content">
30
+
31
+ {%- comment -%} Breadcrumb {%- endcomment -%}
32
+ <nav class="breadcrumb" aria-label="Breadcrumb">
33
+ <a href="/{{ user.username }}">{{ user.username }}</a>
34
+ <span class="breadcrumb__sep">/</span>
35
+ <span aria-current="page">{{ project.slug }}</span>
36
+ </nav>
37
+
38
+ {%- comment -%} Hero {%- endcomment -%}
39
+ <section class="project-hero section-reveal" aria-label="Project overview">
40
+ <div class="section-label">
41
+ <span>&#167;01 &mdash; base camp</span>
42
+ </div>
43
+ <h1 class="project-hero__title" data-editable="title">{{ project.title }}</h1>
44
+ {% if project.repoUrl or project.projectUrl %}
45
+ <div class="project-hero__links">
46
+ {% if project.repoUrl %}
47
+ <a href="{{ project.repoUrl }}" target="_blank" rel="noopener">
48
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" stroke-linecap="round" stroke-linejoin="round"/></svg>
49
+ {{ project.repoUrl | stripProtocol }}
50
+ </a>
51
+ {% endif %}
52
+ {% if project.projectUrl %}
53
+ <a href="{{ project.projectUrl }}" target="_blank" rel="noopener">
54
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3" stroke-linecap="round" stroke-linejoin="round"/></svg>
55
+ {{ project.projectUrl | stripProtocol }}
56
+ </a>
57
+ {% endif %}
58
+ </div>
59
+ {% endif %}
60
+ </section>
61
+
62
+ {%- comment -%} Screenshot {%- endcomment -%}
63
+ {% if project.screenshotUrl %}
64
+ <div class="screenshot section-reveal" role="img" aria-label="{{ project.title }} screenshot">
65
+ <div class="screenshot__chrome">
66
+ <span class="screenshot__dot"></span>
67
+ <span class="screenshot__dot"></span>
68
+ <span class="screenshot__dot"></span>
69
+ {% if project.projectUrl %}
70
+ <span class="screenshot__url">{{ project.projectUrl | stripProtocol }}</span>
71
+ {% endif %}
72
+ </div>
73
+ <div class="screenshot__body">
74
+ <img src="{{ project.screenshotUrl }}" alt="{{ project.title }} screenshot" style="width: 100%; height: 100%; object-fit: cover;">
75
+ </div>
76
+ </div>
77
+ {% endif %}
78
+
79
+ {%- comment -%} Stats {%- endcomment -%}
80
+ <section class="section-reveal" aria-label="Project statistics">
81
+ <div class="section-label">
82
+ <span>&#167;02 &mdash; survey data</span>
83
+ </div>
84
+ <div class="stats-row">
85
+ <div class="stat-cell" role="group" aria-label="Sessions">
86
+ <div class="stat-cell__coord">waypts</div>
87
+ <div class="stat-cell__value">{{ project.totalSessions }}</div>
88
+ <div class="stat-cell__label">sessions</div>
89
+ </div>
90
+ {% if efficiencyMultiplier %}
91
+ <div class="stat-cell stat-cell--leverage" role="group" aria-label="Leverage">
92
+ <div class="stat-cell__coord">leverage</div>
93
+ <div class="mer-leverage-nums">
94
+ <span><span class="mlev-val">{{ project.totalDurationMinutes | formatDuration }}</span> <span class="mlev-lbl">you</span></span>
95
+ <span><span class="mlev-val mlev-val--agent">{{ project.totalAgentDurationMinutes | formatDuration }}</span> <span class="mlev-lbl">agents</span></span>
96
+ <span><span class="mlev-val mlev-val--multi">{{ efficiencyMultiplier }}</span></span>
97
+ </div>
98
+ {% assign totalCombined = project.totalDurationMinutes | plus: project.totalAgentDurationMinutes %}
99
+ {% if totalCombined > 0 %}
100
+ {% assign humanPct = project.totalDurationMinutes | times: 100.0 | divided_by: totalCombined | round %}
101
+ {% assign agentPct = 100 | minus: humanPct %}
102
+ <div class="mer-leverage-bar">
103
+ <div class="mer-leverage-bar__human" style="width: {{ humanPct }}%"></div>
104
+ <div class="mer-leverage-bar__agent" style="width: {{ agentPct }}%"></div>
105
+ </div>
106
+ {% endif %}
107
+ </div>
108
+ {% else %}
109
+ <div class="stat-cell" role="group" aria-label="Duration">
110
+ <div class="stat-cell__coord">time</div>
111
+ <div class="stat-cell__value">{{ project.totalDurationMinutes | formatDuration }}</div>
112
+ <div class="stat-cell__label">total</div>
113
+ </div>
114
+ {% endif %}
115
+ <div class="stat-cell" role="group" aria-label="Lines of code">
116
+ <div class="stat-cell__coord">datum</div>
117
+ <div class="stat-cell__value">{{ project.totalLoc | localeNumber }}</div>
118
+ <div class="stat-cell__label">LOC</div>
119
+ </div>
120
+ <div class="stat-cell" role="group" aria-label="Files changed">
121
+ <div class="stat-cell__coord">feat</div>
122
+ <div class="stat-cell__value">{{ project.totalFilesChanged }}</div>
123
+ <div class="stat-cell__label">files</div>
124
+ </div>
125
+ {% if project.totalTokens %}
126
+ <div class="stat-cell" role="group" aria-label="Tokens used">
127
+ <div class="stat-cell__coord">flux</div>
128
+ <div class="stat-cell__value">{{ project.totalTokens | formatTokens }}</div>
129
+ <div class="stat-cell__label">tokens</div>
130
+ </div>
131
+ {% endif %}
132
+ </div>
133
+ </section>
134
+
135
+ {%- comment -%} Narrative {%- endcomment -%}
136
+ {% if project.narrative != blank %}
137
+ <section class="narrative section-reveal" aria-label="Project narrative">
138
+ <div class="section-label">
139
+ <span>&#167;03 &mdash; field notes</span>
140
+ </div>
141
+ <h2 class="section-heading">Narrative</h2>
142
+ <p>{{ project.narrative }}</p>
143
+ </section>
144
+ {% endif %}
145
+
146
+ {%- comment -%} Phases / Route {%- endcomment -%}
147
+ {% if arc.size > 0 %}
148
+ <section class="route-section section-reveal" aria-label="Project phases">
149
+ <div class="section-label">
150
+ <span>&#167;04 &mdash; route map</span>
151
+ </div>
152
+ <h2 class="section-heading">Phases</h2>
153
+ <div class="route" role="list">
154
+ {% for item in arc %}
155
+ <div class="route-waypoint" role="listitem">
156
+ <div class="route-waypoint__marker"><div class="route-waypoint__marker-dot"></div></div>
157
+ <div class="route-waypoint__phase">Phase {{ item.phase }} &mdash; {{ item.title }}</div>
158
+ <div class="route-waypoint__desc">{{ item.description }}</div>
159
+ </div>
160
+ {% endfor %}
161
+ </div>
162
+ </section>
163
+ {% endif %}
164
+
165
+ {%- comment -%} Elevation Profile (Work Timeline as CSS-only SVG) {%- endcomment -%}
166
+ {% if featuredSessions.size > 0 %}
167
+ <section class="elevation-section section-reveal" aria-label="Work timeline chart">
168
+ <div class="section-label">
169
+ <span>&#167;05 &mdash; elevation profile</span>
170
+ </div>
171
+ <h2 class="section-heading">Work Timeline</h2>
172
+ <div class="elevation-chart">
173
+ {% assign maxLoc = 0 %}
174
+ {% for s in featuredSessions %}
175
+ {% if s.locChanged > maxLoc %}{% assign maxLoc = s.locChanged %}{% endif %}
176
+ {% endfor %}
177
+ {% if maxLoc == 0 %}{% assign maxLoc = 1 %}{% endif %}
178
+ <svg viewBox="0 0 800 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Elevation-style chart showing lines of code per session">
179
+ <!-- Grid lines -->
180
+ <line x1="60" y1="20" x2="60" y2="170" stroke="var(--mer-border-subtle)" stroke-width="0.5"/>
181
+ <line x1="60" y1="170" x2="760" y2="170" stroke="var(--mer-border-subtle)" stroke-width="0.5"/>
182
+ <line x1="60" y1="120" x2="760" y2="120" stroke="var(--mer-border-subtle)" stroke-width="0.5" stroke-dasharray="4,4"/>
183
+ <line x1="60" y1="70" x2="760" y2="70" stroke="var(--mer-border-subtle)" stroke-width="0.5" stroke-dasharray="4,4"/>
184
+ <line x1="60" y1="20" x2="760" y2="20" stroke="var(--mer-border-subtle)" stroke-width="0.5" stroke-dasharray="4,4"/>
185
+
186
+ <!-- Y-axis labels -->
187
+ <text x="50" y="174" text-anchor="end" fill="var(--mer-text-muted)" font-family="IBM Plex Mono" font-size="9">0</text>
188
+ {% assign yMid = maxLoc | divided_by: 2 | round %}
189
+ <text x="50" y="124" text-anchor="end" fill="var(--mer-text-muted)" font-family="IBM Plex Mono" font-size="9">{{ yMid }}</text>
190
+ <text x="50" y="74" text-anchor="end" fill="var(--mer-text-muted)" font-family="IBM Plex Mono" font-size="9">{{ maxLoc }}</text>
191
+
192
+ <!-- Data points + line -->
193
+ {% assign chartW = 700 %}
194
+ {% assign chartH = 150 %}
195
+ {% assign sessCount = featuredSessions.size %}
196
+ {% assign points = "" %}
197
+ {% assign areaPath = "" %}
198
+ {% for s in featuredSessions %}
199
+ {% assign idx = forloop.index0 %}
200
+ {% if sessCount > 1 %}
201
+ {% assign xPos = idx | times: chartW | divided_by: sessCount | minus: 1 | plus: 87 %}
202
+ {% else %}
203
+ {% assign xPos = 410 %}
204
+ {% endif %}
205
+ {% assign locPct = s.locChanged | times: chartH | divided_by: maxLoc %}
206
+ {% assign yPos = 170 | minus: locPct %}
207
+ {% if forloop.first %}
208
+ {% assign points = points | append: xPos | append: "," | append: yPos %}
209
+ {% assign areaPath = "M " | append: xPos | append: " " | append: yPos %}
210
+ {% else %}
211
+ {% assign points = points | append: " " | append: xPos | append: "," | append: yPos %}
212
+ {% assign areaPath = areaPath | append: " L " | append: xPos | append: " " | append: yPos %}
213
+ {% endif %}
214
+ {% if forloop.last %}
215
+ {% assign areaPath = areaPath | append: " L " | append: xPos | append: " 170 L 87 170 Z" %}
216
+ {% endif %}
217
+ {% endfor %}
218
+
219
+ <path d="{{ areaPath }}" fill="url(#elevGradient)" opacity="0.3"/>
220
+ <polyline points="{{ points }}" fill="none" stroke="var(--mer-accent)" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
221
+
222
+ {% for s in featuredSessions %}
223
+ {% assign idx = forloop.index0 %}
224
+ {% if sessCount > 1 %}
225
+ {% assign xPos = idx | times: chartW | divided_by: sessCount | minus: 1 | plus: 87 %}
226
+ {% else %}
227
+ {% assign xPos = 410 %}
228
+ {% endif %}
229
+ {% assign locPct = s.locChanged | times: chartH | divided_by: maxLoc %}
230
+ {% assign yPos = 170 | minus: locPct %}
231
+ <circle cx="{{ xPos }}" cy="{{ yPos }}" r="4" fill="var(--mer-accent)"/>
232
+ {% endfor %}
233
+
234
+ <defs>
235
+ <linearGradient id="elevGradient" x1="0" y1="0" x2="0" y2="1">
236
+ <stop offset="0%" stop-color="var(--mer-accent)" stop-opacity="0.4"/>
237
+ <stop offset="100%" stop-color="var(--mer-accent)" stop-opacity="0"/>
238
+ </linearGradient>
239
+ </defs>
240
+ </svg>
241
+ <div class="elevation-chart__x-labels">
242
+ {% for s in featuredSessions %}
243
+ <span>S{{ forloop.index }} {{ s.recordedAt | formatDateShort }}</span>
244
+ {% endfor %}
245
+ </div>
246
+ </div>
247
+ </section>
248
+ {% endif %}
249
+
250
+ {%- comment -%} Sessions Table {%- endcomment -%}
251
+ {% if featuredSessions.size > 0 %}
252
+ <section class="sessions-section section-reveal" aria-label="All sessions">
253
+ <div class="section-label">
254
+ <span>&#167;06 &mdash; waypoint log</span>
255
+ </div>
256
+ <h2 class="section-heading">Sessions</h2>
257
+ <div class="session-table-wrap">
258
+ <table class="session-table">
259
+ <thead>
260
+ <tr>
261
+ <th scope="col">#</th>
262
+ <th scope="col">Title</th>
263
+ <th scope="col">Date</th>
264
+ <th scope="col">Duration</th>
265
+ <th scope="col">LOC</th>
266
+ <th scope="col">Source</th>
267
+ </tr>
268
+ </thead>
269
+ <tbody>
270
+ {% for s in featuredSessions %}
271
+ <tr>
272
+ <td>{{ forloop.index | prepend: '0' | slice: -2, 2 }}</td>
273
+ <td class="session-table__title-cell"><a href="{{ sessionBaseUrl }}/{{ s.slug }}{{ sessionSuffix }}">{{ s.title }}</a></td>
274
+ <td>{{ s.recordedAt | formatDateShort }}</td>
275
+ <td class="mono-val">{{ s.durationMinutes | formatDuration }}</td>
276
+ <td class="mono-val">{{ s.locChanged | localeNumber }}</td>
277
+ <td>{{ s.sourceTool }}</td>
278
+ </tr>
279
+ {% endfor %}
280
+ </tbody>
281
+ </table>
282
+ </div>
283
+ </section>
284
+ {% endif %}
285
+
286
+ {%- comment -%} Key Decisions {%- endcomment -%}
287
+ {% if arc.size > 0 %}
288
+ <section class="decisions-section section-reveal" aria-label="Key decisions">
289
+ <div class="section-label">
290
+ <span>&#167;07 &mdash; bearing marks</span>
291
+ </div>
292
+ <h2 class="section-heading">Key Decisions</h2>
293
+ <ol class="decision-list">
294
+ {% for item in arc %}
295
+ <li class="decision-item">{{ item.description }}</li>
296
+ {% endfor %}
297
+ </ol>
298
+ </section>
299
+ {% endif %}
300
+
301
+ {%- comment -%} Skills {%- endcomment -%}
302
+ {% if project.skills.size > 0 %}
303
+ <section class="skills-section section-reveal" aria-label="Skills used">
304
+ <div class="section-label">
305
+ <span>&#167;08 &mdash; instrument readings</span>
306
+ </div>
307
+ <h2 class="section-heading">Skills</h2>
308
+ <div class="skills-grid">
309
+ {% for skill in project.skills %}
310
+ <span class="skill-chip">{{ skill }}</span>
311
+ {% endfor %}
312
+ </div>
313
+ </section>
314
+ {% endif %}
315
+
316
+ {%- comment -%} Source Breakdown {%- endcomment -%}
317
+ {% if sourceCounts.size > 0 %}
318
+ <section class="source-section section-reveal" aria-label="Source breakdown">
319
+ <div class="section-label">
320
+ <span>&#167;09 &mdash; signal source</span>
321
+ </div>
322
+ <h2 class="section-heading">Source Breakdown</h2>
323
+ {% assign totalSourceSessions = 0 %}
324
+ {% for src in sourceCounts %}
325
+ {% assign totalSourceSessions = totalSourceSessions | plus: src.count %}
326
+ {% endfor %}
327
+ {% if totalSourceSessions > 0 %}
328
+ <div class="source-bar-large" role="img" aria-label="Source breakdown">
329
+ {% for src in sourceCounts %}
330
+ {% assign srcPct = src.count | times: 100.0 | divided_by: totalSourceSessions | round %}
331
+ <div class="source-bar-large__segment{% if forloop.first %} source-bar-large__segment--claude{% else %} source-bar-large__segment--cursor{% endif %}" style="width: {{ srcPct }}%"></div>
332
+ {% endfor %}
333
+ </div>
334
+ <div class="source-legend">
335
+ {% for src in sourceCounts %}
336
+ {% assign legendPct = src.count | times: 100.0 | divided_by: totalSourceSessions | round %}
337
+ <span><span class="source-legend__dot" style="background: {% if forloop.first %}var(--mer-accent){% else %}var(--mer-text-muted){% endif %}"></span>{{ src.tool }} &mdash; {{ legendPct }}% ({{ src.count }} session{% if src.count != 1 %}s{% endif %})</span>
338
+ {% endfor %}
339
+ </div>
340
+ {% endif %}
341
+ </section>
342
+ {% endif %}
343
+
344
+ {%- comment -%} Featured Sessions Cards {%- endcomment -%}
345
+ {% if featuredSessions.size > 0 %}
346
+ <section class="featured-section section-reveal" aria-label="Featured sessions">
347
+ <div class="section-label">
348
+ <span>&#167;10 &mdash; peak waypoints</span>
349
+ </div>
350
+ <h2 class="section-heading">Featured Sessions</h2>
351
+ <div class="featured-grid">
352
+ {% for s in featuredSessions %}
353
+ <article class="featured-card">
354
+ {% if s.skills.size > 0 %}
355
+ <div class="featured-card__tag">{{ s.skills | first }}</div>
356
+ {% endif %}
357
+ <h3 class="featured-card__title"><a href="{{ sessionBaseUrl }}/{{ s.slug }}{{ sessionSuffix }}">{{ s.title }}</a></h3>
358
+ <div class="featured-card__meta">
359
+ <span><strong>{{ s.durationMinutes | formatDuration }}</strong></span>
360
+ <span><strong>{{ s.locChanged | localeNumber }}</strong> LOC</span>
361
+ <span><strong>{{ s.filesChanged }}</strong> files</span>
362
+ </div>
363
+ </article>
364
+ {% endfor %}
365
+ </div>
366
+ </section>
367
+ {% endif %}
368
+ </main>
369
+
370
+ <footer class="mer-footer">
371
+ <p class="mer-footer__text">surveyed with heyi.am &middot; meridian template</p>
372
+ </footer>
373
+ </div>
374
+
375
+
376
+ </div>