···1919## Architecture
20202121### Tech Stack
2222+2223- **Framework**: SvelteKit 2 with Svelte 5 (using runes: `$state`, `$derived`, `$props`)
2324- **Styling**: Tailwind CSS 4 with container queries (`@container`)
2425- **Deployment**: Cloudflare Workers via `@sveltejs/adapter-cloudflare`
2526- **UI Components**: `@foxui/core`, `@foxui/social` (custom component libraries)
26272728### Grid System
2929+2830The site uses an 8-column grid layout (`COLUMNS = 8` in `src/lib/index.ts`). Each card has:
3131+2932- Desktop position/size: `x`, `y`, `w`, `h`
3033- Mobile position/size: `mobileX`, `mobileY`, `mobileW`, `mobileH`
3134···3437### Key Components
35383639**Website Rendering:**
4040+3741- `Website.svelte` - Read-only view of a user's bento grid
3842- `EditableWebsite.svelte` - Full editing interface with drag-and-drop, card creation, and save functionality
4343+- 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`, ...)
39444045**Card System (`src/lib/cards/`):**
4646+4147- `CardDefinition` type in `types.ts` defines the interface for card types
4242-- Each card type exports a definition with: `type`, `contentComponent`, `editingContentComponent`, optional `creationModalComponent`, `sidebarComponent`, `loadData`, `upload`
4848+- 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`)
4349- Card types: Text, Link, Image, Youtube, BlueskyPost, Embed, Map, Livestream, ATProtoCollections, Section
4450- `AllCardDefinitions` and `CardDefinitionsByType` in `index.ts` aggregate all card types
5151+- See e.g. `src/lib/cards/EmbedCard/` and `src/lib/cards/LivestreamCard/` for examples of implementation.
5252+- 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).
45534654**ATProto Integration (`src/lib/oauth/`):**
5555+4756- `auth.svelte.ts` - OAuth client state and login/logout flow using `@atcute/oauth-browser-client`
4857- `atproto.ts` - ATProto API helpers: `resolveHandle`, `listRecords`, `getRecord`, `putRecord`, `deleteRecord`, `uploadImage`
4958- Data is stored in user's PDS under collection `app.blento.card`
50595160**Data Loading (`src/lib/website/`):**
6161+5262- `load.ts` - Fetches user data from their PDS, with Cloudflare KV caching (`USER_DATA_CACHE`)
5363- `data.ts` - Defines which collections/records to fetch
5464- `context.ts` - Svelte contexts for passing DID, handle, and data down the component tree
55655666### Routes
6767+5768- `/` - Landing page
5869- `/[handle]` - View a user's bento site (loads from their PDS)
5970- `/[handle]/edit` - Edit mode for the user's site
···6273- `/api/geocoding` - Geocoding API for map cards
63746475### Item Type
7676+6577Cards are represented by the `Item` type (`src/lib/types.ts`) with grid position, size, cardType, and cardData properties.
66786779### Collision/Layout Helpers
8080+6881`src/lib/helper.ts` contains grid layout algorithms:
8282+6983- `fixCollisions` - Push cards down when they overlap
7084- `compactItems` - Move cards up to fill gaps
7185- `simulateFinalPosition` - Preview where a dragged card will land
+10-1
README.md
···4455your personal website in a bento style layout, using your bluesky PDS as a backend.
6677-made with svelte, tailwind.
77+made with svelte, tailwind and hosted on cloudflare workers.
88+99+## Development
1010+1111+```
1212+git clone https://github.com/flo-bit/blento.git
1313+cp .env.example .env
1414+pnpm install
1515+pnpm run dev
1616+```
817918## Selfhosting
1019
+3-15
docs/CustomCards.md
···2233WORK IN PROGRESS, EARLY STATE, MIGHT CHANGE.
4455-see `src/lib/cards` for how cards are made.
66-77-Current card definition:
88-99-```ts
1010-export type CardDefinition = {
1111- type: string;
1212- contentComponent: Component<ContentComponentProps>; // this is what your card shows
1313-1414- editingContentComponent?: Component<ContentComponentProps>; // if this is not given, defaults to showing contentComponent in edit mode too
1515- creationModalComponent?: Component<CreationModalComponentProps>; // if this is not given will just add a card
55+see `src/lib/cards` for how cards are made (and e.g. `src/lib/cards/EmbedCard/` and `src/lib/cards/LivestreamCard/` for examples of implementation).
1661717- createNew?: (item: Item) => void; // this is run before the card is added, set some settings here
77+Notes:
1881919- sidebarComponent?: Component<SidebarComponentProps>; // this is the button that will be shown in the sidebar to add your card
2020-};
2121-```
99+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).
+13-9
src/lib/cards/types.ts
···2222};
23232424export type CardDefinition = {
2525- type: string;
2626- contentComponent: Component<ContentComponentProps>;
2727- editingContentComponent?: Component<ContentComponentProps>;
2525+ type: string; // should be unique
2626+ contentComponent: Component<ContentComponentProps>; // content of card
2727+ editingContentComponent?: Component<ContentComponentProps>; // optional content of card in editing mode
28282929- createNew?: (item: Item) => void;
2929+ createNew?: (item: Item) => void; // set some custom cardData stuff here (or custom default sizes)
30303131 creationModalComponent?: Component<CreationModalComponentProps>;
32323333- settingsModalComponent?: Component<SettingsModalComponentProps>;
3434-3535- upload?: (item: Item) => Promise<Item>;
3333+ upload?: (item: Item) => Promise<Item>; // optionally upload some other data needed for this card
36343535+ // one of those two has to be set for a card to appear in the sidebar
3736 sidebarComponent?: Component<SidebarComponentProps>;
3837 sidebarButtonText?: string;
39383939+ // if this component exists, a settings button with a popover will be shown containing this component
4040 settingsComponent?: Component<ContentComponentProps>;
41414242+ // optionally load some extra data
4243 loadData?: (
4344 items: Item[],
4445 { did, handle, platform }: { did: string; handle: string; platform?: App.Platform }
4546 ) => Promise<unknown>;
4646- dataKey?: string;
47474848+ // show color selection popup
4849 allowSetColor?: boolean;
49505151+ // default card background color one of 'base', 'accent', 'transparent', or one of the tailwind colors
5252+ // (actual colors only, without 'gray', 'neutral', 'stone', etc)
5053 defaultColor?: string;
51545555+ // for resizing:
5256 minW?: number;
5357 maxW?: number;
5458···5660 maxH?: number;
57615862 canResize?: boolean;
5959-};
6363+};