2026-02-15 23:04:24 +03:00
|
|
|
<!--
|
|
|
|
|
Component: PerspectivePlan
|
|
|
|
|
Wrapper that applies perspective transformations based on back/front state.
|
|
|
|
|
Style computation moved from manager to component for simpler architecture.
|
|
|
|
|
-->
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import type { PerspectiveManager } from '$shared/lib';
|
|
|
|
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
|
|
|
|
import { type Snippet } from 'svelte';
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
/**
|
|
|
|
|
* Perspective manager
|
|
|
|
|
*/
|
|
|
|
|
manager: PerspectiveManager;
|
|
|
|
|
/**
|
2026-03-02 22:19:35 +03:00
|
|
|
* CSS classes
|
2026-02-15 23:04:24 +03:00
|
|
|
*/
|
|
|
|
|
class?: string;
|
|
|
|
|
/**
|
2026-03-02 22:19:35 +03:00
|
|
|
* Content snippet
|
2026-02-15 23:04:24 +03:00
|
|
|
*/
|
|
|
|
|
children: Snippet<[{ className?: string }]>;
|
|
|
|
|
/**
|
2026-03-02 22:19:35 +03:00
|
|
|
* Constrain region
|
|
|
|
|
* @default 'full'
|
2026-02-15 23:04:24 +03:00
|
|
|
*/
|
|
|
|
|
region?: 'left' | 'right' | 'full';
|
|
|
|
|
/**
|
2026-03-02 22:19:35 +03:00
|
|
|
* Region width percentage
|
|
|
|
|
* @default 50
|
2026-02-15 23:04:24 +03:00
|
|
|
*/
|
|
|
|
|
regionWidth?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let { manager, children, class: className = '', region = 'full', regionWidth = 50 }: Props = $props();
|
|
|
|
|
|
|
|
|
|
const config = $derived(manager.getConfig());
|
|
|
|
|
|
|
|
|
|
// Computed style based on spring position (0 = front, 1 = back)
|
|
|
|
|
const style = $derived.by(() => {
|
|
|
|
|
const distance = manager.spring.current;
|
|
|
|
|
const baseX = config.horizontalOffset ?? 0;
|
|
|
|
|
|
|
|
|
|
// Back state: blurred, scaled down, pushed back
|
|
|
|
|
// Front state: fully visible, in focus
|
|
|
|
|
const scale = 1 - distance * (config.scaleStep ?? 0.5);
|
|
|
|
|
const blur = distance * (config.blurStep ?? 4);
|
|
|
|
|
const opacity = Math.max(0, 1 - distance * (config.opacityStep ?? 0.5));
|
|
|
|
|
const zIndex = 10;
|
|
|
|
|
const pointerEvents = distance < 0.4 ? 'auto' : ('none' as const);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
transform: `translate3d(${baseX}px, 0px, ${-distance * (config.depthStep ?? 100)}px) scale(${scale})`,
|
|
|
|
|
filter: `blur(${blur}px)`,
|
|
|
|
|
opacity,
|
|
|
|
|
pointerEvents,
|
|
|
|
|
zIndex,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Calculate horizontal constraints based on region
|
|
|
|
|
const regionStyleStr = $derived(() => {
|
|
|
|
|
if (region === 'full') return '';
|
|
|
|
|
const side = region === 'left' ? 'left' : 'right';
|
|
|
|
|
return `position: absolute; ${side}: 0; width: ${regionWidth}%; top: 0; bottom: 0;`;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Visibility: front = visible, back = hidden
|
|
|
|
|
const isVisible = $derived(manager.isFront);
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
class={cn('will-change-transform', className)}
|
|
|
|
|
style:transform-style="preserve-3d"
|
|
|
|
|
style:transform={style?.transform}
|
|
|
|
|
style:filter={style?.filter}
|
|
|
|
|
style:opacity={style?.opacity}
|
|
|
|
|
style:pointer-events={style?.pointerEvents}
|
|
|
|
|
style:z-index={style?.zIndex}
|
|
|
|
|
style:custom={regionStyleStr()}
|
|
|
|
|
>
|
|
|
|
|
{@render children({ className: isVisible ? 'visible' : 'hidden' })}
|
|
|
|
|
</div>
|