Refactor project page a bit, consistent arrow icons

This commit is contained in:
Justin Edmund 2025-06-10 01:46:04 -07:00
parent 8d68219dde
commit 42a84f8ad7
7 changed files with 246 additions and 224 deletions

View file

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33337 8H12.6667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.66663 3.33325L2.99996 7.99992L7.66663 12.6666" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 289 B

View file

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33337 8H12.6667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 3.33325L12.6667 7.99992L8 12.6666" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 277 B

View file

@ -4,6 +4,7 @@
import { formatDate } from '$lib/utils/date'
import { renderEdraContent } from '$lib/utils/content'
import { goto } from '$app/navigation'
import ArrowLeft from '$icons/arrow-left.svg'
let { post }: { post: any } = $props()
@ -82,15 +83,7 @@
<footer class="post-footer">
<button onclick={() => goto('/universe')} class="back-button">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" class="back-arrow">
<path
d="M15 8H3.5M3.5 8L8 3.5M3.5 8L8 12.5"
stroke="currentColor"
stroke-width="2.25"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<ArrowLeft class="back-arrow" />
Back to Universe
</button>
</footer>
@ -345,20 +338,28 @@
border-radius: 24px;
outline: none;
&:hover:not(:disabled) {
.back-arrow {
transform: translateX(-3px);
}
&:hover:not(:disabled) :global(.back-arrow) {
transform: translateX(-3px);
}
&:focus-visible {
box-shadow: 0 0 0 3px rgba($red-60, 0.25);
}
.back-arrow {
:global(.back-arrow) {
width: 16px;
height: 16px;
flex-shrink: 0;
transition: transform 0.2s ease;
margin-left: -$unit-half;
:global(path) {
stroke: currentColor;
stroke-width: 2.25;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
}
}
</style>

View file

@ -1,5 +1,7 @@
<script lang="ts">
import type { Project } from '$lib/types/project'
import ArrowLeft from '$icons/arrow-left.svg'
import { goto } from '$app/navigation'
interface Props {
project: Project
@ -48,45 +50,6 @@
</script>
<article class="project-content">
<!-- Project Details -->
<div class="project-details">
<div class="meta-grid">
{#if project.client}
<div class="meta-item">
<span class="meta-label">Client</span>
<span class="meta-value">{project.client}</span>
</div>
{/if}
{#if project.year}
<div class="meta-item">
<span class="meta-label">Year</span>
<span class="meta-value">{project.year}</span>
</div>
{/if}
{#if project.role}
<div class="meta-item">
<span class="meta-label">Role</span>
<span class="meta-value">{project.role}</span>
</div>
{/if}
</div>
{#if project.externalUrl}
<div class="external-link-wrapper">
<a
href={project.externalUrl}
target="_blank"
rel="noopener noreferrer"
class="external-link"
>
Visit Project →
</a>
</div>
{/if}
</div>
<!-- Case Study Content -->
{#if project.caseStudyContent && project.caseStudyContent.content && project.caseStudyContent.content.length > 0}
<div class="case-study-section">
@ -109,13 +72,19 @@
{/if}
<!-- Navigation -->
<nav class="project-nav">
<footer class="project-footer">
{#if project.projectType === 'labs'}
<a href="/labs" class="back-link">← Back to labs</a>
<button onclick={() => goto('/labs')} class="back-button">
<ArrowLeft class="back-arrow" />
Back to labs
</button>
{:else}
<a href="/" class="back-link">← Back to projects</a>
<button onclick={() => goto('/')} class="back-button">
<ArrowLeft class="back-arrow" />
Back to projects
</button>
{/if}
</nav>
</footer>
</article>
<style lang="scss">
@ -126,58 +95,6 @@
gap: $unit-4x;
}
.project-details {
display: flex;
flex-direction: column;
gap: $unit-3x;
padding-bottom: $unit-3x;
border-bottom: 1px solid $grey-90;
}
.meta-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: $unit-2x;
.meta-item {
display: flex;
flex-direction: column;
gap: $unit-half;
}
.meta-label {
font-size: 0.875rem;
color: $grey-60;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.meta-value {
font-size: 1rem;
color: $grey-20;
font-weight: 500;
}
}
.external-link-wrapper {
text-align: center;
}
.external-link {
display: inline-block;
padding: $unit-2x $unit-3x;
background: $grey-10;
color: white;
text-decoration: none;
border-radius: 50px;
font-weight: 500;
font-size: 0.925rem;
transition: background-color 0.2s ease;
&:hover {
background: $grey-20;
}
}
/* Case Study Section */
.case-study-content {
@ -238,9 +155,6 @@
/* Gallery Section */
.gallery-section {
padding-top: $unit-3x;
border-top: 1px solid $grey-90;
h2 {
font-size: 1.75rem;
margin: 0 0 $unit-3x;
@ -262,20 +176,47 @@
}
/* Navigation */
.project-nav {
text-align: center;
padding-top: $unit-3x;
border-top: 1px solid $grey-90;
.project-footer {
padding-bottom: $unit-2x;
}
.back-link {
color: $grey-40;
text-decoration: none;
font-size: 0.925rem;
transition: color 0.2s ease;
.back-button {
color: $red-60;
background-color: transparent;
border: 1px solid transparent;
font: inherit;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
display: inline-flex;
align-items: center;
gap: $unit;
border-radius: 24px;
outline: none;
&:hover {
color: $grey-20;
&:hover:not(:disabled) :global(.back-arrow) {
transform: translateX(-3px);
}
&:focus-visible {
box-shadow: 0 0 0 3px rgba($red-60, 0.25);
}
:global(.back-arrow) {
width: 16px;
height: 16px;
flex-shrink: 0;
transition: transform 0.2s ease;
margin-left: -$unit-half;
:global(path) {
stroke: currentColor;
stroke-width: 2.25;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
}
}
</style>

View file

@ -0,0 +1,113 @@
<script lang="ts">
import ArrowRight from '$icons/arrow-right.svg'
import type { Project } from '$lib/types/project'
interface Props {
project: Project
}
let { project }: Props = $props()
</script>
<div class="project-header-content">
<div class="project-text">
<h1 class="project-title">{project.title}</h1>
</div>
{#if project.externalUrl}
<a
href={project.externalUrl}
target="_blank"
rel="noopener noreferrer"
class="visit-button"
style="--button-bg: {project.highlightColor || '#4d4d4d'}; --button-color: white"
>
Visit <ArrowRight />
</a>
{/if}
</div>
<style lang="scss">
/* Project Header */
.project-header {
width: 100%;
}
.project-header-content {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: $unit-3x;
@include breakpoint('phone') {
flex-direction: column;
gap: $unit-2x;
}
}
.project-text {
flex: 1;
}
.project-title {
font-size: 2.25rem;
letter-spacing: -0.025em;
font-weight: 500;
margin: 0 0 $unit;
color: $grey-10;
@include breakpoint('phone') {
font-size: 2rem;
}
}
.project-subtitle {
font-size: 1.25rem;
color: $grey-40;
margin: 0;
@include breakpoint('phone') {
font-size: 1.125rem;
}
}
.visit-button {
display: inline-flex;
align-items: center;
gap: $unit;
padding: ($unit * 1.5) $unit-2x;
background: var(--button-bg, $grey-10);
color: var(--button-color, white);
text-decoration: none;
border-radius: 50px;
font-weight: 400;
font-size: 1rem;
transition: all 0.2s ease;
white-space: nowrap;
:global(svg) {
width: 16px;
height: 16px;
transition: transform 0.2s ease;
:global(path) {
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
}
&:hover {
background: color-mix(in srgb, var(--button-bg) 90%, black);
:global(svg) {
transform: translateX(2px);
}
}
@include breakpoint('phone') {
align-self: flex-start;
}
}
</style>

View file

@ -3,6 +3,7 @@
import DynamicPostContent from '$components/DynamicPostContent.svelte'
import { getContentExcerpt } from '$lib/utils/content'
import { goto } from '$app/navigation'
import ArrowLeft from '$icons/arrow-left.svg'
import type { PageData } from './$types'
let { data }: { data: PageData } = $props()
@ -45,15 +46,7 @@
<h1>Post Not Found</h1>
<p>{error || "The post you're looking for doesn't exist."}</p>
<button onclick={() => goto('/universe')} class="back-button">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" class="back-arrow">
<path
d="M15 8H3.5M3.5 8L8 3.5M3.5 8L8 12.5"
stroke="currentColor"
stroke-width="2.25"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<ArrowLeft class="back-arrow" />
Back to Universe
</button>
</div>
@ -106,20 +99,28 @@
border-radius: 24px;
outline: none;
&:hover:not(:disabled) {
.back-arrow {
transform: translateX(-3px);
}
&:hover:not(:disabled) :global(.back-arrow) {
transform: translateX(-3px);
}
&:focus-visible {
box-shadow: 0 0 0 3px rgba($red-60, 0.25);
}
.back-arrow {
:global(.back-arrow) {
width: 16px;
height: 16px;
flex-shrink: 0;
transition: transform 0.2s ease;
margin-left: -$unit-half;
:global(path) {
stroke: currentColor;
stroke-width: 2.25;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
}
}
}

View file

@ -1,6 +1,7 @@
<script lang="ts">
import Page from '$components/Page.svelte'
import ProjectPasswordProtection from '$lib/components/ProjectPasswordProtection.svelte'
import ProjectHeaderContent from '$lib/components/ProjectHeaderContent.svelte'
import ProjectContent from '$lib/components/ProjectContent.svelte'
import type { PageData } from './$types'
import type { Project } from '$lib/types/project'
@ -12,14 +13,14 @@
const error = $derived(data.error as string | undefined)
let headerContainer = $state<HTMLElement | null>(null)
// Spring with aggressive bounce settings
const logoPosition = spring(
{ x: 0, y: 0 },
{
stiffness: 0.03, // Extremely low for maximum bounce
damping: 0.1, // Very low for many oscillations
precision: 0.001 // Keep animating for longer
stiffness: 0.03, // Extremely low for maximum bounce
damping: 0.1, // Very low for many oscillations
precision: 0.001 // Keep animating for longer
}
)
@ -72,70 +73,49 @@
<a href="/" class="back-link">← Back to projects</a>
</div>
</Page>
{:else if project.status === 'password-protected'}
<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>
<ProjectPasswordProtection
projectSlug={project.slug}
correctPassword={project.password || ''}
projectType="work"
{:else if project.status === 'password-protected' || project.status === 'published'}
{#snippet projectLayout()}
<div class="project-wrapper">
<div
bind:this={headerContainer}
class="project-header-container"
style="background-color: {project.backgroundColor || '#f5f5f5'}"
onmousemove={handleMouseMove}
onmouseleave={handleMouseLeave}
role="presentation"
aria-hidden="true"
>
{#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 project.logoUrl}
<img
src={project.logoUrl}
alt="{project.title} logo"
class="project-logo"
style="transform: {logoTransform}"
/>
{/if}
</div>
<ProjectContent {project} />
</Page>
</div>
<Page>
<div slot="header" class="project-header">
<ProjectHeaderContent {project} />
</div>
{#if project.status === 'password-protected'}
<ProjectPasswordProtection
projectSlug={project.slug}
correctPassword={project.password || ''}
projectType="work"
>
{#snippet children()}
<ProjectContent {project} />
{/snippet}
</ProjectPasswordProtection>
{:else}
<ProjectContent {project} />
{/if}
</Page>
</div>
{/snippet}
{@render projectLayout()}
{/if}
<style lang="scss">
@ -230,28 +210,6 @@
/* Project Header */
.project-header {
text-align: center;
width: 100%;
}
.project-title {
font-size: 2.5rem;
font-weight: 700;
margin: 0 0 $unit;
color: $grey-10;
@include breakpoint('phone') {
font-size: 2rem;
}
}
.project-subtitle {
font-size: 1.25rem;
color: $grey-40;
margin: 0;
@include breakpoint('phone') {
font-size: 1.125rem;
}
}
</style>