Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions app/components/grainy-liquid/Blob.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<script setup lang="ts">
import type { Mesh } from 'three'
import { Vector3, Color } from 'three'
import vertexShader from './shaders/vertex.glsl'
import fragmentShader from './shaders/fragment.glsl'

interface Props {
colorA?: string
colorB?: string
colorC?: string
speed?: number
amplitude?: number
}

const props = withDefaults(defineProps<Props>(), {
colorA: '#6366f1',
colorB: '#ec4899',
colorC: '#f1f5f9',
speed: 0.8,
amplitude: 0.15
})

const { onBeforeRender } = useLoop()

const blobRef = ref<Mesh | null>(null)

const controlsConfig = {
amplitude: {
value: props.amplitude,
min: 0,
max: 0.5,
step: 0.01,
label: 'Amplitude'
},
frequency: {
value: 1.2,
min: 0.1,
max: 3,
step: 0.1,
label: 'Frequency'
},
speed: {
value: props.speed,
min: 0.1,
max: 2,
step: 0.1,
label: 'Speed'
},
colorA: props.colorA,
colorB: props.colorB,
colorC: props.colorC,
noiseScale: {
value: 2.0,
min: 0.5,
max: 5,
step: 0.1,
label: 'Noise Scale'
},
grainIntensity: {
value: 0.08,
min: 0,
max: 0.2,
step: 0.01,
label: 'Grain'
},
fresnelPower: {
value: 2.0,
min: 0.5,
max: 5,
step: 0.1,
label: 'Fresnel'
}
}

const { amplitude, frequency, speed, colorA, colorB, colorC, noiseScale, grainIntensity, fresnelPower } = useControls(controlsConfig)

function hexToVector3(hex: string): Vector3 {
const color = new Color(hex)
return new Vector3(color.r, color.g, color.b)
}

const uniforms = ref({
u_time: { value: 0.0 },
u_amplitude: { value: amplitude.value },
u_frequency: { value: frequency.value },
u_speed: { value: speed.value },
u_colorA: { value: hexToVector3(colorA.value) },
u_colorB: { value: hexToVector3(colorB.value) },
u_colorC: { value: hexToVector3(colorC.value) },
u_noiseScale: { value: noiseScale.value },
u_grainIntensity: { value: grainIntensity.value },
u_fresnelPower: { value: fresnelPower.value }
})

watch([amplitude, frequency, speed, noiseScale, grainIntensity, fresnelPower], () => {
uniforms.value.u_amplitude.value = amplitude.value
uniforms.value.u_frequency.value = frequency.value
uniforms.value.u_speed.value = speed.value
uniforms.value.u_noiseScale.value = noiseScale.value
uniforms.value.u_grainIntensity.value = grainIntensity.value
uniforms.value.u_fresnelPower.value = fresnelPower.value
})

watch([colorA, colorB, colorC], () => {
uniforms.value.u_colorA.value = hexToVector3(colorA.value)
uniforms.value.u_colorB.value = hexToVector3(colorB.value)
uniforms.value.u_colorC.value = hexToVector3(colorC.value)
})

onBeforeRender(({ elapsed }) => {
if (uniforms.value) {
uniforms.value.u_time.value = elapsed
}

if (blobRef.value) {
blobRef.value.rotation.y += 0.005
blobRef.value.rotation.x += 0.002
}
})
</script>

<template>
<TresMesh ref="blobRef">
<TresIcosahedronGeometry :args="[2, 64]" />
<TresShaderMaterial
:uniforms="uniforms"
:vertex-shader="vertexShader"
:fragment-shader="fragmentShader"
:transparent="false"
:side="0"
/>
</TresMesh>
</template>
23 changes: 23 additions & 0 deletions app/components/grainy-liquid/Experience.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script setup lang="ts">
import { OrbitControls } from '@tresjs/cientos'
import MultiBlob from './MultiBlob.vue'
import { BlendFunction } from 'postprocessing'
</script>

<template>
<TresPerspectiveCamera :position="[0, 0, 8]" />
<OrbitControls :enable-pan="false" :enable-zoom="false" />

<MultiBlob />

<TresAmbientLight :intensity="0.4" />

<Suspense>
<EffectComposerPmndrs>
<NoisePmndrs
premultiply
:blend-function="BlendFunction.OVERLAY"
/>
</EffectComposerPmndrs>
</Suspense>
</template>
62 changes: 62 additions & 0 deletions app/components/grainy-liquid/MultiBlob.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script setup lang="ts">
import Blob from './Blob.vue'

interface BlobConfig {
position: [number, number, number]
scale: [number, number, number]
colorA: string
colorB: string
colorC: string
speed: number
amplitude: number
}

const blobs: BlobConfig[] = [
{
position: [-4, 2, 0],
scale: [1.8, 1.8, 1.8],
colorA: '#6366f1',
colorB: '#8b5cf6',
colorC: '#ddd6fe',
speed: 0.6,
amplitude: 0.15
},
{
position: [4, -2, -1],
scale: [1.5, 1.5, 1.5],
colorA: '#ec4899',
colorB: '#f472b6',
colorC: '#fce7f3',
speed: 0.8,
amplitude: 0.12
},
{
position: [0, 0, -4],
scale: [2.2, 2.2, 2.2],
colorA: '#f8fafc',
colorB: '#e2e8f0',
colorC: '#cbd5e1',
speed: 0.4,
amplitude: 0.8
}
]
</script>

<template>
<TresGroup>
<TresGroup
v-for="(blob, index) in blobs"
:key="index"
:position="blob.position"
:scale="blob.scale"
>
<Blob
:color-a="blob.colorA"
:color-b="blob.colorB"
:color-c="blob.colorC"
:speed="blob.speed"
:amplitude="blob.amplitude"
/>
</TresGroup>
</TresGroup>
</template>
89 changes: 89 additions & 0 deletions app/components/grainy-liquid/WebsiteLayout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<template>
<div class="relative h-screen w-full overflow-hidden bg-gray-50">
<!-- 3D Canvas Background -->
<div class="absolute inset-0">
<TresCanvas window-size clear-color="#f8fafc">
<Experience />
<TheScreenshot />
</TresCanvas>
</div>

<!-- Content Overlay -->
<div class="relative z-10 h-full flex flex-col">
<!-- Header -->
<header class="p-6 md:p-8">
<div class="flex items-center justify-between">
<!-- Logo/Icon -->
<div class="w-12 h-12 flex items-center justify-center">
<div class="w-8 h-8 rounded-full border-2 border-gray-800 relative">
<div class="absolute inset-1 rounded-full border border-gray-800" />
<div class="absolute top-1/2 left-1/2 w-1 h-1 bg-gray-800 rounded-full transform -translate-x-1/2 -translate-y-1/2" />
</div>
</div>

<!-- Nav Text -->
<div class="text-right text-sm text-gray-700 max-w-xs">
Perfect for your next web, social or branding project
</div>
</div>
</header>

<!-- Main Content -->
<main class="flex-1 flex items-center px-6 md:px-8">
<div class="w-full max-w-7xl mx-auto">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
<!-- Left Content -->
<div>
<div class="mb-6">
<p class="text-sm text-gray-600 mb-2">High res</p>
<p class="text-sm text-gray-600 mb-2">grainy</p>
<p class="text-sm text-gray-600 mb-2">abstract</p>
<p class="text-sm text-gray-600">shapes.</p>
</div>

<h1 class="text-6xl md:text-8xl lg:text-9xl font-bold text-gray-900 leading-none tracking-tight" style="font-family: 'Playfair Display', serif;">
Grainy<br>
Shapes
</h1>
</div>

<!-- Right Content (3D space for blobs) -->
<div class="relative">
<!-- This space is filled by the 3D canvas -->
</div>
</div>
</div>
</main>

<!-- Bottom Right Accent -->
<div class="absolute bottom-8 right-8">
<div class="w-24 h-24 rounded-full border border-gray-800 flex items-center justify-center">
<div class="w-16 h-16 rounded-full border border-gray-800" />
</div>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import Experience from './Experience.vue'

// Load Google Font
useHead({
link: [
{
rel: 'preconnect',
href: 'https://fonts.googleapis.com'
},
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossorigin: ''
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700;900&display=swap'
}
]
})
</script>
7 changes: 7 additions & 0 deletions app/components/grainy-liquid/index.global.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup lang="ts">
import WebsiteLayout from './WebsiteLayout.vue'
</script>

<template>
<WebsiteLayout />
</template>
Loading