your personal website on atproto - mirror blento.app

Merge pull request #57 from flo-bit/theme-colors

add theme colors selection

authored by Florian and committed by GitHub 985b5df9 77a880cb

+274 -52
+9 -7
docs/Beta.md
··· 3 3 - site.standard 4 4 - move description to markdownDescription and set description as text only 5 5 6 + - allow editing on mobile 7 + 8 + - get automatic layout for mobile if only edited on desktop (and vice versa) 9 + 10 + - add cards in middle of current position (both mobile and desktop version) 11 + 12 + - show nsfw warnings 13 + 6 14 - big button card 7 15 8 16 - card with big call to action button 9 17 10 - - link card allow changing favicon, og image (+ hide favicon) 11 - 12 18 - video card? 13 19 14 20 - allow setting base and accent color 15 21 16 22 - ask to fill with some default cards on page creation 17 23 18 - - share button (copy share link to blento, maybe post to bluesky?) 19 - 20 - - add icons to "change card to..." popover 21 - 22 24 - when adding images try to add them in a size that best fits aspect ratio 23 25 24 26 - onboarding 25 27 26 - - show alert when user tries to close window with unsaved changes 28 + - fix invalid handle thing
+12 -7
docs/CardIdeas.md
··· 3 3 ## media 4 4 5 5 - general video card 6 - - inline youtube video 6 + - [x] inline youtube video 7 7 - cartoons: aka https://www.opendoodles.com/ 8 8 - excalidraw (/svg card) 9 9 - latest blog post (e.g. leaflet) 10 10 - fake 3d image (with depth map) 11 - - fluid text effect (https://flo-bit.dev/projects/fluid-text-effect/) 12 - - gifs 13 - - little drawing app 11 + - [x] fluid text effect (https://flo-bit.dev/projects/fluid-text-effect/) 12 + - [x] gifs 13 + - [x] little drawing app 14 14 - css voxel art 15 15 - 3d model 16 + - spotify or apple music playlist 16 17 17 18 ## social accounts 18 19 19 20 - instagram card (showing follow button, follower count, latest posts) 20 - - github card (showing activity grid) 21 + - [x] github card (showing activity grid) 21 22 - bluesky account card (showing follow button, follower count, avatar, name, cover image) 22 23 - youtube channel card (showing channel name, latest videos, follow button?) 23 24 - bluesky posts workcloud ··· 40 41 - teal.fm 41 42 - [x] last played songs 42 43 - tangled.sh 44 + - pinned repos 45 + - activity heatmap? 43 46 - popfeed.social 44 47 - reading goal 45 48 - [x] latest ratings 46 49 - lists 47 - - smokesignal.events (https://pdsls.dev/at://did:plc:xbtmt2zjwlrfegqvch7fboei/events.smokesignal.calendar.event/3ltn2qrxf3626) 48 - - statusphere.xyz (https://googlefonts.github.io/noto-emoji-animation/, https://gist.github.com/sanjacob/a0ccdf6d88f15bf158d8895090722d14) 50 + - smokesignal.events 51 + - [x] specific event 52 + - all future events i'm hosting/attending 53 + - [x] statusphere.xyz (TODO: assing to specific record) 49 54 - goals.garden 50 55 - flashes.blue (https://pdsls.dev/at://did:plc:aytgljyikzbtgrnac2u4ccft/blue.flashes.actor.portfolio, https://app.flashes.blue/profile/j4ck.xyz) 51 56 - room: flo-bit.dev/room
+101
src/lib/components/select-theme/SelectTheme.svelte
··· 1 + <script lang="ts"> 2 + import { Paragraph } from '@foxui/core'; 3 + import { ColorSelect } from '@foxui/colors'; 4 + 5 + let accentColors = [ 6 + { class: 'text-red-500', label: 'red' }, 7 + { class: 'text-orange-500', label: 'orange' }, 8 + { class: 'text-amber-500', label: 'amber' }, 9 + { class: 'text-yellow-500', label: 'yellow' }, 10 + { class: 'text-lime-500', label: 'lime' }, 11 + { class: 'text-green-500', label: 'green' }, 12 + { class: 'text-emerald-500', label: 'emerald' }, 13 + { class: 'text-teal-500', label: 'teal' }, 14 + { class: 'text-cyan-500', label: 'cyan' }, 15 + { class: 'text-sky-500', label: 'sky' }, 16 + { class: 'text-blue-500', label: 'blue' }, 17 + { class: 'text-indigo-500', label: 'indigo' }, 18 + { class: 'text-violet-500', label: 'violet' }, 19 + { class: 'text-purple-500', label: 'purple' }, 20 + { class: 'text-fuchsia-500', label: 'fuchsia' }, 21 + { class: 'text-pink-500', label: 'pink' }, 22 + { class: 'text-rose-500', label: 'rose' } 23 + ]; 24 + 25 + let baseColors = [ 26 + { class: 'text-gray-500', label: 'gray' }, 27 + { class: 'text-stone-500', label: 'stone' }, 28 + { class: 'text-zinc-500', label: 'zinc' }, 29 + { class: 'text-neutral-500', label: 'neutral' }, 30 + { class: 'text-slate-500', label: 'slate' } 31 + ]; 32 + 33 + let { 34 + accentColor = $bindable('pink'), 35 + baseColor = $bindable('stone'), 36 + selectAccentColor = true, 37 + selectBaseColor = true, 38 + onchanged 39 + }: { 40 + accentColor?: string; 41 + baseColor?: string; 42 + selectAccentColor?: boolean; 43 + selectBaseColor?: boolean; 44 + onchanged?: (accentColor: string, baseColor: string) => void; 45 + } = $props(); 46 + 47 + let selectedAccentColor = $derived( 48 + accentColors.find((c) => c.label === accentColor) ?? accentColors[15] 49 + ); 50 + 51 + let selectedBaseColor = $derived(baseColors.find((c) => c.label === baseColor) ?? baseColors[1]); 52 + </script> 53 + 54 + {#if selectAccentColor} 55 + <Paragraph class="mb-2">Accent Color</Paragraph> 56 + <ColorSelect 57 + selected={selectedAccentColor} 58 + colors={accentColors} 59 + onselected={(color, previous) => { 60 + if (typeof previous === 'string' || typeof color === 'string') { 61 + return; 62 + } 63 + 64 + document.documentElement.classList.remove(previous.label.toLowerCase()); 65 + document.documentElement.classList.add(color.label.toLowerCase()); 66 + 67 + accentColor = color.label; 68 + 69 + window.dispatchEvent( 70 + new CustomEvent('theme-changed', { detail: { accentColor: color.label } }) 71 + ); 72 + 73 + onchanged?.(accentColor, baseColor); 74 + }} 75 + class="w-64" 76 + /> 77 + {/if} 78 + 79 + {#if selectBaseColor} 80 + <Paragraph class="mt-4 mb-2">Base Color</Paragraph> 81 + <ColorSelect 82 + selected={selectedBaseColor} 83 + colors={baseColors} 84 + onselected={(color, previous) => { 85 + if (typeof previous === 'string' || typeof color === 'string') { 86 + return; 87 + } 88 + 89 + document.documentElement.classList.remove(previous.label.toLowerCase()); 90 + document.documentElement.classList.add(color.label.toLowerCase()); 91 + 92 + baseColor = color.label; 93 + 94 + window.dispatchEvent( 95 + new CustomEvent('theme-changed', { detail: { baseColor: color.label } }) 96 + ); 97 + 98 + onchanged?.(accentColor, baseColor); 99 + }} 100 + /> 101 + {/if}
+43
src/lib/components/select-theme/SelectThemePopover.svelte
··· 1 + <script lang="ts"> 2 + import { buttonVariants, Popover, cn } from '@foxui/core'; 3 + import SelectTheme from './SelectTheme.svelte'; 4 + 5 + let { 6 + accentColor = $bindable('pink'), 7 + baseColor = $bindable('stone'), 8 + selectAccentColor = true, 9 + selectBaseColor = true, 10 + onchanged 11 + }: { 12 + accentColor?: string; 13 + baseColor?: string; 14 + selectAccentColor?: boolean; 15 + selectBaseColor?: boolean; 16 + onchanged?: (accentColor: string, baseColor: string) => void; 17 + } = $props(); 18 + </script> 19 + 20 + <Popover> 21 + {#snippet child({ props })} 22 + <button 23 + {...props} 24 + class={cn( 25 + buttonVariants({ variant: 'link', size: 'default' }), 26 + 'flex cursor-pointer items-center gap-0 -space-x-2 backdrop-blur-none' 27 + )} 28 + > 29 + {#if selectAccentColor} 30 + <div 31 + class=" from-accent-500 to-accent-600 border-accent-700 dark:border-accent-400 z-10 size-6 rounded-full border bg-linear-to-b" 32 + ></div> 33 + {/if} 34 + 35 + {#if selectBaseColor} 36 + <div 37 + class=" from-base-500 to-base-600 border-base-700 dark:border-base-400 size-6 rounded-full border bg-linear-to-b" 38 + ></div> 39 + {/if} 40 + </button> 41 + {/snippet} 42 + <SelectTheme bind:accentColor bind:baseColor {selectAccentColor} {selectBaseColor} {onchanged} /> 43 + </Popover>
+2
src/lib/components/select-theme/index.ts
··· 1 + export { default as SelectTheme } from './SelectTheme.svelte'; 2 + export { default as SelectThemePopover } from './SelectThemePopover.svelte';
+4
src/lib/types.ts
··· 51 51 52 52 // 'side' (default on desktop) or 'top' (always top like mobile view) 53 53 profilePosition?: 'side' | 'top'; 54 + 55 + // theme colors 56 + accentColor?: string; 57 + baseColor?: string; 54 58 }; 55 59 }; 56 60 profile: AppBskyActorDefs.ProfileViewDetailed;
+19 -4
src/lib/website/EditableProfile.svelte
··· 5 5 import MarkdownTextEditor from '$lib/components/MarkdownTextEditor.svelte'; 6 6 import { Avatar, Button } from '@foxui/core'; 7 7 import { getIsMobile } from './context'; 8 - import type { Editor } from '@tiptap/core'; 9 8 import MadeWithBlento from './MadeWithBlento.svelte'; 9 + import { SelectThemePopover } from '$lib/components/select-theme'; 10 10 11 11 let { data = $bindable(), hideBlento = false }: { data: WebsiteData; hideBlento?: boolean } = 12 12 $props(); 13 + 14 + let accentColor = $derived(data.publication?.preferences?.accentColor ?? 'pink'); 15 + let baseColor = $derived(data.publication?.preferences?.baseColor ?? 'stone'); 16 + 17 + function updateTheme(newAccent: string, newBase: string) { 18 + data.publication.preferences ??= {}; 19 + data.publication.preferences.accentColor = newAccent; 20 + data.publication.preferences.baseColor = newBase; 21 + data = { ...data }; 22 + } 13 23 14 24 let profilePosition = $derived(getProfilePosition(data)); 15 25 ··· 130 140 {/if} 131 141 </Button> 132 142 {/if} 143 + 144 + <!-- Theme selection --> 145 + <SelectThemePopover 146 + {accentColor} 147 + {baseColor} 148 + onchanged={(newAccent, newBase) => updateTheme(newAccent, newBase)} 149 + /> 133 150 </div> 134 151 135 152 <div 136 153 class={[ 137 - 'flex flex-col gap-4 pt-16 pb-8', 154 + 'flex flex-col gap-4 pt-16 pb-4', 138 155 profilePosition === 'side' && '@5xl/wrapper:h-screen @5xl/wrapper:pt-24' 139 156 ]} 140 157 > ··· 215 232 /> 216 233 {/if} 217 234 </div> 218 - 219 - <div class={['h-10.5 w-1', profilePosition === 'side' && '@5xl/wrapper:hidden']}></div> 220 235 221 236 {#if !hideBlento} 222 237 <MadeWithBlento class="hidden {profilePosition === 'side' && '@5xl/wrapper:block'}" />
+48 -30
src/lib/website/EditableWebsite.svelte
··· 29 29 import BaseEditingCard from '../cards/BaseCard/BaseEditingCard.svelte'; 30 30 import Context from './Context.svelte'; 31 31 import Head from './Head.svelte'; 32 - import { compressImage } from '../helper'; 33 32 import Account from './Account.svelte'; 33 + import { SelectThemePopover } from '$lib/components/select-theme'; 34 34 import EditBar from './EditBar.svelte'; 35 35 import SaveModal from './SaveModal.svelte'; 36 36 import FloatingEditButton from './FloatingEditButton.svelte'; ··· 45 45 46 46 // Check if floating login button will be visible (to hide MadeWithBlento) 47 47 const showLoginOnEditPage = $derived(!user.isInitializing && !user.isLoggedIn); 48 + 49 + let accentColor = $derived(data.publication?.preferences?.accentColor ?? 'pink'); 50 + let baseColor = $derived(data.publication?.preferences?.baseColor ?? 'stone'); 51 + 52 + function updateTheme(newAccent: string, newBase: string) { 53 + data.publication.preferences ??= {}; 54 + data.publication.preferences.accentColor = newAccent; 55 + data.publication.preferences.baseColor = newBase; 56 + data = { ...data }; 57 + } 48 58 49 59 let imageDragOver = $state(false); 50 60 ··· 572 582 favicon={data.profile.avatar ?? null} 573 583 title={getName(data)} 574 584 image={'/' + data.handle + '/og.png'} 585 + accentColor={data.publication?.preferences?.accentColor} 586 + baseColor={data.publication?.preferences?.baseColor} 575 587 /> 576 588 577 589 <Account {data} /> ··· 631 643 ]} 632 644 > 633 645 {#if getHideProfileSection(data)} 634 - <Button 635 - size="icon" 636 - variant="ghost" 637 - onclick={() => { 638 - data.publication.preferences ??= {}; 639 - data.publication.preferences.hideProfileSection = false; 640 - data = { ...data }; 641 - }} 642 - class="pointer-events-auto absolute top-2 left-2 z-20" 643 - > 644 - <svg 645 - xmlns="http://www.w3.org/2000/svg" 646 - fill="none" 647 - viewBox="0 0 24 24" 648 - stroke-width="1.5" 649 - stroke="currentColor" 650 - class="size-6" 646 + <div class="pointer-events-auto absolute top-2 left-2 z-20 flex gap-2"> 647 + <Button 648 + size="icon" 649 + variant="ghost" 650 + onclick={() => { 651 + data.publication.preferences ??= {}; 652 + data.publication.preferences.hideProfileSection = false; 653 + data = { ...data }; 654 + }} 651 655 > 652 - <path 653 - stroke-linecap="round" 654 - stroke-linejoin="round" 655 - d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" 656 - /> 657 - <path 658 - stroke-linecap="round" 659 - stroke-linejoin="round" 660 - d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" 661 - /> 662 - </svg> 663 - </Button> 656 + <svg 657 + xmlns="http://www.w3.org/2000/svg" 658 + fill="none" 659 + viewBox="0 0 24 24" 660 + stroke-width="1.5" 661 + stroke="currentColor" 662 + class="size-6" 663 + > 664 + <path 665 + stroke-linecap="round" 666 + stroke-linejoin="round" 667 + d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" 668 + /> 669 + <path 670 + stroke-linecap="round" 671 + stroke-linejoin="round" 672 + d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" 673 + /> 674 + </svg> 675 + </Button> 676 + <SelectThemePopover 677 + {accentColor} 678 + {baseColor} 679 + onchanged={(newAccent, newBase) => updateTheme(newAccent, newBase)} 680 + /> 681 + </div> 664 682 {/if} 665 683 <div class="pointer-events-none"></div> 666 684 <!-- svelte-ignore a11y_no_static_element_interactions -->
+15 -3
src/lib/website/Head.svelte
··· 1 1 <script lang="ts"> 2 + import ThemeScript from './ThemeScript.svelte'; 3 + 2 4 let { 3 5 favicon, 4 6 title, 5 7 image, 6 - description 7 - }: { favicon: string | null; title: string | null; image?: string; description?: string } = 8 - $props(); 8 + description, 9 + accentColor, 10 + baseColor 11 + }: { 12 + favicon: string | null; 13 + title: string | null; 14 + image?: string; 15 + description?: string; 16 + accentColor?: string; 17 + baseColor?: string; 18 + } = $props(); 9 19 </script> 20 + 21 + <ThemeScript {accentColor} {baseColor} /> 10 22 11 23 <svelte:head> 12 24 {#if favicon}
+1 -1
src/lib/website/Profile.svelte
··· 34 34 > 35 35 <div 36 36 class={[ 37 - 'flex flex-col gap-4 pt-16 pb-8', 37 + 'flex flex-col gap-4 pt-16 pb-4', 38 38 profilePosition === 'side' && '@5xl/wrapper:h-screen @5xl/wrapper:pt-24' 39 39 ]} 40 40 >
+18
src/lib/website/ThemeScript.svelte
··· 1 + <script lang="ts"> 2 + let { 3 + accentColor = 'pink', 4 + baseColor = 'stone' 5 + }: { 6 + accentColor?: string; 7 + baseColor?: string; 8 + } = $props(); 9 + 10 + let script = $derived( 11 + `<script>(function(){document.documentElement.classList.add(${JSON.stringify(accentColor)},${JSON.stringify(baseColor)});})();<` + 12 + '/script>' 13 + ); 14 + </script> 15 + 16 + <svelte:head> 17 + {@html script} 18 + </svelte:head>
+2
src/lib/website/Website.svelte
··· 60 60 title={getName(data)} 61 61 image={'/' + data.handle + '/og.png'} 62 62 description={getDescription(data)} 63 + accentColor={data.publication?.preferences?.accentColor} 64 + baseColor={data.publication?.preferences?.baseColor} 63 65 /> 64 66 65 67 <Context {data}>