style: timeline runs newest at the top, 2016 at the bottom
1 file changed, +11 -11
+11-11src/pages/index.astro
| @@ -400,12 +400,12 @@ const jsonLd = { | ||
| 400 | 400 | }; |
| 401 | 401 | |
| 402 | 402 | |
| 403 | -// Timeline (left gutter, above the globe): 2016 to now. | |
| 403 | +// Timeline (left gutter, above the globe): now at the top, 2016 at the bottom. | |
| 404 | 404 | const TL_START = 2016; |
| 405 | 405 | const nowDate = new Date(); |
| 406 | 406 | const TL_END = nowDate.getFullYear() + nowDate.getMonth() / 12 + nowDate.getDate() / 365; |
| 407 | 407 | const ym = ([y, m]: [number, number]) => y + (m - 1) / 12; |
| 408 | -const tlPct = (v: number) => (((v - TL_START) / (TL_END - TL_START)) * 100).toFixed(2); | |
| 408 | +const tlPct = (v: number) => (((TL_END - v) / (TL_END - TL_START)) * 100).toFixed(2); | |
| 409 | 409 | const MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']; |
| 410 | 410 | const fmtYm = ([y, m]: [number, number]) => `${MONTHS[m - 1]} ${y}`; |
| 411 | 411 | // Attributes that let a row draw its span on the timeline when hovered. |
| @@ -415,8 +415,9 @@ const tlSpan = (label: string, start: [number, number], end?: [number, number]) | ||
| 415 | 415 | const from = ym(start); |
| 416 | 416 | const to = end ? ym(end) : TL_END; |
| 417 | 417 | return { |
| 418 | - 'data-tl-top': tlPct(from), | |
| 419 | - 'data-tl-height': (Number(tlPct(to)) - Number(tlPct(from))).toFixed(2), | |
| 418 | + // Newest at the top: a span's top edge is its end date. | |
| 419 | + 'data-tl-top': tlPct(to), | |
| 420 | + 'data-tl-height': (Number(tlPct(from)) - Number(tlPct(to))).toFixed(2), | |
| 420 | 421 | 'data-tl-name': label, |
| 421 | 422 | 'data-tl-range': `${fmtYm(start)} to ${end ? fmtYm(end) : 'now'}`.toUpperCase(), |
| 422 | 423 | }; |
| @@ -900,9 +901,8 @@ const pad = (n: number) => String(n).padStart(2, '0'); | ||
| 900 | 901 | name.textContent = el.dataset.tlName ?? ''; |
| 901 | 902 | range.textContent = el.dataset.tlRange ?? ''; |
| 902 | 903 | label.style.top = top; |
| 903 | - // Recent spans start near "now"; put their label above the bar so it | |
| 904 | - // doesn't collide with the now marker. | |
| 905 | - label.classList.toggle('is-above', Number(el.dataset.tlTop) > 85); | |
| 904 | + // Ongoing spans start at "now"; drop their label below the now marker. | |
| 905 | + label.classList.toggle('is-near-now', Number(el.dataset.tlTop) < 4); | |
| 906 | 906 | if (!tl.classList.contains('is-on')) { |
| 907 | 907 | // Coming from hidden: start at zero height at the right spot, then grow. |
| 908 | 908 | tl.classList.add('no-anim'); |
| @@ -1053,8 +1053,8 @@ const pad = (n: number) => String(n).padStart(2, '0'); | ||
| 1053 | 1053 | top 0.4s cubic-bezier(0.2, 0.8, 0.2, 1), |
| 1054 | 1054 | opacity 0.2s; |
| 1055 | 1055 | } |
| 1056 | - .tl-label.is-above { | |
| 1057 | - translate: 0 calc(-100% - 6px); | |
| 1056 | + .tl-label.is-near-now { | |
| 1057 | + translate: 0 14px; | |
| 1058 | 1058 | } |
| 1059 | 1059 | .timeline.is-on .tl-bar, |
| 1060 | 1060 | .timeline.is-on .tl-label { |
| @@ -1070,9 +1070,9 @@ const pad = (n: number) => String(n).padStart(2, '0'); | ||
| 1070 | 1070 | } |
| 1071 | 1071 | .tl-now { |
| 1072 | 1072 | position: absolute; |
| 1073 | - bottom: 0; | |
| 1073 | + top: 0; | |
| 1074 | 1074 | left: 31px; |
| 1075 | - translate: 0 50%; | |
| 1075 | + translate: 0 -50%; | |
| 1076 | 1076 | display: flex; |
| 1077 | 1077 | align-items: center; |
| 1078 | 1078 | gap: 6px; |