Money forecast: calm optimistic start, rising plateau, thin solid projected lines
Deploy production / deploy (push) Successful in 1m13s

- Optimistic: no crash or correction through 2031 (5 years from today), earning the
  9.5% average; cycle resumes after (correction 2035, crash 2040). Salary plateau
  grows 2%/yr real so pay never stalls.
- Realistic keeps its single -30% crash in 2029; pessimistic unchanged.
- Projected lines are now thin, slightly lighter solid lines in every view; legend updated.
- Explanation text lists the crash/correction years and recovery time.
This commit is contained in:
stan committed 2026-10-26 08:25:52 +00:00
1 parent d3b9bb9ba2
commit 78b69c7d8f
6 files changed
+135 -38

No files matched your search

+22 -16
View File
@@ -105,14 +105,17 @@
)
.join(' ');
}
/** Solid part through `dashedFrom` and dashed part from the last solid point onward. */
/** Actual part through `projectedFrom` and projected part from the last actual point onward. */
function segments(item: ChartSeries) {
if (!item.dashedFrom) return { solid: item.points, dashed: [] };
const split = item.points.findLastIndex((point) => point.date <= item.dashedFrom!);
if (!item.projectedFrom) return { actual: item.points, projected: [] };
const split = item.points.findLastIndex((point) => point.date <= item.projectedFrom!);
return split < 0
? { solid: [], dashed: item.points }
: { solid: item.points.slice(0, split + 1), dashed: item.points.slice(split) };
? { actual: [], projected: item.points }
: { actual: item.points.slice(0, split + 1), projected: item.points.slice(split) };
}
/** Actual lines are thick and opaque; projected ones a thin, slightly lighter solid line. */
const lineWidth = (item: ChartSeries, projected: boolean) =>
projected ? (item.emphasis ? 1.5 : 1.1) : item.emphasis ? 3 : 1.75;
function pointAt(points: ChartPoint[], date: string) {
if (!points.length || date < points[0].date || date > points.at(-1)!.date) return null;
return points.findLast((point) => point.date <= date) ?? null;
@@ -236,16 +239,17 @@
{/if}
{#each series as item (item.label)}
{@const parts = segments(item)}
{#each [parts.solid, parts.dashed] as points, index (index)}
{#each [parts.actual, parts.projected] as points, index (index)}
{#if points.length > 1}
<path
d={path(points)}
fill="none"
stroke={item.color}
stroke-width={item.emphasis ? 3 : 1.75}
stroke-dasharray={index ? '6 5' : undefined}
stroke-linecap={index ? 'butt' : 'round'}
stroke-width={lineWidth(item, index === 1)}
stroke-opacity={index ? 0.8 : 1}
stroke-linecap="round"
stroke-linejoin="round"
data-line={index ? 'projected' : 'actual'}
/>
{/if}
{/each}
@@ -306,16 +310,16 @@
</div>
{/if}
</div>
{#if series.length > 1 || series.some((item) => item.dashedFrom)}
{#if series.length > 1 || series.some((item) => item.projectedFrom)}
<figcaption class="legend">
{#each series as item (item.label)}
<span class:emphasis={item.emphasis}
><span class="swatch" style={`background:${item.color}`}></span>{item.label}</span
>
{/each}
{#if series.some((item) => item.dashedFrom)}
<span class="line-key first"><span class="key solid"></span>Actual</span>
<span class="line-key"><span class="key dashed"></span>Projected</span>
{#if series.some((item) => item.projectedFrom)}
<span class="line-key first"><span class="key actual"></span>Actual</span>
<span class="line-key"><span class="key projected"></span>Projected</span>
{/if}
</figcaption>
{/if}
@@ -419,10 +423,12 @@
.key {
display: inline-block;
width: 18px;
border-top: 2px solid var(--site-muted);
border-top: 3px solid var(--site-muted);
border-radius: 2px;
}
.key.dashed {
border-top-style: dashed;
.key.projected {
border-top-width: 1.5px;
opacity: 0.8;
}
.swatch {
display: inline-block;