Fuglene på denne siden ser ut som de bare vandrer rundt og gjør hva som helst, og det er hele poenget, men under "hva som helst" er det en overraskende mengde aritmetikk. Pool-størrelse, et hardt tak på hvor mange som kan være synlige samtidig, tre separate soner de ikke har lov til å forlate, og én fugl som er bygget for aldri helt å synkronisere med noe annet på siden.

poolen og taket
Det finnes en pool på åtte flygende kandidater å trekke fra (lunder, måker, en terne, en skarv, én ørn) pluss tre fugler som alltid er på skjermen, sittende i stedet for flygende (to lunder og en tjeld). Hver sidelasting trekker en tilfeldig delmengde av flypoolen i stedet for å vise alle åtte samtidig:
export function rollSkyColony(rand: () => number = Math.random): SkyColonyRoll {
const guaranteed = CANDIDATES.find((c) => c.guaranteed)!;
const rest = CANDIDATES.filter((c) => !c.guaranteed);
const shuffled = [...rest].sort(() => rand() - 0.5);
const extraCount = 2 + Math.floor(rand() * 3); // 2-4
const chosen = [guaranteed, ...shuffled.slice(0, extraCount)];
...
}Én kandidat blir alltid trukket (den nærmeste lunden, merket guaranteed, stort sett slik at en mobilbesøkende, som bare noensinne ser den ene flygeren uansett viewport, alltid har noe som beveger seg på himmelen) og deretter kommer et tilfeldig antall to til fire til fra resten. Regn på det og antallet flyvende fugler på skjermen er alltid et sted mellom tre og fem, aldri flere, aldri færre, og kombinert med de tre alltid-på sittende fuglene er det totale fugleantallet som sitter på siden samtidig alltid seks til åtte. Det er et bevisst tak, ikke en tilfeldighet i hvordan tilfeldigheten pleier å falle, hvert mulig trekk lander innenfor det området ved konstruksjon.
hvorfor tallene på hver fugl ser så bevisste ut
Hver kandidat bærer to uavhengige timingverdier, en horisontal krysningsvarighet og en vertikal duppevarighet, og jeg valgte dem spesifikt slik at ingen to noensinne deler et enkelt forhold:
// duration 210s / bob 233s, both prime-adjacent-ish and nowhere
// near a simple ratio; tiny 0.4px amplitude, "wanders least".
{
id: 'eagle',
species: 'eagle',
top: '4vh',
left: '50%',
width: 32,
duration: 210,
bobDuration: 233,
bobAmp: 0.4,
opacity: 0.35,
dir: 'ltr'
}Hvis en fugls horisontale sveip og dens vertikale dupp hadde delt en felles faktor, si en 60-sekunders krysning og en 20-sekunders dupp, ville den kombinerte bevegelsen gjenta seg hvert 60. sekund og en tålmodig betrakter ville til slutt lagt merke til loopen. Velg varigheter uten en felles faktor i stedet, og den faktiske gjentakelsesperioden blir deres minste felles multiplum, som for de fleste av disse parene lander et sted i tusenvis av sekunder. Ingen ser på en header lenge nok til å fange det. Det leser som organisk driving fordi det, på en hvilken som helst tidsskala en person faktisk opplever, i praksis aldri gjentar seg.
Ørnen spesifikt får den minste duppamplituden og den lengste duppperioden i hele poolen, 0,4 piksler vandring over 233 sekunder, mot lunder som vandrer over en hel piksel over perioder en tidel så lange. Den sitter høyest på himmelen også, 4vh fra toppen, lengst unna alt annet i poolen, både romlig og rytmisk. Den er den ene fuglen bygget for aldri å synkronisere med noen annen fugls rytme selv ved et uhell, og for å vandre synlig minst mens den gjør det, som er hvor "ørnen flyr alene" faktisk kommer fra, ikke ensomhet i poetisk forstand, bare genuint ukorrelert bevegelse ved konstruksjon.
tre soner, og en lekse om å skille arter fra hverandre
Fugler bor over en hard linje, bakgrunnsøysilhuettens egen målte toppkant, uttrykt i samme vh-enheter som hver kandidats vertikale posisjon slik at forholdet holder ved hvilken som helst viewport-bredde. Fisk bor under sin egen linje, footer-horisontstripens vannlinje, definert som nøyaktig rad 3 av dens 5-rads pikselgrid (land opptar rad 0 til 2, sjø tar rad 3 og 4). Én fisk er det bevisste unntaket, som kort bryter den linjen på sin egen lange syklus, alt annet holder seg strengt på sin egen side.
Jeg delte de sonene av en ekte grunn, ikke bare ryddighet. Tidlig i det ambiente kulisse-arbeidet fikk jeg en rapport om en "fugl" observert svømmende i footer-stripen, og gikk gjennom koden igjen, en fisk-silhuett og en lavtflyvende fugl-silhuett var like nok i form og farge ved et blikk at den som så på det genuint ikke kunne si hvilken de så på. Fiksen var ikke bare å håndheve de vertikale sonene strengere, det var å gjøre de faktiske silhuett-grammatikkene bevisst distinkte fra hverandre, en fisk leser som en sideprofil-torpedoform i et kjølig blågrått, en fugl leser som en helt annen ovenfra-vinger-utstrakt form. To sprites som bare noensinne er riktig form i riktig sone, slik at selv et blikk forteller deg hva du ser på uten å måtte tenke på det.
Sonelinjen selv gikk gjennom sin egen dårlige runde før det. Første versjon valgte bare en top-prosent som så riktig ut på min egen skjerm:
// first attempt: eyeballed the split, no relation to the actual art
const SKY_ZONE_MAX_TOP = '55%';Så fint ut på min egen viewport-bredde og falt fra hverandre i det øyeblikket selve headerkunsten endret høyde, siden tallet ikke var bundet til noe ekte, bare en gjetning. Det som faktisk holder er å måle den ekte silhuetten i stedet for å gjette en prosent:
// Zone safety: the island-shadow layer is fixed, bottom-anchored, 42vh
// tall on desktop, so its own topmost silhouette pixel sits at
// (100-42)=58vh from the viewport top. Every candidate's `top` stays
// well clear of that line instead of a guessed percentage.delen jeg nesten ikke bygde
Kulissen er genuint det eneste stedet på hele denne siden som animerer i det hele tatt, alt annet er statisk eller smetter på plass over 70 millisekunder og stopper. Det ville vært mye enklere å la header og footer være fast, håndplukket kunst slik den aller første versjonen av denne siden faktisk var, fem spesifikke fugler i fem spesifikke positurer, ingen tilfeldighet, ingen persisteringslogikk, ingen sonematematikk å få riktig. Noe av koden ovenfor, spesielt de koprime timing-kommentarene, er den typen ting ingen noensinne vil bevisst legge merke til som leser. Jeg vet det er der, og når en header først begynte å faktisk føles levende i stedet for å se ut som dekorasjon som tilfeldigvis beveget seg, klarte jeg ikke å overtale meg selv tilbake til å shippe den statiske versjonen igjen.
taket holder fortsatt på en mobil
Ingenting av dette er kun desktop heller. Under en 768px viewport blir hver kandidat unntatt den ene guaranteed-lunden skjult helt i CSS, med vilje, slik at en liten skjerm ikke sitter fast med å rendre fire eller fem overlappende flygere inn i en header som bare har plass til å vise dem sammenklemt oppå hverandre. Den ene garanterte fuglen er akkurat den samme som alltid trekkes på desktop også, så mobil trenger ikke et separat trekk eller en separat pool, den bare stille forkaster alt desktop-trekket valgte utover den ene fuglen. Og hele poolen respekterer prefers-reduced-motion på samme måte som resten av sidens kulisser, hver sprite rendres fortsatt i sin egen hvileposisjon, frosset i stedet for manglende, ingenting på siden blir tomt bare fordi noens system ba om mindre bevegelse.