Easy-to-manage touch screens
Free, open-source touch screens. Guided USB setup, a drag-and-drop editor and local control. Made for Home Assistant.
Your science fiction paperbacks, standing on a shelf built from their real spine scans
LifestyleJavaScript · CSSGitHub @energon-a-secasOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
A display case for a science fiction paperback collection, read by its spines
Translation pending
Loading the full README.
Read the full README ↗A shelf read by its spines
Vitrina stands a collection of Spanish-language science fiction paperbacks on a shelf built from their real spine scans. Each book is rendered at the height its catalogue record gives in centimetres, and the width comes from the scan's own aspect ratio, so a row comes out as uneven as the shelf it was photographed from. Click a spine and the book turns around: cover, imprint, translator, cover artist, table of contents.
It exists because the covers of the Ediciones B lines of the late 1980s and 1990s are hard to find anywhere else. La Tercera Fundación scanned them, and this is a way to stand in front of them.
Live: vitrina.neorgon.com
/demo/ shows a real collection
read-only, so the finished thing is visible before you start your own at
/shelf/, which begins empty/u/?handle -- reserve your shelf's address from Share
now. Publishing opens later, and until then nobody else can see any shelf. A
published shelf shows its address, its catalogue books and your shelf labels,
never notes, dates or books added by hand| Key | Does |
|---|---|
1 2 3 | Shelf, Covers, Browse |
/ | Focus the search box |
a | Add a book |
← → | Walk along the row with a book open |
Esc | Close the drawer or dialog |
La Tercera Fundación is a volunteer catalogue of Spanish-language science
fiction, and it has no public API, so scripts/scrape.py reads it: one request
every 1.2 seconds, a descriptive User-Agent, and an on-disk cache so a re-run
costs nothing. robots.txt allows crawlers in general and disallows ClaudeBot,
so the owner runs it and a Claude session never does.
Images are hotlinked, apart from small thumbnails of the shelf's own books.
The catalogue serves them from Cloudflare with a one-year cache header and no
hotlink protection, so the only cover art in this repo is assets/thumbs/,
described below. Otherwise it carries bibliographic facts only: titles, authors,
imprints, ISBNs, formats, page counts, tables of contents. Back cover copy is
deliberately not stored; the drawer links to the record instead.
python3 scripts/scrape.py resolve # my list -> candidate editions
python3 scripts/scrape.py detail # picked editions -> data/library.json
python3 scripts/scrape.py catalog # the collections my books live in
python3 scripts/scrape.py spines # which editions have a scanned spine
python3 scripts/scrape.py cache-images # local fallback copies
python3 scripts/scrape.py cache-images --catalog # ...for every browsable record
python3 scripts/thumbs.py # WebP thumbnails of your own books
Your own books are served from assets/thumbs/, sized to what the page paints:
the shelf's 31 spines drop from 218 KB to 84 KB and its 39 covers from 1.4 MB
to 563 KB. Everything else is asked of the catalogue, which is most of what the
whole-collection view draws and is why this repo carries a copy of what is on
the page and nothing more. If a request fails the shelf tries the catalogue,
then the gitignored data/images/ cache, then gives up and draws a spine.
scripts/rank.py scores the candidates for each wanted title and writes one
brief per book, which is what makes choosing between four editions of the same
novel a reading job rather than a guessing one.
ES modules require an HTTP server (not file://):
make serve # http://localhost:8881
make validate # every test, plain node and python, no install
make routes # regenerate /shelf/, /demo/ and /u/ from _templates/app.html
make validate needs a Node that strips TypeScript types by default (verified on v25.4.0).
The backend tests import convex/lib/*.ts directly, and a Node without that stops at the
first of those imports.
A signed-in person's shelf is kept in Convex, in the vitrina-site project, one
row per book under their Clerk account id; no email or name is stored. A
catalogue book's row holds its catalogue id and what the person wrote about
their copy: a shelf label, a note, the name they list it under and the date they
added it. The bibliographic record is read from data/ in the browser, so
nothing from the catalogue is copied per person. A book added by hand also keeps
a short record (title, authors, year, pages, publisher, collection, dimensions,
https: image URLs). Beyond the books an account has an optional handle, a
published switch, a running count of its books and bytes (at most 2000 books and
8 MiB), and rate-limit records, swept once they are 31 days old. Erasing an
account deletes all of it except a suspension, and holds the handle for 30 days.
Signing in is the fleet's Neorgon Auth Kit, on the one production Clerk instance
every Neorgon site shares. A visitor with no Neorgon session downloads neither
clerk-js nor the Convex client on /shelf/ or /demo/; /u/ loads the client
to read a shared shelf. The shelf kept in a browser never moves to an account on
its own.
Publishing ships closed: until the owner sets PUBLISHING to open on the
production deployment, no shelf is public, whatever its switch says. A published
shelf shows the handle, its catalogue books and their shelf labels, never notes,
dates or books added by hand.
make install # npm install, for the Convex CLI only; the site itself needs none
make where # which deployment the pages name, and which one .env.local pushes to
make push-dev # push convex/ to the dev deployment once
make deploy # deploy convex/ to production; it asks for confirmation
The functions in convex/ are thin wrappers over convex/lib/, which
make validate tests against an in-memory database. scripts/convex_smoke.py
calls the deployed ones end to end, on dev or on production.
vitrina-site/
├── index.html # the home page, which loads no app code
├── _templates/
│ └── app.html # app shell: controls, three views, drawer, dialogs
├── shelf/ demo/ u/ # the three app pages, generated by make routes
├── css/
│ ├── style.css # site styles; tokens come from the CDN base.css
│ └── neorgon-*.css # vendored with the header, footer, beacon and auth kits
├── js/
│ ├── app.js # entry point: loads, renders, then hands over to account.js or profile.js
│ ├── state.js # the shelf, prefs and which shelf is shown; the only module that writes the shelf
│ ├── syncplan.js # pure: between a browser shelf and an account shelf
│ ├── backend.js # the deployment a page names, every Convex function name, the loaders
│ ├── account.js # /shelf/: whose shelf is shown, and every write to the account
│ ├── session.js # what account.js shares with the modules that cannot import it
│ ├── strips.js # the notices above /shelf/, and moving a browser shelf into an account
│ ├── profile.js # /u/: a shared shelf, and its owner's own view of it
│ ├── share.js # the Share dialog: address, publishing, deleting your data
│ ├── accountplan.js # pure: what account.js, strips.js and share.js decide, and authedCall
│ ├── profileplan.js # pure: what /u/ decides: its states, which reply stays, the owner banner
│ ├── erasing.js # the timer the shelf and the Share dialog wait out a deletion with
│ ├── keyguard.js # pure: which keys the page leaves to a dialog or menu it does not own
│ ├── overflow.js # hides the header kit's ⋯ toggle while nothing in it is drawn
│ ├── handles.js # handle rules, mirrored from convex/lib/handles.ts
│ ├── data.js # loading, grouping, ordering, series gaps, the remote-image switch
│ ├── shelf.js # spine geometry: cm to pixels, aspect to width
│ ├── render.js # the three views
│ ├── detail.js # the drawer for one book
│ ├── browse.js # the catalogue, with owned books marked
│ ├── modals.js # add, import, export, shelf report
│ ├── events.js # most listeners, the keyboard and the Shelf tools menu; no inline onclick
│ ├── utils.js # folding, parsing cm and years, toast, download
│ └── neorgon-*.js # vendored kits, the Auth Kit among them; never edited here
├── convex/
│ ├── schema.ts # profiles, entries, shelfMeta, heldHandles, erasures, suspendedSubjects, rateEvents
│ ├── shelf.ts # shelf:mine and the three shelf writes
│ ├── profiles.ts # handles, publishing, byHandle, deleteMyData
│ ├── admin.ts # moderation, for ADMIN_SUBJECTS only
│ ├── purge.ts # erasure in batches, and the daily sweeps crons.ts runs
│ ├── http.ts # the Clerk user.deleted webhook
│ └── lib/ # every rule, as cores make validate imports with no install
├── tests/ # every one runs under make validate, with no install
│ └── support/
│ ├── fakedb.mjs # an in-memory Convex database for the convex/lib tests
│ ├── minidom.mjs # a small DOM the flow tests load a generated page into
│ └── shelfworld.mjs # a Convex deployment, a client and the Auth Kit, faked for the flow tests
├── scripts/
│ ├── tf.py # parser for tercerafundacion.net, no network in it
│ ├── scrape.py # the polite, cached client and its five modes
│ ├── rank.py # scores candidate editions, writes per-book briefs
│ ├── routes.py # writes /shelf/, /demo/ and /u/ from the template
│ ├── catalog_ids.py # keeps catalogue ids append-only
│ └── convex_smoke.py # calls the deployed functions on dev or production
└── data/
├── wantlist.json # what I own, by hand
├── candidates.json # every catalogued edition of each wanted title
├── picks.json # which edition, and why
├── library.json # the shelf: 39 books, full records
├── catalog.json # 774 browsable records across 5 collections
└── spines.json # which editions have a scanned spine