Aniqa's portfolio, lab, and app reviews, built with Astro and hosted on Vercel.
- Portfolio: aniqa.dev — selected work and case studies
- Lab: aniqa.dev/lab — experiments, studies, and notes
- Reviews: aniqa.dev/reviews — indie app reviews
- Source: github.com/aniqatc/aniqa.dev
The lab lives at /lab. Pages are static except likes and sign-ups.
| Portfolio | Lab |
|---|---|
![]() |
![]() |
Entry Open Graph images are in public/og/. After changing a title, blurb, or screenshot, run npm run og and include the updated images.
- Astro 7, strict TypeScript, plain CSS, Sätteri, Vercel adapter
- Fontsource: Bricolage Grotesque, Geist, Geist Mono
- Upstash Redis for likes; Buttondown for sign-ups
- Playwright for screenshots and share cards
- p5.js and Three.js for experiments that need them
- Portfolio case studies and “Building now” section
- Lab archive by year, favorites, random entry, likes, and mouse-only hover previews
- Live experiment stages with optional themes and controls
- Reviews with scores, app screenshots, and shareable scorecards
- Generated Open Graph images, page transitions, and an animated calendar pet
The default is auto in src/lib/pets.ts. Set PET there to a pet name to pin one.
| Dates | Pet |
|---|---|
| Jan 1–7 | Party |
| Jan 8–31 | Penguin |
| Feb 1–14 / Feb 15–29 | Heart / mochi |
| March / April / May / June | Chick / bunny / sprout / bee |
| July–August / September / October / November / December | Frog / owl / pumpkin / acorn / snowball |
Date overrides: Feb 27 cupcake; Mar 27, May 5, June 26, Oct 18 heart; June 30 and Sep 20 cupcake; Oct 31 ghost; Dec 25 tree; Dec 31 party. Mother's Day and Father's Day use party; Eid uses moon; Diwali uses diya for all five days (2 either side of the main date); Lunar New Year uses lantern. Moving-date rules and calendars are in src/lib/pets.ts.
Click the dot by the site name to reveal the pet. It follows pointer movement or taps. Reduced-motion settings reduce its movement and animation.
- Semantic links and controls, accessible names, and visible keyboard focus
- Hover previews only on mouse; links work on touch screens
- Reduced-motion preferences disable or reduce animations
src/content/projects/ Portfolio case studies
src/content/experiments/ Lab entries and studies
src/content/reviews/ App reviews
src/components/ Shared Astro components
src/lib/ Content helpers and site data
src/pages/ Routes and API endpoints
src/scripts/ Browser interactions
src/styles/ Tokens and shared styles
templates/ Entry scaffolds
scripts/ Build and image tools
public/og/ Generated preview images
- One folder per entry, with
index.md; schemas are insrc/content.config.ts. - Lab entries with
sketch.tsorsketch.astrorun on the page. Others link out. stage: frameis fixed-ratio;stage: fitgrows to fit studies.themes: trueadds day/night controls to a stage.draft: truehides entries from production;favorite: truepins a lab entry.- Projects and lab entries need
screenshot.pngand descriptivescreenshotAlt.
Requires Node.js 22.22 or newer.
npm install
cp .env.example .env.local
npx playwright install chromium
npm run devThe site builds without credentials. Add the variables needed for these features:
| Variable | Used for |
|---|---|
BUTTONDOWN_API_KEY |
Email sign-ups |
UPSTASH_REDIS_REST_URL |
Likes database URL |
UPSTASH_REDIS_REST_TOKEN |
Likes database token |
GITHUB_TOKEN |
Optional GitHub API reads, such as star counts |
VERCEL_DEPLOY_HOOK |
GitHub Actions secret for daily star refresh; not an .env.local variable |
Upstash also accepts KV_REST_API_URL and KV_REST_API_TOKEN as aliases.
| Command | What it does |
|---|---|
npm run dev |
Start the site at localhost:4321 (foreground; stop with Ctrl+C). |
npm run start |
Alias for npm run dev. |
npm run build |
Type-check and build. |
npm run check |
Run Astro checks. |
npm run lint |
Run ESLint. |
npm run format / format:check |
Format files / check formatting. |
npm run new |
Scaffold a project, experiment, or review. |
npm run screenshots |
Capture content screenshots; pass slugs to select entries. |
npm run og |
Generate Open Graph images. |
npm run scorecard -- <slug> |
Generate a review scorecard. |
There is no preview script for the Vercel adapter.
Each entry is a folder under src/content/. Start with the matching command below, then:
- Edit the new folder's
index.md: complete its frontmatter and write-up. - Add
screenshot.pngand describe what it shows inscreenshotAlt. - Run
npm run og, then check the page locally withnpm run dev.
The scaffold prints the new folder path and the next steps. Use draft: true to keep a project or experiment out of production while you work. Reviews start as drafts automatically.
npm run new -- --kind project --title "Title" --slug slug- Edit
src/content/projects/<slug>/index.md. Fill inreleased,type,stack,blurb,summary, andorder. - Write
Why,What it does, andWhat I learnedin first person. Keep each point specific and short. AddHardest partor datedRevisitedonly when you have something useful to add. - Add the screenshot and alt text. The screenshot script captures the live site in dark mode.
npm run new -- --kind experiment --sketch none --title "Title" --slug slug- In
src/content/experiments/<slug>/index.md, fill inlive(the site host) andrepo(the GitHub repository name, if public). - Write
Why I built itandWhat I learned. Keep the notes brief and specific. If this is a CodePen migration, put its URL inv1and describe the migration inRevisited. - Run
npm run screenshots -- <slug>to capture the live site in light mode. Add alt text describing the screenshot.
npm run new -- --kind experiment --sketch canvas --title "Title" --slug slug- Choose
canvas,css,p5, orthreein--sketch. The scaffold createssketch.tsfor canvas, p5, and Three.js, orsketch.astrofor CSS art. Install p5.js or Three.js if needed, then restart the dev server. - In
index.md, fill in the type, stack, summary, and optionalhint. Write anIdeaand a few shortNotesabout what you tried, tuned, or learned. - Set
stage: framefor a fixed stage orstage: fitwhen the stage should grow with the sketch. Capture the screenshot withnpm run screenshots -- <slug>and add alt text.
npm run new -- --kind experiment --sketch study --title "Title" --slug slug- Edit
sketch.astroto build the study. Use the shared controls and styles insrc/styles/demo.css. - In
index.md, settypetoCSS studyorJS study, and fill in the summary and hint. Write anIdeaandNotesthat explain the concept, the technique, and what you noticed while testing it. - For a CodePen migration, put the original URL in
v1and add a datedRevisitednote. Capture the screenshot withnpm run screenshots -- <slug>and add alt text.
npm run new -- --kind review --app "App name" --maker handle --live example.com- Fill in the review's date,
triedOn, scores, verdict, and brand colour inindex.md. - Write
First look,What I liked,Small ideas, andWho it's for. Use concrete details from trying the app; keep suggestions kind and specific. To show a detail, put a screenshot in the review folder and add it to the point, either framed under it or as a picture note in the text (see Pictures in a review). - For a web app, run
npm run screenshots -- reviews/<slug>to capture the app and its icon. For a phone or tablet app, add screenshots from the device and setdevicetophoneortablet; for a Mac app, see Mac apps. Optionalprice,versionandplatformsshow with the facts. - Run
npm run scorecard -- <slug>to make the share image. Check the draft locally, removedraft: truewhen ready, then runnpm run ogfor the review's link preview.
The Try it card at the end of a review says “Give it a go, and if you like it, let them know. Early feedback means a lot when you’re building something new.” For a review where that doesn’t fit, like a mixed one or a big company’s app, set tryIt in its frontmatter to say something else.
The home page intro shows one review: the newest, under Reviewing. To keep one there instead, add featured: true to its frontmatter and it shows under Hooked on (the newest featured one wins if there are several).
Games use the same reviews, with their own scores and a few different labels. Start one with:
npm run new -- --kind review --game --app "Game name" --maker studiohandle --live https://store.steampowered.com/app/…(or run npm run new, pick review, and answer game when it asks). That copies templates/review-game/ and sets
kind: game, which changes:
| App | Game | |
|---|---|---|
| Scores | First impression, Onboarding, Usefulness, Ease of use, Pricing / value | First impression, Learning curve, Fun, Feel, Pricing / value |
| Score keys | onboarding, usefulness, easeOfUse |
learningCurve, fun, feel |
| Facts | Tried on | Played on, plus Time played from played: 15 hours |
| Eyebrow | App review 01 | Game review 01 |
| Star on the tile | Still using it | Still playing it |
- What the game scores mean: Learning curve is how quickly it clicked and whether the tutorial helped or got in the way; Fun is whether it's actually fun to play; Feel is the controls, responsiveness and performance; Pricing / value is the price against the hours you got out of it (replayability goes here).
- Screenshot: for a PC or console game,
device: screenshows one plain 16:9 screenshot with no device around it: put an in-game shot (1920 Ă— 1080 or larger) inscreenshot.png. A phone, iPad or Mac game usesphone,tabletordesktoplike an app. - Logo: the game's icon, or its store capsule art cropped square, in
logo.png. - Link: set
liveto the store page; it reads Steam ↗, itch.io ↗ or App Store ↗ on its own. - Writing: the same sections (First look, What I liked, Small ideas, Who it's for). Keep story spoilers out, or flag them first. "Who it's for" can say what it's like ("if you liked X, you'll like this").
- Big studios: the Try it card's "Early feedback means a lot" is for indie games; for a big studio's game, set
tryItto something else. - Mixing up scores fails the build: a game needs
learningCurve,funandfeel, and an apponboarding,usefulnessandeaseOfUse, so a game withoutkind: game(or the reverse) gets a clear error. - When the first game goes up, change the /reviews lede in
src/pages/reviews/index.astrofrom "I try apps…" to "I try apps and games…".
When an app changes after you've reviewed it (the maker took your feedback, or a gripe went away), add to the review instead of rewriting it, so readers can see what changed and what you thought before.
-
Date it. Add
updated: YYYY-MM-DDto the frontmatter. It shows under the review's date in the facts, and search engines get it as the modified date. Bumpversion(andtriedOn, if your Mac or phone changed). -
Write an Update section right after the TL;DR, so returning readers see it first. Start it with the date and version in italics; that line shows small and grey, like the facts. Say what changed and which score moved. For a second update later, add a new
## Updateabove the old one, newest first.## Update _Dec 12, 2026 · version 1.4_ @capt_sherman shipped a Health tab, so water and the eye breaks now live together. That was my main gripe, so ease of use goes from 4 to 5.
-
Change the scores in the frontmatter. Don't strike them out; the Update says what moved and why.
-
Strike what's no longer true with
~~ ~~, and put a tag after it in backticks. Keep it to the sentence or clause that changed, not whole paragraphs; it shows grey and still readable.Water sits with the Pomodoro timer, ~~which is actually my main gripe~~ `Fixed in 1.4`. That being said…
-
Tag shipped ideas in Small ideas without striking them, since the idea is still what you asked for:
`Shipped in 1.4`at the end of the point. Both tags get a âś“ and look the same; only the word changes. -
The tile: for 30 days after
updated, the review's tile on /reviews and the home page gets a refresh badge, explained by the legend in the All reviews heading. It drops off at the first deploy after that. -
Refresh the images:
npm run scorecard -- <slug>andnpm run og, so the share card and link preview show the new scores. On X, quote-post your original post with the update.
There are two ways to show a picture in a review's notes. Both open it full size when clicked (or focused and opened with Enter), with its caption under it, and you can mix them in one review. Crop to the part that matters.
Framed, under the point: the picture sits in a narrow frame below the text. Leave a blank line before it.
1. **Make it yours.** There are 21 tabs, but you can remove what you don't need.
Add a caption with an italic line straight after the image. It shows in a strip along the bottom of the frame.

_The tab grid, with Pet care open_Two images on one line sit side by side, for phone screens, and share one caption.
 
_Before and after the first entry_Picture note, in the text: give the image the title "note" and put it right after the words it shows. It
becomes a small chip in the line, so the sentence keeps reading normally.
1. **Make it yours.** There are 21 tabs , but you can remove what you don't need.Add a caption after a colon in the title. It shows under the picture when it opens.
1. **The pet.** Bombay  peeks
out of the notch.| You write | It shows as | Caption from |
|---|---|---|
 |
A framed picture under the point | An italic line after it |
  |
Two framed pictures side by side | An italic line after them |
 |
A chip in the text that opens the picture | None |
 |
A chip in the text that opens the picture | The text after note: |
The alt text describes the picture for screen readers either way; any other title leaves a picture framed.
Set device: desktop. screenshot.png is only the app, with no background, and the site draws the Mac screen around it: the wallpaper (tinted with brand), the menu bar, the notch, and the Dock. The image is treated as a 2Ă— (Retina) capture and shown at half its pixel size, so it is never stretched.
desktop |
What it shows | How to capture |
|---|---|---|
window (default) |
The window in the middle of a MacBook, with a Dock | Cmd+Shift+4, Space, then Option-click the window (no shadow) |
menubar |
A popover hanging under its menu bar icon | Cmd+Shift+4, Space, then click the popover, or a tight drag; leave out the icon |
notch |
A panel growing out of the notch | Drag-select from the top edge of the screen around the panel |
Add the app icon as logo.png; it becomes the icon in the Dock and the menu bar (without it, the first letter on the brand colour). The site adds a soft shadow; set shadow: false if the capture already has one.
- Rebuilt the portfolio in Astro; added the lab, reviews, likes, pets, and generated share images.
- Published the original React portfolio. See the
react-v1tag.
Code is MIT. Writing, experiments, and images are © Aniqa, all rights reserved.

