atmo.rsvp
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

cooler og images

Florian b1b46dec c8e9f0a5

+789 -3
+1 -1
src/routes/p/[actor]/e/[rkey]/og.png/+server.ts
··· 49 49 } 50 50 return new ImageResponse( 51 51 EventOgImage, 52 - { width: 1200, height: 630, debug: false }, 52 + { width: 1200, height: 630, debug: false, format: 'png' }, 53 53 { 54 54 name: eventData.name, 55 55 dateStr,
+30 -2
src/routes/p/[actor]/e/[rkey]/og.png/EventOgImage.svelte
··· 1 1 <script lang="ts"> 2 - import Avatar from 'svelte-boring-avatars'; 2 + import Avatar from './avatars'; 3 3 4 4 let { 5 5 name, ··· 14 14 } = $props(); 15 15 </script> 16 16 17 - <div class="flex h-full w-full p-0" style="background: radial-gradient(ellipse 100% 80% at 30% 120%, #14b8a6 0%, #042f2e 25%, #090b0c 65%);"> 17 + <div 18 + class="relative flex h-full w-full p-0 bg-black" 19 + > 20 + {#if thumbnailUrl} 21 + <img 22 + src={thumbnailUrl} 23 + alt="" 24 + class="absolute inset-0 w-full h-full bg-black" 25 + style="object-fit: cover; filter: blur(64px);" 26 + width="400" 27 + height="210" 28 + /> 29 + {:else} 30 + <Avatar 31 + class="absolute inset-0 h-full w-full scale-400" 32 + size={300} 33 + name={rkey} 34 + variant="marble" 35 + colors={['#92A1C6', '#146A7C', '#F0AB3D', '#C271B4', '#C20D90']} 36 + square 37 + /> 38 + {/if} 39 + 40 + <div 41 + class="absolute inset-0" 42 + style="background: linear-gradient(190deg, rgba(0,0,0,1) 0%, rgba(0,0,0,0.9) 50%, rgba(0,0,0,0.3) 100%);" 43 + > 44 + . 45 + </div> 18 46 <div class="flex min-w-0 flex-1 flex-col justify-center p-12"> 19 47 <h1 20 48 class="text-5xl leading-tight font-bold text-neutral-50"
+9
src/routes/p/[actor]/e/[rkey]/og.png/avatars/index.ts
··· 1 + import Avatar from './lib/components/Avatar.svelte'; 2 + export default Avatar; 3 + 4 + export { default as AvatarBauhaus } from './lib/components/AvatarBauhaus.svelte'; 5 + export { default as AvatarBeam } from './lib/components/AvatarBeam.svelte'; 6 + export { default as AvatarMarble } from './lib/components/AvatarMarble.svelte'; 7 + export { default as AvatarPixel } from './lib/components/AvatarPixel.svelte'; 8 + export { default as AvatarRing } from './lib/components/AvatarRing.svelte'; 9 + export { default as AvatarSunset } from './lib/components/AvatarSunset.svelte';
+61
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/Avatar.svelte
··· 1 + <script lang="ts"> 2 + import AvatarBauhaus from './AvatarBauhaus.svelte'; 3 + import AvatarBeam from './AvatarBeam.svelte'; 4 + import AvatarMarble from './AvatarMarble.svelte'; 5 + import AvatarPixel from './AvatarPixel.svelte'; 6 + import AvatarRing from './AvatarRing.svelte'; 7 + import AvatarSunset from './AvatarSunset.svelte'; 8 + import { DEFAULTS } from './CONSTANTS'; 9 + const variants = ['pixel', 'bauhaus', 'ring', 'beam', 'sunset', 'marble'] as const; 10 + const deprecatedVariants = { geometric: 'beam', abstract: 'bauhaus' } as const; 11 + 12 + type DeprecatedVariantsKeys = keyof typeof deprecatedVariants; 13 + type Variants = typeof variants; 14 + 15 + export let variant: Variants[number] = 'marble'; 16 + export let colors = DEFAULTS.colors; 17 + export let name = DEFAULTS.name; 18 + export let square = DEFAULTS.square; 19 + export let size = DEFAULTS.size; 20 + let className = ''; 21 + 22 + let props = { colors, name, square, size }; 23 + 24 + let finalVariant: Variants[number] = 'bauhaus'; 25 + 26 + const avatars = { 27 + bauhaus: AvatarBauhaus, 28 + beam: AvatarBeam, 29 + ring: AvatarRing, 30 + marble: AvatarMarble, 31 + pixel: AvatarPixel, 32 + sunset: AvatarSunset 33 + }; 34 + 35 + const checkedVariant = (_variant: Variants[number]) => { 36 + if (Object.keys(deprecatedVariants).includes(_variant)) { 37 + finalVariant = deprecatedVariants[_variant as DeprecatedVariantsKeys]; 38 + return; 39 + } 40 + 41 + if (variants.includes(_variant)) { 42 + finalVariant = _variant; 43 + return; 44 + } 45 + 46 + finalVariant = 'marble'; 47 + }; 48 + 49 + export { className as class }; 50 + 51 + // Reacts to prop changes creating a new object 52 + // Thus, rerendering the Child beacuse of the key-block 53 + $: props = { colors, name, square, size, class: className }; 54 + $: checkedVariant(variant); 55 + </script> 56 + 57 + {#key props} 58 + {#key finalVariant} 59 + <svelte:component this={avatars[finalVariant]} {...props} /> 60 + {/key} 61 + {/key}
+90
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarBauhaus.svelte
··· 1 + <script lang="ts"> 2 + import { getNumber, getUnit, getRandomColor, getBoolean } from '../utils'; 3 + import { DEFAULTS } from './CONSTANTS'; 4 + 5 + const ELEMENTS = 4; 6 + const SIZE = 80; 7 + function generateColors(name: string, colors: string[]) { 8 + const numFromName = getNumber(name); 9 + const range = colors && colors.length; 10 + 11 + const elementsProperties = Array.from({ length: ELEMENTS }, (_, i) => ({ 12 + color: getRandomColor(numFromName + i, colors, range), 13 + translateX: getUnit(numFromName * (i + 1), SIZE / 2 - (i + 17), 1), 14 + translateY: getUnit(numFromName * (i + 1), SIZE / 2 - (i + 17), 2), 15 + rotate: getUnit(numFromName * (i + 1), 360), 16 + isSquare: getBoolean(numFromName, 2), 17 + })); 18 + 19 + return elementsProperties; 20 + } 21 + 22 + export let size = DEFAULTS.size; 23 + export let name = DEFAULTS.name; 24 + export let square = DEFAULTS.square; 25 + export let colors = DEFAULTS.colors; 26 + 27 + const properties = generateColors(name, colors); 28 + 29 + const maskId = 'mask__bauhaus'; 30 + </script> 31 + 32 + <svg 33 + viewBox={'0 0 ' + SIZE + ' ' + SIZE} 34 + fill="none" 35 + xmlns="http://www.w3.org/2000/svg" 36 + width={size} 37 + height={size} 38 + data-testid="avatar-bauhaus" 39 + > 40 + <mask id={maskId} maskUnits="userSpaceOnUse" x={0} y={0} width={SIZE} height={SIZE}> 41 + <rect width={SIZE} height={SIZE} rx={square ? undefined : SIZE * 2} fill="white" /> 42 + </mask> 43 + <g mask="url(#{maskId})"> 44 + <rect width={SIZE} height={SIZE} fill={properties[0].color} /> 45 + <rect 46 + x={(SIZE - 60) / 2} 47 + y={(SIZE - 20) / 2} 48 + width={SIZE} 49 + height={properties[1].isSquare ? SIZE : SIZE / 8} 50 + fill={properties[1].color} 51 + transform={'translate(' + 52 + properties[1].translateX + 53 + ' ' + 54 + properties[1].translateY + 55 + ') rotate(' + 56 + properties[1].rotate + 57 + ' ' + 58 + SIZE / 2 + 59 + ' ' + 60 + SIZE / 2 + 61 + ')'} 62 + /> 63 + <circle 64 + cx={SIZE / 2} 65 + cy={SIZE / 2} 66 + fill={properties[2].color} 67 + r={SIZE / 5} 68 + transform={'translate(' + properties[2].translateX + ' ' + properties[2].translateY + ')'} 69 + /> 70 + <line 71 + x1={0} 72 + y1={SIZE / 2} 73 + x2={SIZE} 74 + y2={SIZE / 2} 75 + stroke-width={2} 76 + stroke={properties[3].color} 77 + transform={'translate(' + 78 + properties[3].translateX + 79 + ' ' + 80 + properties[3].translateY + 81 + ') rotate(' + 82 + properties[3].rotate + 83 + ' ' + 84 + SIZE / 2 + 85 + ' ' + 86 + SIZE / 2 + 87 + ')'} 88 + /> 89 + </g> 90 + </svg>
+126
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarBeam.svelte
··· 1 + <script lang="ts"> 2 + import { getBoolean, getContrast, getNumber, getRandId, getRandomColor, getUnit } from '../utils'; 3 + import { DEFAULTS } from './CONSTANTS'; 4 + 5 + const SIZE = 36; 6 + 7 + function generateData(name: string, colors: string[]) { 8 + const numFromName = getNumber(name); 9 + const range = colors && colors.length; 10 + const wrapperColor = getRandomColor(numFromName, colors, range); 11 + const preTranslateX = getUnit(numFromName, 10, 1); 12 + const wrapperTranslateX = preTranslateX < 5 ? preTranslateX + SIZE / 9 : preTranslateX; 13 + const preTranslateY = getUnit(numFromName, 10, 2); 14 + const wrapperTranslateY = preTranslateY < 5 ? preTranslateY + SIZE / 9 : preTranslateY; 15 + 16 + const data = { 17 + wrapperColor: wrapperColor, 18 + faceColor: getContrast(wrapperColor), 19 + backgroundColor: getRandomColor(numFromName + 13, colors, range), 20 + wrapperTranslateX: wrapperTranslateX, 21 + wrapperTranslateY: wrapperTranslateY, 22 + wrapperRotate: getUnit(numFromName, 360), 23 + wrapperScale: 1 + getUnit(numFromName, SIZE / 12) / 10, 24 + isMouthOpen: getBoolean(numFromName, 2), 25 + isCircle: getBoolean(numFromName, 1), 26 + eyeSpread: getUnit(numFromName, 5), 27 + mouthSpread: getUnit(numFromName, 3), 28 + faceRotate: getUnit(numFromName, 10, 3), 29 + faceTranslateX: 30 + wrapperTranslateX > SIZE / 6 ? wrapperTranslateX / 2 : getUnit(numFromName, 8, 1), 31 + faceTranslateY: 32 + wrapperTranslateY > SIZE / 6 ? wrapperTranslateY / 2 : getUnit(numFromName, 7, 2), 33 + }; 34 + 35 + return data; 36 + } 37 + 38 + export let size = DEFAULTS.size; 39 + export let name = DEFAULTS.name; 40 + export let square = DEFAULTS.square; 41 + export let colors = DEFAULTS.colors; 42 + 43 + const data = generateData(name, colors); 44 + 45 + const maskId = getRandId('mask__beam'); 46 + </script> 47 + 48 + <svg 49 + viewBox={'0 0 ' + SIZE + ' ' + SIZE} 50 + fill="none" 51 + xmlns="http://www.w3.org/2000/svg" 52 + width={size} 53 + height={size} 54 + data-testid="avatar-beam" 55 + > 56 + <mask id={maskId} maskUnits="userSpaceOnUse" x={0} y={0} width={SIZE} height={SIZE}> 57 + <rect width={SIZE} height={SIZE} rx={square ? undefined : SIZE * 2} fill="white" /> 58 + </mask> 59 + <g mask="url(#{maskId})"> 60 + <rect width={SIZE} height={SIZE} fill={data.backgroundColor} /> 61 + <rect 62 + x="0" 63 + y="0" 64 + width={SIZE} 65 + height={SIZE} 66 + transform={'translate(' + 67 + data.wrapperTranslateX + 68 + ' ' + 69 + data.wrapperTranslateY + 70 + ') rotate(' + 71 + data.wrapperRotate + 72 + ' ' + 73 + SIZE / 2 + 74 + ' ' + 75 + SIZE / 2 + 76 + ') scale(' + 77 + data.wrapperScale + 78 + ')'} 79 + fill={data.wrapperColor} 80 + rx={data.isCircle ? SIZE : SIZE / 6} 81 + /> 82 + <g 83 + transform={'translate(' + 84 + data.faceTranslateX + 85 + ' ' + 86 + data.faceTranslateY + 87 + ') rotate(' + 88 + data.faceRotate + 89 + ' ' + 90 + SIZE / 2 + 91 + ' ' + 92 + SIZE / 2 + 93 + ')'} 94 + > 95 + {#if data.isMouthOpen} 96 + <path 97 + d={'M15 ' + (19 + data.mouthSpread) + 'c2 1 4 1 6 0'} 98 + stroke={data.faceColor} 99 + fill="none" 100 + stroke-linecap="round" 101 + /> 102 + {:else} 103 + <path d={'M13,' + (19 + data.mouthSpread) + ' a1,0.75 0 0,0 10,0'} fill={data.faceColor} /> 104 + {/if} 105 + 106 + <rect 107 + x={14 - data.eyeSpread} 108 + y={14} 109 + width={1.5} 110 + height={2} 111 + rx={1} 112 + stroke="none" 113 + fill={data.faceColor} 114 + /> 115 + <rect 116 + x={20 + data.eyeSpread} 117 + y={14} 118 + width={1.5} 119 + height={2} 120 + rx={1} 121 + stroke="none" 122 + fill={data.faceColor} 123 + /> 124 + </g> 125 + </g> 126 + </svg>
+96
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarMarble.svelte
··· 1 + <script lang="ts"> 2 + import { getNumber, getUnit, getRandomColor, getRandId } from '../utils'; 3 + import { DEFAULTS } from './CONSTANTS'; 4 + 5 + const ELEMENTS = 3; 6 + const SIZE = 80; 7 + 8 + function generateColors(name: string, colors: string[]) { 9 + const numFromName = getNumber(name); 10 + const range = colors && colors.length; 11 + 12 + const elementsProperties = Array.from({ length: ELEMENTS }, (_, i) => ({ 13 + color: getRandomColor(numFromName + i, colors, range), 14 + translateX: getUnit(numFromName * (i + 1), SIZE / 10, 1), 15 + translateY: getUnit(numFromName * (i + 1), SIZE / 10, 2), 16 + scale: 1.2 + getUnit(numFromName * (i + 1), SIZE / 20) / 10, 17 + rotate: getUnit(numFromName * (i + 1), 360, 1) 18 + })); 19 + 20 + return elementsProperties; 21 + } 22 + 23 + export let size = DEFAULTS.size; 24 + export let name = DEFAULTS.name; 25 + export let square = DEFAULTS.square; 26 + export let colors = DEFAULTS.colors; 27 + let className = ''; 28 + 29 + export { className as class }; 30 + 31 + const properties = generateColors(name, colors); 32 + 33 + const maskId = getRandId('mask__marble'); 34 + const filterId = getRandId('filter__marble'); 35 + </script> 36 + 37 + <svg 38 + viewBox={'0 0 ' + SIZE + ' ' + SIZE} 39 + fill="none" 40 + xmlns="http://www.w3.org/2000/svg" 41 + width={size} 42 + height={size} 43 + data-testid="avatar-marble" 44 + class={className} 45 + > 46 + <mask id={maskId} maskUnits="userSpaceOnUse" x={0} y={0} width={SIZE} height={SIZE}> 47 + <rect width={SIZE} height={SIZE} rx={square ? undefined : SIZE * 2} fill="white" /> 48 + </mask> 49 + <g mask="url(#{maskId})"> 50 + <rect width={SIZE} height={SIZE} rx="2" fill={properties[0].color} /> 51 + <path 52 + filter="url(#{filterId})" 53 + style="mix-blend-mode: overlay;" 54 + d="M32.414 59.35L50.376 70.5H72.5v-71H33.728L26.5 13.381l19.057 27.08L32.414 59.35z" 55 + fill={properties[1].color} 56 + transform={'translate(' + 57 + properties[1].translateX + 58 + ' ' + 59 + properties[1].translateY + 60 + ') rotate(' + 61 + properties[1].rotate + 62 + ' ' + 63 + SIZE / 2 + 64 + ' ' + 65 + SIZE / 2 + 66 + ') scale(' + 67 + properties[2].scale + 68 + ')'} 69 + /> 70 + <path 71 + filter="url(#{filterId})" 72 + d="M22.216 24L0 46.75l14.108 38.129L78 86l-3.081-59.276-22.378 4.005 12.972 20.186-23.35 27.395L22.215 24z" 73 + fill={properties[2].color} 74 + transform={'translate(' + 75 + properties[2].translateX + 76 + ' ' + 77 + properties[2].translateY + 78 + ') rotate(' + 79 + properties[2].rotate + 80 + ' ' + 81 + SIZE / 2 + 82 + ' ' + 83 + SIZE / 2 + 84 + ') scale(' + 85 + properties[2].scale + 86 + ')'} 87 + /> 88 + </g> 89 + <defs> 90 + <filter id={filterId} filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> 91 + <feFlood flood-opacity={0} result="BackgroundImageFix" /> 92 + <feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" /> 93 + <feGaussianBlur stdDeviation={7} result="effect1_foregroundBlur" /> 94 + </filter> 95 + </defs> 96 + </svg>
+114
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarPixel.svelte
··· 1 + <script lang="ts"> 2 + import { getNumber, getRandId, getRandomColor } from '../utils'; 3 + import { DEFAULTS } from './CONSTANTS'; 4 + 5 + const ELEMENTS = 64; 6 + const SIZE = 80; 7 + 8 + function generateColors(name: string, colors: string[]) { 9 + const numFromName = getNumber(name); 10 + const range = colors && colors.length; 11 + 12 + const elementsProperties = Array.from({ length: ELEMENTS }, (_, i) => ({ 13 + color: getRandomColor(numFromName % (i + 13), colors, range), 14 + })); 15 + 16 + return elementsProperties; 17 + } 18 + 19 + export let size = DEFAULTS.size; 20 + export let name = DEFAULTS.name; 21 + export let square = DEFAULTS.square; 22 + export let colors = DEFAULTS.colors; 23 + 24 + const properties = generateColors(name, colors); 25 + 26 + const maskId = getRandId('mask__pixel'); 27 + </script> 28 + 29 + <svg 30 + viewBox={'0 0 ' + SIZE + ' ' + SIZE} 31 + fill="none" 32 + xmlns="http://www.w3.org/2000/svg" 33 + width={size} 34 + height={size} 35 + data-testid="avatar-pixel" 36 + > 37 + <mask 38 + id={maskId} 39 + style:mask-type="alpha" 40 + maskUnits="userSpaceOnUse" 41 + x={0} 42 + y={0} 43 + width={SIZE} 44 + height={SIZE} 45 + > 46 + <rect width={SIZE} height={SIZE} rx={square ? undefined : SIZE * 2} fill="white" /> 47 + </mask> 48 + <g mask="url(#{maskId})"> 49 + <rect width={10} height={10} fill={properties[0].color} /> 50 + <rect x={20} width={10} height={10} fill={properties[1].color} /> 51 + <rect x={40} width={10} height={10} fill={properties[2].color} /> 52 + <rect x={60} width={10} height={10} fill={properties[3].color} /> 53 + <rect x={10} width={10} height={10} fill={properties[4].color} /> 54 + <rect x={30} width={10} height={10} fill={properties[5].color} /> 55 + <rect x={50} width={10} height={10} fill={properties[6].color} /> 56 + <rect x={70} width={10} height={10} fill={properties[7].color} /> 57 + <rect y={10} width={10} height={10} fill={properties[8].color} /> 58 + <rect y={20} width={10} height={10} fill={properties[9].color} /> 59 + <rect y={30} width={10} height={10} fill={properties[10].color} /> 60 + <rect y={40} width={10} height={10} fill={properties[11].color} /> 61 + <rect y={50} width={10} height={10} fill={properties[12].color} /> 62 + <rect y={60} width={10} height={10} fill={properties[13].color} /> 63 + <rect y={70} width={10} height={10} fill={properties[14].color} /> 64 + <rect x={20} y={10} width={10} height={10} fill={properties[15].color} /> 65 + <rect x={20} y={20} width={10} height={10} fill={properties[16].color} /> 66 + <rect x={20} y={30} width={10} height={10} fill={properties[17].color} /> 67 + <rect x={20} y={40} width={10} height={10} fill={properties[18].color} /> 68 + <rect x={20} y={50} width={10} height={10} fill={properties[19].color} /> 69 + <rect x={20} y={60} width={10} height={10} fill={properties[20].color} /> 70 + <rect x={20} y={70} width={10} height={10} fill={properties[21].color} /> 71 + <rect x={40} y={10} width={10} height={10} fill={properties[22].color} /> 72 + <rect x={40} y={20} width={10} height={10} fill={properties[23].color} /> 73 + <rect x={40} y={30} width={10} height={10} fill={properties[24].color} /> 74 + <rect x={40} y={40} width={10} height={10} fill={properties[25].color} /> 75 + <rect x={40} y={50} width={10} height={10} fill={properties[26].color} /> 76 + <rect x={40} y={60} width={10} height={10} fill={properties[27].color} /> 77 + <rect x={40} y={70} width={10} height={10} fill={properties[28].color} /> 78 + <rect x={60} y={10} width={10} height={10} fill={properties[29].color} /> 79 + <rect x={60} y={20} width={10} height={10} fill={properties[30].color} /> 80 + <rect x={60} y={30} width={10} height={10} fill={properties[31].color} /> 81 + <rect x={60} y={40} width={10} height={10} fill={properties[32].color} /> 82 + <rect x={60} y={50} width={10} height={10} fill={properties[33].color} /> 83 + <rect x={60} y={60} width={10} height={10} fill={properties[34].color} /> 84 + <rect x={60} y={70} width={10} height={10} fill={properties[35].color} /> 85 + <rect x={10} y={10} width={10} height={10} fill={properties[36].color} /> 86 + <rect x={10} y={20} width={10} height={10} fill={properties[37].color} /> 87 + <rect x={10} y={30} width={10} height={10} fill={properties[38].color} /> 88 + <rect x={10} y={40} width={10} height={10} fill={properties[39].color} /> 89 + <rect x={10} y={50} width={10} height={10} fill={properties[40].color} /> 90 + <rect x={10} y={60} width={10} height={10} fill={properties[41].color} /> 91 + <rect x={10} y={70} width={10} height={10} fill={properties[42].color} /> 92 + <rect x={30} y={10} width={10} height={10} fill={properties[43].color} /> 93 + <rect x={30} y={20} width={10} height={10} fill={properties[44].color} /> 94 + <rect x={30} y={30} width={10} height={10} fill={properties[45].color} /> 95 + <rect x={30} y={40} width={10} height={10} fill={properties[46].color} /> 96 + <rect x={30} y={50} width={10} height={10} fill={properties[47].color} /> 97 + <rect x={30} y={60} width={10} height={10} fill={properties[48].color} /> 98 + <rect x={30} y={70} width={10} height={10} fill={properties[49].color} /> 99 + <rect x={50} y={10} width={10} height={10} fill={properties[50].color} /> 100 + <rect x={50} y={20} width={10} height={10} fill={properties[51].color} /> 101 + <rect x={50} y={30} width={10} height={10} fill={properties[52].color} /> 102 + <rect x={50} y={40} width={10} height={10} fill={properties[53].color} /> 103 + <rect x={50} y={50} width={10} height={10} fill={properties[54].color} /> 104 + <rect x={50} y={60} width={10} height={10} fill={properties[55].color} /> 105 + <rect x={50} y={70} width={10} height={10} fill={properties[56].color} /> 106 + <rect x={70} y={10} width={10} height={10} fill={properties[57].color} /> 107 + <rect x={70} y={20} width={10} height={10} fill={properties[58].color} /> 108 + <rect x={70} y={30} width={10} height={10} fill={properties[59].color} /> 109 + <rect x={70} y={40} width={10} height={10} fill={properties[60].color} /> 110 + <rect x={70} y={50} width={10} height={10} fill={properties[61].color} /> 111 + <rect x={70} y={60} width={10} height={10} fill={properties[62].color} /> 112 + <rect x={70} y={70} width={10} height={10} fill={properties[63].color} /> 113 + </g> 114 + </svg>
+60
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarRing.svelte
··· 1 + <script lang="ts"> 2 + import { getNumber, getRandId, getRandomColor } from '../utils'; 3 + import { DEFAULTS } from './CONSTANTS'; 4 + 5 + const SIZE = 90; 6 + const COLORS = 5; 7 + 8 + function generateColors(colors: string[], name: string) { 9 + const numFromName = getNumber(name); 10 + const range = colors && colors.length; 11 + const colorsShuffle = Array.from({ length: COLORS }, (_, i) => 12 + getRandomColor(numFromName + (i + 1), colors, range) 13 + ); 14 + const iconColors = []; 15 + iconColors[0] = colorsShuffle[0]; 16 + iconColors[1] = colorsShuffle[1]; 17 + iconColors[2] = colorsShuffle[1]; 18 + iconColors[3] = colorsShuffle[2]; 19 + iconColors[4] = colorsShuffle[2]; 20 + iconColors[5] = colorsShuffle[3]; 21 + iconColors[6] = colorsShuffle[3]; 22 + iconColors[7] = colorsShuffle[0]; 23 + iconColors[8] = colorsShuffle[4]; 24 + 25 + return iconColors; 26 + } 27 + 28 + export let size = DEFAULTS.size; 29 + export let name = DEFAULTS.name; 30 + export let square = DEFAULTS.square; 31 + export let colors = DEFAULTS.colors; 32 + 33 + const cellColors = generateColors(colors, name); 34 + 35 + const maskId = getRandId('mask__ring'); 36 + </script> 37 + 38 + <svg 39 + viewBox={'0 0 ' + SIZE + ' ' + SIZE} 40 + fill="none" 41 + xmlns="http://www.w3.org/2000/svg" 42 + width={size} 43 + height={size} 44 + data-testid="avatar-ring" 45 + > 46 + <mask id={maskId} maskUnits="userSpaceOnUse" x={0} y={0} width={SIZE} height={SIZE}> 47 + <rect width={SIZE} height={SIZE} rx={square ? undefined : SIZE * 2} fill="white" /> 48 + </mask> 49 + <g mask="url(#{maskId})"> 50 + <path d="M0 0h90v45H0z" fill={cellColors[0]} /> 51 + <path d="M0 45h90v45H0z" fill={cellColors[1]} /> 52 + <path d="M83 45a38 38 0 00-76 0h76z" fill={cellColors[2]} /> 53 + <path d="M83 45a38 38 0 01-76 0h76z" fill={cellColors[3]} /> 54 + <path d="M77 45a32 32 0 10-64 0h64z" fill={cellColors[4]} /> 55 + <path d="M77 45a32 32 0 11-64 0h64z" fill={cellColors[5]} /> 56 + <path d="M71 45a26 26 0 00-52 0h52z" fill={cellColors[6]} /> 57 + <path d="M71 45a26 26 0 01-52 0h52z" fill={cellColors[7]} /> 58 + <circle cx={45} cy={45} r={23} fill={cellColors[8]} /> 59 + </g> 60 + </svg>
+71
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarSunset.svelte
··· 1 + <script lang="ts"> 2 + import { getNumber, getRandId, getRandomColor } from '../utils'; 3 + import { DEFAULTS } from './CONSTANTS'; 4 + 5 + const ELEMENTS = 4; 6 + const SIZE = 80; 7 + 8 + function generateColors(name: string, colors: string[]) { 9 + const numFromName = getNumber(name); 10 + const range = colors && colors.length; 11 + 12 + const elementsProperties = Array.from({ length: ELEMENTS }, (_, i) => ({ 13 + color: getRandomColor(numFromName + i, colors, range), 14 + })); 15 + 16 + return elementsProperties; 17 + } 18 + 19 + export let size = DEFAULTS.size; 20 + export let name = DEFAULTS.name; 21 + export let square = DEFAULTS.square; 22 + export let colors = DEFAULTS.colors; 23 + 24 + const properties = generateColors(name, colors); 25 + const _name = name.replace(/\s/g, ''); 26 + 27 + const maskId = getRandId('mask__sunset'); 28 + const gradient0Id = getRandId('gradient_paint0_linear_' + _name); 29 + const gradient1Id = getRandId('gradient_paint1_linear_' + _name); 30 + </script> 31 + 32 + <svg 33 + viewBox={'0 0 ' + SIZE + ' ' + SIZE} 34 + fill="none" 35 + xmlns="http://www.w3.org/2000/svg" 36 + width={size} 37 + height={size} 38 + data-testid="avatar-sunset" 39 + > 40 + <mask id={maskId} maskUnits="userSpaceOnUse" x={0} y={0} width={SIZE} height={SIZE}> 41 + <rect width={SIZE} height={SIZE} rx={square ? undefined : SIZE * 2} fill="white" /> 42 + </mask> 43 + <g mask="url(#{maskId})"> 44 + <path fill={'url(#gradient_paint0_linear_' + _name + ')'} d="M0 0h80v40H0z" /> 45 + <path fill={'url(#gradient_paint1_linear_' + _name + ')'} d="M0 40h80v40H0z" /> 46 + </g> 47 + <defs> 48 + <linearGradient 49 + id={gradient0Id} 50 + x1={SIZE / 2} 51 + y1={0} 52 + x2={SIZE / 2} 53 + y2={SIZE / 2} 54 + gradientUnits="userSpaceOnUse" 55 + > 56 + <stop stop-color={properties[0].color} /> 57 + <stop offset={1} stop-color={properties[1].color} /> 58 + </linearGradient> 59 + <linearGradient 60 + id={gradient1Id} 61 + x1={SIZE / 2} 62 + y1={SIZE / 2} 63 + x2={SIZE / 2} 64 + y2={SIZE} 65 + gradientUnits="userSpaceOnUse" 66 + > 67 + <stop stop-color={properties[2].color} /> 68 + <stop offset={1} stop-color={properties[3].color} /> 69 + </linearGradient> 70 + </defs> 71 + </svg>
+6
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/CONSTANTS.ts
··· 1 + export const DEFAULTS = { 2 + colors: ['#92A1C6', '#146A7C', '#F0AB3D', '#C271B4', '#C20D90'], 3 + name: 'Clara Barton', 4 + square: false, 5 + size: 40, 6 + };
+61
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/utils.ts
··· 1 + export const getNumber = (name: string) => { 2 + const charactersArray = Array.from(name); 3 + let charactersCodesSum = 0; 4 + 5 + charactersArray.forEach((charactersArrayItem) => { 6 + return (charactersCodesSum += charactersArrayItem.charCodeAt(0)); 7 + }); 8 + 9 + return charactersCodesSum; 10 + }; 11 + 12 + export const getModulus = (num: number, max: number) => { 13 + return num % max; 14 + }; 15 + 16 + export const getDigit = (number: number, ntn: number) => { 17 + return Math.floor((number / Math.pow(10, ntn)) % 10); 18 + }; 19 + 20 + export const getBoolean = (number: number, ntn: number) => { 21 + return !(getDigit(number, ntn) % 2); 22 + }; 23 + 24 + export const getAngle = (x: number, y: number) => { 25 + return (Math.atan2(y, x) * 180) / Math.PI; 26 + }; 27 + 28 + export const getUnit = (number: number, range: number, index?: number) => { 29 + let value = number % range; 30 + 31 + if (index && getDigit(number, index) % 2 === 0) { 32 + return -value; 33 + } else return value; 34 + }; 35 + 36 + export const getRandomColor = (number: number, colors: string[], range: number) => { 37 + return colors[number % range]; 38 + }; 39 + 40 + export const getContrast = (hexcolor: string) => { 41 + // If a leading # is provided, remove it 42 + if (hexcolor.slice(0, 1) === '#') { 43 + hexcolor = hexcolor.slice(1); 44 + } 45 + 46 + // Convert to RGB value 47 + var r = parseInt(hexcolor.substr(0, 2), 16); 48 + var g = parseInt(hexcolor.substr(2, 2), 16); 49 + var b = parseInt(hexcolor.substr(4, 2), 16); 50 + 51 + // Get YIQ ratio 52 + var yiq = (r * 299 + g * 587 + b * 114) / 1000; 53 + 54 + // Check contrast 55 + return yiq >= 128 ? 'black' : 'white'; 56 + }; 57 + 58 + export const getRandId = (prefix?: string) => { 59 + // doesn't use substr 60 + return (prefix || '') + Math.random().toString(36).substring(2); 61 + };
+64
src/routes/p/[actor]/notify-updates/+server.ts
··· 1 + import { error, json } from '@sveltejs/kit'; 2 + import { getActor } from '$lib/actor'; 3 + import { isActorIdentifier, type ResourceUri } from '@atcute/lexicons/syntax'; 4 + import { contrail, listEventRecordsFromContrail } from '$lib/contrail'; 5 + 6 + export async function GET({ params }) { 7 + if (!isActorIdentifier(params.actor)) { 8 + throw error(404, 'Not found'); 9 + } 10 + 11 + const did = await getActor(params.actor); 12 + 13 + if (!did) { 14 + throw error(404, 'Not found'); 15 + } 16 + 17 + // Paginate through all events 18 + const uris: string[] = []; 19 + let cursor: string | undefined; 20 + 21 + do { 22 + const response = await listEventRecordsFromContrail({ 23 + actor: params.actor, 24 + limit: 100, 25 + cursor 26 + }); 27 + 28 + if (!response) { 29 + if (uris.length === 0) throw error(500, 'Failed to list events'); 30 + break; 31 + } 32 + 33 + uris.push(...response.records.map((r) => r.uri)); 34 + cursor = response.cursor ?? undefined; 35 + } while (cursor); 36 + 37 + let totalIndexed = 0; 38 + let totalDeleted = 0; 39 + const allErrors: string[] = []; 40 + 41 + // Batch in groups of 25 (API limit) 42 + for (let i = 0; i < uris.length; i += 25) { 43 + const batch = uris.slice(i, i + 25); 44 + try { 45 + const result = await contrail.post('rsvp.atmo.notifyOfUpdate', { 46 + input: { uris: batch as ResourceUri[] } 47 + }); 48 + if (result.ok) { 49 + totalIndexed += result.data.indexed; 50 + totalDeleted += result.data.deleted; 51 + if (result.data.errors) allErrors.push(...result.data.errors); 52 + } 53 + } catch { 54 + allErrors.push(`Batch starting at index ${i} failed`); 55 + } 56 + } 57 + 58 + return json({ 59 + total: uris.length, 60 + indexed: totalIndexed, 61 + deleted: totalDeleted, 62 + errors: allErrors.length > 0 ? allErrors : undefined 63 + }); 64 + }