Initial version of lava lamp generator

This commit is contained in:
Dan 2026-01-10 20:21:59 +00:00
parent b27f9cfb39
commit a05e9bf10b
7 changed files with 1582 additions and 637 deletions

View file

@ -754,12 +754,12 @@
&::before {
content: "";
position: absolute;
left: -2rem;
left: -1rem;
top: 50%;
transform: translateY(-50%);
width: 3rem;
height: 3px;
background: linear-gradient(90deg, transparent, #0096ff);
width: 5px;
height: 100%;
background: linear-gradient(180deg, transparent, #0096ff, transparent);
//box-shadow: 0 0 10px rgba(0, 150, 255, 0.8);
}
@ -767,12 +767,12 @@
&::after {
content: "";
position: absolute;
right: -2rem;
right: -1rem;
top: 50%;
transform: translateY(-50%);
width: 3rem;
height: 3px;
background: linear-gradient(90deg, #ff7800, transparent);
width: 5px;
height: 100%;
background: linear-gradient(180deg, transparent, #ff7800, transparent);
//box-shadow: 0 0 10px rgba(255, 120, 0, 0.8);
}
@ -785,19 +785,6 @@
@include media-up(md) {
font-size: 3rem;
&::before,
&::after {
width: 6rem;
}
&::before {
left: -2rem;
}
&::after {
right: -2rem;
}
}
}

View file

@ -0,0 +1,74 @@
---
title: "Adoptables"
date: 2026-01-10
description: "Customisable widgets and animations for your website"
icon: "adoptables"
draft: false
---
Welcome to my adoptables collection! These are customisable widgets you can embed on your own website. Each adoptable comes with a live preview and customisation options. Just configure it how you like, grab the code, and paste it into your site.
All adoptables are:
- **Free to use** - No attribution required (but appreciated!)
- **Probably customisable** - Tweak your own colors and settings if applicable
- **Self-contained** - Single script tag, no dependencies
- **Responsive** - Scales to fit your layout
---
## Lava Lamp
My brother had a blue and yellow lava lamp when I was younger and I was obsessed with it, so making one for my website was an obvious choice... and now I'd like to share it with everyone!
Using the pixelation effect with very high contrast colours is going to result in an aura on the blobs due to the way the SVG filter works, I am trying to find a solution for this.
{{< lavalamp-adoptable >}}
---
## Usage Instructions
1. **Customise** - Use the controls to adjust colors, animation speed, and other settings
2. **Get Code** - Click "Get Embed Code" to generate your custom script tag
3. **Embed** - Copy the code and paste it anywhere in your HTML
4. **Style** - Wrap it in a div and set width/height to control the lamp size
### Example Container
The lava lamp will scale to fit its container. You can control the size like this:
```html
<div style="width: 200px; height: 350px;">
<script
src="https://ritual.sh/js/adoptables/lavalamp.js"
data-bg-color-1="#F8E45C"
data-bg-color-2="#FF7800"
data-blob-color-1="#FF4500"
data-blob-color-2="#FF6347"
data-case-color="#333333"
data-blob-count="6"
data-speed="1.0"
data-blob-size="1.0"
></script>
</div>
```
### Customisation Options
- `data-bg-color-1` & `data-bg-color-2`: Background gradient colors
- `data-blob-color-1` & `data-blob-color-2`: Blob gradient colors
- `data-case-color`: Color for the top cap and bottom base
- `data-blob-count`: Number of blobs (3-12)
- `data-speed`: Animation speed multiplier (0.5-1.5x)
- `data-blob-size`: Blob size multiplier (0.5-2.0x)
- `data-pixelate`: Set to "true" for a retro pixelated effect
- `data-pixel-size`: Pixel size for pixelation effect (2-10, default 4)
---
## More Coming Soon
The companion cube is coming soon!
Got ideas for adoptables you'd like to see? Let me know!

View file

@ -5,22 +5,23 @@ description: "A pure CSS lavalamp animation with floating blobs"
icon: "lavalamp"
demo_url: ""
source_url: ""
draft: true
draft: false
---
A mesmerizing lavalamp effect created entirely with HTML and CSS. Features smooth floating animations and gradient blobs that rise and fall like a classic lava lamp.
A mesmerizing lavalamp effect created with HTML, CSS, and JavaScript. Features smooth floating animations and gradient blobs that rise and fall like a classic lava lamp.
## Want Your Own Customizable Lava Lamp?
Check out the [Adoptables page](/resources/adoptables/) where you can customize your own lava lamp with your choice of colors, blob count, and animation speed - then get the embed code to add it to your own website!
## Features
- Pure CSS animation (no JavaScript required)
- Customizable colors and blob shapes
- Smooth, organic floating motion
- Customizable colors and blob shapes
- Adjustable animation speed and blob count
- Responsive design
- Self-contained embed code
## How It Works
The animation uses CSS keyframes to create the illusion of blobs floating up and down. Multiple blob elements with different animation delays create a realistic lava lamp effect.
## Usage
Simply copy the HTML and CSS into your project. You can customize the colors by changing the gradient values and adjust the animation speed by modifying the animation duration.
The animation uses CSS keyframes to create the illusion of blobs floating up and down. Multiple blob elements with different animation delays and paths create a realistic lava lamp effect. An SVG filter creates the "goo" effect that makes the blobs merge together smoothly.

View file

@ -1,607 +0,0 @@
<div id="button-generator-app">
<div class="generator-container">
<div class="preview-section">
<h3 class="hidden-md-down">Preview</h3>
<div class="preview-container">
<div class="preview-wrapper">
<canvas id="button-canvas" width="88" height="31"></canvas>
</div>
</div>
<button id="download-button" class="btn-primary">Download Button</button>
<div class="presets-container hidden-md-down">
<h3>Presets</h3>
<button id="preset-random" class="btn-secondary">Random Button</button>
<button id="preset-classic" class="btn-secondary">Classic Style</button>
<button id="preset-modern" class="btn-secondary">Modern Style</button>
</div>
</div>
<div class="controls-section">
<div class="control-group">
<h3 class="control-group-header">
<span>Text Line 1</span>
<span class="toggle-icon"></span>
</h3>
<div class="control-group-content">
<label for="button-text">Text</label>
<input
type="text"
id="button-text"
value="RITUAL.SH"
maxlength="20"
/>
<label class="checkbox-label">
<input type="checkbox" id="text-enabled" checked />
<span>Enable Text Line 1</span>
</label>
<label for="font-size"
>Font Size: <span id="font-size-value">14</span>px</label
>
<input type="range" id="font-size" min="6" max="24" value="14" />
<label for="text-x"
>Horizontal Position: <span id="text-x-value">50</span>%</label
>
<input type="range" id="text-x" min="0" max="100" value="50" />
<label for="text-y"
>Vertical Position: <span id="text-y-value">35</span>%</label
>
<input type="range" id="text-y" min="0" max="100" value="35" />
<label for="text-color-type">Text Color Type</label>
<select id="text-color-type">
<option value="solid">Solid Color</option>
<option value="gradient">Gradient</option>
</select>
<div id="text-solid-color">
<label for="text-color">Text Color</label>
<input type="color" id="text-color" value="#ffffff" />
</div>
<div id="text-gradient-color" style="display: none">
<label for="text-gradient-color1">Gradient Color 1</label>
<input type="color" id="text-gradient-color1" value="#ffffff" />
<label for="text-gradient-color2">Gradient Color 2</label>
<input type="color" id="text-gradient-color2" value="#00ffff" />
<label for="text-gradient-angle"
>Gradient Angle:
<span id="text-gradient-angle-value">0</span>°</label
>
<input
type="range"
id="text-gradient-angle"
min="0"
max="360"
value="0"
/>
</div>
<label class="checkbox-label">
<input type="checkbox" id="text-outline" />
<span>Outline</span>
</label>
<input
type="color"
id="outline-color"
value="#000000"
style="display: none"
/>
<label for="font-family">Font</label>
<select id="font-family">
<option value="Lato">Lato</option>
<option value="Roboto">Roboto</option>
<option value="Open Sans">Open Sans</option>
<option value="Montserrat">Montserrat</option>
<option value="Oswald">Oswald</option>
<option value="Bebas Neue">Bebas Neue</option>
<option value="Roboto Mono">Roboto Mono</option>
<option value="VT323">VT323</option>
<option value="Press Start 2P">Press Start 2P</option>
<option value="DSEG7-Classic">DSEG7</option>
</select>
<div class="checkbox-row">
<label class="checkbox-label">
<input type="checkbox" id="font-bold" />
<span>Bold</span>
</label>
<label class="checkbox-label">
<input type="checkbox" id="font-italic" />
<span>Italic</span>
</label>
</div>
</div>
</div>
<div class="control-group">
<h3 class="control-group-header">
<span>Text Line 1 Animation</span>
<span class="toggle-icon"></span>
</h3>
<div class="control-group-content">
<label class="checkbox-label">
<input type="checkbox" id="animate-text-wave" />
<span>Wave Animation</span>
</label>
<div id="wave-controls" style="display: none">
<label for="wave-amplitude"
>Amplitude: <span id="wave-amplitude-value">3</span>px</label
>
<input
type="range"
id="wave-amplitude"
min="0"
max="10"
value="3"
step="0.5"
/>
<label for="wave-speed"
>Speed: <span id="wave-speed-value">1.0</span>x</label
>
<input
type="range"
id="wave-speed"
min="0.5"
max="3"
value="1.0"
step="0.1"
/>
</div>
<label class="checkbox-label">
<input type="checkbox" id="animate-text-rainbow" />
<span>Rainbow Text</span>
</label>
<div id="rainbow-text-controls" style="display: none">
<label for="text-rainbow-speed"
>Speed: <span id="text-rainbow-speed-value">1.0</span>x</label
>
<input
type="range"
id="text-rainbow-speed"
min="0.5"
max="5"
value="1.0"
step="0.1"
/>
</div>
</div>
</div>
<div class="control-group">
<h3 class="control-group-header">
<span>Text Line 2</span>
<span class="toggle-icon"></span>
</h3>
<div class="control-group-content">
<label for="button-text2">Text</label>
<input type="text" id="button-text2" value="" maxlength="20" />
<label class="checkbox-label">
<input type="checkbox" id="text2-enabled" />
<span>Enable Text Line 2</span>
</label>
<label for="font-size2"
>Font Size: <span id="font-size2-value">12</span>px</label
>
<input type="range" id="font-size2" min="6" max="24" value="12" />
<label for="text2-x"
>Horizontal Position: <span id="text2-x-value">50</span>%</label
>
<input type="range" id="text2-x" min="0" max="100" value="50" />
<label for="text2-y"
>Vertical Position: <span id="text2-y-value">65</span>%</label
>
<input type="range" id="text2-y" min="0" max="100" value="65" />
<label for="text2-color-type">Text Color Type</label>
<select id="text2-color-type">
<option value="solid">Solid Color</option>
<option value="gradient">Gradient</option>
</select>
<div id="text2-solid-color">
<label for="text2-color">Text Color</label>
<input type="color" id="text2-color" value="#ffffff" />
</div>
<div id="text2-gradient-color" style="display: none">
<label for="text2-gradient-color1">Gradient Color 1</label>
<input type="color" id="text2-gradient-color1" value="#ffffff" />
<label for="text2-gradient-color2">Gradient Color 2</label>
<input type="color" id="text2-gradient-color2" value="#00ffff" />
<label for="text2-gradient-angle"
>Gradient Angle:
<span id="text2-gradient-angle-value">0</span>°</label
>
<input
type="range"
id="text2-gradient-angle"
min="0"
max="360"
value="0"
/>
</div>
<label class="checkbox-label">
<input type="checkbox" id="text2-outline" />
<span>Outline</span>
</label>
<input
type="color"
id="outline2-color"
value="#000000"
style="display: none"
/>
<label for="font-family2">Font</label>
<select id="font-family2">
<option value="Lato">Lato</option>
<option value="Roboto">Roboto</option>
<option value="Open Sans">Open Sans</option>
<option value="Montserrat">Montserrat</option>
<option value="Oswald">Oswald</option>
<option value="Bebas Neue">Bebas Neue</option>
<option value="Roboto Mono">Roboto Mono</option>
<option value="VT323">VT323</option>
<option value="Press Start 2P">Press Start 2P</option>
<option value="DSEG7-Classic">DSEG7</option>
</select>
<div class="checkbox-row">
<label class="checkbox-label">
<input type="checkbox" id="font-bold2" />
<span>Bold</span>
</label>
<label class="checkbox-label">
<input type="checkbox" id="font-italic2" />
<span>Italic</span>
</label>
</div>
</div>
</div>
<div class="control-group">
<h3 class="control-group-header">
<span>Text Line 2 Animation</span>
<span class="toggle-icon"></span>
</h3>
<div class="control-group-content">
<label class="checkbox-label">
<input type="checkbox" id="animate-text-wave2" />
<span>Wave Animation</span>
</label>
<div id="wave-controls2" style="display: none">
<label for="wave-amplitude2"
>Amplitude: <span id="wave-amplitude2-value">3</span>px</label
>
<input
type="range"
id="wave-amplitude2"
min="0"
max="10"
value="3"
step="0.5"
/>
<label for="wave-speed2"
>Speed: <span id="wave-speed2-value">1.0</span>x</label
>
<input
type="range"
id="wave-speed2"
min="0.5"
max="3"
value="1.0"
step="0.1"
/>
</div>
<label class="checkbox-label">
<input type="checkbox" id="animate-text-rainbow2" />
<span>Rainbow Text</span>
</label>
<div id="rainbow-text2-controls" style="display: none">
<label for="text-rainbow-speed2"
>Speed: <span id="text-rainbow-speed2-value">1.0</span>x</label
>
<input
type="range"
id="text-rainbow-speed2"
min="0.5"
max="5"
value="1.0"
step="0.1"
/>
</div>
</div>
</div>
<div class="control-group">
<h3 class="control-group-header">
<span>Background</span>
<span class="toggle-icon"></span>
</h3>
<div class="control-group-content">
<label for="bg-type">Background Type</label>
<select id="bg-type">
<option value="solid">Solid Color</option>
<option value="gradient">Gradient</option>
<option value="texture">Texture</option>
</select>
<div id="solid-controls">
<label for="bg-color">Background Color</label>
<input type="color" id="bg-color" value="#0066cc" />
</div>
<div id="gradient-controls" style="display: none">
<label for="gradient-color1">Color 1</label>
<input type="color" id="gradient-color1" value="#0066cc" />
<label for="gradient-color2">Color 2</label>
<input type="color" id="gradient-color2" value="#00ccff" />
<label for="gradient-angle"
>Angle: <span id="gradient-angle-value">90</span>°</label
>
<input
type="range"
id="gradient-angle"
min="0"
max="360"
value="90"
/>
</div>
<div id="texture-controls" style="display: none">
<label for="texture-type">Texture Pattern</label>
<select id="texture-type">
<option value="dots">Dots</option>
<option value="grid">Grid</option>
<option value="diagonal">Diagonal Lines</option>
<option value="checkerboard">Checkerboard</option>
<option value="noise">Noise</option>
<option value="stars">Stars</option>
</select>
<label for="texture-color1">Base Color</label>
<input type="color" id="texture-color1" value="#0066cc" />
<label for="texture-color2">Pattern Color</label>
<input type="color" id="texture-color2" value="#0099ff" />
<label for="texture-scale"
>Pattern Scale: <span id="texture-scale-value">50</span>%</label
>
<input
type="range"
id="texture-scale"
min="10"
max="100"
value="50"
/>
</div>
</div>
</div>
<div class="control-group">
<h3 class="control-group-header">
<span>Background Animation</span>
<span class="toggle-icon"></span>
</h3>
<div class="control-group-content">
<label class="checkbox-label">
<input type="checkbox" id="animate-bg-rainbow" />
<span>Rainbow Flash</span>
</label>
<div id="rainbow-bg-controls" style="display: none">
<label for="rainbow-speed"
>Speed: <span id="rainbow-speed-value">0.5</span>x</label
>
<input
type="range"
id="rainbow-speed"
min="0.1"
max="3"
value="0.5"
step="0.1"
/>
</div>
<label class="checkbox-label">
<input type="checkbox" id="animate-bg-rainbow-gradient" />
<span>Rainbow Gradient</span>
</label>
</div>
</div>
<div class="control-group">
<h3 class="control-group-header">
<span>Border</span>
<span class="toggle-icon"></span>
</h3>
<div class="control-group-content">
<label for="border-width"
>Border Width: <span id="border-width-value">2</span>px</label
>
<input type="range" id="border-width" min="0" max="5" value="2" />
<label for="border-color">Border Color</label>
<input type="color" id="border-color" value="#000000" />
<label for="border-style">Border Style</label>
<select id="border-style">
<option value="solid">Solid</option>
<option value="inset">Inset (3D)</option>
<option value="outset">Outset (3D)</option>
<option value="ridge">Ridge</option>
</select>
</div>
</div>
<div class="control-group">
<h3 class="control-group-header">
<span>Special Effects</span>
<span class="toggle-icon"></span>
</h3>
<div class="control-group-content">
<p class="info-text">
Almost all animations should stack, so pick as many as you want.
</p>
<label class="checkbox-label">
<input type="checkbox" id="animate-glitch" />
<span>Glitch Effect</span>
</label>
<div id="glitch-controls" style="display: none">
<label for="glitch-intensity"
>Intensity: <span id="glitch-intensity-value">3</span></label
>
<input
type="range"
id="glitch-intensity"
min="1"
max="10"
value="3"
/>
</div>
<label class="checkbox-label">
<input type="checkbox" id="animate-pulse" />
<span>Pulse Effect</span>
</label>
<div id="pulse-controls" style="display: none">
<label for="pulse-scale"
>Scale: <span id="pulse-scale-value">1.1</span>x</label
>
<input
type="range"
id="pulse-scale"
min="1.0"
max="1.3"
value="1.1"
step="0.05"
/>
</div>
<label class="checkbox-label">
<input type="checkbox" id="animate-shimmer" />
<span>Shimmer Effect</span>
</label>
<label class="checkbox-label">
<input type="checkbox" id="animate-scanline" />
<span>Scanline Effect</span>
</label>
<div id="scanline-controls" style="display: none">
<label for="scanline-intensity"
>Intensity: <span id="scanline-intensity-value">0.3</span></label
>
<input
type="range"
id="scanline-intensity"
min="0.1"
max="1"
value="0.3"
step="0.1"
/>
<label for="scanline-speed"
>Speed: <span id="scanline-speed-value">1.0</span>x</label
>
<input
type="range"
id="scanline-speed"
min="0.5"
max="3"
value="1.0"
step="0.1"
/>
</div>
<label class="checkbox-label">
<input type="checkbox" id="animate-rgb-split" />
<span>RGB Split Effect</span>
</label>
<div id="rgb-split-controls" style="display: none">
<label for="rgb-split-intensity"
>Intensity: <span id="rgb-split-intensity-value">2</span>px</label
>
<input
type="range"
id="rgb-split-intensity"
min="1"
max="5"
value="2"
step="0.5"
/>
</div>
<label class="checkbox-label">
<input type="checkbox" id="animate-noise" />
<span>Noise Effect</span>
</label>
<div id="noise-controls" style="display: none">
<label for="noise-intensity"
>Intensity: <span id="noise-intensity-value">0.1</span></label
>
<input
type="range"
id="noise-intensity"
min="0.05"
max="0.5"
value="0.1"
step="0.05"
/>
</div>
{{/*
<label class="checkbox-label">
<input type="checkbox" id="animate-rotate" />
<span>Rotate Effect</span>
</label>
<div id="rotate-controls" style="display: none">
<label for="rotate-angle"
>Max Angle: <span id="rotate-angle-value">5</span>°</label
>
<input
type="range"
id="rotate-angle"
min="2"
max="15"
value="5"
step="1"
/>
<label for="rotate-speed"
>Speed: <span id="rotate-speed-value">1.0</span>x</label
>
<input
type="range"
id="rotate-speed"
min="0.5"
max="3"
value="1.0"
step="0.1"
/>
</div>
*/}}
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,755 @@
<div id="lavalamp-adoptable-app">
<div class="adoptable-container">
<div class="preview-section">
<h3>Preview</h3>
<div class="preview-grid">
<div class="preview-item">
<div class="preview-wrapper preview-flexible">
<div
id="lavalamp-preview-flex"
class="lavalamp-preview-container"
></div>
</div>
<span class="preview-label">40px × 60px</span>
</div>
<div class="preview-item">
<div class="preview-wrapper preview-100">
<div
id="lavalamp-preview-100"
class="lavalamp-preview-container"
></div>
</div>
<span class="preview-label">100px × 200px</span>
</div>
<div class="preview-item">
<div class="preview-wrapper preview-200">
<div
id="lavalamp-preview-200"
class="lavalamp-preview-container"
></div>
</div>
<span class="preview-label">200px × 350px</span>
</div>
</div>
</div>
<div class="controls-section">
<h3>Customize Your Lava Lamp</h3>
<div class="control-group">
<label for="bg-color-1">Background Color 1</label>
<input type="color" id="bg-color-1" value="#F8E45C" />
</div>
<div class="control-group">
<label for="bg-color-2">Background Color 2</label>
<input type="color" id="bg-color-2" value="#FF7800" />
</div>
<div class="control-group">
<label for="blob-color-1">Blob Color 1</label>
<input type="color" id="blob-color-1" value="#FF4500" />
</div>
<div class="control-group">
<label for="blob-color-2">Blob Color 2</label>
<input type="color" id="blob-color-2" value="#FF6347" />
</div>
<div class="control-group">
<label for="case-color">Case Color</label>
<input type="color" id="case-color" value="#333333" />
</div>
<div class="control-group">
<label for="blob-count"
>Number of Blobs: <span id="blob-count-value">6</span></label
>
<input type="range" id="blob-count" min="3" max="12" value="6" />
</div>
<div class="control-group">
<label for="blob-size"
>Blob Size: <span id="blob-size-value">1.0</span>x</label
>
<input
type="range"
id="blob-size"
min="0.5"
max="2.0"
step="0.1"
value="1.0"
/>
</div>
<div class="control-group">
<label for="speed">Speed: <span id="speed-value">1.0</span>x</label>
<input
type="range"
id="speed"
min="0.5"
max="1.5"
step="0.1"
value="1.0"
/>
</div>
<div class="control-group">
<label>
<input type="checkbox" id="pixelate" />
Pixelate
</label>
</div>
<div class="control-group" id="pixel-size-group" style="display: none">
<label for="pixel-size"
>Pixel Size: <span id="pixel-size-value">4</span>px</label
>
<input type="range" id="pixel-size" min="2" max="10" value="4" />
</div>
<div class="control-group">
<button id="get-code-btn" class="btn-primary">Get Embed Code</button>
</div>
</div>
</div>
<div id="embed-code-section" style="display: none; margin-top: 2rem">
<h3>Embed Code</h3>
<p>Copy this code and paste it anywhere on your website:</p>
<div class="highlight">
<pre><code id="embed-code-display" class="language-html" data-lang="html"></code></pre>
</div>
</div>
</div>
<style>
.adoptable-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin: 2rem 0;
}
@media (max-width: 768px) {
.adoptable-container {
grid-template-columns: 1fr;
}
}
.preview-section {
display: flex;
flex-direction: column;
}
.preview-section h3 {
margin-top: 0;
margin-bottom: 1rem;
}
.preview-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-bottom: 1rem;
}
@media (max-width: 1024px) {
.preview-grid {
grid-template-columns: 1fr;
}
}
.preview-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.preview-wrapper {
border: 2px solid #333;
border-radius: 8px;
padding: 1rem;
background-image:
linear-gradient(45deg, #ccc 25%, transparent 25%),
linear-gradient(-45deg, #ccc 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #ccc 75%),
linear-gradient(-45deg, transparent 75%, #ccc 75%);
background-size: 20px 20px;
background-position:
0 0,
0 10px,
10px -10px,
-10px 0px;
background-color: #fff;
}
.preview-flexible {
width: 40px;
height: 75px;
padding: 3px;
}
.preview-100 {
width: 100px;
height: 200px;
}
.preview-200 {
width: 200px;
height: 350px;
}
.preview-label {
font-size: 0.875rem;
color: #666;
text-align: center;
}
.lavalamp-preview-container {
width: 100%;
height: 100%;
}
.controls-section h3 {
margin-top: 0;
}
.control-group {
margin-bottom: 1.5rem;
}
.control-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.control-group input[type="color"] {
width: 100%;
height: 40px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
.control-group input[type="range"] {
width: 100%;
}
.btn-primary,
.btn-secondary {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-primary:hover {
background: #0056b3;
}
.btn-secondary {
background: #6c757d;
color: white;
margin-top: 1rem;
}
.btn-secondary:hover {
background: #545b62;
}
/* Lava Lamp Styles */
.lavalamp-adoptable {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.lavalamp-adoptable .lamp-cap {
width: 60%;
height: 8%;
flex-shrink: 0;
border-radius: 50% 50% 0 0;
box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.3);
position: relative;
z-index: 10;
}
.lavalamp-adoptable .lamp-body {
position: relative;
width: 100%;
flex: 1;
clip-path: polygon(20% 0, 80% 0, 100% 101%, 0% 101%);
overflow: hidden;
}
.lavalamp-adoptable .lamp-body::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.15) 20%,
rgba(255, 255, 255, 0.05) 40%,
transparent 60%
);
pointer-events: none;
}
.lavalamp-adoptable .blobs-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 2;
}
.lavalamp-adoptable .blob {
position: absolute;
border-radius: 50%;
animation: lavalamp-float var(--duration) ease-in-out infinite;
opacity: 0.95;
mix-blend-mode: normal;
z-index: 3;
}
.lavalamp-adoptable .lamp-base {
width: 100%;
height: 15%;
flex-shrink: 0;
border-radius: 0 0 50% 50%;
box-shadow:
inset 0 2px 5% rgba(255, 255, 255, 0.2),
inset 0 -2px 5% rgba(0, 0, 0, 0.5);
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes lavalamp-float {
0%,
100% {
transform: translate(var(--start-x), var(--start-y)) scale(1);
}
25% {
transform: translate(var(--mid1-x), var(--mid1-y))
scale(var(--scale1, 1.1));
}
50% {
transform: translate(var(--mid2-x), var(--mid2-y))
scale(var(--scale2, 0.9));
}
75% {
transform: translate(var(--mid3-x), var(--mid3-y))
scale(var(--scale3, 1.05));
}
}
</style>
<script>
(function () {
"use strict";
const previewContainers = {
flex: document.getElementById("lavalamp-preview-flex"),
small: document.getElementById("lavalamp-preview-100"),
large: document.getElementById("lavalamp-preview-200"),
};
const bgColor1Input = document.getElementById("bg-color-1");
const bgColor2Input = document.getElementById("bg-color-2");
const blobColor1Input = document.getElementById("blob-color-1");
const blobColor2Input = document.getElementById("blob-color-2");
const caseColorInput = document.getElementById("case-color");
const blobCountInput = document.getElementById("blob-count");
const speedInput = document.getElementById("speed");
const blobSizeInput = document.getElementById("blob-size");
const pixelateInput = document.getElementById("pixelate");
const pixelSizeInput = document.getElementById("pixel-size");
const pixelSizeGroup = document.getElementById("pixel-size-group");
const blobCountValue = document.getElementById("blob-count-value");
const speedValue = document.getElementById("speed-value");
const blobSizeValue = document.getElementById("blob-size-value");
const pixelSizeValue = document.getElementById("pixel-size-value");
const getCodeBtn = document.getElementById("get-code-btn");
const embedCodeSection = document.getElementById("embed-code-section");
const embedCodeDisplay = document.getElementById("embed-code-display");
const copyCodeBtn = document.getElementById("copy-code-btn");
const copyStatus = document.getElementById("copy-status");
let lampInstances = {
flex: { lampElements: {}, blobs: [] },
small: { lampElements: {}, blobs: [] },
large: { lampElements: {}, blobs: [] },
};
// Initialize the preview lamps
function initPreview() {
Object.keys(previewContainers).forEach((key, index) => {
const previewContainer = previewContainers[key];
const gooFilterId = `goo-preview-${key}`;
// Create SVG filters
const svg = document.createElementNS(
"http://www.w3.org/2000/svg",
"svg",
);
svg.style.position = "absolute";
svg.style.width = "0";
svg.style.height = "0";
const pixelateFilterId = `pixelate-preview-${key}`;
svg.innerHTML = `
<defs>
<filter id="${gooFilterId}">
<feGaussianBlur in="SourceGraphic" stdDeviation="7" result="blur" />
<feColorMatrix
in="blur"
mode="matrix"
values="
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 18 -7"
result="goo"
/>
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
<filter id="${pixelateFilterId}" x="0%" y="0%" width="100%" height="100%">
<feFlood x="0" y="0" height="1" width="1"/>
<feComposite width="4" height="4" class="pixelate-composite"/>
<feTile result="a"/>
<feComposite in="SourceGraphic" in2="a" operator="in"/>
<feMorphology operator="dilate" radius="2" class="pixelate-morphology"/>
</filter>
</defs>
`;
const container = document.createElement("div");
container.className = "lavalamp-adoptable";
container.style.width = "100%";
container.style.height = "100%";
const lampCap = document.createElement("div");
lampCap.className = "lamp-cap";
const lampBody = document.createElement("div");
lampBody.className = "lamp-body";
const blobsContainer = document.createElement("div");
blobsContainer.className = "blobs-container";
// Initially set the goo filter, will be adjusted after layout
blobsContainer.style.filter = `url(#${gooFilterId})`;
const lampBase = document.createElement("div");
lampBase.className = "lamp-base";
lampBody.appendChild(blobsContainer);
container.appendChild(svg);
container.appendChild(lampCap);
container.appendChild(lampBody);
container.appendChild(lampBase);
previewContainer.appendChild(container);
lampInstances[key].lampElements = {
container,
lampCap,
lampBody,
lampBase,
blobsContainer,
svg,
pixelateFilterId,
};
});
// Apply initial styles
updatePreview();
// Adjust goo filter for small lamps after layout
adjustGooFilters();
}
// Adjust goo filters based on container width (matches embedded script logic)
function adjustGooFilters() {
Object.keys(lampInstances).forEach((key) => {
const instance = lampInstances[key];
const { lampElements } = instance;
if (!lampElements.lampBody) return;
const containerWidth = lampElements.lampBody.offsetWidth;
});
}
// Create a blob element for a specific instance
function createBlob(lampBody, lampHeight) {
const blob = document.createElement("div");
blob.className = "blob";
const containerWidth = lampBody.offsetWidth;
const containerHeight = lampHeight || lampBody.offsetHeight;
const blobSizeMultiplier = parseFloat(blobSizeInput.value);
const size =
(Math.random() * 0.15 + 0.25) * containerWidth * blobSizeMultiplier;
const duration = (Math.random() * 8 + 12) / parseFloat(speedInput.value);
const maxX = containerWidth - size;
const startX = Math.random() * maxX;
blob.style.width = `${size}px`;
blob.style.height = `${size}px`;
blob.style.left = `${startX}px`;
blob.style.bottom = "10px";
blob.style.position = "absolute";
blob.style.setProperty("--duration", `${duration}s`);
blob.style.setProperty("--start-x", "0px");
blob.style.setProperty("--start-y", "0px");
blob.style.setProperty(
"--mid1-x",
`${(Math.random() * 0.3 - 0.15) * containerWidth}px`,
);
blob.style.setProperty(
"--mid1-y",
`${-(Math.random() * 0.15 + 0.25) * containerHeight}px`,
);
blob.style.setProperty(
"--mid2-x",
`${(Math.random() * 0.4 - 0.2) * containerWidth}px`,
);
blob.style.setProperty(
"--mid2-y",
`${-(Math.random() * 0.2 + 0.5) * containerHeight}px`,
);
blob.style.setProperty(
"--mid3-x",
`${(Math.random() * 0.3 - 0.15) * containerWidth}px`,
);
blob.style.setProperty(
"--mid3-y",
`${-(Math.random() * 0.15 + 0.8) * containerHeight}px`,
);
blob.style.setProperty(
"--scale1",
(Math.random() * 0.3 + 1.0).toFixed(2),
);
blob.style.setProperty(
"--scale2",
(Math.random() * 0.3 + 0.85).toFixed(2),
);
blob.style.setProperty(
"--scale3",
(Math.random() * 0.3 + 0.95).toFixed(2),
);
blob.style.animationDelay = `${Math.random() * -20}s`;
return blob;
}
// Update blob count for all instances
function updateBlobCount() {
const count = parseInt(blobCountInput.value);
Object.keys(lampInstances).forEach((key) => {
const instance = lampInstances[key];
const { lampElements, blobs } = instance;
if (!lampElements.blobsContainer) return;
while (blobs.length > count) {
const blob = blobs.pop();
lampElements.blobsContainer.removeChild(blob);
}
while (blobs.length < count) {
const blob = createBlob(lampElements.lampBody);
updateBlobColors(blob);
blobs.push(blob);
lampElements.blobsContainer.appendChild(blob);
}
});
}
// Update blob colors
function updateBlobColors(blob) {
const color1 = blobColor1Input.value;
const color2 = blobColor2Input.value;
blob.style.background = `radial-gradient(circle at 30% 30%, ${color1}, ${color2})`;
}
// Adjust brightness helper
function adjustBrightness(color, percent) {
const num = parseInt(color.replace("#", ""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const G = ((num >> 8) & 0x00ff) + amt;
const B = (num & 0x0000ff) + amt;
return (
"#" +
(
0x1000000 +
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
(G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
(B < 255 ? (B < 1 ? 0 : B) : 255)
)
.toString(16)
.slice(1)
);
}
// Update the preview for all instances
function updatePreview() {
Object.keys(lampInstances).forEach((key) => {
const instance = lampInstances[key];
const { lampElements, blobs } = instance;
if (!lampElements.lampBody) return;
// Update background gradient
lampElements.lampBody.style.background = `linear-gradient(180deg, ${bgColor1Input.value} 0%, ${bgColor2Input.value} 100%)`;
// Update cap and base color
const baseColor = caseColorInput.value;
lampElements.lampCap.style.background = `linear-gradient(180deg, ${adjustBrightness(baseColor, 40)} 0%, ${baseColor} 50%, ${adjustBrightness(baseColor, -20)} 100%)`;
lampElements.lampBase.style.background = `linear-gradient(180deg, ${baseColor} 0%, ${adjustBrightness(baseColor, -20)} 40%, ${adjustBrightness(baseColor, -40)} 100%)`;
lampElements.lampBase.style.borderTop = `1px solid ${adjustBrightness(bgColor2Input.value, -30)}`;
// Update all blob colors
blobs.forEach((blob) => updateBlobColors(blob));
});
// Update blob count
updateBlobCount();
}
// Update pixelation filters for all instances
function updatePixelation() {
const isPixelated = pixelateInput.checked;
const pixelSize = parseInt(pixelSizeInput.value);
Object.keys(lampInstances).forEach((key) => {
const instance = lampInstances[key];
const { lampElements } = instance;
if (isPixelated) {
// Update the filter in the SVG
const filter = lampElements.svg.querySelector(
`#${lampElements.pixelateFilterId}`,
);
const composite = filter.querySelector("feComposite[width]");
const morphology = filter.querySelector("feMorphology");
composite.setAttribute("width", pixelSize);
composite.setAttribute("height", pixelSize);
morphology.setAttribute("radius", Math.floor(pixelSize / 2));
// Apply filter to container
lampElements.container.style.filter = `url(#${lampElements.pixelateFilterId})`;
} else {
// Remove filter
lampElements.container.style.filter = "";
}
});
}
// Generate embed code
function generateEmbedCode() {
const siteUrl = window.location.origin;
let pixelateAttrs = "";
if (pixelateInput.checked) {
pixelateAttrs = '\n data-pixelate="true"';
if (parseInt(pixelSizeInput.value) !== 4) {
pixelateAttrs += `\n data-pixel-size="${pixelSizeInput.value}"`;
}
}
return `<script src="${siteUrl}/js/adoptables/lavalamp.js"
data-bg-color-1="${bgColor1Input.value}"
data-bg-color-2="${bgColor2Input.value}"
data-blob-color-1="${blobColor1Input.value}"
data-blob-color-2="${blobColor2Input.value}"
data-case-color="${caseColorInput.value}"
data-blob-count="${blobCountInput.value}"
data-speed="${speedInput.value}"
data-blob-size="${blobSizeInput.value}"${pixelateAttrs}><\/script>`;
}
// Event listeners
bgColor1Input.addEventListener("input", updatePreview);
bgColor2Input.addEventListener("input", updatePreview);
blobColor1Input.addEventListener("input", updatePreview);
blobColor2Input.addEventListener("input", updatePreview);
caseColorInput.addEventListener("input", updatePreview);
blobCountInput.addEventListener("input", function () {
blobCountValue.textContent = this.value;
updatePreview();
});
speedInput.addEventListener("input", function () {
speedValue.textContent = parseFloat(this.value).toFixed(1);
// Speed changes require recreating blobs for all instances
Object.keys(lampInstances).forEach((key) => {
const instance = lampInstances[key];
instance.blobs.forEach((blob) =>
instance.lampElements.blobsContainer.removeChild(blob),
);
instance.blobs = [];
});
updatePreview();
});
blobSizeInput.addEventListener("input", function () {
blobSizeValue.textContent = parseFloat(this.value).toFixed(1);
// Size changes require recreating blobs for all instances
Object.keys(lampInstances).forEach((key) => {
const instance = lampInstances[key];
instance.blobs.forEach((blob) =>
instance.lampElements.blobsContainer.removeChild(blob),
);
instance.blobs = [];
});
updatePreview();
});
pixelateInput.addEventListener("change", function () {
// Show/hide pixel size slider
if (this.checked) {
pixelSizeGroup.style.display = "block";
} else {
pixelSizeGroup.style.display = "none";
}
updatePixelation();
});
pixelSizeInput.addEventListener("input", function () {
pixelSizeValue.textContent = this.value;
updatePixelation();
});
getCodeBtn.addEventListener("click", function () {
embedCodeDisplay.textContent = generateEmbedCode();
embedCodeSection.style.display = "block";
embedCodeSection.scrollIntoView({ behavior: "smooth" });
});
// Initialize
initPreview();
})();
</script>

View file

@ -0,0 +1,364 @@
(function () {
"use strict";
const currentScript = document.currentScript;
if (!currentScript) {
console.error("Lava Lamp: Could not find current script tag");
return;
}
// Read configuration from data attributes with defaults
const config = {
bgColor1: currentScript.dataset["bgColor-1"] || "#F8E45C",
bgColor2: currentScript.dataset["bgColor-2"] || "#FF7800",
blobColor1: currentScript.dataset["blobColor-1"] || "#FF4500",
blobColor2: currentScript.dataset["blobColor-2"] || "#FF6347",
caseColor: currentScript.dataset.caseColor || "#333333",
blobCount: parseInt(currentScript.dataset.blobCount) || 6,
speed: parseFloat(currentScript.dataset.speed) || 1.0,
blobSize: parseFloat(currentScript.dataset.blobSize) || 1.0,
pixelate: currentScript.dataset.pixelate === "true" || false,
pixelSize: parseInt(currentScript.dataset.pixelSize) || 4,
};
// Helper function to adjust color brightness
function adjustBrightness(color, percent) {
const num = parseInt(color.replace("#", ""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const G = ((num >> 8) & 0x00ff) + amt;
const B = (num & 0x0000ff) + amt;
return (
"#" +
(
0x1000000 +
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
(G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
(B < 255 ? (B < 1 ? 0 : B) : 255)
)
.toString(16)
.slice(1)
);
}
// Create a host element with shadow DOM for isolation
const host = document.createElement("div");
host.style.width = "100%";
host.style.height = "100%";
host.style.display = "block";
if (config.pixelate) {
host.style.overflow = "hidden";
}
// Attach shadow DOM
const shadowRoot = host.attachShadow({ mode: "open" });
currentScript.parentNode.insertBefore(host, currentScript.nextSibling);
const gooFilterId = "goo-filter";
// Inject CSS into shadow DOM
const style = document.createElement("style");
style.textContent = `
.lavalamp-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
${config.pixelate ? "filter: url(#pixelate-filter);" : ""}
}
.lamp-cap {
width: 60%;
height: 8%;
flex-shrink: 0;
border-radius: 50% 50% 0 0;
box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.3);
position: relative;
z-index: 10;
}
.lamp-body {
position: relative;
width: 100%;
flex: 1;
clip-path: polygon(20% 0, 80% 0, 100% 101%, 0% 101%);
overflow: hidden;
}
.lamp-body::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.15) 20%,
rgba(255, 255, 255, 0.05) 40%,
transparent 60%
);
pointer-events: none;
}
.blobs-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
filter: url(#${gooFilterId});
pointer-events: none;
z-index: 2;
}
.blob {
position: absolute;
border-radius: 50%;
animation: lavalamp-float var(--duration) ease-in-out infinite;
opacity: 0.95;
mix-blend-mode: normal;
z-index: 3;
}
.lamp-base {
width: 100%;
height: 15%;
flex-shrink: 0;
border-radius: 0 0 50% 50%;
box-shadow:
inset 0 2px 5px rgba(255, 255, 255, 0.2),
inset 0 -2px 5px rgba(0, 0, 0, 0.5);
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes lavalamp-float {
0%, 100% {
transform: translate(var(--start-x), var(--start-y)) scale(1);
}
25% {
transform: translate(var(--mid1-x), var(--mid1-y)) scale(var(--scale1, 1.1));
}
50% {
transform: translate(var(--mid2-x), var(--mid2-y)) scale(var(--scale2, 0.9));
}
75% {
transform: translate(var(--mid3-x), var(--mid3-y)) scale(var(--scale3, 1.05));
}
}
`;
shadowRoot.appendChild(style);
// Create the HTML structure
const container = document.createElement("div");
container.className = "lavalamp-container";
// SVG filters for goo effect and pixelation
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.style.position = "absolute";
svg.style.width = "0";
svg.style.height = "0";
svg.innerHTML = `
<defs>
<filter id="${gooFilterId}">
<feGaussianBlur in="SourceGraphic" stdDeviation="7" result="blur" />
<feColorMatrix
in="blur"
mode="matrix"
values="
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 18 -7"
result="goo"
/>
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
${
config.pixelate
? `
<filter id="pixelate-filter" x="0%" y="0%" width="100%" height="100%">
<feFlood x="0" y="0" height="1" width="1"/>
<feComposite width="${config.pixelSize}" height="${config.pixelSize}"/>
<feTile result="a"/>
<feComposite in="SourceGraphic" in2="a" operator="in"/>
<feMorphology operator="dilate" radius="${Math.floor(config.pixelSize / 2)}"/>
</filter>
`
: ""
}
</defs>
`;
const lampCap = document.createElement("div");
lampCap.className = "lamp-cap";
lampCap.style.background = `linear-gradient(180deg, ${adjustBrightness(config.caseColor, 40)} 0%, ${config.caseColor} 50%, ${adjustBrightness(config.caseColor, -20)} 100%)`;
const lampBody = document.createElement("div");
lampBody.className = "lamp-body";
lampBody.style.background = `linear-gradient(180deg, ${config.bgColor1} 0%, ${config.bgColor2} 100%)`;
const blobsContainer = document.createElement("div");
blobsContainer.className = "blobs-container";
const lampBase = document.createElement("div");
lampBase.className = "lamp-base";
lampBase.style.background = `linear-gradient(180deg, ${config.caseColor} 0%, ${adjustBrightness(config.caseColor, -20)} 40%, ${adjustBrightness(config.caseColor, -40)} 100%)`;
lampBase.style.borderTop = `1px solid ${adjustBrightness(config.bgColor2, -30)}`;
// Assemble the structure
lampBody.appendChild(blobsContainer);
container.appendChild(svg);
container.appendChild(lampCap);
container.appendChild(lampBody);
container.appendChild(lampBase);
// Append to shadow DOM
shadowRoot.appendChild(container);
// Blob creation and animation
let blobs = [];
function createBlob() {
const blob = document.createElement("div");
blob.className = "blob";
// Get container dimensions
const containerWidth = lampBody.offsetWidth;
const containerHeight = lampBody.offsetHeight;
// Size relative to container width (25-40% of width)
const size =
(Math.random() * 0.15 + 0.25) * containerWidth * config.blobSize;
const duration = (Math.random() * 8 + 12) / config.speed;
// Max X position accounting for blob size and container margins
const maxX = Math.max(0, containerWidth - size);
const startX = maxX > 0 ? Math.random() * maxX : 0;
// Create gradient for blob
const blobGradient = `radial-gradient(circle at 30% 30%, ${config.blobColor1}, ${config.blobColor2})`;
blob.style.width = `${size}px`;
blob.style.height = `${size}px`;
blob.style.left = `${startX}px`;
blob.style.bottom = "10px";
blob.style.position = "absolute";
blob.style.background = blobGradient;
blob.style.setProperty("--duration", `${duration}s`);
blob.style.setProperty("--start-x", "0px");
blob.style.setProperty("--start-y", "0px");
// Movement waypoints
blob.style.setProperty(
"--mid1-x",
`${(Math.random() * 0.3 - 0.15) * containerWidth}px`,
);
blob.style.setProperty(
"--mid1-y",
`${-(Math.random() * 0.15 + 0.25) * containerHeight}px`,
);
blob.style.setProperty(
"--mid2-x",
`${(Math.random() * 0.4 - 0.2) * containerWidth}px`,
);
blob.style.setProperty(
"--mid2-y",
`${-(Math.random() * 0.2 + 0.5) * containerHeight}px`,
);
blob.style.setProperty(
"--mid3-x",
`${(Math.random() * 0.3 - 0.15) * containerWidth}px`,
);
blob.style.setProperty(
"--mid3-y",
`${-(Math.random() * 0.15 + 0.8) * containerHeight}px`,
);
// Scale variations
blob.style.setProperty("--scale1", (Math.random() * 0.3 + 1.0).toFixed(2));
blob.style.setProperty("--scale2", (Math.random() * 0.3 + 0.85).toFixed(2));
blob.style.setProperty("--scale3", (Math.random() * 0.3 + 0.95).toFixed(2));
// Random delay to stagger animations
blob.style.animationDelay = `${Math.random() * -20}s`;
return blob;
}
function updateBlobCount() {
while (blobs.length > config.blobCount) {
const blob = blobs.pop();
blobsContainer.removeChild(blob);
}
while (blobs.length < config.blobCount) {
const blob = createBlob();
blobs.push(blob);
blobsContainer.appendChild(blob);
}
}
// Initialize
function init() {
// Wait for container to have dimensions
// Not sure if this is a great idea, what if this never happens for some reason
// This is as good as I can come up with right now
if (lampBody.offsetWidth === 0 || lampBody.offsetHeight === 0) {
setTimeout(init, 100);
return;
}
// Scale goo filter blur based on container width
const containerWidth = lampBody.offsetWidth;
// Apply scaled goo filter
let blurAmount, alphaMultiplier, alphaBias;
if (containerWidth < 80) {
blurAmount = 3;
alphaMultiplier = 12;
alphaBias = -5;
} else {
blurAmount = Math.max(4, Math.min(7, containerWidth / 20));
alphaMultiplier = 18;
alphaBias = -7;
}
const gooFilter = svg.querySelector(`#${gooFilterId} feGaussianBlur`);
if (gooFilter) {
gooFilter.setAttribute("stdDeviation", blurAmount);
}
const colorMatrix = svg.querySelector(`#${gooFilterId} feColorMatrix`);
if (colorMatrix) {
colorMatrix.setAttribute(
"values",
`
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 ${alphaMultiplier} ${alphaBias}
`,
);
}
updateBlobCount();
}
// Start when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

371
static/lavalamp-demo.html Normal file
View file

@ -0,0 +1,371 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lava Lamp Adoptable - Demo Examples</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
color: #fff;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
text-align: center;
margin-bottom: 1rem;
font-size: 2.5rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.subtitle {
text-align: center;
margin-bottom: 3rem;
opacity: 0.9;
font-size: 1.1rem;
}
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-bottom: 3rem;
}
.demo-item {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 1.5rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
align-items: center;
}
.demo-title {
font-size: 1.2rem;
margin-bottom: 1rem;
font-weight: 600;
}
.lamp-container {
background-image:
linear-gradient(45deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%),
linear-gradient(
-45deg,
rgba(255, 255, 255, 0.1) 25%,
transparent 25%
),
linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%),
linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%);
background-size: 20px 20px;
background-position:
0 0,
0 10px,
10px -10px,
-10px 0px;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 1rem;
border: 2px solid rgba(255, 255, 255, 0.2);
}
.code-section {
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 2rem;
margin-top: 2rem;
}
.code-section h2 {
margin-bottom: 1rem;
font-size: 1.8rem;
}
pre {
background: rgba(0, 0, 0, 0.5);
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
font-family: "Courier New", monospace;
font-size: 0.9rem;
line-height: 1.5;
}
code {
color: #ffd700;
}
.size-label {
margin-top: 0.5rem;
font-size: 0.875rem;
opacity: 0.8;
}
@media (max-width: 768px) {
.demo-grid {
grid-template-columns: 1fr;
}
h1 {
font-size: 2rem;
}
}
</style>
</head>
<body>
<div class="container">
<h1>🌋 Lava Lamp Adoptable Demo</h1>
<p class="subtitle">
See the lava lamp in action at various sizes and color schemes!
</p>
<div class="demo-grid">
<!-- Classic Orange -->
<div class="demo-item">
<div class="demo-title">Classic Orange</div>
<div class="lamp-container" style="width: 150px; height: 280px">
<script
src="/js/adoptables/lavalamp.js"
data-bg-color-1="#F8E45C"
data-bg-color-2="#FF7800"
data-blob-color-1="#FF4500"
data-blob-color-2="#FF6347"
data-case-color="#333333"
data-blob-count="6"
data-speed="1.0"
data-blob-size="1.0"
></script>
</div>
<div class="size-label">150px × 280px</div>
</div>
<!-- Purple Dreams -->
<div class="demo-item">
<div class="demo-title">Purple Dreams</div>
<div class="lamp-container" style="width: 120px; height: 250px">
<script
src="/js/adoptables/lavalamp.js"
data-bg-color-1="#9D50BB"
data-bg-color-2="#6E48AA"
data-blob-color-1="#DA22FF"
data-blob-color-2="#9733EE"
data-case-color="#1a0033"
data-blob-count="5"
data-speed="0.8"
data-blob-size="1.2"
></script>
</div>
<div class="size-label">120px × 250px</div>
</div>
<!-- Ocean Blue -->
<div class="demo-item">
<div class="demo-title">Ocean Blue</div>
<div class="lamp-container" style="width: 100px; height: 200px">
<script
src="/js/adoptables/lavalamp.js"
data-bg-color-1="#4facfe"
data-bg-color-2="#00f2fe"
data-blob-color-1="#0066ff"
data-blob-color-2="#00ccff"
data-case-color="#001a33"
data-blob-count="8"
data-speed="1.2"
data-blob-size="0.8"
></script>
</div>
<div class="size-label">100px × 200px</div>
</div>
<!-- Lava Red -->
<div class="demo-item">
<div class="demo-title">Lava Red</div>
<div class="lamp-container" style="width: 180px; height: 320px">
<script
src="/js/adoptables/lavalamp.js"
data-bg-color-1="#ff5858"
data-bg-color-2="#c9184a"
data-blob-color-1="#ff0000"
data-blob-color-2="#cc0000"
data-case-color="#2d0a0a"
data-blob-count="7"
data-speed="0.9"
data-blob-size="1.3"
></script>
</div>
<div class="size-label">180px × 320px</div>
</div>
<!-- Mint Fresh -->
<div class="demo-item">
<div class="demo-title">Mint Fresh</div>
<div class="lamp-container" style="width: 140px; height: 260px">
<script
src="/js/adoptables/lavalamp.js"
data-bg-color-1="#a8edea"
data-bg-color-2="#fed6e3"
data-blob-color-1="#00d4aa"
data-blob-color-2="#00aacc"
data-case-color="#0d3d3d"
data-blob-count="6"
data-speed="1.1"
data-blob-size="1.0"
></script>
</div>
<div class="size-label">140px × 260px</div>
</div>
<!-- Sunset Glow -->
<div class="demo-item">
<div class="demo-title">Sunset Glow</div>
<div class="lamp-container" style="width: 110px; height: 220px">
<script
src="/js/adoptables/lavalamp.js"
data-bg-color-1="#ffeaa7"
data-bg-color-2="#fd79a8"
data-blob-color-1="#ff6b6b"
data-blob-color-2="#feca57"
data-case-color="#2d3436"
data-blob-count="5"
data-speed="0.7"
data-blob-size="1.5"
></script>
</div>
<div class="size-label">110px × 220px</div>
</div>
<!-- Cyber Green (Pixelated) -->
<div class="demo-item">
<div class="demo-title">Cyber Green (Pixelated)</div>
<div class="lamp-container" style="width: 160px; height: 300px">
<script
src="/js/adoptables/lavalamp.js"
data-bg-color-1="#38ef7d"
data-bg-color-2="#11998e"
data-blob-color-1="#00ff00"
data-blob-color-2="#00cc66"
data-case-color="#0a0a0a"
data-blob-count="9"
data-speed="1.3"
data-blob-size="0.7"
data-pixelate="true"
data-pixel-size="6"
></script>
</div>
<div class="size-label">160px × 300px (6px pixels)</div>
</div>
<!-- Pink Bubble (Pixelated) -->
<div class="demo-item">
<div class="demo-title">Pink Bubble (Pixelated)</div>
<div class="lamp-container" style="width: 130px; height: 240px">
<script
src="/js/adoptables/lavalamp.js"
data-bg-color-1="#ffeef8"
data-bg-color-2="#ffb3d9"
data-blob-color-1="#ff1493"
data-blob-color-2="#ff69b4"
data-case-color="#4d1933"
data-blob-count="4"
data-speed="0.6"
data-blob-size="1.8"
data-pixelate="true"
data-pixel-size="3"
></script>
</div>
<div class="size-label">130px × 240px (3px pixels)</div>
</div>
<!-- Tiny (Pixelated) -->
<div class="demo-item">
<div class="demo-title">Tiny Retro (Pixelated)</div>
<div
class="lamp-container"
style="width: 40px; height: 80px; padding: 1px"
>
<script
src="/js/adoptables/lavalamp.js"
data-bg-color-1="#00d9ff"
data-bg-color-2="#0099cc"
data-blob-color-1="#ffcc00"
data-blob-color-2="#ffcc00"
data-case-color="#1a1a1a"
data-blob-count="4"
data-speed="1.5"
data-blob-size="1.5"
data-pixelate="true"
data-pixel-size="3"
></script>
</div>
<div class="size-label">40px × 80px (3px pixels)</div>
</div>
</div>
<div class="code-section">
<h2>How to Use</h2>
<p style="margin-bottom: 1rem">
Simply copy and paste this code into your website. The lava lamp will
scale to fit any container size you specify!
</p>
<pre><code>&lt;div style="width: 200px; height: 350px;"&gt;
&lt;script src="https://ritual.sh/js/adoptables/lavalamp.js"
data-bg-color-1="#F8E45C"
data-bg-color-2="#FF7800"
data-blob-color-1="#FF4500"
data-blob-color-2="#FF6347"
data-case-color="#333333"
data-blob-count="6"
data-speed="1.0"
data-blob-size="1.0"&gt;&lt;/script&gt;
&lt;/div&gt;</code></pre>
<h2 style="margin-top: 2rem">Customization Options</h2>
<ul style="line-height: 2; margin-top: 1rem; margin-left: 1.5rem">
<li>
<code>data-bg-color-1</code> & <code>data-bg-color-2</code>:
Background gradient colors
</li>
<li>
<code>data-blob-color-1</code> & <code>data-blob-color-2</code>:
Blob gradient colors
</li>
<li>
<code>data-case-color</code>: Color for the top cap and bottom base
</li>
<li><code>data-blob-count</code>: Number of blobs (3-12)</li>
<li>
<code>data-speed</code>: Animation speed multiplier (0.5-1.5x)
</li>
<li><code>data-blob-size</code>: Blob size multiplier (0.5-2.0x)</li>
</ul>
<p style="margin-top: 2rem; text-align: center; opacity: 0.8">
🌟 Create your own custom lava lamp at
<a
href="/resources/adoptables/"
style="color: #ffd700; text-decoration: none"
>ritual.sh/resources/adoptables</a
>
</p>
</div>
</div>
</body>
</html>