heyiam 0.2.29 → 0.3.1

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 (186) hide show
  1. package/README.md +45 -0
  2. package/dist/auth.js +29 -3
  3. package/dist/config.js +10 -1
  4. package/dist/db.js +0 -1
  5. package/dist/export.js +124 -27
  6. package/dist/format-utils.js +5 -0
  7. package/dist/github.js +381 -0
  8. package/dist/index.js +168 -0
  9. package/dist/mount.js +300 -102
  10. package/dist/parsers/claude.js +2 -28
  11. package/dist/parsers/codex.js +2 -26
  12. package/dist/parsers/cursor.js +2 -26
  13. package/dist/parsers/duration.js +35 -0
  14. package/dist/parsers/gemini.js +2 -20
  15. package/dist/parsers/index.js +22 -3
  16. package/dist/parsers/types.js +0 -1
  17. package/dist/public/assets/index-Coilyhtr.css +1 -0
  18. package/dist/public/assets/index-D0noVMFu.js +44 -0
  19. package/dist/public/index.html +2 -2
  20. package/dist/redact.js +4 -104
  21. package/dist/render/build-render-data.js +9 -2
  22. package/dist/render/index.js +32 -5
  23. package/dist/render/liquid.js +147 -7
  24. package/dist/render/mock-data.js +303 -0
  25. package/dist/render/templates/aurora/portfolio.liquid +192 -0
  26. package/dist/render/templates/aurora/project.liquid +260 -0
  27. package/dist/render/templates/aurora/session.liquid +223 -0
  28. package/dist/render/templates/aurora/styles.css +1184 -0
  29. package/dist/render/templates/bauhaus/portfolio.liquid +169 -0
  30. package/dist/render/templates/bauhaus/project.liquid +300 -0
  31. package/dist/render/templates/bauhaus/session.liquid +333 -0
  32. package/dist/render/templates/bauhaus/styles.css +1645 -0
  33. package/dist/render/templates/blueprint/portfolio.liquid +153 -0
  34. package/dist/render/templates/blueprint/project.liquid +286 -0
  35. package/dist/render/templates/blueprint/session.liquid +248 -0
  36. package/dist/render/templates/blueprint/styles.css +1289 -0
  37. package/dist/render/templates/canvas/portfolio.liquid +203 -0
  38. package/dist/render/templates/canvas/project.liquid +235 -0
  39. package/dist/render/templates/canvas/session.liquid +223 -0
  40. package/dist/render/templates/canvas/styles.css +1440 -0
  41. package/dist/render/templates/carbon/portfolio.liquid +160 -0
  42. package/dist/render/templates/carbon/project.liquid +249 -0
  43. package/dist/render/templates/carbon/session.liquid +190 -0
  44. package/dist/render/templates/carbon/styles.css +1097 -0
  45. package/dist/render/templates/chalk/portfolio.liquid +189 -0
  46. package/dist/render/templates/chalk/project.liquid +245 -0
  47. package/dist/render/templates/chalk/session.liquid +215 -0
  48. package/dist/render/templates/chalk/styles.css +1161 -0
  49. package/dist/render/templates/circuit/portfolio.liquid +152 -0
  50. package/dist/render/templates/circuit/project.liquid +247 -0
  51. package/dist/render/templates/circuit/session.liquid +205 -0
  52. package/dist/render/templates/circuit/styles.css +1409 -0
  53. package/dist/render/templates/cosmos/portfolio.liquid +222 -0
  54. package/dist/render/templates/cosmos/project.liquid +327 -0
  55. package/dist/render/templates/cosmos/session.liquid +239 -0
  56. package/dist/render/templates/cosmos/styles.css +1157 -0
  57. package/dist/render/templates/daylight/portfolio.liquid +207 -0
  58. package/dist/render/templates/daylight/project.liquid +229 -0
  59. package/dist/render/templates/daylight/session.liquid +219 -0
  60. package/dist/render/templates/daylight/styles.css +1315 -0
  61. package/dist/render/templates/editorial/portfolio.liquid +110 -0
  62. package/dist/render/templates/editorial/project.liquid +202 -0
  63. package/dist/render/templates/editorial/session.liquid +171 -0
  64. package/dist/render/templates/editorial/styles.css +826 -0
  65. package/dist/render/templates/ember/portfolio.liquid +306 -0
  66. package/dist/render/templates/ember/project.liquid +232 -0
  67. package/dist/render/templates/ember/session.liquid +202 -0
  68. package/dist/render/templates/ember/styles.css +1289 -0
  69. package/dist/render/templates/glacier/portfolio.liquid +261 -0
  70. package/dist/render/templates/glacier/project.liquid +288 -0
  71. package/dist/render/templates/glacier/session.liquid +217 -0
  72. package/dist/render/templates/glacier/styles.css +1204 -0
  73. package/dist/render/templates/grid/portfolio.liquid +255 -0
  74. package/dist/render/templates/grid/project.liquid +306 -0
  75. package/dist/render/templates/grid/session.liquid +260 -0
  76. package/dist/render/templates/grid/styles.css +1445 -0
  77. package/dist/render/templates/kinetic/portfolio.liquid +158 -0
  78. package/dist/render/templates/kinetic/project.liquid +242 -0
  79. package/dist/render/templates/kinetic/session.liquid +228 -0
  80. package/dist/render/templates/kinetic/styles.css +948 -0
  81. package/dist/render/templates/meridian/portfolio.liquid +243 -0
  82. package/dist/render/templates/meridian/project.liquid +376 -0
  83. package/dist/render/templates/meridian/session.liquid +298 -0
  84. package/dist/render/templates/meridian/styles.css +1375 -0
  85. package/dist/render/templates/minimal/portfolio.liquid +71 -0
  86. package/dist/render/templates/minimal/project.liquid +154 -0
  87. package/dist/render/templates/minimal/session.liquid +140 -0
  88. package/dist/render/templates/minimal/styles.css +529 -0
  89. package/dist/render/templates/mono/portfolio.liquid +281 -0
  90. package/dist/render/templates/mono/project.liquid +275 -0
  91. package/dist/render/templates/mono/session.liquid +276 -0
  92. package/dist/render/templates/mono/styles.css +1022 -0
  93. package/dist/render/templates/neon/portfolio.liquid +207 -0
  94. package/dist/render/templates/neon/project.liquid +225 -0
  95. package/dist/render/templates/neon/session.liquid +195 -0
  96. package/dist/render/templates/neon/styles.css +1271 -0
  97. package/dist/render/templates/noir/portfolio.liquid +137 -0
  98. package/dist/render/templates/noir/project.liquid +220 -0
  99. package/dist/render/templates/noir/session.liquid +241 -0
  100. package/dist/render/templates/noir/styles.css +1229 -0
  101. package/dist/render/templates/obsidian/portfolio.liquid +247 -0
  102. package/dist/render/templates/obsidian/project.liquid +280 -0
  103. package/dist/render/templates/obsidian/session.liquid +241 -0
  104. package/dist/render/templates/obsidian/styles.css +1407 -0
  105. package/dist/render/templates/paper/portfolio.liquid +257 -0
  106. package/dist/render/templates/paper/project.liquid +235 -0
  107. package/dist/render/templates/paper/session.liquid +271 -0
  108. package/dist/render/templates/paper/styles.css +1513 -0
  109. package/dist/render/templates/parallax/portfolio.liquid +295 -0
  110. package/dist/render/templates/parallax/project.liquid +275 -0
  111. package/dist/render/templates/parallax/session.liquid +295 -0
  112. package/dist/render/templates/parallax/styles.css +1880 -0
  113. package/dist/render/templates/parchment/portfolio.liquid +280 -0
  114. package/dist/render/templates/parchment/project.liquid +289 -0
  115. package/dist/render/templates/parchment/session.liquid +346 -0
  116. package/dist/render/templates/parchment/styles.css +1401 -0
  117. package/dist/render/templates/partials/_beats.liquid +16 -0
  118. package/dist/render/templates/partials/_breadcrumb.liquid +9 -0
  119. package/dist/render/templates/partials/_footer.liquid +7 -0
  120. package/dist/render/templates/partials/_growth-chart.liquid +7 -0
  121. package/dist/render/templates/partials/_key-decisions.liquid +20 -0
  122. package/dist/render/templates/partials/_links.liquid +16 -0
  123. package/dist/render/templates/partials/_narrative.liquid +8 -0
  124. package/dist/render/templates/partials/_phases.liquid +20 -0
  125. package/dist/render/templates/partials/_portfolio-header.liquid +20 -0
  126. package/dist/render/templates/partials/_portfolio-projects.liquid +16 -0
  127. package/dist/render/templates/partials/_portfolio-stats.liquid +19 -0
  128. package/dist/render/templates/partials/_qa.liquid +13 -0
  129. package/dist/render/templates/partials/_screenshot.liquid +15 -0
  130. package/dist/render/templates/partials/_session-cards.liquid +30 -0
  131. package/dist/render/templates/partials/_session-header.liquid +39 -0
  132. package/dist/render/templates/partials/_session-sidebar.liquid +30 -0
  133. package/dist/render/templates/partials/_skills.liquid +12 -0
  134. package/dist/render/templates/partials/_source-breakdown.liquid +22 -0
  135. package/dist/render/templates/partials/_stats.liquid +38 -0
  136. package/dist/render/templates/partials/_work-timeline.liquid +7 -0
  137. package/dist/render/templates/project.liquid +7 -4
  138. package/dist/render/templates/radar/portfolio.liquid +223 -0
  139. package/dist/render/templates/radar/project.liquid +278 -0
  140. package/dist/render/templates/radar/session.liquid +300 -0
  141. package/dist/render/templates/radar/styles.css +1055 -0
  142. package/dist/render/templates/showcase/portfolio.liquid +221 -0
  143. package/dist/render/templates/showcase/project.liquid +237 -0
  144. package/dist/render/templates/showcase/session.liquid +210 -0
  145. package/dist/render/templates/showcase/styles.css +1284 -0
  146. package/dist/render/templates/signal/portfolio.liquid +217 -0
  147. package/dist/render/templates/signal/project.liquid +278 -0
  148. package/dist/render/templates/signal/session.liquid +282 -0
  149. package/dist/render/templates/signal/styles.css +1401 -0
  150. package/dist/render/templates/strata/portfolio.liquid +180 -0
  151. package/dist/render/templates/strata/project.liquid +282 -0
  152. package/dist/render/templates/strata/session.liquid +261 -0
  153. package/dist/render/templates/strata/styles.css +1354 -0
  154. package/dist/render/templates/styles.css +1190 -0
  155. package/dist/render/templates/terminal/portfolio.liquid +102 -0
  156. package/dist/render/templates/terminal/project.liquid +161 -0
  157. package/dist/render/templates/terminal/session.liquid +145 -0
  158. package/dist/render/templates/terminal/styles.css +497 -0
  159. package/dist/render/templates/verdant/portfolio.liquid +321 -0
  160. package/dist/render/templates/verdant/project.liquid +309 -0
  161. package/dist/render/templates/verdant/session.liquid +237 -0
  162. package/dist/render/templates/verdant/styles.css +1261 -0
  163. package/dist/render/templates/zen/portfolio.liquid +124 -0
  164. package/dist/render/templates/zen/project.liquid +187 -0
  165. package/dist/render/templates/zen/session.liquid +203 -0
  166. package/dist/render/templates/zen/styles.css +1211 -0
  167. package/dist/render/templates.js +90 -0
  168. package/dist/routes/auth.js +7 -3
  169. package/dist/routes/context.js +17 -10
  170. package/dist/routes/delete.js +195 -0
  171. package/dist/routes/enhance.js +57 -40
  172. package/dist/routes/export.js +14 -4
  173. package/dist/routes/github.js +254 -0
  174. package/dist/routes/index.js +2 -0
  175. package/dist/routes/portfolio-render-data.js +160 -0
  176. package/dist/routes/preview.js +555 -108
  177. package/dist/routes/projects.js +61 -24
  178. package/dist/routes/publish.js +320 -31
  179. package/dist/routes/settings.js +194 -1
  180. package/dist/routes/sse.js +9 -0
  181. package/dist/search.js +6 -0
  182. package/dist/server.js +11 -3
  183. package/dist/settings.js +112 -9
  184. package/package.json +3 -4
  185. package/dist/public/assets/index-CC9G8EF1.js +0 -21
  186. package/dist/public/assets/index-Dalqz2mC.css +0 -1
@@ -0,0 +1,137 @@
1
+ <div class="noir-page" data-render-version="2" data-template="noir" data-username="{{ user.username }}">
2
+
3
+ <!-- Hero Profile -->
4
+ {% if hasProfile %}
5
+ <section class="noir-section noir-hero" aria-label="Profile">
6
+ {% if user.photoUrl %}
7
+ <img
8
+ src="{{ user.photoUrl }}"
9
+ alt="{{ user.displayName }}"
10
+ class="noir-hero__photo"
11
+ width="400"
12
+ height="500"
13
+ data-portfolio-field="photoBase64"{% unless user.photoUrl %} data-portfolio-empty="true"{% endunless %}>
14
+ {% endif %}
15
+ <div class="noir-hero__content">
16
+ {% if user.displayName != blank %}
17
+ <h1 class="noir-hero__name" data-portfolio-field="displayName">{{ user.displayName }}</h1>
18
+ {% endif %}
19
+ {% if user.bio != blank %}
20
+ <p class="noir-hero__bio" data-portfolio-field="bio">{{ user.bio }}</p>
21
+ {% endif %}
22
+ {% if user.location != blank %}
23
+ <p class="noir-hero__location" data-portfolio-field="location">{{ user.location }}</p>
24
+ {% endif %}
25
+ <p class="noir-hero__contact">
26
+ <a href="{% if user.email %}mailto:{{ user.email }}{% endif %}" data-portfolio-field="email"{% unless user.email %} data-portfolio-empty="true"{% endunless %}><span data-portfolio-text>{{ user.email }}</span></a><span class="noir-middot" data-portfolio-field="linkedinUrl"{% unless user.linkedinUrl %} data-portfolio-empty="true"{% endunless %}>&middot;</span><a href="{% if user.linkedinUrl %}{{ user.linkedinUrl }}{% endif %}" data-portfolio-field="linkedinUrl"{% unless user.linkedinUrl %} data-portfolio-empty="true"{% endunless %}>LinkedIn</a><span class="noir-middot" data-portfolio-field="githubUrl"{% unless user.githubUrl %} data-portfolio-empty="true"{% endunless %}>&middot;</span><a href="{% if user.githubUrl %}{{ user.githubUrl }}{% endif %}" data-portfolio-field="githubUrl"{% unless user.githubUrl %} data-portfolio-empty="true"{% endunless %}>GitHub</a><span class="noir-middot" data-portfolio-field="twitterHandle"{% unless user.twitterHandle %} data-portfolio-empty="true"{% endunless %}>&middot;</span><a href="{% if user.twitterHandle %}https://x.com/{{ user.twitterHandle }}{% endif %}" data-portfolio-field="twitterHandle"{% unless user.twitterHandle %} data-portfolio-empty="true"{% endunless %}><span data-portfolio-text>{% if user.twitterHandle %}@{{ user.twitterHandle }}{% endif %}</span></a><span class="noir-middot" data-portfolio-field="websiteUrl"{% unless user.websiteUrl %} data-portfolio-empty="true"{% endunless %}>&middot;</span><a href="{% if user.websiteUrl %}{{ user.websiteUrl }}{% endif %}" data-portfolio-field="websiteUrl"{% unless user.websiteUrl %} data-portfolio-empty="true"{% endunless %}><span data-portfolio-text>{{ user.websiteUrl | stripProtocol }}</span></a>
27
+ </p>
28
+ {% if user.resumeUrl != blank %}
29
+ <a href="{{ user.resumeUrl }}" class="noir-hero__resume">
30
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><polyline points="9 15 12 18 15 15"/></svg>
31
+ Resume (PDF)
32
+ </a>
33
+ {% endif %}
34
+ </div>
35
+ </section>
36
+ {% endif %}
37
+
38
+ <hr class="noir-rule">
39
+
40
+ <!-- Aggregate Stats -->
41
+ <section class="noir-section" aria-label="Portfolio statistics">
42
+ <div class="noir-stats-row" role="list">
43
+ <div class="noir-stats-row__item" role="listitem">
44
+ <span class="noir-stats-row__value">{{ projects.size }}</span>
45
+ <span class="noir-stats-row__label">Projects</span>
46
+ </div>
47
+ <div class="noir-stats-row__item" role="listitem">
48
+ <span class="noir-stats-row__value">{{ totalSessions | localeNumber }}</span>
49
+ <span class="noir-stats-row__label">Sessions</span>
50
+ </div>
51
+ <div class="noir-stats-row__item" role="listitem">
52
+ <span class="noir-stats-row__value">{{ totalLoc | formatLoc }}</span>
53
+ <span class="noir-stats-row__label">Lines Changed</span>
54
+ </div>
55
+ </div>
56
+ </section>
57
+
58
+ {% if efficiencyMultiplier %}
59
+ <!-- Leverage -->
60
+ <section aria-label="AI leverage" role="figure">
61
+ <hr class="noir-rule">
62
+ <div class="noir-leverage">
63
+ <span class="noir-leverage__multi">{{ efficiencyMultiplier }}</span>
64
+ <span class="noir-leverage__sep" aria-hidden="true">&mdash;</span>
65
+ <span class="noir-leverage__detail">{{ totalDurationMinutes | formatDuration }} you{% if totalAgentDurationMinutes %}, {{ totalAgentDurationMinutes | formatDuration }} agents{% endif %}</span>
66
+ </div>
67
+ <hr class="noir-rule">
68
+ </section>
69
+ {% endif %}
70
+
71
+ <!-- Work Timeline -->
72
+ {% if projects.size > 0 %}
73
+ <section class="noir-section" aria-label="Work timeline">
74
+ <h2 class="noir-heading">Work Timeline</h2>
75
+ <div class="noir-timeline" role="list">
76
+ {% for p in projects %}
77
+ <div class="noir-timeline__row" role="listitem">
78
+ <span class="noir-timeline__label">{{ p.title }}</span>
79
+ <div class="noir-timeline__bar-track">
80
+ <div class="noir-timeline__bar" style="width: {% if projects[0].totalDurationMinutes > 0 %}{{ p.totalDurationMinutes | times: 100 | divided_by: projects[0].totalDurationMinutes }}%{% else %}0%{% endif %};"></div>
81
+ </div>
82
+ <span class="noir-timeline__value">{{ p.totalDurationMinutes | formatDuration }}</span>
83
+ </div>
84
+ {% endfor %}
85
+ </div>
86
+ </section>
87
+
88
+ <hr class="noir-rule">
89
+ {% endif %}
90
+
91
+ <!-- Projects -->
92
+ {% if projects.size > 0 %}
93
+ <section class="noir-section" aria-label="Projects">
94
+ <h2 class="noir-heading">Projects</h2>
95
+ <div class="noir-projects">
96
+ {% for p in projects %}
97
+ <a href="/{{ user.username }}/{{ p.slug }}" class="noir-project" aria-label="{{ p.title }} project">
98
+ <div>
99
+ <h3 class="noir-project__title">{{ p.title }}</h3>
100
+ {% if p.narrative != blank %}
101
+ <p class="noir-project__narrative">{{ p.narrative }}</p>
102
+ {% endif %}
103
+ {% if p.skills.size > 0 %}
104
+ <div class="noir-project__skills" aria-label="Skills">
105
+ {% for skill in p.skills %}
106
+ <span class="noir-chip">{{ skill }}</span>
107
+ {% endfor %}
108
+ </div>
109
+ {% endif %}
110
+ <p class="noir-project__meta">{{ p.totalSessions }} sessions &middot; {{ p.totalDurationMinutes | formatDuration }} &middot; {{ p.totalLoc | formatLoc }} LOC</p>
111
+ </div>
112
+ <div class="noir-project__sidebar">
113
+ <div>
114
+ <span class="noir-project__stat-num">{{ p.totalLoc | formatLoc }}</span>
115
+ <span class="noir-project__stat-label">LOC</span>
116
+ </div>
117
+ {% if p.sourceCounts.size > 0 %}
118
+ <div class="noir-source-bar" aria-label="Source mix: {% for sc in p.sourceCounts %}{{ sc.tool }} {% assign pct = sc.count | times: 100 | divided_by: p.totalSessions %}{{ pct }}%{% unless forloop.last %}, {% endunless %}{% endfor %}">
119
+ {% for sc in p.sourceCounts %}
120
+ {% assign pct = sc.count | times: 100 | divided_by: p.totalSessions %}
121
+ <div class="noir-source-bar__fill{% if forloop.index > 1 %}--secondary{% endif %}" style="width: {{ pct }}%;"></div>
122
+ {% endfor %}
123
+ </div>
124
+ {% endif %}
125
+ </div>
126
+ </a>
127
+ {% endfor %}
128
+ </div>
129
+ </section>
130
+ {% endif %}
131
+
132
+ <!-- Footer -->
133
+ <footer class="noir-footer">
134
+ <p class="noir-footer__text">Built with heyi.am</p>
135
+ </footer>
136
+
137
+ </div>
@@ -0,0 +1,220 @@
1
+ <div class="noir-page" data-render-version="2" data-template="noir"{% if sessionBaseUrl %} data-session-base-url="{{ sessionBaseUrl }}"{% endif %} data-username="{{ user.username }}" data-project-slug="{{ project.slug }}">
2
+
3
+ <!-- Breadcrumb -->
4
+ <nav class="noir-breadcrumb" aria-label="Breadcrumb">
5
+ <a href="/{{ user.username }}">{{ user.username }}</a>
6
+ <span class="noir-breadcrumb__sep">/</span>
7
+ <span>{{ project.slug }}</span>
8
+ </nav>
9
+
10
+ <!-- Project Header -->
11
+ <section class="noir-section noir-project-header" aria-label="Project header">
12
+ <h1 class="noir-project-header__title" data-editable="title">{{ project.title }}</h1>
13
+ {% if project.repoUrl != blank or project.projectUrl != blank %}
14
+ <div class="noir-project-header__links">
15
+ {% if project.repoUrl != blank %}
16
+ <a href="{{ project.repoUrl }}" aria-label="GitHub repository">{{ project.repoUrl | stripProtocol }}</a>
17
+ {% endif %}
18
+ {% if project.projectUrl != blank %}
19
+ <a href="{{ project.projectUrl }}" aria-label="Live site">{{ project.projectUrl | stripProtocol }}</a>
20
+ {% endif %}
21
+ </div>
22
+ {% endif %}
23
+ </section>
24
+
25
+ <!-- Screenshot -->
26
+ {% if project.screenshotUrl != blank %}
27
+ <section class="noir-section" aria-label="Project screenshot">
28
+ <div class="noir-screenshot" role="img" aria-label="{{ project.title }} screenshot">
29
+ <div class="noir-screenshot__chrome">
30
+ <span class="noir-screenshot__dot"></span>
31
+ <span class="noir-screenshot__dot"></span>
32
+ <span class="noir-screenshot__dot"></span>
33
+ </div>
34
+ <img src="{{ project.screenshotUrl }}" alt="{{ project.title }}" class="noir-screenshot__img">
35
+ </div>
36
+ </section>
37
+ {% endif %}
38
+
39
+ <hr class="noir-rule">
40
+
41
+ <!-- Stats -->
42
+ <section class="noir-section" aria-label="Project statistics">
43
+ <div class="noir-stats-grid" role="list">
44
+ <div class="noir-stat-cell" role="listitem">
45
+ <span class="noir-stat-cell__value">{{ project.totalSessions }}</span>
46
+ <span class="noir-stat-cell__label">Sessions</span>
47
+ </div>
48
+ <div class="noir-stat-cell" role="listitem">
49
+ <span class="noir-stat-cell__value">{{ project.totalLoc | formatLoc }}</span>
50
+ <span class="noir-stat-cell__label">LOC</span>
51
+ </div>
52
+ <div class="noir-stat-cell" role="listitem">
53
+ <span class="noir-stat-cell__value">{{ project.totalFilesChanged | localeNumber }}</span>
54
+ <span class="noir-stat-cell__label">Files</span>
55
+ </div>
56
+ {% if project.totalTokens %}
57
+ <div class="noir-stat-cell" role="listitem">
58
+ <span class="noir-stat-cell__value">{{ project.totalTokens | formatTokens }}</span>
59
+ <span class="noir-stat-cell__label">Tokens</span>
60
+ </div>
61
+ {% endif %}
62
+ </div>
63
+ </section>
64
+
65
+ {% if efficiencyMultiplier %}
66
+ <!-- Leverage -->
67
+ <section aria-label="AI leverage" role="figure">
68
+ <hr class="noir-rule">
69
+ <div class="noir-leverage">
70
+ <span class="noir-leverage__multi">{{ efficiencyMultiplier }}</span>
71
+ <span class="noir-leverage__sep" aria-hidden="true">&mdash;</span>
72
+ <span class="noir-leverage__detail">{{ project.totalDurationMinutes | formatDuration }} you{% if project.totalAgentDurationMinutes %}, {{ project.totalAgentDurationMinutes | formatDuration }} agents{% endif %}</span>
73
+ </div>
74
+ <hr class="noir-rule">
75
+ </section>
76
+ {% endif %}
77
+
78
+ <!-- Narrative -->
79
+ {% if project.narrative != blank %}
80
+ <section class="noir-section" aria-label="Project narrative">
81
+ <h2 class="noir-heading">Narrative</h2>
82
+ <div class="noir-narrative">
83
+ <p>{{ project.narrative }}</p>
84
+ </div>
85
+ </section>
86
+
87
+ <hr class="noir-rule">
88
+ {% endif %}
89
+
90
+ <!-- Sessions -->
91
+ {% if featuredSessions.size > 0 %}
92
+ <section class="noir-section" aria-label="Sessions timeline">
93
+ <h2 class="noir-heading">Sessions</h2>
94
+ <div class="noir-session-timeline" role="list">
95
+ {% for s in featuredSessions %}
96
+ <div class="noir-session-row" role="listitem" style="--bar-width: {% if featuredSessions[0].locChanged > 0 %}{{ s.locChanged | times: 100 | divided_by: featuredSessions[0].locChanged }}%{% else %}0%{% endif %};">
97
+ <span class="noir-session-row__num">{{ forloop.index | prepend: '00' | slice: -2, 2 }}</span>
98
+ <a href="{{ sessionBaseUrl }}/{{ s.slug }}{{ sessionSuffix }}" class="noir-session-row__title">{{ s.title }}</a>
99
+ <div class="noir-session-row__bar-wrap">
100
+ <div class="noir-session-row__bar-track">
101
+ <div class="noir-session-row__bar"></div>
102
+ </div>
103
+ <span class="noir-session-row__loc">{{ s.locChanged | formatLoc }} LOC</span>
104
+ </div>
105
+ <span class="noir-session-row__date">{{ s.recordedAt | formatDateShort }}</span>
106
+ </div>
107
+ {% endfor %}
108
+ </div>
109
+ </section>
110
+
111
+ <hr class="noir-rule">
112
+ {% endif %}
113
+
114
+ <!-- Phases -->
115
+ {% if arc.size > 0 %}
116
+ <section class="noir-section" aria-label="Project phases">
117
+ <h2 class="noir-heading">Phases</h2>
118
+ <div class="noir-phases">
119
+ {% for phase in arc %}
120
+ <article class="noir-phase">
121
+ <span class="noir-phase__number">{{ phase.phase | prepend: '00' | slice: -2, 2 }}</span>
122
+ <div>
123
+ <h3 class="noir-phase__name">{{ phase.title }}</h3>
124
+ {% if phase.dates != blank %}
125
+ <p class="noir-phase__dates">{{ phase.dates }}</p>
126
+ {% endif %}
127
+ {% if phase.description != blank %}
128
+ <p class="noir-phase__desc">{{ phase.description }}</p>
129
+ {% endif %}
130
+ </div>
131
+ </article>
132
+ {% endfor %}
133
+ </div>
134
+ </section>
135
+
136
+ <hr class="noir-rule">
137
+ {% endif %}
138
+
139
+ <!-- Skills -->
140
+ {% if project.skills.size > 0 %}
141
+ <section class="noir-section" aria-label="Technologies used">
142
+ <h2 class="noir-heading">Skills</h2>
143
+ <div class="noir-chips">
144
+ {% for skill in project.skills %}
145
+ <span class="noir-chip">{{ skill }}</span>
146
+ {% endfor %}
147
+ </div>
148
+ </section>
149
+
150
+ <hr class="noir-rule">
151
+ {% endif %}
152
+
153
+ <!-- Key Decisions -->
154
+ {% if arc.size > 0 %}
155
+ <section class="noir-section" aria-label="Key decisions">
156
+ <h2 class="noir-heading">Key Decisions</h2>
157
+ <ol class="noir-decisions">
158
+ {% for item in arc %}
159
+ <li class="noir-decision">
160
+ <span class="noir-decision__num">{{ forloop.index }}</span>
161
+ <p class="noir-decision__text">{{ item.description }}</p>
162
+ </li>
163
+ {% endfor %}
164
+ </ol>
165
+ </section>
166
+
167
+ <hr class="noir-rule">
168
+ {% endif %}
169
+
170
+ <!-- Source Breakdown -->
171
+ {% if sourceCounts.size > 0 %}
172
+ <section class="noir-section" aria-label="Source breakdown">
173
+ <h2 class="noir-heading">Source Breakdown</h2>
174
+ <div class="noir-source-breakdown">
175
+ {% if sourceBarSegments.size > 0 %}
176
+ <div class="noir-source-bar-large" aria-label="Source distribution">
177
+ {% for seg in sourceBarSegments %}
178
+ <div class="noir-source-bar-large__fill{% unless forloop.first %}--secondary{% endunless %}" style="width: {{ seg.percent }}%;"></div>
179
+ {% endfor %}
180
+ </div>
181
+ {% endif %}
182
+ <div class="noir-source-legend">
183
+ {% for sc in sourceCounts %}
184
+ <span class="noir-source-legend__item">
185
+ <span class="noir-source-legend__swatch {% if forloop.first %}noir-source-legend__swatch--primary{% else %}noir-source-legend__swatch--secondary{% endif %}"></span>
186
+ {{ sc.tool }} &mdash; {{ sc.count }} sessions
187
+ </span>
188
+ {% endfor %}
189
+ </div>
190
+ </div>
191
+ </section>
192
+
193
+ <hr class="noir-rule">
194
+ {% endif %}
195
+
196
+ <!-- Featured Session Cards -->
197
+ {% if featuredSessions.size > 0 %}
198
+ <section class="noir-section" aria-label="Featured sessions">
199
+ <h2 class="noir-heading">Featured Sessions</h2>
200
+ <div class="noir-featured-sessions">
201
+ {% for s in featuredSessions %}
202
+ <a href="{{ sessionBaseUrl }}/{{ s.slug }}{{ sessionSuffix }}" class="noir-featured-card">
203
+ <span class="noir-featured-card__tag">{{ s.sourceTool }}</span>
204
+ <h3 class="noir-featured-card__title">{{ s.title }}</h3>
205
+ <span class="noir-featured-card__meta">{{ s.durationMinutes | formatDuration }} &middot; {{ s.locChanged | formatLoc }} LOC</span>
206
+ {% if s.agentSummary.agents.size > 0 %}
207
+ <span class="noir-featured-card__agents">{{ s.agentSummary.agents.size }} agents</span>
208
+ {% endif %}
209
+ </a>
210
+ {% endfor %}
211
+ </div>
212
+ </section>
213
+ {% endif %}
214
+
215
+ <!-- Footer -->
216
+ <footer class="noir-footer">
217
+ <p class="noir-footer__text">Built with heyi.am</p>
218
+ </footer>
219
+
220
+ </div>
@@ -0,0 +1,241 @@
1
+ <div class="noir-page" data-render-version="2" data-template="noir">
2
+
3
+ <!-- Breadcrumb -->
4
+ <nav class="noir-breadcrumb" aria-label="Breadcrumb">
5
+ {% if user.username != blank %}
6
+ <a href="/{{ user.username }}">{{ user.username }}</a>
7
+ <span class="noir-breadcrumb__sep">/</span>
8
+ {% endif %}
9
+ {% if projectSlug != blank %}
10
+ <a href="/{{ user.username }}/{{ projectSlug }}">{{ projectSlug }}</a>
11
+ <span class="noir-breadcrumb__sep">/</span>
12
+ {% endif %}
13
+ <span>{{ session.title }}</span>
14
+ </nav>
15
+
16
+ <!-- Session Header -->
17
+ <section class="noir-section noir-session-header" aria-label="Session header">
18
+ <h1 class="noir-session-header__title">{{ session.title }}</h1>
19
+ <div class="noir-session-header__meta">
20
+ {% if session.recordedAt != blank %}
21
+ <span class="noir-session-header__meta-item">
22
+ <span class="noir-session-header__meta-value">{{ session.recordedAt | formatDate }}</span>
23
+ </span>
24
+ {% endif %}
25
+ {% if session.sourceTool != blank %}
26
+ <span class="noir-session-header__meta-item">
27
+ Source: <span class="noir-session-header__meta-value">{{ session.sourceTool }}</span>
28
+ </span>
29
+ {% endif %}
30
+ </div>
31
+ </section>
32
+
33
+ <!-- Dev Take -->
34
+ {% if session.devTake != blank %}
35
+ <section class="noir-section" aria-label="Developer take">
36
+ <div class="noir-dev-take">
37
+ <p class="noir-dev-take__label">Dev Take</p>
38
+ <p class="noir-dev-take__text">"{{ session.devTake }}"</p>
39
+ </div>
40
+ </section>
41
+ {% endif %}
42
+
43
+ <hr class="noir-rule">
44
+
45
+ <!-- Stats -->
46
+ <section class="noir-section" aria-label="Session statistics">
47
+ <div class="noir-stats-row-session" role="list">
48
+ <div class="noir-stat-cell" role="listitem">
49
+ <span class="noir-stat-cell__value">{{ session.durationMinutes | formatDuration }}</span>
50
+ <span class="noir-stat-cell__label">Duration</span>
51
+ </div>
52
+ <div class="noir-stat-cell" role="listitem">
53
+ <span class="noir-stat-cell__value">{{ session.turns | localeNumber }}</span>
54
+ <span class="noir-stat-cell__label">Turns</span>
55
+ </div>
56
+ <div class="noir-stat-cell" role="listitem">
57
+ <span class="noir-stat-cell__value">{{ session.locChanged | formatLoc }}</span>
58
+ <span class="noir-stat-cell__label">LOC</span>
59
+ </div>
60
+ <div class="noir-stat-cell" role="listitem">
61
+ <span class="noir-stat-cell__value">{{ session.filesChanged }}</span>
62
+ <span class="noir-stat-cell__label">Files Changed</span>
63
+ </div>
64
+ </div>
65
+ </section>
66
+
67
+ <hr class="noir-rule">
68
+
69
+ <!-- Main Content + Sidebar -->
70
+ <div class="noir-layout">
71
+
72
+ <!-- Main Column -->
73
+ <div>
74
+
75
+ <!-- Narrative -->
76
+ {% if session.narrative != blank %}
77
+ <section class="noir-section" aria-label="Narrative">
78
+ <h2 class="noir-heading">Narrative</h2>
79
+ <div class="noir-narrative">
80
+ <p>{{ session.narrative }}</p>
81
+ </div>
82
+ </section>
83
+
84
+ <hr class="noir-rule">
85
+ {% endif %}
86
+
87
+ <!-- Context -->
88
+ {% if session.context != blank %}
89
+ <section class="noir-section" aria-label="Context">
90
+ <h2 class="noir-heading">Context</h2>
91
+ <div class="noir-narrative">
92
+ <p>{{ session.context }}</p>
93
+ </div>
94
+ </section>
95
+
96
+ <hr class="noir-rule">
97
+ {% endif %}
98
+
99
+ <!-- Highlights -->
100
+ {% if session.highlights.size > 0 %}
101
+ <section class="noir-section" aria-label="Highlights">
102
+ <h2 class="noir-heading">Highlights</h2>
103
+ <ul class="noir-highlights">
104
+ {% for h in session.highlights %}
105
+ <li>{{ h }}</li>
106
+ {% endfor %}
107
+ </ul>
108
+ </section>
109
+
110
+ <hr class="noir-rule">
111
+ {% endif %}
112
+
113
+ <!-- Execution Path -->
114
+ {% if session.beats.size > 0 %}
115
+ <section class="noir-section" aria-label="Execution path">
116
+ <h2 class="noir-heading">Execution Path</h2>
117
+ <ol class="noir-beats">
118
+ {% for beat in session.beats %}
119
+ <li class="noir-beat">
120
+ <span class="noir-beat__number">{{ beat.stepNumber | prepend: '00' | slice: -2, 2 }}</span>
121
+ <div>
122
+ <h3 class="noir-beat__title">{{ beat.title }}</h3>
123
+ {% if beat.body != blank %}
124
+ <p class="noir-beat__desc">{{ beat.body }}</p>
125
+ {% endif %}
126
+ </div>
127
+ </li>
128
+ {% endfor %}
129
+ </ol>
130
+ </section>
131
+
132
+ <hr class="noir-rule">
133
+ {% endif %}
134
+
135
+ <!-- Q&A -->
136
+ {% if session.qaPairs.size > 0 %}
137
+ <section class="noir-section" aria-label="Questions and answers">
138
+ <h2 class="noir-heading">Q&amp;A</h2>
139
+ <div class="noir-qa-list">
140
+ {% for qa in session.qaPairs %}
141
+ <article class="noir-qa">
142
+ <h3 class="noir-qa__question">{{ qa.question }}</h3>
143
+ <p class="noir-qa__answer">{{ qa.answer }}</p>
144
+ </article>
145
+ {% endfor %}
146
+ </div>
147
+ </section>
148
+
149
+ <hr class="noir-rule">
150
+ {% endif %}
151
+
152
+ <!-- Agent Summary -->
153
+ {% if session.agentSummary.agents.size > 0 %}
154
+ <section class="noir-section" aria-label="Agent summary">
155
+ <h2 class="noir-heading">Agent Summary</h2>
156
+ <p class="noir-agent-count">{{ session.agentSummary.agents.size }} sub-sessions</p>
157
+
158
+ <div class="noir-agents" role="table" aria-label="Agent breakdown">
159
+ {% for sub in session.agentSummary.agents %}
160
+ <div class="noir-agent-row" role="row">
161
+ <span class="noir-agent-row__role" role="cell">{{ sub.role }}</span>
162
+ <span class="noir-agent-row__duration" role="cell">{{ sub.duration_minutes | formatDuration }}</span>
163
+ <span class="noir-agent-row__loc" role="cell">{{ sub.loc_changed }} LOC</span>
164
+ </div>
165
+ {% endfor %}
166
+ </div>
167
+
168
+ <!-- Agent bar chart -->
169
+ <div class="noir-agent-bars" aria-label="Agent duration chart">
170
+ {% for sub in session.agentSummary.agents %}
171
+ <div class="noir-agent-bar-row">
172
+ <span class="noir-agent-bar-row__label">{{ sub.role }}</span>
173
+ <div class="noir-agent-bar-row__track">
174
+ <div class="noir-agent-bar-row__fill" style="width: {% if session.agentSummary.agents[0].duration_minutes > 0 %}{{ sub.duration_minutes | times: 100 | divided_by: session.agentSummary.agents[0].duration_minutes }}%{% else %}0%{% endif %};"></div>
175
+ </div>
176
+ <span class="noir-agent-bar-row__value">{{ sub.duration_minutes | formatDuration }}</span>
177
+ </div>
178
+ {% endfor %}
179
+ </div>
180
+ </section>
181
+ {% endif %}
182
+
183
+ </div>
184
+
185
+ <!-- Sidebar -->
186
+ <aside class="noir-sidebar" aria-label="Session details">
187
+
188
+ <!-- Tools Used -->
189
+ {% if session.toolBreakdown.size > 0 %}
190
+ <div class="noir-sidebar-section">
191
+ <h2 class="noir-sidebar-section__title">Tools Used</h2>
192
+ <table class="noir-tool-table" aria-label="Tool usage counts">
193
+ <tbody>
194
+ {% for tool in session.toolBreakdown %}
195
+ <tr>
196
+ <td>{{ tool.tool }}</td>
197
+ <td>{{ tool.count }}</td>
198
+ </tr>
199
+ {% endfor %}
200
+ </tbody>
201
+ </table>
202
+ </div>
203
+ {% endif %}
204
+
205
+ <!-- Files Changed -->
206
+ {% if session.topFiles.size > 0 %}
207
+ <div class="noir-sidebar-section">
208
+ <h2 class="noir-sidebar-section__title">Files Changed</h2>
209
+ <ul class="noir-file-list" aria-label="Changed files">
210
+ {% for f in session.topFiles %}
211
+ <li class="noir-file-item">
212
+ <span class="noir-file-item__name" title="{{ f.path }}">{{ f.path }}</span>
213
+ <span class="noir-file-item__diff">+{{ f.additions }}{% if f.deletions > 0 %} -{{ f.deletions }}{% endif %}</span>
214
+ </li>
215
+ {% endfor %}
216
+ </ul>
217
+ </div>
218
+ {% endif %}
219
+
220
+ <!-- Skills -->
221
+ {% if session.skills.size > 0 %}
222
+ <div class="noir-sidebar-section">
223
+ <h2 class="noir-sidebar-section__title">Skills</h2>
224
+ <div class="noir-chips">
225
+ {% for skill in session.skills %}
226
+ <span class="noir-chip">{{ skill }}</span>
227
+ {% endfor %}
228
+ </div>
229
+ </div>
230
+ {% endif %}
231
+
232
+ </aside>
233
+
234
+ </div>
235
+
236
+ <!-- Footer -->
237
+ <footer class="noir-footer">
238
+ <p class="noir-footer__text">Built with heyi.am</p>
239
+ </footer>
240
+
241
+ </div>