revise section 2 (alignment niches), section 5 land mechanism, departure fix; add hero

This commit is contained in:
wassname
2026-07-11 20:57:54 +08:00
parent 6f5bda6dc2
commit c700fd78a2
8 changed files with 443 additions and 318 deletions
+6
View File
@@ -37,6 +37,8 @@ html { font-size: 17px; }
body { max-width: 42em; margin: 4rem auto; padding: 0 1.25rem;
background: var(--bg); color: var(--fg);
font-family: Georgia, 'Times New Roman', serif; line-height: 1.55; }
.hero { width: min(96vw, 1200px); margin: 1.4rem 50% 2rem; transform: translateX(-50%); }
.hero img { width: 100%; height: auto; display: block; }
h1,h2,h3 { font-weight: normal; line-height: 1.2; margin: 2.2rem 0 0.6rem; }
h1 { font-size: 2rem; } h2 { font-size: 1.5rem; border-bottom: 1px solid var(--rule); padding-bottom: .2rem; }
h3 { font-size: 1.2rem; color: #333; }
@@ -51,6 +53,10 @@ code { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .88em;
stays a legible size (a wide canvas stretched to the column would shrink the text) */
.tree { margin: 1.5rem auto; max-width: 560px; overflow-x: auto; }
.tree svg { width: 100%; height: auto; display: block; }
@media (max-width: 600px) {
body { margin-top: 2rem; }
.hero { width: 100vw; margin-top: 1rem; margin-bottom: 1.5rem; }
}
hr { border: none; border-top: 1px solid var(--rule); margin: 2.5rem 0; }
footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--rule);
color: var(--muted); font-size: .85rem; }
+22 -5
View File
@@ -55,14 +55,32 @@ def edge_pts(a, b):
# same row: side to side
return (ax + aw / 2, ay, bx - bw / 2, by) if bx > ax else (ax - aw / 2, ay, bx + bw / 2, by)
def edge_path(e):
a, b = e["from"], e["to"]
ax, ay, aw = pos[a]
bx, by, bw = pos[b]
route = e.get("route")
if route == "outside-right":
x1, x2 = ax + aw / 2, bx + bw / 2
lane = max(x1, x2) + 55
return f"M{x1:.0f},{ay:.0f} C{lane:.0f},{ay:.0f} {lane:.0f},{by:.0f} {x2:.0f},{by:.0f}", (lane, (ay + by) / 2)
if route == "outside-left":
x1, x2 = ax - aw / 2, bx - bw / 2
lane = min(x1, x2) - 45
return f"M{x1:.0f},{ay:.0f} C{lane:.0f},{ay:.0f} {lane:.0f},{by:.0f} {x2:.0f},{by:.0f}", (lane, (ay + by) / 2)
x1, y1, x2, y2 = edge_pts(a, b)
my = (y1 + y2) / 2
path = f"M{x1:.0f},{y1:.0f} C{x1:.0f},{my:.0f} {x2:.0f},{my:.0f} {x2:.0f},{y2:.0f}"
lf = 0.30 if abs(y2 - y1) > ROW_H else 0.5
return path, (x1 + lf * (x2 - x1), y1 + lf * (y2 - y1) - 2)
FLOWSCALE = 26 # edge thickness = flow (probability mass through the edge) * this
edge_labels = []
for e in d["edges"]:
x1, y1, x2, y2 = edge_pts(e["from"], e["to"])
my = (y1 + y2) / 2
path, label_pos = edge_path(e)
sw = max(1.0, e.get("flow", 0.02) * FLOWSCALE) # thick where the mass pours, thin in the trickle
dash = ' stroke-dasharray="5 5"' if e.get("dashed") else ""
out.append(f'<path d="M{x1:.0f},{y1:.0f} C{x1:.0f},{my:.0f} {x2:.0f},{my:.0f} {x2:.0f},{y2:.0f}" '
out.append(f'<path d="{path}" '
f'fill="none" stroke="#a9a99f" stroke-width="{sw:.1f}"{dash}/>')
if e.get("label") and not e.get("dashed"): # dashed (secondary) edges stay unlabelled
# place the label near the source (in the gap just below the parent) so labels on
@@ -70,8 +88,7 @@ for e in d["edges"]:
# adjacent edges: label at the midpoint (siblings have spread apart there, so they
# don't overlap). tier-skipping edges: push the label near the source, into the gap
# below the parent, so it doesn't land on the boxes in the row it crosses.
lf = 0.30 if abs(y2 - y1) > ROW_H else 0.5
edge_labels.append((x1 + lf * (x2 - x1), y1 + lf * (y2 - y1) - 2, e["label"]))
edge_labels.append((*label_pos, e["label"]))
# draw labels last, each on a background halo so no stroke cuts through the text
for lx, ly, lab in edge_labels:
lw = len(lab) * 6.6 + 10