Skip to main content
ejGames ships an optional kit of utilities in /_sdk/kit/. They are imported with paths that start with /_sdk/…. Your own files use relative paths (./util.js). For the grid we will use these pieces from dom.js and library.js:
  • h(tag, props, ...children) creates elements without writing createElement every time. class sets the class, onclick adds the event, and the rest (title, data-…) is set as an attribute. style can be a string ("--tono: 120") or an object ({ opacity: "0.5" }). For CSS variables, use the string.
  • img(url) creates an <img> with lazy loading (loading="lazy"). It adds the class img-loaded when it loads, img-error if it fails, and img-empty if there was no URL, so CSS decides what to show in each case.
  • keyed(container, list, key, paintOne) redraws by reusing the nodes that already existed. paintOne(game, previous) receives that game’s previous node (or nothing) and returns the one to use. With hundreds of games it stays fast, and focus does not jump.
  • visible(games) removes hidden games, missing games, emulators and console games, which library.all does include (those belong in Homebrew), and sort(games, "title") sorts ("title", "recent", "playtime", "added", "release" or "rating").
Replace main.js with this:
A few ideas in this code:
  • Thumbnails in the grid. coverThumb is about 40 KB. cover is the large image. In lists and grids, always use thumbnails.
  • The “signature” is the trick for not rebuilding cards. If the name and the cover are the same, the previous card is reused and only the classes change (favorite). When the cover for a newly added game arrives, the signature changes and a new card is created.
  • With no cover you see the initials (initials("Hollow Knight") → HK) on a gradient whose color comes from the name (hueOf()), and the image stays invisible until it loads.
  • ejg.game.launch(id) launches the game (ejGames plays the launch sound). If it fails, the promise rejects and we show it with ejg.ui.toast(text, "error").
  • ejg.ui.open("add-folder") opens the launcher screen for adding game folders. There are more: "settings", "search", "menu", "stats", "profiles", "collections", "theme"…
And the grid CSS (add it to style.css):
content-visibility: auto saves a lot with large libraries, but it clips anything that sticks out of the card (shadows, focus rings, zooms). That is why the card has padding: 8px: it leaves room for the focus effect in the next step.
Save. You now have a cover grid, and a click launches the game.