heyiam 0.3.6 → 0.3.9

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 (92) hide show
  1. package/dist/archive.js +28 -0
  2. package/dist/db.js +9 -0
  3. package/dist/export.js +3 -0
  4. package/dist/llm/project-enhance.js +11 -5
  5. package/dist/mount.js +43 -18
  6. package/dist/public/assets/index-ByoBtx7P.css +1 -0
  7. package/dist/public/assets/index-LQHTU1Wz.js +37 -0
  8. package/dist/public/index.html +2 -2
  9. package/dist/render/build-render-data.js +1 -0
  10. package/dist/render/liquid.js +14 -1
  11. package/dist/render/mock-data.js +6 -0
  12. package/dist/render/select-profile-skills.js +54 -0
  13. package/dist/render/templates/aurora/portfolio.liquid +4 -4
  14. package/dist/render/templates/aurora/styles.css +6 -6
  15. package/dist/render/templates/bauhaus/portfolio.liquid +4 -4
  16. package/dist/render/templates/bauhaus/project.liquid +2 -2
  17. package/dist/render/templates/bauhaus/styles.css +1 -1
  18. package/dist/render/templates/blueprint/portfolio.liquid +4 -4
  19. package/dist/render/templates/blueprint/styles.css +1 -1
  20. package/dist/render/templates/canvas/portfolio.liquid +4 -4
  21. package/dist/render/templates/canvas/styles.css +2 -2
  22. package/dist/render/templates/carbon/portfolio.liquid +4 -4
  23. package/dist/render/templates/carbon/styles.css +7 -7
  24. package/dist/render/templates/chalk/portfolio.liquid +4 -4
  25. package/dist/render/templates/chalk/styles.css +44 -2
  26. package/dist/render/templates/circuit/portfolio.liquid +4 -4
  27. package/dist/render/templates/cosmos/portfolio.liquid +4 -4
  28. package/dist/render/templates/cosmos/styles.css +4 -4
  29. package/dist/render/templates/daylight/portfolio.liquid +4 -4
  30. package/dist/render/templates/editorial/portfolio.liquid +4 -4
  31. package/dist/render/templates/editorial/project.liquid +1 -1
  32. package/dist/render/templates/editorial/styles.css +6 -1
  33. package/dist/render/templates/ember/portfolio.liquid +4 -4
  34. package/dist/render/templates/ember/styles.css +2 -2
  35. package/dist/render/templates/glacier/portfolio.liquid +4 -4
  36. package/dist/render/templates/glacier/project.liquid +3 -3
  37. package/dist/render/templates/glacier/styles.css +2 -2
  38. package/dist/render/templates/grid/portfolio.liquid +4 -4
  39. package/dist/render/templates/grid/styles.css +2 -2
  40. package/dist/render/templates/kinetic/portfolio.liquid +4 -4
  41. package/dist/render/templates/kinetic/project.liquid +1 -1
  42. package/dist/render/templates/kinetic/styles.css +5 -5
  43. package/dist/render/templates/meridian/portfolio.liquid +4 -4
  44. package/dist/render/templates/meridian/styles.css +1 -1
  45. package/dist/render/templates/minimal/portfolio.liquid +3 -3
  46. package/dist/render/templates/minimal/project.liquid +1 -1
  47. package/dist/render/templates/mono/portfolio.liquid +4 -4
  48. package/dist/render/templates/mono/styles.css +15 -1
  49. package/dist/render/templates/neon/portfolio.liquid +4 -4
  50. package/dist/render/templates/neon/styles.css +11 -20
  51. package/dist/render/templates/noir/portfolio.liquid +4 -4
  52. package/dist/render/templates/noir/styles.css +5 -5
  53. package/dist/render/templates/obsidian/portfolio.liquid +4 -4
  54. package/dist/render/templates/obsidian/styles.css +2 -2
  55. package/dist/render/templates/paper/portfolio.liquid +4 -4
  56. package/dist/render/templates/paper/project.liquid +2 -2
  57. package/dist/render/templates/paper/styles.css +60 -1
  58. package/dist/render/templates/parallax/portfolio.liquid +4 -4
  59. package/dist/render/templates/parallax/styles.css +1 -1
  60. package/dist/render/templates/parchment/portfolio.liquid +4 -4
  61. package/dist/render/templates/parchment/styles.css +3 -3
  62. package/dist/render/templates/partials/_work-timeline.liquid +1 -1
  63. package/dist/render/templates/project.liquid +1 -1
  64. package/dist/render/templates/radar/portfolio.liquid +4 -4
  65. package/dist/render/templates/radar/project.liquid +1 -1
  66. package/dist/render/templates/radar/styles.css +4 -4
  67. package/dist/render/templates/showcase/portfolio.liquid +4 -4
  68. package/dist/render/templates/showcase/project.liquid +1 -1
  69. package/dist/render/templates/showcase/styles.css +7 -7
  70. package/dist/render/templates/signal/portfolio.liquid +4 -4
  71. package/dist/render/templates/signal/styles.css +5 -5
  72. package/dist/render/templates/strata/portfolio.liquid +4 -4
  73. package/dist/render/templates/strata/styles.css +3 -3
  74. package/dist/render/templates/styles.css +39 -28
  75. package/dist/render/templates/terminal/portfolio.liquid +2 -2
  76. package/dist/render/templates/terminal/project.liquid +1 -1
  77. package/dist/render/templates/verdant/portfolio.liquid +4 -4
  78. package/dist/render/templates/verdant/styles.css +17 -2
  79. package/dist/render/templates/zen/portfolio.liquid +4 -4
  80. package/dist/render/templates/zen/styles.css +8 -8
  81. package/dist/routes/context.js +26 -3
  82. package/dist/routes/enhance.js +10 -3
  83. package/dist/routes/export.js +1 -0
  84. package/dist/routes/github.js +1 -1
  85. package/dist/routes/portfolio-render-data.js +15 -1
  86. package/dist/routes/preview.js +31 -1
  87. package/dist/routes/projects.js +8 -1
  88. package/dist/routes/publish.js +9 -2
  89. package/dist/settings.js +2 -0
  90. package/package.json +1 -1
  91. package/dist/public/assets/index-7mUuxgqY.js +0 -37
  92. package/dist/public/assets/index-CMyamplX.css +0 -1
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>app</title>
8
- <script type="module" crossorigin src="/assets/index-7mUuxgqY.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-CMyamplX.css">
8
+ <script type="module" crossorigin src="/assets/index-LQHTU1Wz.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-ByoBtx7P.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -105,5 +105,6 @@ export function buildProjectRenderData(opts) {
105
105
  allSessions: opts.allSessionCards,
106
106
  sessionBaseUrl: opts.sessionBaseUrl,
107
107
  sessionSuffix: opts.sessionSuffix,
108
+ ...(opts.hideSessionDates ? { hideSessionDates: true } : {}),
108
109
  };
109
110
  }
@@ -110,7 +110,20 @@ export function renderProject(data, extras, templateName) {
110
110
  const slug = typeof existingSlug === 'string' && existingSlug !== ''
111
111
  ? existingSlug
112
112
  : (slugByToken.get(id) ?? '');
113
- return { ...rest, slug, rawLog: [] };
113
+ const merged = { ...rest, slug, rawLog: [] };
114
+ // When dates are hidden, strip every timestamp from the session so
115
+ // nothing leaks through the page source. The chart and overlay treat
116
+ // a missing `date` as "ordinal mode" — no axis labels, no gap markers.
117
+ if (data.hideSessionDates) {
118
+ delete merged.date;
119
+ delete merged.endTime;
120
+ const children = merged.children;
121
+ if (Array.isArray(children)) {
122
+ for (const c of children)
123
+ delete c.date;
124
+ }
125
+ }
126
+ return merged;
114
127
  });
115
128
  // Encode JSON safe for single-quoted HTML attributes
116
129
  const sessionsJson = JSON.stringify(chartSessions).replace(/'/g, '&#39;');
@@ -26,6 +26,7 @@ export function getMockPortfolioData() {
26
26
  {
27
27
  slug: 'budgetwise',
28
28
  title: 'BudgetWise',
29
+ tagline: 'I built a personal finance tracker that categorizes your transactions without the bank-screen-scraping nonsense.',
29
30
  narrative: 'A personal finance tracker with AI-powered categorization. Built the Prisma schema, REST API, and React dashboard in a single sprint using Claude as a coding partner.',
30
31
  totalSessions: 8,
31
32
  totalLoc: 3200,
@@ -33,6 +34,7 @@ export function getMockPortfolioData() {
33
34
  totalAgentDurationMinutes: 2064,
34
35
  totalFilesChanged: 45,
35
36
  skills: ['TypeScript', 'React', 'Prisma', 'Node.js'],
37
+ profileSkills: ['TypeScript', 'React', 'Prisma', 'Node.js'],
36
38
  sourceCounts: [{ tool: 'claude', count: 5 }, { tool: 'cursor', count: 3 }],
37
39
  publishedCount: 6,
38
40
  sessions: [
@@ -49,6 +51,7 @@ export function getMockPortfolioData() {
49
51
  {
50
52
  slug: 'shellhook',
51
53
  title: 'ShellHook',
54
+ tagline: 'I built a git hooks manager that makes team hooks actually shareable — no more "did you run the setup script?"',
52
55
  narrative: 'Git hooks manager that auto-installs and shares hooks across teams. Zero config, works with any shell, supports pre-commit, pre-push, and custom triggers.',
53
56
  totalSessions: 5,
54
57
  totalLoc: 1800,
@@ -56,6 +59,7 @@ export function getMockPortfolioData() {
56
59
  totalAgentDurationMinutes: 900,
57
60
  totalFilesChanged: 22,
58
61
  skills: ['Rust', 'Shell', 'Git'],
62
+ profileSkills: ['Rust', 'Shell', 'Git'],
59
63
  sourceCounts: [{ tool: 'claude', count: 3 }, { tool: 'cursor', count: 2 }],
60
64
  publishedCount: 4,
61
65
  sessions: [
@@ -69,6 +73,7 @@ export function getMockPortfolioData() {
69
73
  {
70
74
  slug: 'pixelboard',
71
75
  title: 'PixelBoard',
76
+ tagline: 'I built a collaborative pixel art canvas that stays in sync even when two people draw the same pixel at once.',
72
77
  narrative: 'Collaborative pixel art canvas with real-time sync. WebSocket-based with conflict-free replicated data types for seamless multi-user editing.',
73
78
  totalSessions: 12,
74
79
  totalLoc: 4500,
@@ -76,6 +81,7 @@ export function getMockPortfolioData() {
76
81
  totalAgentDurationMinutes: 2160,
77
82
  totalFilesChanged: 60,
78
83
  skills: ['TypeScript', 'WebSocket', 'Canvas API', 'Redis'],
84
+ profileSkills: ['TypeScript', 'WebSocket', 'Canvas API', 'Redis'],
79
85
  sourceCounts: [{ tool: 'claude', count: 8 }, { tool: 'cursor', count: 4 }],
80
86
  publishedCount: 8,
81
87
  sessions: [
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Pick which skills to show on a portfolio card.
3
+ *
4
+ * The full skill list still lives on the project detail page; this trims
5
+ * the list to a handful for the dense card grid. Shared between the
6
+ * publish flow (`portfolio-render-data.ts`) and the preview flow
7
+ * (`preview.ts`) so what the user sees in the template browser matches
8
+ * what they actually ship.
9
+ */
10
+ /** Cap for the number of skill chips shown on a portfolio card. */
11
+ export const PROFILE_SKILL_CAP = 4;
12
+ /**
13
+ * Return the capped, ranked subset of skills to render on the profile card.
14
+ *
15
+ * Ranks by **sessions-touched count** — the skill that appeared in the most
16
+ * distinct sessions wins. A skill mentioned in 8 of 10 sessions ranks above
17
+ * one mentioned in 1 of 10, regardless of how many times it appeared inside
18
+ * any single session (so one verbose session can't dominate the ranking).
19
+ * Ties are broken by the skill's order in `projectSkills` (stable, canonical).
20
+ *
21
+ * Invariants:
22
+ * - Output length ≤ PROFILE_SKILL_CAP.
23
+ * - Output is a subset of `projectSkills` (preserves canonical casing).
24
+ * - No duplicates.
25
+ * - Empty `projectSkills` → empty output.
26
+ * - Skills appearing in sessions but not in `projectSkills` are ignored
27
+ * (the enhance cache is the source of truth for which skills count).
28
+ */
29
+ export function selectProfileSkills(input) {
30
+ const { projectSkills, sessionSkills } = input;
31
+ if (projectSkills.length === 0)
32
+ return [];
33
+ const canonical = new Set(projectSkills);
34
+ const sessionsTouched = new Map();
35
+ for (const skill of projectSkills)
36
+ sessionsTouched.set(skill, 0);
37
+ for (const perSession of sessionSkills) {
38
+ const seen = new Set();
39
+ for (const skill of perSession) {
40
+ if (canonical.has(skill) && !seen.has(skill)) {
41
+ seen.add(skill);
42
+ sessionsTouched.set(skill, (sessionsTouched.get(skill) ?? 0) + 1);
43
+ }
44
+ }
45
+ }
46
+ const order = new Map(projectSkills.map((s, i) => [s, i]));
47
+ return projectSkills
48
+ .slice()
49
+ .sort((a, b) => {
50
+ const diff = (sessionsTouched.get(b) ?? 0) - (sessionsTouched.get(a) ?? 0);
51
+ return diff !== 0 ? diff : (order.get(a) ?? 0) - (order.get(b) ?? 0);
52
+ })
53
+ .slice(0, PROFILE_SKILL_CAP);
54
+ }
@@ -94,17 +94,17 @@
94
94
  <div class="aurora-card-header">
95
95
  <h3>{{ p.title }}</h3>
96
96
  </div>
97
- {% if p.narrative %}
98
- <p class="aurora-card-narrative">{{ p.narrative | truncate: 120 }}</p>
97
+ {% if p.tagline != blank %}
98
+ <p class="aurora-card-narrative">{{ p.tagline }}</p>
99
99
  {% endif %}
100
100
  <div class="aurora-card-stats">
101
101
  <span>{{ p.totalSessions }} sessions</span>
102
102
  <span>{{ p.totalDurationMinutes | formatDuration }}</span>
103
103
  <span>{{ p.totalLoc | localeNumber }} LOC</span>
104
104
  </div>
105
- {% if p.skills.size > 0 %}
105
+ {% if p.profileSkills.size > 0 %}
106
106
  <div class="aurora-tags">
107
- {% for skill in p.skills %}
107
+ {% for skill in p.profileSkills %}
108
108
  <span class="aurora-tag">{{ skill }}</span>
109
109
  {% endfor %}
110
110
  </div>
@@ -245,7 +245,7 @@ body { background: var(--aurora-bg); color: var(--aurora-text); }
245
245
  font-size: 1.375rem;
246
246
  }
247
247
  .aurora .aurora-stats--project .aurora-stat-label {
248
- font-size: 0.6875rem;
248
+ font-size: 0.75rem;
249
249
  margin-top: 0.125rem;
250
250
  }
251
251
  .aurora .aurora-stat {
@@ -375,7 +375,7 @@ body { background: var(--aurora-bg); color: var(--aurora-text); }
375
375
  color: var(--aurora-text);
376
376
  }
377
377
  .aurora .aurora-card-narrative {
378
- font-size: 0.9375rem;
378
+ font-size: 1rem;
379
379
  color: var(--aurora-text-secondary);
380
380
  line-height: 1.65;
381
381
  margin-bottom: 1.25rem;
@@ -495,7 +495,7 @@ body { background: var(--aurora-bg); color: var(--aurora-text); }
495
495
 
496
496
  /* ── Narrative ── */
497
497
  .aurora .aurora-narrative p {
498
- font-size: 0.9375rem;
498
+ font-size: 1rem;
499
499
  color: var(--aurora-text-secondary);
500
500
  line-height: 1.75;
501
501
  margin-bottom: 1rem;
@@ -651,7 +651,7 @@ body { background: var(--aurora-bg); color: var(--aurora-text); }
651
651
  text-shadow: 0 0 10px rgba(45, 212, 191, 0.3);
652
652
  }
653
653
  .aurora .aurora-decision-text {
654
- font-size: 0.9375rem;
654
+ font-size: 1rem;
655
655
  color: var(--aurora-text-secondary);
656
656
  line-height: 1.5;
657
657
  }
@@ -815,7 +815,7 @@ body { background: var(--aurora-bg); color: var(--aurora-text); }
815
815
  border-inline-start: 3px solid var(--aurora-accent);
816
816
  border-radius: var(--aurora-radius);
817
817
  padding: 1.25rem 1.5rem;
818
- font-size: 0.9375rem;
818
+ font-size: 1rem;
819
819
  color: var(--aurora-text-secondary);
820
820
  line-height: 1.7;
821
821
  font-style: italic;
@@ -1048,7 +1048,7 @@ body { background: var(--aurora-bg); color: var(--aurora-text); }
1048
1048
  margin-bottom: 2.5rem;
1049
1049
  }
1050
1050
  .aurora .aurora-narrative-section p {
1051
- font-size: 0.9375rem;
1051
+ font-size: 1rem;
1052
1052
  color: var(--aurora-text-secondary);
1053
1053
  line-height: 1.75;
1054
1054
  }
@@ -110,12 +110,12 @@
110
110
  <div class="project-header">
111
111
  <h3 class="project-title">{{ p.title }}</h3>
112
112
  </div>
113
- {% if p.narrative %}
114
- <p class="project-narrative">{{ p.narrative }}</p>
113
+ {% if p.tagline != blank %}
114
+ <p class="project-narrative">{{ p.tagline }}</p>
115
115
  {% endif %}
116
- {% if p.skills.size > 0 %}
116
+ {% if p.profileSkills.size > 0 %}
117
117
  <div class="project-skills" aria-label="Skills used">
118
- {% for skill in p.skills %}
118
+ {% for skill in p.profileSkills %}
119
119
  <span class="skill-chip">{{ skill }}</span>
120
120
  {% endfor %}
121
121
  </div>
@@ -218,7 +218,7 @@
218
218
  <tr>
219
219
  <th scope="col">#</th>
220
220
  <th scope="col">Session</th>
221
- <th scope="col">Date</th>
221
+ {% unless hideSessionDates %}<th scope="col">Date</th>{% endunless %}
222
222
  <th scope="col">Duration</th>
223
223
  <th scope="col">LOC</th>
224
224
  <th scope="col">Source</th>
@@ -230,7 +230,7 @@
230
230
  <tr>
231
231
  <td><span class="session-num">{{ forloop.index }}</span></td>
232
232
  <td><a href="{{ sessionBaseUrl }}/{{ s.slug }}{{ sessionSuffix }}" class="session-title-link">{{ s.title }}</a></td>
233
- <td>{{ s.recordedAt | formatDateShort }}</td>
233
+ {% unless hideSessionDates %}<td>{{ s.recordedAt | formatDateShort }}</td>{% endunless %}
234
234
  <td>{{ s.durationMinutes | formatDuration }}</td>
235
235
  <td>{{ s.locChanged | localeNumber }}</td>
236
236
  <td>{% if s.sourceTool %}<span class="session-source-badge badge-{{ s.sourceTool | downcase | replace: ' ', '-' }}">{{ s.sourceTool }}</span>{% endif %}</td>
@@ -539,7 +539,7 @@
539
539
 
540
540
  .bauhaus .stat-cell-label {
541
541
  font-family: var(--font-mono);
542
- font-size: 11px;
542
+ font-size: 12px;
543
543
  font-weight: 500;
544
544
  text-transform: uppercase;
545
545
  letter-spacing: 1px;
@@ -107,17 +107,17 @@
107
107
  </h2>
108
108
  <span class="bp-project-ref">REF-{{ forloop.index | prepend: '00' | slice: -3, 3 }}</span>
109
109
  </div>
110
- {% if p.narrative != blank %}
111
- <p class="bp-project-narrative">{{ p.narrative | truncate: 160 }}</p>
110
+ {% if p.tagline != blank %}
111
+ <p class="bp-project-narrative">{{ p.tagline }}</p>
112
112
  {% endif %}
113
113
  <div class="bp-project-meta" aria-label="Project statistics">
114
114
  <span>{{ p.totalSessions }} sessions</span>
115
115
  <span>{{ p.totalDurationMinutes | formatDuration }}</span>
116
116
  <span>{{ p.totalLoc | localeNumber }} LOC</span>
117
117
  </div>
118
- {% if p.skills.size > 0 %}
118
+ {% if p.profileSkills.size > 0 %}
119
119
  <ul class="bp-skills-list" aria-label="Skills">
120
- {% for skill in p.skills limit: 6 %}
120
+ {% for skill in p.profileSkills %}
121
121
  <li class="bp-skill-chip">{{ skill }}</li>
122
122
  {% endfor %}
123
123
  </ul>
@@ -318,7 +318,7 @@
318
318
  }
319
319
  .bp-stat-label {
320
320
  font-family: var(--bp-font-mono);
321
- font-size: 10px;
321
+ font-size: 12px;
322
322
  text-transform: uppercase;
323
323
  letter-spacing: 0.06em;
324
324
  color: var(--bp-fg-muted);
@@ -124,13 +124,13 @@
124
124
  <h3 class="project-card__title">{{ p.title }}</h3>
125
125
  <span class="project-card__stats">{{ p.totalSessions }} sessions &middot; {{ p.totalDurationMinutes | formatDuration }} &middot; {{ p.totalLoc | formatLoc }} LOC</span>
126
126
  </div>
127
- {% if p.narrative %}
128
- <p class="project-card__narrative">{{ p.narrative }}</p>
127
+ {% if p.tagline != blank %}
128
+ <p class="project-card__narrative">{{ p.tagline }}</p>
129
129
  {% endif %}
130
130
  <div class="project-card__footer">
131
- {% if p.skills.size > 0 %}
131
+ {% if p.profileSkills.size > 0 %}
132
132
  <div class="project-card__skills">
133
- {% for skill in p.skills %}
133
+ {% for skill in p.profileSkills %}
134
134
  <span class="skill-pill">{{ skill }}</span>
135
135
  {% endfor %}
136
136
  </div>
@@ -205,7 +205,7 @@
205
205
  }
206
206
  .canvas .stat__label {
207
207
  font-family: var(--canvas-font-mono);
208
- font-size: 0.6875rem;
208
+ font-size: 0.75rem;
209
209
  color: var(--canvas-text-tertiary);
210
210
  text-transform: uppercase;
211
211
  letter-spacing: 0.1em;
@@ -340,7 +340,7 @@
340
340
  }
341
341
  .canvas .hero-stat__label {
342
342
  font-family: var(--canvas-font-mono);
343
- font-size: 0.6875rem;
343
+ font-size: 0.75rem;
344
344
  color: var(--canvas-text-tertiary);
345
345
  text-transform: uppercase;
346
346
  letter-spacing: 0.1em;
@@ -76,12 +76,12 @@
76
76
  <h3 class="card__title"><a href="/{{ user.username }}/{{ p.slug }}">{{ p.title }}</a></h3>
77
77
  </div>
78
78
  <div class="card__body">
79
- {% if p.narrative != blank %}
80
- <p class="card__narrative">{{ p.narrative | truncate: 120 }}</p>
79
+ {% if p.tagline != blank %}
80
+ <p class="card__narrative">{{ p.tagline }}</p>
81
81
  {% endif %}
82
- {% if p.skills.size > 0 %}
82
+ {% if p.profileSkills.size > 0 %}
83
83
  <ul class="skill-list" aria-label="Skills used in {{ p.title }}">
84
- {% for skill in p.skills limit: 6 %}
84
+ {% for skill in p.profileSkills %}
85
85
  <li class="skill-chip">{{ skill }}</li>
86
86
  {% endfor %}
87
87
  </ul>
@@ -195,7 +195,7 @@ body { background: var(--carbon-bg); color: var(--carbon-text); }
195
195
 
196
196
  .carbon .stat-cell__label {
197
197
  font-family: var(--font-mono);
198
- font-size: 0.6875rem;
198
+ font-size: 0.75rem;
199
199
  color: var(--carbon-text-muted);
200
200
  text-transform: uppercase;
201
201
  letter-spacing: 0.08em;
@@ -309,7 +309,7 @@ body { background: var(--carbon-bg); color: var(--carbon-text); }
309
309
  }
310
310
 
311
311
  .carbon .card__narrative {
312
- font-size: 0.9375rem;
312
+ font-size: 1rem;
313
313
  color: var(--carbon-text-secondary);
314
314
  line-height: 1.65;
315
315
  margin-block-end: 1rem;
@@ -426,7 +426,7 @@ body { background: var(--carbon-bg); color: var(--carbon-text); }
426
426
  }
427
427
 
428
428
  .carbon.heyiam-project .stat-cell__label {
429
- font-size: 0.625rem;
429
+ font-size: 0.75rem;
430
430
  }
431
431
 
432
432
  .carbon.heyiam-project .section-heading,
@@ -481,7 +481,7 @@ body { background: var(--carbon-bg); color: var(--carbon-text); }
481
481
  }
482
482
 
483
483
  .carbon .narrative p {
484
- font-size: 0.9375rem;
484
+ font-size: 1rem;
485
485
  color: var(--carbon-text-secondary);
486
486
  line-height: 1.75;
487
487
  margin-block-end: 1rem;
@@ -489,7 +489,7 @@ body { background: var(--carbon-bg); color: var(--carbon-text); }
489
489
  .carbon .narrative p:last-child { margin-block-end: 0; }
490
490
 
491
491
  .carbon .narrative-text {
492
- font-size: 0.9375rem;
492
+ font-size: 1rem;
493
493
  color: var(--carbon-text-secondary);
494
494
  line-height: 1.75;
495
495
  }
@@ -578,7 +578,7 @@ body { background: var(--carbon-bg); color: var(--carbon-text); }
578
578
  display: flex;
579
579
  gap: 0.75rem;
580
580
  margin-block-end: 0.75rem;
581
- font-size: 0.9375rem;
581
+ font-size: 1rem;
582
582
  color: var(--carbon-text-secondary);
583
583
  line-height: 1.6;
584
584
  }
@@ -739,7 +739,7 @@ body { background: var(--carbon-bg); color: var(--carbon-text); }
739
739
  }
740
740
 
741
741
  .carbon .dev-take__text {
742
- font-size: 0.9375rem;
742
+ font-size: 1rem;
743
743
  color: var(--carbon-text);
744
744
  line-height: 1.7;
745
745
  font-style: italic;
@@ -98,17 +98,17 @@
98
98
  {% for p in projects %}
99
99
  <article class="chalk-card fade-in">
100
100
  <h3 class="project-name"><a href="/{{ user.username }}/{{ p.slug }}">{{ p.title }}</a></h3>
101
- {% if p.narrative %}
102
- <p class="project-narrative">{{ p.narrative | truncate: 120 }}</p>
101
+ {% if p.tagline != blank %}
102
+ <p class="project-narrative">{{ p.tagline }}</p>
103
103
  {% endif %}
104
104
  <div class="project-meta" aria-label="Project statistics">
105
105
  <span>{{ p.totalSessions }} sessions</span>
106
106
  <span>{{ p.totalDurationMinutes | formatDuration }}</span>
107
107
  <span>{{ p.totalLoc | formatLoc }} LOC</span>
108
108
  </div>
109
- {% if p.skills.size > 0 %}
109
+ {% if p.profileSkills.size > 0 %}
110
110
  <ul class="skills-list" aria-label="Skills">
111
- {% for skill in p.skills %}
111
+ {% for skill in p.profileSkills %}
112
112
  <li class="skill-chip">{{ skill }}</li>
113
113
  {% endfor %}
114
114
  </ul>
@@ -326,7 +326,7 @@ a:visited { color: inherit; }
326
326
  }
327
327
  .stat-label {
328
328
  font-family: var(--font-mono);
329
- font-size: 11px;
329
+ font-size: 12px;
330
330
  text-transform: uppercase;
331
331
  letter-spacing: 0.06em;
332
332
  color: var(--fg-muted);
@@ -389,7 +389,7 @@ a:visited { color: inherit; }
389
389
  }
390
390
  .stat-item-label {
391
391
  font-family: var(--font-mono);
392
- font-size: 11px;
392
+ font-size: 12px;
393
393
  text-transform: uppercase;
394
394
  letter-spacing: 0.06em;
395
395
  color: var(--fg-muted);
@@ -1156,6 +1156,48 @@ a.project-card:hover, a.session-card:hover {
1156
1156
  }
1157
1157
  }
1158
1158
 
1159
+ @media (max-width: 480px) {
1160
+ .hero {
1161
+ padding: 24px 0 20px;
1162
+ }
1163
+ .hero h1 {
1164
+ font-size: 1.5rem;
1165
+ }
1166
+ .heyiam-project .hero h1,
1167
+ .heyiam-session .hero h1 {
1168
+ font-size: 1.375rem;
1169
+ }
1170
+ .stats-grid {
1171
+ grid-template-columns: 1fr;
1172
+ }
1173
+ .chalk-card {
1174
+ padding: 16px;
1175
+ }
1176
+ .project-name {
1177
+ font-size: 1.125rem;
1178
+ }
1179
+ .sessions-table {
1180
+ font-size: 11px;
1181
+ }
1182
+ .sessions-table thead th,
1183
+ .sessions-table tbody td {
1184
+ padding: 6px 4px;
1185
+ }
1186
+ .agent-row {
1187
+ grid-template-columns: 80px 1fr 48px;
1188
+ gap: 6px;
1189
+ font-size: 12px;
1190
+ }
1191
+ .hero-meta {
1192
+ flex-direction: column;
1193
+ gap: 4px;
1194
+ }
1195
+ .profile-links {
1196
+ flex-direction: column;
1197
+ gap: 4px;
1198
+ }
1199
+ }
1200
+
1159
1201
 
1160
1202
  /* Live-edit empty field hiding */
1161
1203
  [data-portfolio-empty="true"] { display: none; }
@@ -126,17 +126,17 @@
126
126
  <div class="project-card-header">
127
127
  <h3><a href="/{{ user.username }}/{{ p.slug }}">{{ p.title }}</a></h3>
128
128
  </div>
129
- {% if p.narrative %}
130
- <p class="project-card-narrative">{{ p.narrative }}</p>
129
+ {% if p.tagline != blank %}
130
+ <p class="project-card-narrative">{{ p.tagline }}</p>
131
131
  {% endif %}
132
132
  <div class="project-card-stats">
133
133
  <span><span class="val">{{ p.totalSessions }}</span> sessions</span>
134
134
  <span><span class="val">{{ p.totalDurationMinutes | formatDuration }}</span></span>
135
135
  <span><span class="val">{{ p.totalLoc | formatLoc }}</span> LOC</span>
136
136
  </div>
137
- {% if p.skills.size > 0 %}
137
+ {% if p.profileSkills.size > 0 %}
138
138
  <div class="skill-pins">
139
- {% for skill in p.skills %}
139
+ {% for skill in p.profileSkills %}
140
140
  <span class="skill-pin">{{ skill }}</span>
141
141
  {% endfor %}
142
142
  </div>
@@ -119,17 +119,17 @@
119
119
  <div class="cos-project-card-header">
120
120
  <h3><a href="/{{ user.username }}/{{ p.slug }}">{{ p.title }}</a></h3>
121
121
  </div>
122
- {% if p.narrative %}
123
- <p class="cos-project-card-narrative">{{ p.narrative }}</p>
122
+ {% if p.tagline != blank %}
123
+ <p class="cos-project-card-narrative">{{ p.tagline }}</p>
124
124
  {% endif %}
125
125
  <div class="cos-project-card-meta">
126
126
  <span><strong>{{ p.totalSessions }}</strong> sessions</span>
127
127
  <span><strong>{{ p.totalDurationMinutes | formatDuration }}</strong></span>
128
128
  <span><strong>{{ p.totalLoc | localeNumber }}</strong> LOC</span>
129
129
  </div>
130
- {% if p.skills.size > 0 %}
130
+ {% if p.profileSkills.size > 0 %}
131
131
  <div class="cos-skill-chips">
132
- {% for skill in p.skills %}
132
+ {% for skill in p.profileSkills %}
133
133
  <span class="cos-skill-chip">{{ skill }}</span>
134
134
  {% endfor %}
135
135
  </div>
@@ -246,7 +246,7 @@ body { background: var(--cos-bg); color: var(--cos-text); }
246
246
  }
247
247
  .cosmos .cos-narrative p {
248
248
  color: var(--cos-text-secondary);
249
- font-size: 0.9375rem;
249
+ font-size: 1rem;
250
250
  line-height: 1.75;
251
251
  margin-block-end: 1rem;
252
252
  }
@@ -431,7 +431,7 @@ body { background: var(--cos-bg); color: var(--cos-text); }
431
431
  }
432
432
  .cosmos .cos-project-card-narrative {
433
433
  color: var(--cos-text-secondary);
434
- font-size: 0.9375rem;
434
+ font-size: 1rem;
435
435
  line-height: 1.6;
436
436
  margin-block-end: 1.25rem;
437
437
  }
@@ -710,7 +710,7 @@ body { background: var(--cos-bg); color: var(--cos-text); }
710
710
  }
711
711
  .cosmos .cos-decision p {
712
712
  color: var(--cos-text-secondary);
713
- font-size: 0.9375rem;
713
+ font-size: 1rem;
714
714
  line-height: 1.5;
715
715
  }
716
716
 
@@ -890,7 +890,7 @@ body { background: var(--cos-bg); color: var(--cos-text); }
890
890
  }
891
891
  .cosmos .cos-dev-take blockquote {
892
892
  color: var(--cos-text-secondary);
893
- font-size: 0.9375rem;
893
+ font-size: 1rem;
894
894
  line-height: 1.7;
895
895
  font-style: italic;
896
896
  border: none;
@@ -92,15 +92,15 @@
92
92
  <h3 class="dl-project-title">
93
93
  <a href="/{{ user.username }}/{{ p.slug }}">{{ p.title }}</a>
94
94
  </h3>
95
- {% if p.narrative %}
96
- <p class="dl-project-narrative">{{ p.narrative }}</p>
95
+ {% if p.tagline != blank %}
96
+ <p class="dl-project-narrative">{{ p.tagline }}</p>
97
97
  {% endif %}
98
98
  <p class="dl-project-meta">
99
99
  {{ p.totalSessions }} session{% if p.totalSessions != 1 %}s{% endif %} &middot; {{ p.totalDurationMinutes | formatDuration }} &middot; {{ p.totalLoc | localeNumber }} LOC
100
100
  </p>
101
- {% if p.skills.size > 0 %}
101
+ {% if p.profileSkills.size > 0 %}
102
102
  <div class="dl-project-skills">
103
- {% for skill in p.skills %}
103
+ {% for skill in p.profileSkills %}
104
104
  <span class="dl-skill-chip">{{ skill }}</span>
105
105
  {% endfor %}
106
106
  </div>
@@ -86,17 +86,17 @@
86
86
  {% for p in projects %}
87
87
  <a href="/{{ user.username }}/{{ p.slug }}" class="ed-project-card">
88
88
  <h3 class="ed-project-card-title">{{ p.title }}</h3>
89
- {% if p.narrative %}
90
- <p class="ed-project-card-narrative">{{ p.narrative }}</p>
89
+ {% if p.tagline != blank %}
90
+ <p class="ed-project-card-narrative">{{ p.tagline }}</p>
91
91
  {% endif %}
92
92
  <div class="ed-project-card-stats">
93
93
  <span class="ed-project-card-stat"><strong>{{ p.totalSessions }}</strong> sessions</span>
94
94
  <span class="ed-project-card-stat"><strong>{{ p.totalLoc | localeNumber }}</strong> LOC</span>
95
95
  <span class="ed-project-card-stat"><strong>{{ p.totalDurationMinutes | formatDuration }}</strong></span>
96
96
  </div>
97
- {% if p.skills.size > 0 %}
97
+ {% if p.profileSkills.size > 0 %}
98
98
  <div class="ed-chip-list">
99
- {% for skill in p.skills %}
99
+ {% for skill in p.profileSkills %}
100
100
  <span class="chip chip--violet">{{ skill }}</span>
101
101
  {% endfor %}
102
102
  </div>
@@ -90,7 +90,7 @@
90
90
  {% if sessionsJson %}
91
91
  <section class="ed-card-section" aria-label="Agent work timeline">
92
92
  <h2 class="ed-section-title">Work Timeline</h2>
93
- <div data-work-timeline data-sessions='{{ sessionsJson | raw }}'></div>
93
+ <div data-work-timeline data-sessions='{{ sessionsJson | raw }}'{% if hideSessionDates %} data-hide-dates="1"{% endif %}></div>
94
94
  </section>
95
95
  {% endif %}
96
96