Pocket Frogs resource — 44,528 combinations, weekly sets, and frog builder.
- Layer order matters.
renderFrogalways draws the grayscalefrog_base_256.png, then the genus mask, thenoverlay_256.png. Each layer is retinted by drawing it into a tiny canvas, adjusting the pixel colors (base uses theCOLORStable, genus usesPATTERN_COLORS) and painting that result into the preview canvas. - Overlay blending adds depth.
_multiplyOverlayimplements a manual multiply-style blend that mixes the overlay sprite’s luminance with whatever is underneath, so the frog keeps the shadows/shininess from the art. When the chosen color isGlass(color index 22) the routine also applies_applyGlassEffect, which cuts the alpha values by half to reproduce transparency. - Glitch-free fallback. Sprite loading is asynchronous, but a placeholder ellipse is drawn whenever
loadSpriterejects. That keeps the UI from freezing when files aren’t ready or the browser balks at the sprite cache.
Four genera have an extra sprite layer (frog_<genusId>_extra_256.png) that is composited on top of the standard layers. This layer is never tinted — it renders at its original colors regardless of the frog’s body color or pattern.
The layer order differs by genus because that is how the game itself composites them:
| Genus | ID | Layer order |
|---|---|---|
| Porto | 115 | base → overlay → genus → extra |
| Florens | 116 | base → genus → extra → overlay |
| Triquetra | 119 | base → genus → extra → overlay |
| Paschali | 120 | base → genus → extra → overlay |
Porto is the special case: its genus layer (the frog body) renders above the shading overlay, with the extra layer on top of that. All other third-layer genera follow the standard order with the extra layer slotted in before the overlay.
The set of genera that have a third layer is tracked in EXTRA_LAYER_GENERA (a Set in both index.html and api/og.js). Add a genus ID there and drop the corresponding frog_<id>_extra_256.png into frog_sprites/ to enable it.
loadSpritekeeps a promise cache so eachImageonly downloads once, and every sprite is fetched fromfrog_sprites/withcrossOrigin='anonymous'. That promise is reused anywhererenderFrogneeds the base, genus, or overlay textures, which was critical when dozens of species canvases try to draw simultaneously.
- Pattern 15 (
Chroma) is special: it doesn’t just tint the genus mask with a fixed color; it updates every frame.chromaCanvasesholds the canvas context plus the exposed RGB values and whether the current frog is glass, andanimateChromaruns onrequestAnimationFrameto sweepchromaHuethrough the HSV spectrum. That loop is also whyrenderFroghas to register the canvas in the map rather than drawing a single frame, the animation keeps running until the frog disappears from the DOM.
- Species cards and weekly set tiles render lazily: each canvas starts with
_drawFrogPlaceholder, and anIntersectionObserverpaints the frog only when it scrolls into view (and clears it when it leaves). This keeps the browsers from decoding 120 canvas images upfront while the user is still scanning filters. - Sets data is fetched from two sources (local
sets.txtfirst, thenhttps://www.nimblebit.com/sets.txtif needed), and the status element shows a friendly error when both fail. That double-fetch strategy came from noticing the GitHub Action snapshot already ships with a cached copy.
Hopefully these notes keep the rendering behavior transparent for anyone working in this folder. If you need a deeper dive, the relevant helpers live around the renderFrog, _tintLayer, and animateChroma functions inside index.html.
