Add basic interactive headers
This commit is contained in:
parent
9ba787cd8b
commit
193649bc2d
1 changed files with 140 additions and 54 deletions
|
|
@ -9,6 +9,30 @@
|
||||||
|
|
||||||
const project = $derived(data.project as Project | null)
|
const project = $derived(data.project as Project | null)
|
||||||
const error = $derived(data.error as string | undefined)
|
const error = $derived(data.error as string | undefined)
|
||||||
|
|
||||||
|
let headerContainer = $state<HTMLElement | null>(null)
|
||||||
|
let logoTransform = $state('translate(0, 0)')
|
||||||
|
|
||||||
|
function handleMouseMove(e: MouseEvent) {
|
||||||
|
if (!headerContainer) return
|
||||||
|
|
||||||
|
const rect = headerContainer.getBoundingClientRect()
|
||||||
|
const x = e.clientX - rect.left
|
||||||
|
const y = e.clientY - rect.top
|
||||||
|
|
||||||
|
const centerX = rect.width / 2
|
||||||
|
const centerY = rect.height / 2
|
||||||
|
|
||||||
|
// Calculate movement based on mouse position relative to center
|
||||||
|
const moveX = ((x - centerX) / centerX) * 15 // 15px max movement
|
||||||
|
const moveY = ((y - centerY) / centerY) * 15
|
||||||
|
|
||||||
|
logoTransform = `translate(${moveX}px, ${moveY}px)`
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseLeave() {
|
||||||
|
logoTransform = 'translate(0, 0)'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
|
|
@ -36,46 +60,69 @@
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
{:else if project.status === 'password-protected'}
|
{:else if project.status === 'password-protected'}
|
||||||
<Page>
|
<div class="project-wrapper">
|
||||||
<ProjectPasswordProtection
|
<div
|
||||||
projectSlug={project.slug}
|
bind:this={headerContainer}
|
||||||
correctPassword={project.password || ''}
|
class="project-header-container"
|
||||||
projectType="work"
|
style="background-color: {project.backgroundColor || '#f5f5f5'}"
|
||||||
|
onmousemove={handleMouseMove}
|
||||||
|
onmouseleave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
{#snippet children()}
|
|
||||||
<div slot="header" class="project-header">
|
|
||||||
{#if project.logoUrl}
|
|
||||||
<div
|
|
||||||
class="project-logo"
|
|
||||||
style="background-color: {project.backgroundColor || '#f5f5f5'}"
|
|
||||||
>
|
|
||||||
<img src={project.logoUrl} alt="{project.title} logo" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<h1 class="project-title">{project.title}</h1>
|
|
||||||
{#if project.subtitle}
|
|
||||||
<p class="project-subtitle">{project.subtitle}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<ProjectContent {project} />
|
|
||||||
{/snippet}
|
|
||||||
</ProjectPasswordProtection>
|
|
||||||
</Page>
|
|
||||||
{:else}
|
|
||||||
<Page>
|
|
||||||
<div slot="header" class="project-header">
|
|
||||||
{#if project.logoUrl}
|
{#if project.logoUrl}
|
||||||
<div class="project-logo" style="background-color: {project.backgroundColor || '#f5f5f5'}">
|
<img
|
||||||
<img src={project.logoUrl} alt="{project.title} logo" />
|
src={project.logoUrl}
|
||||||
</div>
|
alt="{project.title} logo"
|
||||||
{/if}
|
class="project-logo"
|
||||||
<h1 class="project-title">{project.title}</h1>
|
style="transform: {logoTransform}"
|
||||||
{#if project.subtitle}
|
/>
|
||||||
<p class="project-subtitle">{project.subtitle}</p>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<ProjectContent {project} />
|
<Page>
|
||||||
</Page>
|
<ProjectPasswordProtection
|
||||||
|
projectSlug={project.slug}
|
||||||
|
correctPassword={project.password || ''}
|
||||||
|
projectType="work"
|
||||||
|
>
|
||||||
|
{#snippet children()}
|
||||||
|
<div slot="header" class="project-header">
|
||||||
|
<h1 class="project-title">{project.title}</h1>
|
||||||
|
{#if project.subtitle}
|
||||||
|
<p class="project-subtitle">{project.subtitle}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<ProjectContent {project} />
|
||||||
|
{/snippet}
|
||||||
|
</ProjectPasswordProtection>
|
||||||
|
</Page>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="project-wrapper">
|
||||||
|
<div
|
||||||
|
bind:this={headerContainer}
|
||||||
|
class="project-header-container"
|
||||||
|
style="background-color: {project.backgroundColor || '#f5f5f5'}"
|
||||||
|
onmousemove={handleMouseMove}
|
||||||
|
onmouseleave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
{#if project.logoUrl}
|
||||||
|
<img
|
||||||
|
src={project.logoUrl}
|
||||||
|
alt="{project.title} logo"
|
||||||
|
class="project-logo"
|
||||||
|
style="transform: {logoTransform}"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<Page>
|
||||||
|
<div slot="header" class="project-header">
|
||||||
|
<h1 class="project-title">{project.title}</h1>
|
||||||
|
{#if project.subtitle}
|
||||||
|
<p class="project-subtitle">{project.subtitle}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<ProjectContent {project} />
|
||||||
|
</Page>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -112,30 +159,69 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Project Wrapper */
|
||||||
|
.project-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
@include breakpoint('phone') {
|
||||||
|
margin-top: $unit-3x;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.page) {
|
||||||
|
margin-top: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Project Header Container */
|
||||||
|
.project-header-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-top-left-radius: $card-corner-radius;
|
||||||
|
border-top-right-radius: $card-corner-radius;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@include breakpoint('phone') {
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include breakpoint('small-phone') {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Project Logo */
|
||||||
|
.project-logo {
|
||||||
|
width: 85px;
|
||||||
|
height: 85px;
|
||||||
|
object-fit: contain;
|
||||||
|
transition: transform 0.15s cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||||
|
will-change: transform;
|
||||||
|
|
||||||
|
@include breakpoint('phone') {
|
||||||
|
width: 75px;
|
||||||
|
height: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include breakpoint('small-phone') {
|
||||||
|
width: 65px;
|
||||||
|
height: 65px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Project Header */
|
/* Project Header */
|
||||||
.project-header {
|
.project-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-logo {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
margin: 0 auto $unit-2x;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: $unit-2x;
|
|
||||||
padding: $unit-2x;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-title {
|
.project-title {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue