commit f5bf54905913b50641f4e0c48d46e4c870761584
parent 4864fd7e6526d9d36d2cd9892218d06d0eb48862
Author: Damian Myrda <damian@prime8.dev>
Date: Fri, 23 Oct 2026 00:52:52 +0000
web: jj-style graph rail, one line with a node per role
Diffstat:
2 files changed, 41 insertions(+), 45 deletions(-)
diff --git a/internal/web/styles/experience.css b/internal/web/styles/experience.css
@@ -65,7 +65,7 @@
/* sit beside the graph rail, never over it: start at the card's edge and
line the lanes up with the card text */
padding: 12px 24px 8px;
- margin: -4px 0 0 12ch;
+ margin: -4px 0 0 3ch;
background: var(--bg);
border-bottom: 1px solid var(--border);
font-size: var(--fs-14);
@@ -194,89 +194,86 @@
}
/*
- * Terminal graph rail, one segment per job:
+ * Graph rail, jj log style: one continuous line down the list with a node
+ * on each job's role line, newest first, and ~ under the oldest.
*
- * @ now <- end of the job (jj's working copy for the current one)
- * │ <- .graph-rail: 1px line through the node's character cell
- * + feb 2026 <- start of the job
- * │ <- across the gap to the next (older) job
- * * feb 2026
+ * @ hark / ...
+ * │
+ * ○ mandolin / ...
+ * │
+ * ○ amplifier health / ...
+ * ~
*
- * The active job's nodes and dates go accent, its line accent-dim.
+ * Dates live on the cards, so the rail carries only the shape. The active
+ * job lights its node and the line down to the job before it.
*/
.exp-timeline {
--row: 1.5em;
+ --node-top: 46px; /* card border + padding + role line, beside the logo */
flex-shrink: 0;
- width: 12ch;
+ width: 3ch;
position: relative;
- padding-top: 24px;
font-size: var(--fs-14);
line-height: 1.5;
font-variant-ligatures: none;
font-feature-settings: "liga" 0, "calt" 0;
font-synthesis: none;
- letter-spacing: 0;
color: var(--fg-dim);
cursor: default;
user-select: none;
}
-.graph-head,
-.graph-foot {
- height: var(--row);
- white-space: pre;
+.exp-row:has(.logo-hark) .exp-timeline {
+ --node-top: 39px;
}
+.graph-node,
.graph-foot {
position: absolute;
left: 0;
- bottom: 24px;
+ width: 1ch;
+ height: var(--row);
+ text-align: center;
}
-.graph-node,
-.graph-date {
- color: var(--fg-dim);
+.graph-node {
+ top: var(--node-top);
transition: color 80ms linear;
}
-/* hairline through the node column */
-.graph-rail,
+.graph-foot {
+ top: calc(var(--node-top) + var(--row));
+}
+
+/* hairline through the node column, broken around each node */
.exp-row + .exp-row .exp-timeline::before,
.exp-row:not(:last-child) .exp-timeline::after {
+ content: "";
position: absolute;
- /* centre of the node's character cell, so the line runs through it */
left: calc(0.5ch - 0.5px);
width: 1px;
background: var(--border);
+ transition: background-color 80ms linear;
}
-/* from under the end node to above the start node */
-.graph-rail {
- top: calc(24px + var(--row));
- bottom: calc(24px + var(--row));
- transition: background-color 80ms linear;
+/* from the previous job's line, across the gap, down to this node */
+.exp-row + .exp-row .exp-timeline::before {
+ top: -16px;
+ height: calc(16px + var(--node-top));
}
-/* from under the start node to the bottom of the row */
+/* from under this node to the bottom of the row */
.exp-row:not(:last-child) .exp-timeline::after {
- content: "";
+ top: calc(var(--node-top) + var(--row));
bottom: 0;
- height: 24px;
-}
-
-/* across the gap between cards, down to the next node */
-.exp-row + .exp-row .exp-timeline::before {
- content: "";
- top: -16px;
- height: calc(16px + 24px);
}
-.exp-row.active .graph-node,
-.exp-row.active .graph-date {
+.exp-row.active .graph-node {
color: var(--accent);
}
-.exp-row.active .graph-rail {
+.exp-row.active .exp-timeline::after,
+.exp-row.active + .exp-row .exp-timeline::before {
background: var(--accent-dim);
}
@@ -284,8 +281,8 @@
.exp-card,
.exp-bar,
.graph-node,
- .graph-date,
- .graph-rail {
+ .exp-timeline::before,
+ .exp-timeline::after {
transition: none;
}
}
diff --git a/internal/web/templates/partials/experience.gohtml b/internal/web/templates/partials/experience.gohtml
@@ -27,9 +27,8 @@
{{$on := or (eq $exp.ID $.SelectedExperienceID) (and (not $.SelectedExperienceID) (eq $index 0))}}
<div class="exp-row{{if $on}} active{{end}}" data-index="{{$index}}" data-id="{{$exp.ID}}">
<div class="exp-timeline" aria-hidden="true">
- <div class="graph-head"><span class="graph-node">{{if $exp.Current}}@{{else}}*{{end}}</span> <span class="graph-date">{{if $exp.Current}}now{{else}}{{shortEndDate $exp.EndDate $exp.Current}}{{end}}</span></div>
- <span class="graph-rail"></span>
- <div class="graph-foot"><span class="graph-node">+</span> <span class="graph-date">{{shortMonthYear $exp.StartDate}}</span></div>
+ <span class="graph-node">{{if $exp.Current}}@{{else}}○{{end}}</span>
+ {{if eq (inc $index) (len $.Experience)}}<span class="graph-foot">~</span>{{end}}
</div>
<div class="exp-card" id="exp-{{$exp.ID}}" data-index="{{$index}}" data-id="{{$exp.ID}}">