···19## Architecture
2021### Tech Stack
022- **Framework**: SvelteKit 2 with Svelte 5 (using runes: `$state`, `$derived`, `$props`)
23- **Styling**: Tailwind CSS 4 with container queries (`@container`)
24- **Deployment**: Cloudflare Workers via `@sveltejs/adapter-cloudflare`
25- **UI Components**: `@foxui/core`, `@foxui/social` (custom component libraries)
2627### Grid System
028The site uses an 8-column grid layout (`COLUMNS = 8` in `src/lib/index.ts`). Each card has:
029- Desktop position/size: `x`, `y`, `w`, `h`
30- Mobile position/size: `mobileX`, `mobileY`, `mobileW`, `mobileH`
31···34### Key Components
3536**Website Rendering:**
037- `Website.svelte` - Read-only view of a user's bento grid
38- `EditableWebsite.svelte` - Full editing interface with drag-and-drop, card creation, and save functionality
03940**Card System (`src/lib/cards/`):**
041- `CardDefinition` type in `types.ts` defines the interface for card types
42-- Each card type exports a definition with: `type`, `contentComponent`, `editingContentComponent`, optional `creationModalComponent`, `sidebarComponent`, `loadData`, `upload`
43- Card types: Text, Link, Image, Youtube, BlueskyPost, Embed, Map, Livestream, ATProtoCollections, Section
44- `AllCardDefinitions` and `CardDefinitionsByType` in `index.ts` aggregate all card types
004546**ATProto Integration (`src/lib/oauth/`):**
047- `auth.svelte.ts` - OAuth client state and login/logout flow using `@atcute/oauth-browser-client`
48- `atproto.ts` - ATProto API helpers: `resolveHandle`, `listRecords`, `getRecord`, `putRecord`, `deleteRecord`, `uploadImage`
49- Data is stored in user's PDS under collection `app.blento.card`
5051**Data Loading (`src/lib/website/`):**
052- `load.ts` - Fetches user data from their PDS, with Cloudflare KV caching (`USER_DATA_CACHE`)
53- `data.ts` - Defines which collections/records to fetch
54- `context.ts` - Svelte contexts for passing DID, handle, and data down the component tree
5556### Routes
057- `/` - Landing page
58- `/[handle]` - View a user's bento site (loads from their PDS)
59- `/[handle]/edit` - Edit mode for the user's site
···62- `/api/geocoding` - Geocoding API for map cards
6364### Item Type
065Cards are represented by the `Item` type (`src/lib/types.ts`) with grid position, size, cardType, and cardData properties.
6667### Collision/Layout Helpers
068`src/lib/helper.ts` contains grid layout algorithms:
069- `fixCollisions` - Push cards down when they overlap
70- `compactItems` - Move cards up to fill gaps
71- `simulateFinalPosition` - Preview where a dragged card will land
···19## Architecture
2021### Tech Stack
22+23- **Framework**: SvelteKit 2 with Svelte 5 (using runes: `$state`, `$derived`, `$props`)
24- **Styling**: Tailwind CSS 4 with container queries (`@container`)
25- **Deployment**: Cloudflare Workers via `@sveltejs/adapter-cloudflare`
26- **UI Components**: `@foxui/core`, `@foxui/social` (custom component libraries)
2728### Grid System
29+30The site uses an 8-column grid layout (`COLUMNS = 8` in `src/lib/index.ts`). Each card has:
31+32- Desktop position/size: `x`, `y`, `w`, `h`
33- Mobile position/size: `mobileX`, `mobileY`, `mobileW`, `mobileH`
34···37### Key Components
3839**Website Rendering:**
40+41- `Website.svelte` - Read-only view of a user's bento grid
42- `EditableWebsite.svelte` - Full editing interface with drag-and-drop, card creation, and save functionality
43+- 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`, ...)
4445**Card System (`src/lib/cards/`):**
46+47- `CardDefinition` type in `types.ts` defines the interface for card types
48+- 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`)
49- Card types: Text, Link, Image, Youtube, BlueskyPost, Embed, Map, Livestream, ATProtoCollections, Section
50- `AllCardDefinitions` and `CardDefinitionsByType` in `index.ts` aggregate all card types
51+- See e.g. `src/lib/cards/EmbedCard/` and `src/lib/cards/LivestreamCard/` for examples of implementation.
52+- 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).
5354**ATProto Integration (`src/lib/oauth/`):**
55+56- `auth.svelte.ts` - OAuth client state and login/logout flow using `@atcute/oauth-browser-client`
57- `atproto.ts` - ATProto API helpers: `resolveHandle`, `listRecords`, `getRecord`, `putRecord`, `deleteRecord`, `uploadImage`
58- Data is stored in user's PDS under collection `app.blento.card`
5960**Data Loading (`src/lib/website/`):**
61+62- `load.ts` - Fetches user data from their PDS, with Cloudflare KV caching (`USER_DATA_CACHE`)
63- `data.ts` - Defines which collections/records to fetch
64- `context.ts` - Svelte contexts for passing DID, handle, and data down the component tree
6566### Routes
67+68- `/` - Landing page
69- `/[handle]` - View a user's bento site (loads from their PDS)
70- `/[handle]/edit` - Edit mode for the user's site
···73- `/api/geocoding` - Geocoding API for map cards
7475### Item Type
76+77Cards are represented by the `Item` type (`src/lib/types.ts`) with grid position, size, cardType, and cardData properties.
7879### Collision/Layout Helpers
80+81`src/lib/helper.ts` contains grid layout algorithms:
82+83- `fixCollisions` - Push cards down when they overlap
84- `compactItems` - Move cards up to fill gaps
85- `simulateFinalPosition` - Preview where a dragged card will land
+10-1
README.md
···45your personal website in a bento style layout, using your bluesky PDS as a backend.
67-made with svelte, tailwind.
00000000089## Selfhosting
10
···45your personal website in a bento style layout, using your bluesky PDS as a backend.
67+made with svelte, tailwind and hosted on cloudflare workers.
8+9+## Development
10+11+```
12+git clone https://github.com/flo-bit/blento.git
13+cp .env.example .env
14+pnpm install
15+pnpm run dev
16+```
1718## Selfhosting
19
+3-15
docs/CustomCards.md
···23WORK IN PROGRESS, EARLY STATE, MIGHT CHANGE.
45-see `src/lib/cards` for how cards are made.
6-7-Current card definition:
8-9-```ts
10-export type CardDefinition = {
11- type: string;
12- contentComponent: Component<ContentComponentProps>; // this is what your card shows
13-14- editingContentComponent?: Component<ContentComponentProps>; // if this is not given, defaults to showing contentComponent in edit mode too
15- creationModalComponent?: Component<CreationModalComponentProps>; // if this is not given will just add a card
1617- createNew?: (item: Item) => void; // this is run before the card is added, set some settings here
1819- sidebarComponent?: Component<SidebarComponentProps>; // this is the button that will be shown in the sidebar to add your card
20-};
21-```
···23WORK IN PROGRESS, EARLY STATE, MIGHT CHANGE.
45+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).
000000000067+Notes:
89+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).
00
···22};
2324export type CardDefinition = {
25+ type: string; // should be unique
26+ contentComponent: Component<ContentComponentProps>; // content of card
27+ editingContentComponent?: Component<ContentComponentProps>; // optional content of card in editing mode
2829+ createNew?: (item: Item) => void; // set some custom cardData stuff here (or custom default sizes)
3031 creationModalComponent?: Component<CreationModalComponentProps>;
3233+ upload?: (item: Item) => Promise<Item>; // optionally upload some other data needed for this card
003435+ // one of those two has to be set for a card to appear in the sidebar
36 sidebarComponent?: Component<SidebarComponentProps>;
37 sidebarButtonText?: string;
3839+ // if this component exists, a settings button with a popover will be shown containing this component
40 settingsComponent?: Component<ContentComponentProps>;
4142+ // optionally load some extra data
43 loadData?: (
44 items: Item[],
45 { did, handle, platform }: { did: string; handle: string; platform?: App.Platform }
46 ) => Promise<unknown>;
04748+ // show color selection popup
49 allowSetColor?: boolean;
5051+ // default card background color one of 'base', 'accent', 'transparent', or one of the tailwind colors
52+ // (actual colors only, without 'gray', 'neutral', 'stone', etc)
53 defaultColor?: string;
5455+ // for resizing:
56 minW?: number;
57 maxW?: number;
58···60 maxH?: number;
6162 canResize?: boolean;
63+};