your personal website on atproto - mirror blento.app

small fixes, more docs

Florian 16a8feff 1d2c5f48

+143 -53
+23 -11
README.md
··· 1 1 # blento 2 2 3 - 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). 3 + Alpha version can be tried at https://blento.app 4 4 5 - your personal website in a bento style layout, using your bluesky PDS as a backend. 5 + Your personal website in a bento style layout, using your bluesky personal data server as a backend. 6 6 7 7 made with svelte, tailwind and hosted on cloudflare workers. 8 8 9 - ## Development 9 + ## Why? 10 10 11 - ``` 12 - git clone https://github.com/flo-bit/blento.git 13 - cd blento 14 - cp .env.example .env 15 - pnpm install 16 - pnpm run dev 17 - ``` 11 + 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. 12 + 13 + 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). 14 + 15 + 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). 16 + 17 + 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). 18 18 19 19 ## Selfhosting 20 20 ··· 22 22 23 23 ## Making Custom cards 24 24 25 - See [docs/CustomCards](./docs/CustomCards.md) 25 + See [docs/CustomCards](./docs/CustomCards.md) 26 + 27 + ## Contributing 28 + 29 + See [docs/Contributing](./docs/Contributing.md) 30 + 31 + ## Idea for a card? 32 + 33 + Open an issue 34 + 35 + ## License 36 + 37 + MIT
+4 -4
docs/Beta.md
··· 1 - # Todo for beta 1 + # Todo for beta version 2 2 3 3 - fix opengraph image stuff 4 4 5 5 - site.standard 6 - - move subpages to own lexicon 6 + - move subpages to own lexicon (app.blento.page) 7 7 - move description to markdownDescription and set description as text only 8 8 9 - - fix recent blentos only showing the last ~5 9 + - fix recent blentos only showing the last ~5 blentos 10 10 11 11 - card with big call to action button 12 12 13 13 - link card: save favicon and og image as blobs 14 14 15 - - video card 15 + - video card? 16 16 17 17 - allow changing profile picture 18 18
+22
docs/Contributing.md
··· 1 + # Contributing Guidelines 2 + 3 + Contributions are very welcome 🎉 4 + 5 + For creating new cards see [here](CustomCards.md) (and check out [existing card ideas](CardIdeas.md)) 6 + 7 + ## Development 8 + 9 + ``` 10 + git clone https://github.com/flo-bit/blento.git 11 + cd blento 12 + cp .env.example .env 13 + pnpm install 14 + pnpm run dev 15 + ``` 16 + 17 + ## AI assisted development 18 + 19 + You can submit PRs written with AI but please make sure: 20 + 21 + - there's no extra unnecessary changes/unnecessary verbose code (keep it simple) 22 + - 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
··· 1 1 # Custom Cards 2 2 3 - WORK IN PROGRESS, EARLY STATE, MIGHT CHANGE. 4 - 5 3 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). 6 4 7 5 Notes: 8 6 9 - 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). 7 + - 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). 8 + - Please test newly created cards both when editing (/your-user/edit) and in your user profile when saved (/your-user)
+1 -1
package.json
··· 1 1 { 2 2 "name": "blento", 3 3 "private": true, 4 - "version": "0.1.0", 4 + "version": "0.2.0", 5 5 "type": "module", 6 6 "scripts": { 7 7 "dev": "vite dev",
+29
src/lib/cards/BlueskyMediaCard/SidebarItemBlueskyMediaCard.svelte
··· 1 + <script lang="ts"> 2 + import { Button } from '@foxui/core'; 3 + 4 + let { onclick }: { onclick: () => void } = $props(); 5 + </script> 6 + 7 + <Button {onclick} variant="ghost" class="w-full justify-start"> 8 + <svg 9 + xmlns="http://www.w3.org/2000/svg" 10 + class="text-accent-600 dark:text-accent-400" 11 + viewBox="0 0 24 24" 12 + fill="none" 13 + stroke="currentColor" 14 + stroke-width="2" 15 + stroke-linecap="round" 16 + stroke-linejoin="round" 17 + ><path d="m22 11-1.296-1.296a2.4 2.4 0 0 0-3.408 0L11 16" /><path 18 + d="M4 8a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2" 19 + /><circle cx="13" cy="7" r="1" fill="currentColor" /><rect 20 + x="8" 21 + y="2" 22 + width="14" 23 + height="14" 24 + rx="2" 25 + /></svg 26 + > 27 + 28 + Bluesky media 29 + </Button>
+3 -1
src/lib/cards/BlueskyMediaCard/index.ts
··· 1 1 import type { CardDefinition } from '../types'; 2 2 import BlueskyMediaCard from './BlueskyMediaCard.svelte'; 3 3 import CreateBlueskyMediaCardModal from './CreateBlueskyMediaCardModal.svelte'; 4 + import SidebarItemBlueskyMediaCard from './SidebarItemBlueskyMediaCard.svelte'; 4 5 5 6 export const BlueskyMediaCardDefinition = { 6 7 type: 'blueskyMedia', 7 8 contentComponent: BlueskyMediaCard, 8 9 createNew: (card) => {}, 9 10 creationModalComponent: CreateBlueskyMediaCardModal, 10 - sidebarButtonText: 'Bluesky Media' 11 + sidebarButtonText: 'Bluesky Media', 12 + sidebarComponent: SidebarItemBlueskyMediaCard 11 13 } as CardDefinition & { type: 'blueskyMedia' };
-1
src/lib/cards/SectionCard/index.ts
··· 20 20 card.mobileW = COLUMNS; 21 21 }, 22 22 23 - sidebarButtonText: 'Section Headline', 24 23 defaultColor: 'transparent', 25 24 maxH: 1, 26 25 canResize: false
+14 -7
src/lib/cards/SpecialCards/UpdatedBlentos/UpdatedBlentosCard.svelte
··· 2 2 import type { ContentComponentProps } from '$lib/cards/types'; 3 3 import { getAdditionalUserData } from '$lib/website/context'; 4 4 import type { ProfileViewDetailed } from '@atproto/api/dist/client/types/app/bsky/actor/defs'; 5 - import { onMount } from 'svelte'; 6 5 7 6 let { item }: ContentComponentProps = $props(); 8 7 9 8 const data = getAdditionalUserData(); 10 9 // svelte-ignore state_referenced_locally 11 10 const profiles = data[item.cardType] as ProfileViewDetailed[]; 11 + 12 + $inspect(profiles); 12 13 </script> 13 14 14 - <div class="h-full flex flex-col"> 15 - <div class="text-2xl font-bold px-4 py-2">Recently updated blentos</div> 16 - <div class="flex grow max-w-full items-center gap-4 overflow-x-scroll overflow-y-hidden px-4"> 15 + <div class="flex h-full flex-col"> 16 + <div class="px-4 py-2 text-2xl font-bold">Recently updated blentos</div> 17 + <div class="flex max-w-full grow items-center gap-4 overflow-x-scroll overflow-y-hidden px-4"> 17 18 {#each profiles as profile} 18 19 <a 19 20 href="/{profile.handle}" 20 - class="bg-base-100 dark:bg-base-800 hover:bg-base-200 dark:hover:bg-base-700 flex h-52 w-44 min-w-44 flex-col items-center justify-center gap-2 rounded-xl transition-colors duration-150 p-2 accent:bg-accent-200/30 accent:hover:bg-accent-200/50" 21 + class="bg-base-100 dark:bg-base-800 hover:bg-base-200 dark:hover:bg-base-700 accent:bg-accent-200/30 accent:hover:bg-accent-200/50 flex h-52 w-44 min-w-44 flex-col items-center justify-center gap-2 rounded-xl p-2 transition-colors duration-150" 21 22 target="_blank" 22 23 > 23 - <img src={profile.avatar} class="aspect-square size-28 rounded-full" alt="" /> 24 - <div class="line-clamp-1 text-md font-bold text-center">{profile.displayName || profile.handle}</div> 24 + <img 25 + src={profile.avatar} 26 + class="bg-base-200 dark:bg-base-700 accent:bg-accent-300 aspect-square size-28 rounded-full" 27 + alt="" 28 + /> 29 + <div class="text-md line-clamp-1 text-center font-bold"> 30 + {profile.displayName || profile.handle} 31 + </div> 25 32 </a> 26 33 {/each} 27 34 </div>
+45 -25
src/lib/website/EditableWebsite.svelte
··· 779 779 variant="ghost" 780 780 class="backdrop-blur-none" 781 781 onclick={() => { 782 + newCard('section'); 783 + }} 784 + > 785 + <svg 786 + xmlns="http://www.w3.org/2000/svg" 787 + viewBox="0 0 24 24" 788 + fill="none" 789 + stroke="currentColor" 790 + stroke-width="2" 791 + stroke-linecap="round" 792 + stroke-linejoin="round" 793 + ><path d="M6 12h12" /><path d="M6 20V4" /><path d="M18 20V4" /></svg 794 + > 795 + </Button> 796 + 797 + <Button 798 + size="iconLg" 799 + variant="ghost" 800 + class="backdrop-blur-none" 801 + onclick={() => { 782 802 newCard('text'); 783 803 }} 784 804 > ··· 788 808 stroke="currentColor" 789 809 stroke-linecap="round" 790 810 stroke-linejoin="round" 791 - stroke-width="1.5" 811 + stroke-width="2" 792 812 d="m15 16l2.536-7.328a1.02 1.02 1 0 1 1.928 0L22 16m-6.303-2h5.606M2 16l4.039-9.69a.5.5 0 0 1 .923 0L11 16m-7.696-3h6.392" 793 813 /></svg 794 814 > ··· 809 829 xmlns="http://www.w3.org/2000/svg" 810 830 fill="none" 811 831 viewBox="-2 -2 28 28" 812 - stroke-width="1.5" 832 + stroke-width="2" 813 833 stroke="currentColor" 814 834 > 815 835 <path ··· 837 857 xmlns="http://www.w3.org/2000/svg" 838 858 fill="none" 839 859 viewBox="0 0 24 24" 840 - stroke-width="1.5" 860 + stroke-width="2" 841 861 stroke="currentColor" 842 862 class="size-6" 843 863 > ··· 858 878 xmlns="http://www.w3.org/2000/svg" 859 879 fill="none" 860 880 viewBox="0 0 24 24" 861 - stroke-width="1.5" 881 + stroke-width="2" 862 882 stroke="currentColor" 863 883 > 864 884 <path ··· 870 890 </Button> 871 891 872 892 {#if dev} 873 - <Button 874 - size="iconLg" 875 - variant="ghost" 876 - class="backdrop-blur-none" 877 - onclick={() => { 878 - videoInputRef?.click(); 879 - }} 880 - > 881 - <svg 882 - xmlns="http://www.w3.org/2000/svg" 883 - fill="none" 884 - viewBox="0 0 24 24" 885 - stroke-width="1.5" 886 - stroke="currentColor" 893 + <Button 894 + size="iconLg" 895 + variant="ghost" 896 + class="backdrop-blur-none" 897 + onclick={() => { 898 + videoInputRef?.click(); 899 + }} 887 900 > 888 - <path 889 - stroke-linecap="round" 890 - stroke-linejoin="round" 891 - d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" 892 - /> 893 - </svg> 894 - </Button> 901 + <svg 902 + xmlns="http://www.w3.org/2000/svg" 903 + fill="none" 904 + viewBox="0 0 24 24" 905 + stroke-width="1.5" 906 + stroke="currentColor" 907 + > 908 + <path 909 + stroke-linecap="round" 910 + stroke-linejoin="round" 911 + d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" 912 + /> 913 + </svg> 914 + </Button> 895 915 {/if} 896 916 897 917 <Button