PageSourceSearch

https://forboc.ai/src/components/ReadVine.js

js forboc.ai collected 2026-10-05 13:39:14 UTC 2,694 bytes, 52 lines download raw bytes

1/**
2 * Read vine
3 *
4 * How far down the descent a reader is, grown along the header's lower edge.
5 * It was a 2px rule that filled, and at night a design judge read it as "a
6 * full-width 1px light line — a hard mechanical edge on an organic page" and
7 * "a thin line, not a vein". So it is a tendril: tapering from its root at the
8 * left, wandering a little, and putting out a leaf at the start of each
9 * chapter, so the chapters passed are leaves already grown. navCurrent.js
10 * reveals it to the reader's depth; this only draws it at full length.
11 */
12import { round } from './stem/seed.js';
13import { leaf } from './stem/geometry.js';
14
15/**
16 * @param {object} spec
17 * @param {number} spec.width   the header's width, in px
18 * @param {number} spec.height  the band it grows in, in px
19 * @param {number[]} spec.nodes where each chapter starts, as fractions of the run
20 * @returns {string} SVG markup
21 */
22export const vineSvg = ({ width, height, nodes }) => {
23    const mid = height / 2;
24    const steps = Math.max(24, Math.round(width / 24));
25    // Two slow waves, so it wanders rather than oscillates.
26    const y = (t) => mid + Math.sin(t * Math.PI * 7.3) * height * 0.09 + Math.sin(1.7 + t * Math.PI * 17.9) * height * 0.04;
27    const half = (t) => 1.15 * (1 - t) + 0.35;
28    const pts = Array.from({ length: steps + 1 }, (_, i) => i / steps);
29    const down = pts.map((t) => `${round(t * width)},${round(y(t) - half(t))}`);
30    const up = [...pts].reverse().map((t) => `${round(t * width)},${round(y(t) + half(t))}`);
31    const stem = `M${down.join('L')}L${up.join('L')}Z`;
32    // A leaf at each chapter, turned alternately into the bar and out of it,
33    // lying along the stem rather than across it (across, they read as a
34    // ruler's ticks), and shrinking a little along the run as a shoot's do.
35    const leaves = nodes.map((f, k) => {
36        const x = f * width;
37        const side = k % 2 ? 1 : -1;
38        const len = height * (0.62 - f * 0.14);
39        return `<path d="${leaf(x, y(f), side * 0.62, len, len * 0.34)}" fill="url(#read-vine)"/>`;
40    }).join('');
41    return `<svg width="${round(width)}" height="${round(height)}" viewBox="0 0 ${round(width)} ${round(height)}" aria-hidden="true" focusable="false">
42        <defs>
43            <linearGradient id="read-vine" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="${round(width)}" y2="0">
44                <stop offset="0" stop-color="var(--vine-root)"/>
45                <stop offset="0.62" stop-color="var(--vine-mid)"/>
46                <stop offset="1" stop-color="var(--vine-tip)"/>
47            </linearGradient>
48        </defs>
49        <path d="${stem}" fill="url(#read-vine)"/>
50        ${leaves}
51    </svg>`;
52};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.