PageSourceSearch

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

js forboc.ai collected 2026-10-05 13:39:18 UTC 7,130 bytes, 115 lines download raw bytes

1/**
2 * Fungus
3 *
4 * The grown light's source. Each night chapter carries a colony — an emerald
5 * tint at one edge of the page (colonies.css) — and both design judges found
6 * what that is without anything to cast it: "night is only a gradient; the
7 * only glows are sourceless", "moss light never shows as a light source". A
8 * glow at the edge of a forest at night is foxfire: bracket fungus on a trunk,
9 * lit from its own gills. So the page's edge is the trunk, and the fungus
10 * grows out of it where the colony is, shelves stacked and overlapping, each
11 * with a lit underside and a faint pool of its own light.
12 *
13 * Seeded by the chapter, like the plants, so each colony is its own growth.
14 * Drawn growing rightward from x=0; the right-hand colonies are mirrored in CSS.
15 */
16import { seedOf, rngOf, round } from './stem/seed.js';
17
18/* One shelf: a cap bulging out from the trunk, its underside a flatter arc,
19   and the gills along that underside lit. */
20const shelf = (x, y, w, h, rings = [0.72, 0.46]) => {
21    const cap = `M${round(x)},${round(y - h * 0.62)}`
22        + `C${round(x + w * 0.5)},${round(y - h * 1.18)} ${round(x + w * 1.06)}
22,${round(y - h * 0.62)} ${round(x + w)},${round(y)}`
23        + `C${round(x + w * 0.74)},${round(y + h * 0.14)} ${round(x + w * 0.3)},${round(y + h * 0.2)} ${round(x)},${round(y + h * 0.14)}Z`;
24    const gills = `M${round(x + w)},${round(y)}C${round(x + w * 0.74)},${round(y + h * 0.14)} ${round(x + w * 0.3)},${round(y + h * 0.2)} ${round(x)},${round(y + h * 0.14)}`;
25    // Growth bands: the cap's own outline at earlier sizes, drawn from where
26    // it meets the bark, the rings a bracket lays down as it grows.
27    const bands = rings.map((f) => `M${round(x)},${round(y - h * 0.62 * f)}`
28        + `C${round(x + w * 0.5 * f)},${round(y - h * 1.18 * f)} ${round(x + w * 1.06 * f)},${round(y - h * 0.62 * f)} ${round(x + w * f)},${round(y)}`).join('');
29    return { cap, gills, bands };
30};
31
32/* The bark it grows from: a ragged sliver at the page's edge, so the shelves
33   stand on a trunk and do not float. */
34const bark = (rand, height) => {
35    const n = 16;
36    const pts = Array.from({ length: n + 1 }, (_, i) => `${round(4 + rand() * 6)},${round((i / n) * height)}`);
37    return `M-2,${round(height * 0.04)}L${pts.slice(1, -1).join('L')}L-2,${round(height * 0.96)}Z`;
38};
39
40/**
41 * @param {object} spec
42 * @param {string} spec.seed   the chapter's id
43 * @param {number} spec.width  how far from the edge it may grow, in px
44 * @param {number} spec.height the stretch of edge it grows along, in px
45 * @returns {string} SVG markup
46 */
47export const fungusSvg = ({ seed, width, height }) => {
48    const rand = rngOf(seedOf(`fungus-${seed}`));
49    const uid = `fg-${seed.replace(/[^a-z0-9]/gi, '')}`;
50    const count = 3 + Math.floor(rand() * 4);
51    // Shelves climb the trunk in a loose tier, the largest low, each smaller
52    // one above it and reaching less far.
53    // Tiers overlap closely, as brackets do, and each sits a little out from
54    // or in to the bark rather than in one column.
55    let y = height * (0.74 + rand() * 0.1);
56    const shelves = Array.from({ length: count }, (_, i) => {
57        const k = 1 - i / (count + 1);
58        // Now and then a young shelf, much smaller, between the grown ones:
59        // a clump of equals was "five near-identical half-discs".
60        const young = rand() < 0.3 ? 0.35 + rand() * 0.2 : 1;
61        const w = width * (0.4 + rand() * 0.5) * (0.55 + k * 0.45) * young;
62        const h = w * (0.44 + rand() * 0.26);
63        const x = 2 + rand() * width * 0.08;
64        const rings = Array.from({ length: 1 + Math.floor(rand() * 3) }, (_, r) => 0.82 - r * 0.22 - rand() * 0.08);
65        // Each tilts a little on its attachment, up or down.
66        const tilt = (rand() - 0.5) * 16;
67        const s = { ...shelf(x, y, w, h, rings), w, h, y, x, tilt };
68        y -= h * (0.38 + rand() * 0.5);
69        return s;
70    }).filter((s) => s.y > s.h).reverse();
71    const pools = shelves.map((s) => `<ellipse cx="${round(s.x + s.w * 0.45)}" cy="${round(s.y + s.h * 0.55)}
71" rx="${round(s.w * 0.9)}" ry="${round(s.h * 1.1)}" fill="url(#${uid}-pool)"/>`).join('');
72    // Drawn top down, so each lower, larger shelf overlaps the one above it.
73    const caps = shelves.map((s) => `<g transform="rotate(${round(s.tilt)} ${round(s.x)} ${round(s.y)})"><path d="${s.cap}" fill="url(#${uid}-cap)"/>`
74        + `<path d="${s.bands}" fill="none" stroke="var(--fungus-band)" stroke-width="0.9" opacity="0.5"/>`
75        + `<path d="${s.gills}" fill="none" stroke="var(--fungus-lit)" stroke-width="${round(Math.max(1, s.h * 0.07))}" stroke-linecap="round"/></g>`).join('');
76    // Moss where the shelves meet the bark, and a few spores in their light.
77    const moss = Array.from({ length: 5 + Math.floor(rand() * 5) }, () => {
78        const my = height * (0.2 + rand() * 0.75);
79        const r = 2 + rand() * 5;
80        return `<ellipse cx="${round(r * 0.4)}" cy="${round(my)}" rx="${round(r)}" ry="${round(r * (1.3 + rand()))}" fill="var(--fungus-moss)"/>`;
81    }).join('');
82    const spores = Array.from({ length: 4 + Math.floor(rand() * 5) }, () => {
83        const s = shelves[Math.floor(rand() * shelves.length)];
84        return s ? `<circle cx="${round(s.x + s.w * (0.2 + rand() * 0.9))}" cy="${round(s.y + s.h * (0.5 + rand() * 1.4))}" r="${round(0.6 + rand() * 0.9)}" fill="var(--fungus-lit)" opacity="${round(0.35 + rand() * 0.45)}"/>` : '';
85    }).join('');
86    return `<svg class="fungus-svg" width="${round(width)}" height="${round(height)}" viewBox="0 0 ${round(width)} ${round(height)}" aria-hidden="true" focusable="false">
87        <defs>
88            <radialGradient id="${uid}-pool">
89                <stop offset="0" stop-color="var(--fungus-lit)" stop-opacity="0.2"/>
90                <stop offset="0.5" stop-color="var(--fungus-lit)" stop-opacity="0.06"/>
91                <stop offset="1" stop-color="var(--fungus-lit)" stop-opacity="0"/>
92            </radialGradient>
93            <!-- Dark above, where nothing lights the cap, and warming to the
94                 lit edge below. -->
95            <linearGradient id="${uid}-cap" x1="0" y1="0" x2="0" y2="1">
96                <stop offset="0" stop-color="var(--fungus-cap)"/>
97                <stop offset="0.8" stop-color="var(--fungus-cap-lit)"/>
98            </linearGradient>
99            <!-- The trunk's edge, darkening into the page's edge and fading
100                 out along it: solid, it read as "a black bar with a
101                 ruler-straight edge". -->
102            <linearGradient id="${uid}-bark" x1="0" y1="0" x2="0" y2="1">
103                <stop offset="0" stop-color="var(--fungus-bark)" stop-opacity="0"/>
104                <stop offset="0.3" stop-color="var(--fungus-bark)" stop-opacity="0.75"/>
105                <stop offset="0.7" stop-color="var(--fungus-bark)" stop-opacity="0.75"/>
106                <stop offset="1" stop-color="var(--fungus-bark)" stop-opacity="0"/>
107            </linearGradient>
108        </defs>
109        ${pools}
110        <path d="${bark(rand, height)}" fill="url(#${uid}-bark)"/>
111        ${moss}
112        ${caps}
113        ${spores}
114    </svg>`;
115};

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.