diff --git a/assets/js/adoptables/lavalamp-adoptable.js b/assets/js/adoptables/lavalamp-adoptable.js new file mode 100644 index 0000000..114dc1a --- /dev/null +++ b/assets/js/adoptables/lavalamp-adoptable.js @@ -0,0 +1,386 @@ +(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 = ` + + + + + + + + + + + + + + + `; + + 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 ` + +``` + +### 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! diff --git a/content/resources/lavalamp/index.md b/content/resources/lavalamp/index.md index e18dc73..66b2243 100644 --- a/content/resources/lavalamp/index.md +++ b/content/resources/lavalamp/index.md @@ -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. diff --git a/content/updates/2026-01-11-button-generator.md b/content/updates/2026-01-11-button-generator.md new file mode 100644 index 0000000..23c41fb --- /dev/null +++ b/content/updates/2026-01-11-button-generator.md @@ -0,0 +1,10 @@ +--- +title: "2026 01 11 Button Generator" +date: 2026-01-11T08:32:16Z +tags: [] +description: "" +build: + render: never +--- + +Added an 88x31 button generator to the resources section - it can do animated gifs too! \ No newline at end of file diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index bdd3455..ea512a3 100755 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -39,5 +39,11 @@ {{ end }} + + + {{ if or (findRE "{{<\\s*lavalamp-adoptable" .RawContent) (findRE "{{%\\s*lavalamp-adoptable" .RawContent) }} + {{ $lavalampAdoptable := resources.Get "js/adoptables/lavalamp-adoptable.js" | resources.Minify | resources.Fingerprint }} + + {{ end }} diff --git a/layouts/partials/site-scripts.html b/layouts/partials/site-scripts.html index b8ee8b1..d940fd3 100644 --- a/layouts/partials/site-scripts.html +++ b/layouts/partials/site-scripts.html @@ -7,11 +7,18 @@ {{ $filtered := slice }} {{ range $remaining }} {{ $path := .RelPermalink }} - {{ if and (not (strings.Contains $path "/terminal.js")) (not (strings.Contains $path "/init.js")) (not (strings.Contains $path "/button-generator.js")) }} + {{ if and (not (strings.Contains $path "/terminal.js")) (not (strings.Contains $path "/init.js")) (not (strings.Contains $path "/button-generator.js")) (not (strings.Contains $path "/adoptables/")) }} {{ $filtered = $filtered | append . }} {{ end }} {{ end }} -{{ $allFiles := slice $terminalShell | append $filtered | append $init | append $commandFiles | append $subfolderFiles }} +{{ $filteredSubfolders := slice }} +{{ range $subfolderFiles }} + {{ $path := .RelPermalink }} + {{ if not (strings.Contains $path "/adoptables/") }} + {{ $filteredSubfolders = $filteredSubfolders | append . }} + {{ end }} +{{ end }} +{{ $allFiles := slice $terminalShell | append $filtered | append $init | append $commandFiles | append $filteredSubfolders }} {{ $terminalBundle := $allFiles | resources.Concat "js/terminal-bundle.js" | resources.Minify | resources.Fingerprint }} + +
150px × 280px
+ + + +
+
Purple Dreams
+
+ +
+
120px × 250px
+
+ + +
+
Ocean Blue
+
+ +
+
100px × 200px
+
+ + +
+
Lava Red
+
+ +
+
180px × 320px
+
+ + +
+
Mint Fresh
+
+ +
+
140px × 260px
+
+ + +
+
Sunset Glow
+
+ +
+
110px × 220px
+
+ + +
+
Cyber Green (Pixelated)
+
+ +
+
160px × 300px (6px pixels)
+
+ + +
+
Pink Bubble (Pixelated)
+
+ +
+
130px × 240px (3px pixels)
+
+ + +
+
Tiny Retro (Pixelated)
+
+ +
+
40px × 80px (3px pixels)
+
+ + +
+

How to Use

+

+ Simply copy and paste this code into your website. The lava lamp will + scale to fit any container size you specify! +

+
<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>
+ +

Customization Options

+ + +

+ 🌟 Create your own custom lava lamp at + ritual.sh/resources/adoptables +

+
+ + +