More Poz (#57)

* update profile pictures

* small clean up

* more poz on clicks

* update profiles

* edgepoz

* clean up

* add poz eye image
This commit is contained in:
Visual-Dawg 2024-04-21 13:34:27 +02:00 committed by GitHub
parent b0bd3f6430
commit 6370f521eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 338 additions and 45 deletions

View file

@ -21,10 +21,11 @@
"image": "https://cdn.discordapp.com/avatars/378704069726044170/415dcb2ef8d1ef635e35e1d04d523cba.webp", "image": "https://cdn.discordapp.com/avatars/378704069726044170/415dcb2ef8d1ef635e35e1d04d523cba.webp",
"class": "outline-amber-500", "class": "outline-amber-500",
"coordinates": [568, 594], "coordinates": [568, 594],
"size": 120 "size": 120,
"tag": "le_mod"
}, },
{ {
"image": "https://cdn.discordapp.com/avatars/623781003382751243/95435efc86709ac7e347ca205bda665e.webp", "image": "https://cdn.discordapp.com/avatars/623781003382751243/0705cf015336ae06cefcdcb2800a49e9.webp",
"class": "outline-orange-500", "class": "outline-orange-500",
"coordinates": [525, 764], "coordinates": [525, 764],
"size": 80 "size": 80
@ -120,13 +121,6 @@
"coordinates": [263, 653], "coordinates": [263, 653],
"size": 65 "size": 65
}, },
{
"image": "https://cdn.discordapp.com/avatars/273110996938260481/5d2ea7a0ad5a29e0de5b3918bfa82ab2.webp",
"class": "outline-yellow-500 bg-black ",
"coordinates": [893, 622],
"size": 80,
"quote": "\"piss blob\""
},
{ {
"image": "https://cdn.discordapp.com/avatars/231040215085481984/7f378337240110b76e6e9baa31f83670.webp", "image": "https://cdn.discordapp.com/avatars/231040215085481984/7f378337240110b76e6e9baa31f83670.webp",
"class": "outline-amber-500", "class": "outline-amber-500",
@ -159,7 +153,7 @@
"size": 35 "size": 35
}, },
{ {
"image": "https://cdn.discordapp.com/avatars/390226958241366016/513b77487a9fa01b1256c4dc8e1b0c55.webp", "image": "https://cdn.discordapp.com/avatars/390226958241366016/4c149bb6d2ba4ed1265a36c2c9c4418a.webp",
"class": "outline-blue-500 ", "class": "outline-blue-500 ",
"coordinates": [858, 707], "coordinates": [858, 707],
"size": 45 "size": 45
@ -177,7 +171,7 @@
"size": 47 "size": 47
}, },
{ {
"image": "https://cdn.discordapp.com/avatars/194584980922433536/83b3e21452d96c9a3443717dbcddb4bf.webp", "image": "https://cdn.discordapp.com/avatars/194584980922433536/7e8f880edc7b213bbb4ad02a50c9fb36.webp",
"class": "outline-blue-500 bg-black ", "class": "outline-blue-500 bg-black ",
"coordinates": [69, 561], "coordinates": [69, 561],
"size": 54 "size": 54
@ -201,7 +195,7 @@
"size": 49 "size": 49
}, },
{ {
"image": "https://cdn.discordapp.com/avatars/317785409763541002/8e8d743abd3f8aafc87ebd8484a57f26.webp", "image": "https://cdn.discordapp.com/avatars/317785409763541002/4c6f2c001a577909b0904678c3309522.webp",
"class": "outline-blue-500 bg-black ", "class": "outline-blue-500 bg-black ",
"coordinates": [119, 202], "coordinates": [119, 202],
"size": 49 "size": 49
@ -213,7 +207,7 @@
"size": 69 "size": 69
}, },
{ {
"image": "https://cdn.discordapp.com/avatars/706672850907430942/1e2a38423ce667a69e05c4ad8712ccab.webp", "image": "https://cdn.discordapp.com/avatars/706672850907430942/5d5f32f579d9061c5392377fd334c1b3.webp",
"class": "outline-stone-500 bg-black ", "class": "outline-stone-500 bg-black ",
"coordinates": [771, 818], "coordinates": [771, 818],
"size": 49 "size": 49

View file

@ -1,7 +1,22 @@
/* eslint-disable no-useless-escape */ /* eslint-disable no-useless-escape */
import {
interval,
map,
of,
scan,
startWith,
switchMap,
merge,
timeInterval,
filter,
take,
pipe as rxpipe,
Observable
} from 'rxjs'
import { inview } from 'svelte-inview' import { inview } from 'svelte-inview'
import { pick } from 'remeda' import { pick } from 'remeda'
import { Observable, debounceTime, share, startWith, throttleTime } from 'rxjs' import { writable } from 'svelte/store'
/** /**
* Fade: The initial opacity from 0 to 1. * Fade: The initial opacity from 0 to 1.
@ -84,7 +99,7 @@ export function getIsMobile() {
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
a a
) || ) ||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test( /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
a.substr(0, 4) a.substr(0, 4)
) )
) )
@ -131,3 +146,93 @@ export function trimText(text, maxLenght) {
export function getFileNameWithoutExtension(filePath) { export function getFileNameWithoutExtension(filePath) {
return filePath.split('/').at(-1).replace(/\..*$/, '') return filePath.split('/').at(-1).replace(/\..*$/, '')
} }
/**
* A custom operator which maps inputs to their amount (level),
* and completes once a target level has been reached.
*
* On inactivity the level decreases (fallof).
*
* Used here to do fancy stuff with clicks
*/
export function createThresholdStream({ clicksTarget = 69, clicksEachMs = 400, fallof = 20 }) {
const FALLOF = -clicksTarget / fallof
return rxpipe(
timeInterval(),
filter(({ interval }) => interval < clicksEachMs),
map(() => 1),
switchMap((value) =>
merge(
of(value),
/** If no new value comes in, start decreasing the progress */
interval(clicksEachMs + 100).pipe(
take(clicksTarget), // Prevent this interval from running forever
map(() => FALLOF)
)
)
),
scan((level, value) => Math.min(clicksTarget, Math.max(level + value, 0))),
startWith(0)
)
}
/**
* Tell the browser to preload an image
*
* @param {string} src
*/
export function preloadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image()
image.src = src
image.onload = resolve
image.onerror = reject
})
}
/**
* A writable store but as as an observable.
* Observables are much nicher than regular stores.
* @template T
* @param {T} init
* @returns {Observable<T> & { update: (updater: (state: T) => T) => void}}
*/
export function writableObservable(init) {
const { update, subscribe } = writable(init)
const observable = new Observable((subscriber) => {
const unsubscribe = subscribe((value) => subscriber.next(value))
return unsubscribe
})
observable.update = update
return observable
}
/**
* Convert a store to an observable
* @template T
* @param {import('svelte/store').Readable<T>} store
* @returns {Observable<T>}
*/
export function convertStoreToObservable(store) {
return new Observable((subscriber) => {
return store.subscribe((value) => subscriber.next(value))
})
}
/**
* Checks if two rectangles are intersecting
* @param {{size: number, coordinates: [x: number, y: number]}} rect1
* @param {{size: number, coordinates: [x: number, y: number]} rect2
*/
export function isIntersecting(rect1, rect2) {
return !(
rect1.coordinates[0] + rect1.size < rect2.coordinates[0] ||
rect2.coordinates[0] + rect2.size < rect1.coordinates[0] ||
rect1.coordinates[1] + rect1.size < rect2.coordinates[1] ||
rect2.coordinates[1] + rect2.size < rect1.coordinates[1]
)
}

38
src/lib/Types.ts Normal file
View file

@ -0,0 +1,38 @@
import type { Observable } from 'rxjs'
export interface CommunityProfile {
image: string
coordinates: [number, number]
containerClass: string[]
size: number
quote?: string
onDragStart?: (event: DragEvent) => void
onDragEnd?: (event: DragEvent) => void
onHover?: (event: MouseEvent) => void
}
export interface CommunityContext {
/** The size of the biggest profile picture. Non reactive for now */
biggestSize: number
/** The size of the smallest profile picture. Non reactive for now */
smallestSize: number
/** Get the HTML wrapper element / drag bounds element */
getSectionElement: () => HTMLElement
/** State for community profile stuff like interactions */
profilesState$: Observable<ProfilesState> & {
update: (updater: (state: ProfilesState) => ProfilesState) => void
}
}
interface ProfilesState {
/**
* Which profiles intersect with each other
* Alphabetically sorted, like `a-b`, not `b-a`, to prevent duplicates.
* No tuples, so that a set can look them up and delete them easily.
*/
intersections: `${string}-${string}`[]
/** All tagged profiles */
profiles: Record<string, { size: number; coordinates: [x: number, y: number] }>
/** Current active events. Currently not used, but maybe in the future */
events: string[]
}

View file

@ -3,8 +3,9 @@
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte' import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
import { spring } from 'svelte/motion' import { spring } from 'svelte/motion'
import { contextId as ctxId } from '../../routes/home-slices/CommunitySlice.svelte' import { contextId as ctxId } from '../../routes/home-slices/CommunitySlice.svelte'
import { lerp } from '$lib/Helper.mjs' import { convertStoreToObservable, isIntersecting, lerp } from '$lib/Helper.mjs'
import { inview } from 'svelte-inview' import { inview } from 'svelte-inview'
import { Subject, distinctUntilChanged, throttle, throttleTime } from 'rxjs'
/** @type {string} */ /** @type {string} */
export let image export let image
@ -14,6 +15,8 @@
export let size export let size
/** @type {[number, number]} */ /** @type {[number, number]} */
export let coordinates export let coordinates
/** @type {string | undefined} User description */
export let tag = undefined
/** @type {string | undefined} */ /** @type {string | undefined} */
export let quote = undefined export let quote = undefined
@ -28,12 +31,17 @@
export let imageWrapper export let imageWrapper
/** @type {HTMLImageElement}*/ /** @type {HTMLImageElement}*/
export let imageElement export let imageElement
/** @type {string|undefined}*/
export let style = undefined
export let hasDelay = true
export let spawnInstanly = false
const { biggestSize, getSectionElement } = getContext(contextId) /** @type {import('$lib/Types.ts').CommunityContext} **/
const { biggestSize, getSectionElement, profilesState$ } = getContext(contextId)
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const relativeSize = size / biggestSize const relativeSize = size / biggestSize
const delay = Math.pow(1 - size / biggestSize, 4) * 4654 const delay = hasDelay ? Math.pow(1 - size / biggestSize, 4) * 4654 : 0
const dragCoordinates = spring([0, 0], { const dragCoordinates = spring([0, 0], {
damping: lerp(0.2, 0.03, relativeSize), damping: lerp(0.2, 0.03, relativeSize),
stiffness: lerp(0.2, 0.01, relativeSize), stiffness: lerp(0.2, 0.01, relativeSize),
@ -50,7 +58,21 @@
function onViewEnter() { function onViewEnter() {
if (imageElement.__error) return if (imageElement.__error) return
setTimeout(() => (hasEnteredView = true), 550) setTimeout(
() => {
hasEnteredView = true
if (tag && profilesState$) {
profilesState$.update((state) => {
// No drag yet, so we can just use the normal coordinates
state.profiles[tag] = { size, coordinates }
return state
})
}
dispatch('enteredView', { dragCoordinates, imageElement, element, delay })
},
spawnInstanly ? 0 : 550
)
// Only load the library if the element entered the view, to improve performance // Only load the library if the element entered the view, to improve performance
import('interactjs').then(({ default: interact }) => { import('interactjs').then(({ default: interact }) => {
@ -82,12 +104,51 @@
}) })
} }
const draggedSubscription = convertStoreToObservable(dragCoordinates)
.pipe(throttleTime(80))
.subscribe((drag) => {
const displayedPosition = getDisplayedPosition(coordinates, drag)
dispatch('dragged', displayedPosition)
if (!tag) return
// This whole things looks so slow, but if it works. Feel free to PR nicer way though
profilesState$.update((state) => {
state.profiles[tag] = { coordinates: displayedPosition, size }
const otherIntersections = state.intersections.filter((pair) => !pair.includes(tag))
const thisIntersections = Object.entries(state.profiles)
.filter(
([otherTag, rectangle]) =>
otherTag !== tag &&
isIntersecting(rectangle, { size, coordinates: displayedPosition })
)
.map(([otherTag]) => [otherTag, tag].sort().join('-'))
const allIntersections = [...otherIntersections, ...thisIntersections]
state.intersections = allIntersections
return state
})
})
onMount(() => { onMount(() => {
// Nesecarry as the load image event might not get fired when its already loaded ( for example after a page reload ) // Nesecarry as the load image event might not get fired when its already loaded ( for example after a page reload )
hasImageLoaded = hasImageLoaded || imageElement.complete hasImageLoaded = hasImageLoaded || imageElement.complete
}) })
onDestroy(() => interactionjs?.off()) onDestroy(() => {
draggedSubscription.unsubscribe()
interactionjs?.off()
})
/**
* @param {[x: number, y: number]}origin
* @param {[x: number, y: number]}dragCoordinates
*/
function getDisplayedPosition(origin, dragCoordinates) {
return [origin.at(0) + dragCoordinates.at(0), origin.at(1) + dragCoordinates.at(1)]
}
</script> </script>
<div <div
@ -97,11 +158,11 @@
hasImageLoaded ? 'opacity-100' : 'opacity-0' hasImageLoaded ? 'opacity-100' : 'opacity-0'
)} )}
style:translate={coordinates.map((xy) => xy + 'px').join(' ')} style:translate={coordinates.map((xy) => xy + 'px').join(' ')}
style="width: {size}px; height: {size}px;--delay: {delay}ms;" style="width: {size}px; height: {size}px;--delay: {delay}ms; "
aria-hidden="true" aria-hidden="true"
bind:this={element} bind:this={element}
> >
<div <button
class={clsx( class={clsx(
'group absolute inset-0 h-full w-full touch-none select-none', 'group absolute inset-0 h-full w-full touch-none select-none',
isAnimating && 'opacity-0' isAnimating && 'opacity-0'
@ -110,10 +171,11 @@
use:inview={{ unobserveOnEnter: true, threshold: 0.2 }} use:inview={{ unobserveOnEnter: true, threshold: 0.2 }}
class:_animate={hasImageLoaded && isAnimating && hasEnteredView} class:_animate={hasImageLoaded && isAnimating && hasEnteredView}
on:inview_enter={onViewEnter} on:inview_enter={onViewEnter}
on:click
> >
<div class="" bind:this={imageWrapper}> <div class="" bind:this={imageWrapper}>
<img <img
class="group h-full w-full touch-none select-none rounded-[50%] object-cover outline outline-4 {$$restProps.class}" class="group aspect-square h-full w-full touch-none select-none rounded-[50%] object-cover outline outline-4 {$$restProps.class}"
bind:this={imageElement} bind:this={imageElement}
on:load={() => (hasImageLoaded = true)} on:load={() => (hasImageLoaded = true)}
src={image} src={image}
@ -127,6 +189,7 @@
width={size} width={size}
height={size} height={size}
onerror="this.__error = true" onerror="this.__error = true"
{style}
/> />
<slot /> <slot />
</div> </div>
@ -136,7 +199,7 @@
{quote} {quote}
</div> </div>
{/if} {/if}
</div> </button>
</div> </div>
<style lang="postcss"> <style lang="postcss">

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1 @@
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="prefix__a" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="32" height="32"><path d="M0 0h32v32H0V0z" fill="#fff"/></mask><g mask="url(#prefix__a)"><path d="M31.103 20.992c0 2.01-.525 3.436-1.345 4.475-.827 1.05-2.016 1.784-3.476 2.29-2.961 1.029-6.772 1.039-10.182 1.039s-7.273-.01-10.287-1.04c-1.487-.508-2.701-1.244-3.546-2.296-.835-1.04-1.37-2.463-1.37-4.468 0-3.748.807-8.31 3.108-11.903 2.267-3.541 6.013-6.2 12.095-6.2 5.764 0 9.469 2.664 11.771 6.232 2.331 3.613 3.232 8.18 3.232 11.871z" fill="#FBC546" stroke="#000" stroke-width="1.794"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.454 22.413a.853.853 0 010 1.706H12.63a.853.853 0 010-1.706h6.825z" fill="#593604"/><path fill-rule="evenodd" clip-rule="evenodd" d="M27.133 19.853c0-.943-.764-1.706-1.706-1.706h-3.413a1.706 1.706 0 100 3.412h3.413c.942 0 1.706-.763 1.706-1.706zM11.776 19.853c0-.943-.764-1.706-1.706-1.706H6.657a1.706 1.706 0 100 3.412h3.413c.942 0 1.706-.763 1.706-1.706z" fill="#ED6B44"/><path fill-rule="evenodd" clip-rule="evenodd" d="M7.293 14.654c-.086-.127-.293-.11-.293.044v.002c0 .938.76 1.698 1.698 1.698h.017c.937 0 1.698-.76 1.698-1.698v-.002c0-.153-.207-.17-.293-.044-.306.449-.82.744-1.405.744h-.017c-.584 0-1.1-.295-1.405-.744z" fill="#000"/><path d="M23.15 9.586a.825.825 0 010 1.358c-1.318.96-4.025 2.594-7.15 2.594s-5.832-1.635-7.15-2.594a.825.825 0 010-1.358c1.318-.96 4.025-2.594 7.15-2.594s5.832 1.635 7.15 2.594z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M21.293 14.654c-.086-.127-.293-.11-.293.044v.002c0 .938.76 1.698 1.698 1.698h.017c.937 0 1.698-.76 1.698-1.698v-.002c0-.153-.207-.17-.293-.044-.306.449-.82.744-1.405.744h-.017c-.585 0-1.1-.295-1.405-.744z" fill="#000"/><mask id="prefix__b" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="10" y="6" width="12" height="8"><path d="M20.306 7.947c2.158.936 2.158 3.69 0 4.626-1.244.54-2.721.96-4.306.96s-3.062-.42-4.306-.96c-2.158-.937-2.158-3.69 0-4.626 1.244-.54 2.721-.96 4.306-.96s3.062.42 4.306.96z" fill="#fff"/></mask><g mask="url(#prefix__b)" fill-rule="evenodd" clip-rule="evenodd"><path d="M12.515 9.47a3.468 3.468 0 013.468-3.468h.034a3.468 3.468 0 013.468 3.467v.006a3.468 3.468 0 01-3.468 3.467h-.034a3.468 3.468 0 01-3.468-3.467v-.006z" fill="#FBC546"/><path d="M13.958 9.47c0-1.127.91-2.04 2.032-2.04h.02c1.122 0 2.032.913 2.032 2.04v.004c0 1.127-.91 2.04-2.032 2.04h-.02a2.037 2.037 0 01-2.032-2.04V9.47z" fill="#000"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -12,16 +12,20 @@
import amongUsGreenImage from '$lib/images/amongus/green.webp' import amongUsGreenImage from '$lib/images/amongus/green.webp'
import { discordLink } from '$lib/constants.mjs' import { discordLink } from '$lib/constants.mjs'
import profiles from '../../content/profiles.json' import profiles from '../../content/profiles.json'
import Poz from './community/Poz.svelte'
import { writable } from 'svelte/store'
import { Observable } from 'rxjs'
import { writableObservable } from '$lib/Helper.mjs'
let sectionElement let sectionElement
let isDraggingChan = false let isDraggingChan = false
const validSizes = [16, 20, 24, 32, 40, 48, 64, 80, 96, 100, 128, 160, 240, 320, 640] const validSizes = [16, 20, 24, 32, 40, 48, 64, 80, 96, 100, 128, 160, 240, 320, 640]
/** @type {Promise<import('./Types').CommunityProfile[]>}*/ /** @type {Promise<import('$lib/Types').CommunityProfile[]>}*/
let allProfilesPromise = new Promise(() => {}) let allProfilesPromise = new Promise(() => {})
/** @type {import('./Types').CommunityProfile[]} */ /** @type {import('$lib/Types').CommunityProfile[]} */
const extraProfiles = [ const extraProfiles = [
{ {
image: 'imgs/chan/joy.svg', image: 'imgs/chan/joy.svg',
@ -39,14 +43,6 @@
onHover: ({ detail: { srcElement } }) => onHover: ({ detail: { srcElement } }) =>
!isDraggingChan && (srcElement.src = 'imgs/chan/wink.svg') !isDraggingChan && (srcElement.src = 'imgs/chan/wink.svg')
}, },
{
// jacekpoz
image: '/imgs/profile_pictures/jacekpoz.svg',
coordinates: [893, 622],
size: 80,
class: 'outline-yellow-500 bg-black ',
quote: '"piss blob"'
},
{ {
image: amongUsGreenImage, image: amongUsGreenImage,
coordinates: [873, 224], coordinates: [873, 224],
@ -88,7 +84,8 @@
(previousSize, { size }) => (size < previousSize ? size : previousSize), (previousSize, { size }) => (size < previousSize ? size : previousSize),
Number.POSITIVE_INFINITY Number.POSITIVE_INFINITY
), ),
getSectionElement: () => sectionElement getSectionElement: () => sectionElement,
profilesState$: writableObservable({ events: [], intersections: [], profiles: {} })
}) })
onMount(() => { onMount(() => {
@ -143,6 +140,8 @@
on:hover={onHover} on:hover={onHover}
/> />
{/each} {/each}
<Poz />
</div> </div>
</div> </div>
{/await} {/await}

View file

@ -1,10 +0,0 @@
export interface CommunityProfile {
image: string
coordinates: [number, number]
containerClass: string[]
size: number
quote?: string
onDragStart?: (event: DragEvent) => void
onDragEnd?: (event: DragEvent) => void
onHover?: (event: MouseEvent) => void
}

View file

@ -0,0 +1,103 @@
<script>
import { createThresholdStream, lerp, preloadImage } from '$lib/Helper.mjs'
import DiscordProfilePicture from '$lib/components/DiscordProfilePicture.svelte'
import { Subject, filter, first, map, merge, of, startWith, switchMap, timer } from 'rxjs'
import edgePoz from '$lib/images/poz/msedgepoz.webp'
import { getContext, onDestroy } from 'svelte'
import { contextId } from '../CommunitySlice.svelte'
const thePozArmy = Object.values(import.meta.glob('$lib/images/poz/*', { eager: true })).map(
(x) => x.default
)
/** @type {import('$lib/Types').CommunityContext}*/
const { profilesState$ } = getContext(contextId)
$: touches$$$$Voice = $profilesState$.intersections.includes('le_mod-poz')
const origin = [893, 622]
let newPosition
const clicksTarget = 9
const shakeMax = 24
const clicksInput$ = new Subject()
const level$ = clicksInput$.pipe(
createThresholdStream({ clicksTarget, clicksEachMs: 250, fallof: 10 })
)
const relativeLevel$ = level$.pipe(map((clicks) => clicks / clicksTarget))
const hasFinished$ = relativeLevel$.pipe(
filter((clicks) => clicks >= 1),
first(),
map(() => true),
startWith(false)
)
const showMainPoz$ = hasFinished$.pipe(
filter((is) => is === true),
switchMap(() => timer(550)),
first(),
map(() => false),
startWith(true)
)
const shake$ = relativeLevel$.pipe(
switchMap((progress) => {
const shakeModifier = shakeMax * Math.max(progress, 0.01)
return merge(
of({
x: Math.random() * shakeModifier,
y: Math.random() * shakeModifier
}),
// Reset back to the original value after 140ms
timer(140).pipe(map(() => ({ x: 0, y: 0 })))
)
}),
startWith({ x: 0, y: 0 })
)
// Preload images when the user start clicking our beloved Poz
const preloadSubscription = relativeLevel$
.pipe(
filter((level) => level >= 0.1),
first()
)
.subscribe(() => thePozArmy.forEach(preloadImage))
onDestroy(() => preloadSubscription.unsubscribe())
</script>
{#if $hasFinished$}
{#each thePozArmy as poz}
{@const maxSize = 75}
{@const size = 35 * Math.random() + 40}
<DiscordProfilePicture
image={poz}
coordinates={newPosition ?? origin}
{size}
class={'bg-black/50 outline-yellow-500 '}
spawnInstantly={false}
isAnimating={false}
tag="poz"
on:enteredView={({ detail: { dragCoordinates } }) => {
dragCoordinates.update(([x, y]) => {
x += lerp(400, 0, (size / maxSize) * (1 - Math.random())) * (Math.random() > 0.5 ? 1 : -1)
y += lerp(400, 0, (size / maxSize) * (1 - Math.random())) * (Math.random() > 0.5 ? 1 : -1)
return [x, y]
})
}}
/>
{/each}
{/if}
{#if $showMainPoz$}
<div class="absolute z-20">
<DiscordProfilePicture
image={touches$$$$Voice ? edgePoz : '/imgs/profile_pictures/jacekpoz.svg'}
coordinates={origin}
size={80}
class={'bg-black outline-yellow-500 '}
quote={'"piss blob"'}
intersectionHandler={(image) => (image = '"piss blob"')}
tag="poz"
on:click={() => clicksInput$.next(0)}
style={`scale:${$relativeLevel$ * 0.5 + 1};transition: scale 80ms linear; translate: ${$shake$.x}px ${$shake$.y}px; `}
on:dragged={({ detail }) => (newPosition = detail)}
></DiscordProfilePicture>
</div>
{/if}