···11+# Repository Guidelines
22+33+## Project Structure & Module Organization
44+- `src/routes` contains SvelteKit routes, including dynamic handle pages in `src/routes/[handle]/[[page]]`, edit flows in `src/routes/[handle]/[[page]]/edit` and `src/routes/edit`, and API endpoints under `src/routes/api`.
55+- `src/lib` holds reusable modules: card implementations in `src/lib/cards`, shared UI in `src/lib/components`, OAuth helpers in `src/lib/oauth`, and site data/loading in `src/lib/website`.
66+- Root app setup lives in `src/app.html` and `src/app.css`.
77+- `static` is for public assets served as-is.
88+- `docs` includes contributor-facing docs like custom cards and self-hosting.
99+1010+## Build, Test, and Development Commands
1111+- `pnpm dev` starts the Vite dev server.
1212+- `pnpm build` creates a production build.
1313+- `pnpm preview` builds and runs locally with Wrangler.
1414+- `pnpm check` runs `svelte-check` for type diagnostics.
1515+- `pnpm lint` runs Prettier check and ESLint.
1616+- `pnpm format` auto-formats the codebase with Prettier.
1717+- `pnpm deploy` builds and deploys to Cloudflare Workers.
1818+1919+## Coding Style & Naming Conventions
2020+- Indentation uses tabs, single quotes, and 100-column width (see `.prettierrc`).
2121+- Svelte components use PascalCase filenames; utilities and helpers use camelCase.
2222+- Prefer TypeScript in `src` and keep module boundaries aligned with `src/lib` subfolders (cards, components, website, oauth).
2323+2424+## Testing Guidelines
2525+- There is no dedicated test runner yet. Use `pnpm check` and `pnpm lint` before submitting changes.
2626+- For UI changes, verify key flows manually (login, card editing, save/load, and route navigation across `[handle]` pages).
2727+2828+## Commit & Pull Request Guidelines
2929+- Keep commits short and direct; recent history favors lowercase, imperative summaries (e.g., `small fixes`).
3030+- PRs should include a clear description, linked issues when relevant, and screenshots for UI changes.
3131+3232+## Configuration & Deployment Notes
3333+- Copy `.env.example` to `.env` and adjust `PUBLIC_*` values for local or self-hosted setups.
3434+- Cloudflare configuration lives in `wrangler.jsonc`; deployments use Wrangler via `pnpm deploy`.
+11-9
CLAUDE.md
···4455## Project Overview
6677-Blento is a Bluesky-powered customizable bento grid website builder. Users authenticate via ATProto OAuth and can create personalized websites with draggable/resizable cards that are stored directly in their Bluesky PDS (Personal Data Server) using the `app.blento.card` collection.
77+Blento is a Bluesky-powered customizable bento grid website builder. Users authenticate via ATProto OAuth and create draggable/resizable cards stored in their Bluesky PDS (Personal Data Server) using the `app.blento.card` collection.
8899## Commands
1010···3232- Desktop position/size: `x`, `y`, `w`, `h`
3333- Mobile position/size: `mobileX`, `mobileY`, `mobileW`, `mobileH`
34343535-Grid margins: 20px desktop, 12px mobile.
3535+Grid margins: 16px desktop, 12px mobile.
36363737### Key Components
38383939**Website Rendering:**
40404141-- `Website.svelte` - Read-only view of a user's bento grid
4242-- `EditableWebsite.svelte` - Full editing interface with drag-and-drop, card creation, and save functionality
4141+- `src/lib/website/Website.svelte` - Read-only view of a user's bento grid
4242+- `src/lib/website/EditableWebsite.svelte` - Full editing interface with drag-and-drop, card creation, and save functionality
4343+- `src/lib/website/Settings.svelte` and `src/lib/website/Profile.svelte` - Editing panels and profile UI
4344- Styling: two colors: base color (one the gray-ish tailwind colors: `gray`, `neutral`, `stone`, ...) and accent color (one of the not-gray-ish tailwind color: `rose`, `red`, `amber`, ...)
44454546**Card System (`src/lib/cards/`):**
46474748- `CardDefinition` type in `types.ts` defines the interface for card types
4849- Each card type exports a definition with: `type`, `contentComponent`, optional `editingContentComponent`, `creationModalComponent`, `sidebarComponent`, `loadData`, `upload` (see more info and description in `src/lib/cards/types.ts`)
4949-- Card types: Text, Link, Image, Youtube, BlueskyPost, Embed, Map, Livestream, ATProtoCollections, Section
5050+- Card types include Text, Link, Image, Bluesky, Embed, Map, Livestream, ATProto collections, and special cards (see `src/lib/cards`).
5051- `AllCardDefinitions` and `CardDefinitionsByType` in `index.ts` aggregate all card types
5152- See e.g. `src/lib/cards/EmbedCard/` and `src/lib/cards/LivestreamCard/` for examples of implementation.
5253- Cards should be styled to work in light and dark mode (with `dark:` class modifier) as well as when cards are colorful (= bg-color-500 for the card background) (with `accent:` modifier).
···59606061**Data Loading (`src/lib/website/`):**
61626262-- `load.ts` - Fetches user data from their PDS, with Cloudflare KV caching (`USER_DATA_CACHE`)
6363+- `load.ts` - Fetches user data from their PDS, with optional KV caching via `UserCache`
6364- `data.ts` - Defines which collections/records to fetch
6465- `context.ts` - Svelte contexts for passing DID, handle, and data down the component tree
65666667### Routes
67686869- `/` - Landing page
6969-- `/[handle]` - View a user's bento site (loads from their PDS)
7070-- `/[handle]/edit` - Edit mode for the user's site
7070+- `/[handle]/[[page]]` - View a user's bento site (loads from their PDS)
7171+- `/[handle]/[[page]]/edit` - Edit mode for a user's site page
7172- `/edit` - Self-hosted edit mode
7273- `/api/links` - Link preview API
7374- `/api/geocoding` - Geocoding API for map cards
7575+- `/api/instagram`, `/api/reloadRecent`, `/api/update` - Additional data endpoints
74767577### Item Type
7678···80828183`src/lib/helper.ts` contains grid layout algorithms:
82848383-- `fixCollisions` - Push cards down when they overlap
8585+- `fixAllCollisions` - Push cards down when they overlap
8486- `compactItems` - Move cards up to fill gaps
8587- `simulateFinalPosition` - Preview where a dragged card will land
+27-11
README.md
···11# blento
2233-WORK IN PROGRESS, not ready for use yet, but you can test it out at: https://blento.app (no guarantee that your blento wont be broken at some point though and might have to be recreated).
33+Alpha version can be tried at https://blento.app
4455-your personal website in a bento style layout, using your bluesky PDS as a backend.
55+Your personal website in a bento style layout, using your bluesky personal data server as a backend.
6677made with svelte, tailwind and hosted on cloudflare workers.
8899-## Development
99+1010+https://github.com/user-attachments/assets/2b6f5e99-b5d4-4484-ab95-35445067bb80
1111+1212+1313+## Why?
10141111-```
1212-git clone https://github.com/flo-bit/blento.git
1313-cd blento
1414-cp .env.example .env
1515-pnpm install
1616-pnpm run dev
1717-```
1515+This started as a replacement/alternative for bento.me which is shutting down in a few weeks (Feb 2026) after being bought by a competitor ^^ I wanted to build a version that couldn't just shut down or where it would be very easy to spin up a new version (with all the data) should the old one disappear.
1616+1717+That's why all your data is saved on your bluesky personal data server, so you can start setting up your website on blento.app, but then anytime you want to start self hosting you easily take your data with you (dedicated forks optimized for self hosting on different platforms coming soon).
1818+1919+Should blento.app shut down at some point, someone else can also spin up a new version that shows all blentos (note: it's MIT licensed so you *could* do that now too and offer a competing service, but please don't (except for self-hosting your own profile ofc), legal != nice).
2020+2121+One other note: for most independence I encourage everyone to get their own domain and either self host or redirect to blento.app/your-profile (still working on a way to make this as easy as possible for non-technical users, if you have any suggestions please reach out).
18221923## Selfhosting
2024···22262327## Making Custom cards
24282525-See [docs/CustomCards](./docs/CustomCards.md)2929+See [docs/CustomCards](./docs/CustomCards.md)
3030+3131+## Contributing
3232+3333+See [docs/Contributing](./docs/Contributing.md)
3434+3535+## Idea for a card?
3636+3737+Open an issue
3838+3939+## License
4040+4141+MIT
+37
docs/Beta.md
···11+# Todo for beta version
22+33+- fix opengraph image stuff (caching issue?)
44+55+- site.standard
66+ - move subpages to own lexicon (app.blento.page)
77+ - move description to markdownDescription and set description as text only
88+99+- fix recent blentos only showing the last ~5 blentos
1010+1111+- card with big call to action button
1212+1313+- link card: save favicon and og image as blobs
1414+1515+- video card?
1616+1717+- allow changing profile picture
1818+1919+- allow editing profile stuff inline (in sidebar profile)
2020+2121+- allow setting base and accent color
2222+2323+- go straight to edit mode (and redirect to edit mode on succesfull login)
2424+2525+- ask to fill with some default cards on page creation
2626+2727+- share button (copy share link to blento, maybe post to bluesky?)
2828+2929+- add icons to "change card to..." popover
3030+3131+- show social icon instead of favicon in link card if platform found for link
3232+3333+- when adding images try to add them in a size that best fits aspect ratio
3434+3535+- onboarding
3636+3737+- show alert when user tries to close window with unsaved changes
···11+# Contributing Guidelines
22+33+Contributions are very welcome 🎉
44+55+For creating new cards see [here](CustomCards.md) (and check out [existing card ideas](CardIdeas.md))
66+77+## Development
88+99+```
1010+git clone https://github.com/flo-bit/blento.git
1111+cd blento
1212+cp .env.example .env
1313+pnpm install
1414+pnpm run dev
1515+```
1616+1717+## AI assisted development
1818+1919+You can submit PRs written with AI but please make sure:
2020+2121+- there's no extra unnecessary changes/unnecessary verbose code (keep it simple)
2222+- you test everything yourself (in light/dark mode, with and without colored cards, in edit mode and not in edit mode)
+2-3
docs/CustomCards.md
···11# Custom Cards
2233-WORK IN PROGRESS, EARLY STATE, MIGHT CHANGE.
44-53see `src/lib/cards` for how cards are made (and e.g. `src/lib/cards/EmbedCard/` and `src/lib/cards/LivestreamCard/` for examples of implementation).
6475Notes:
8699-Cards should be styled to work in light and dark mode (with dark: class modifier) as well as when cards are colorful (= bg-color-500 for the card) (with accent: modifier).
77+- Cards should be styled to work in light and dark mode (with dark: class modifier) as well as when cards are colorful (= bg-color-500 for the card) (with accent: modifier).
88+- Please test newly created cards both when editing (/your-user/edit) and in your user profile when saved (/your-user)
+4
docs/Lexicon.md
···11+# Lexicons used by blento.app
22+33+- `site.standard.publication`
44+- `app.blento.card`
···11-import type { CardDefinition } from '../types';
11+import type { CardDefinition } from '$lib/cards/types';
22import DinoGameCard from './DinoGameCard.svelte';
33import SidebarItemDinoGameCard from './SidebarItemDinoGameCard.svelte';
44
···11-export const image_collection = 'com.example.image' as const;
22-33-// collections and records we want to grab
44-export const data = {
55- 'app.bsky.actor.profile': ['self'],
66-77- 'app.blento.card': 'all',
88- 'app.blento.settings': ['self']
99-} as const;
···11-import type { data } from './data';
22-import type { Record as ListRecord } from '@atproto/api/dist/client/types/com/atproto/repo/listRecords';
33-44-export type Collection = keyof typeof data;
55-66-export type IndividualCollections = {
77- [K in Collection]: (typeof data)[K] extends readonly unknown[] ? K : never;
88-}[Collection];
99-1010-export type ListCollections = Exclude<Collection, IndividualCollections>;
1111-1212-export type ElementType<C extends Collection> = (typeof data)[C] extends readonly (infer U)[]
1313- ? U
1414- : unknown;
1515-1616-export type DownloadedData = { [C in Collection]: Record<string, ListRecord> };
src/lib/website/utils.ts
src/lib/oauth/utils.ts
+3-2
src/routes/+page.server.ts
···11import { loadData } from '$lib/website/load';
22import { env } from '$env/dynamic/public';
33+import type { UserCache } from '$lib/types';
3445export async function load({ platform, url }) {
56 const hostname = url.hostname;
···89 if (hostname === 'flo-bit.blento.app') {
910 handle = 'flo-bit.dev';
1011 }
1212+ const cache = platform?.env?.USER_DATA_CACHE as unknown;
11131212- const data = await loadData(handle, platform);
1313- return { ...data, handle };
1414+ return await loadData(handle, cache as UserCache);
1415}
+3-12
src/routes/+page.svelte
···11<script lang="ts">
22- import { refreshData, setAdditionalUserData } from '$lib/helper.js';
33- import { type Item } from '$lib/types.js';
44- import Website from '$lib/Website.svelte';
22+ import { refreshData } from '$lib/helper.js';
33+ import Website from '$lib/website/Website.svelte';
54 import { onMount } from 'svelte';
6576 let { data } = $props();
88-99- // svelte-ignore state_referenced_locally
1010- setAdditionalUserData(data.additionalData);
117128 onMount(() => {
139 refreshData(data);
1410 });
1511</script>
16121717-<Website
1818- {data}
1919- handle={data.handle}
2020- did={data.did}
2121- items={Object.values(data.data['app.blento.card']).map((i) => i.value) as Item[]}
2222-/>
1313+<Website {data} />
-9
src/routes/[handle]/+layout.server.ts
···11-import { loadData } from '$lib/website/load';
22-import { env } from '$env/dynamic/private';
33-import { error } from '@sveltejs/kit';
44-55-export async function load({ params, platform }) {
66- if (env.PUBLIC_IS_SELFHOSTED) error(404);
77- const data = await loadData(params.handle, platform);
88- return { ...data, handle: params.handle };
99-}
-24
src/routes/[handle]/+page.svelte
···11-<script lang="ts">
22- import { page } from '$app/state';
33- import { refreshData, setAdditionalUserData } from '$lib/helper.js';
44- import { type Item } from '$lib/types.js';
55- import Website from '$lib/Website.svelte';
66- import { onMount } from 'svelte';
77-88- let { data } = $props();
99-1010- // svelte-ignore state_referenced_locally
1111- setAdditionalUserData(data.additionalData);
1212-1313- onMount(() => {
1414- refreshData(data);
1515- })
1616-</script>
1717-1818-<Website
1919- {data}
2020- handle={page.params.handle}
2121- did={data.did}
2222- items={Object.values(data.data['app.blento.card']).map((i) => i.value) as Item[]}
2323- settings={data.data['app.blento.settings']?.['self']?.value}
2424-/>
···11# todo
2233-- [x] video card or image from bluesky
43- general video card
55-- edit already created cards (e.g. change link)
66-- link card: save favicon and og image to pds
77-- [x] more cards list
88-- paste handler for card creation (+ when entering link)
99-- [x] text cards: align text top middle bottom and left center right
1010-- change general settings:
1111- - show profile
1212- - profile on side or top
1313- - base, accent color
1414- - title
1515- - favicon
1616-- [x] set custom card size
1717-- spacer card
184- option to hide cards on mobile
1919-2020-- [x] og images
215- separate og image for main page
2222-2323-- more cards:
2424- - instagram
2525- - github
2626- - bluesky account
2727- - bluesky feed
2828- - bluesky post (fixed or latest)
2929- - social accounts card (multiple)
3030- - cartoons: aka https://www.opendoodles.com/
3131- - excalidraw
3232- - [x] map
3333- - [x] youtube video
3434- - youtube channel
3535- - guestbook
3636-3737-- other atproto apps
3838- - leaflet
3939- - skywatched
4040- - teal.fm
4141- - tangled.sh
4242- - popfeed.social
4343- - smoke signal
4444- - statusphere.xyz
4545- - goals.garden
4646-4747-- [x] add some caching to user data
4848-4949-- other fun card ideas:
5050- - svader
5151- - zdog
5252- - tixy
5353-546- image cards: different images for dark and light mode
5555-- allow changing avatar and description to be different than bluesky
567- allow adding background image
5757-- [x] borderless cards
5885959-- selfhosting options:
6060- - [x] cloudflare workers
6161- - other serverless option
6262- - github pages
99+- analytics (get page views)
1010+- custom subdomain
1111+1212+## selfhosting
63136464-- analytics (get page views)
6565-- custom subdomain1414+- [x] cloudflare workers
1515+- other serverless option? or adapter-auto
1616+- github pages (adapter-static)