Code used for this site, feel free to copy them.
Water Button - Original from originkit.dev
Original button is from originkit.dev
HTML
<a href="#" class="water-button" data-water-color="#3fbfc7">
<span class="water-button-glass" aria-hidden="true"></span>
<span class="water-button-tint" aria-hidden="true"></span>
<span class="water-button-canvas-wrap" aria-hidden="true">
<canvas class="water-button-canvas"></canvas>
</span>
<span class="water-button-label">Hello! Mi me ma mo muu!</span>
</a>
CSS
.water-button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: fit-content;
box-sizing: border-box;
padding: 12px 30px;
border-radius: 999px;
background: transparent;
cursor: pointer;
isolation: isolate;
text-decoration: none;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.water-button:hover {
transform: translateY(-1px);
}
.water-button:active {
transform: scale(0.97);
}
.water-button-glass {
position: absolute;
inset: 0;
border-radius: inherit;
overflow: hidden;
background: rgba(255, 255, 255, 0.48);
border: 1px solid rgba(255, 255, 255, 0.30);
backdrop-filter: blur(13px) saturate(208%);
-webkit-backdrop-filter: blur(13px) saturate(208%);
pointer-events: none;
}
.water-button-tint {
position: absolute;
inset: 0;
border-radius: inherit;
background: rgba(0, 0, 0, 0.1);
pointer-events: none;
}
.water-button-canvas-wrap {
position: absolute;
inset: 0;
pointer-events: none;
}
.water-button-canvas {
display: block;
width: 100%;
height: 100%;
}
.water-button-label {
position: relative;
z-index: 3;
font-family: system-ui, sans-serif;
font-size: 0.9rem;
font-weight: 700;
letter-spacing: -0.01em;
color: #ffffff;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
text-align: center;
white-space: pre;
user-select: none;
display: block;
}
JavaScript
const COLUMNS = 64;
const SUBSTEPS = 6;
const GRAVITY = 6000;
const VISCOSITY = 2.6;
const CURSOR_REACH = 46;
const SWEEP_FORCE = 3;
const DIP_FORCE = 1.5;
const CLICK_FORCE = 260;
const MAX_KICK = 8;
const MAX_FILL = 0.9;
const STILL = 0.05;
const SLEEP_FRAMES = 20;
const WATER_AMOUNT = 69;
const MAX_DROPS = 96;
const DROP_GRAVITY = 1400;
const DROP_DRAG = 0.6;
const DROP_VOLUME = 0.18;
const DROP_RADIUS = 1.25;
const SPRAY_THRESHOLD = 1.6;
const SPRAY_RATE = 0.5;
const SPRAY_PER_FRAME = 4;
const SPRAY_LIFT = 150;
const BREAK_SPEED = 210;
const MIN_SPRAY_DEPTH = 2;
const CFL_SAFETY = 0.5;
const MAX_SUBSTEPS = 40;
const RIPPLE = 1;
const MAX_PIXEL_SCALE = 8;
const MAX_PIXELS = 2000000;
const BORDER_COLOR = 'rgba(120, 120, 120, 0.7)';
const BORDER_STROKE = 1;
function parseColor(color) {
const value = (color ?? '').trim();
const hex = value.replace('#', '');
if (/^[0-9a-f]{6}$/i.test(hex)) {
return [
parseInt(hex.slice(0, 2), 16),
parseInt(hex.slice(2, 4), 16),
parseInt(hex.slice(4, 6), 16),
1,
];
}
const match = value.match(/rgba?\(([^)]+)\)/i);
if (match) {
const parts = match[1].split(',').map((p) => parseFloat(p));
return [parts[0] || 0, parts[1] || 0, parts[2] || 0, parts[3] === undefined ? 1 : parts[3]];
}
return [62, 155, 214, 1];
}
const clampKick = (value) => Math.max(-MAX_KICK, Math.min(MAX_KICK, value));
const shift = (channel, f) =>
Math.round(Math.max(0, Math.min(255, channel + (f > 0 ? (255 - channel) * f : channel * f))));
function roundRectPath(ctx, x, y, w, h, radius) {
const r = Math.max(0, Math.min(radius, Math.min(w, h) / 2));
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
function mountWaterButton(root) {
const canvas = root.querySelector('.water-button-canvas');
const context = canvas?.getContext('2d');
if (!canvas || !context) return;
const ctx = context;
let last = 0;
let pixScale = 1;
let W = 0;
let H = 0;
let maxSub = 1 / 60;
const h = new Float32Array(COLUMNS);
const u = new Float32Array(COLUMNS + 1);
const flux = new Float32Array(COLUMNS + 1);
let rest = 0;
let colW = 1;
const dropX = new Float32Array(MAX_DROPS);
const dropY = new Float32Array(MAX_DROPS);
const dropVX = new Float32Array(MAX_DROPS);
const dropVY = new Float32Array(MAX_DROPS);
const dropLive = new Uint8Array(MAX_DROPS);
let flying = 0;
let hovering = false;
let asleep = false;
let quiet = 0;
const pointer = { x: -9999, y: -9999, vx: 0, vy: 0 };
const amount = Math.min(MAX_FILL, Math.max(0, WATER_AMOUNT / 100));
const ripple = RIPPLE;
const waterColor = root.dataset.waterColor || '#00EEFF';
const [cr, cg, cb, ca] = parseColor(waterColor);
const surfaceTone = `rgba(${shift(cr, 0.45)}, ${shift(cg, 0.45)}, ${shift(cb, 0.45)}, ${ca})`;
const bodyTone = `rgba(${cr}, ${cg}, ${cb}, ${ca})`;
const depthTone = `rgba(${shift(cr, -0.35)}, ${shift(cg, -0.35)}, ${shift(cb, -0.35)}, ${ca})`;
const glintTone = `rgba(${shift(cr, 0.75)}, ${shift(cg, 0.75)}, ${shift(cb, 0.75)}, ${Math.min(1, ca + 0.25)})`;
const [rr, rg, rb, ra] = parseColor(BORDER_COLOR);
const borderTone = `rgba(${rr}, ${rg}, ${rb}, ${ra})`;
const borderBase = `rgba(${rr}, ${rg}, ${rb}, ${ra * 0.35})`;
const borderFade = `rgba(${rr}, ${rg}, ${rb}, 0)`;
const rounded = 999;
const colX = (i) => (i + 0.5) * colW;
function pixelScale() {
const rect = root.getBoundingClientRect();
const zoom = rect.width > 1 && W > 1 ? rect.width / W : 1;
let s = Math.min(MAX_PIXEL_SCALE, (window.devicePixelRatio || 1) * zoom);
const area = Math.max(1, W * H);
if (area * s * s > MAX_PIXELS) s = Math.sqrt(MAX_PIXELS / area);
return Math.max(1, s);
}
function raster() {
pixScale = pixelScale();
canvas.width = Math.round(W * pixScale);
canvas.height = Math.round(H * pixScale);
ctx.setTransform(pixScale, 0, 0, pixScale, 0, 0);
}
function build() {
W = Math.max(2, root.clientWidth);
H = Math.max(2, root.clientHeight);
raster();
colW = W / COLUMNS;
rest = H * amount;
h.fill(rest);
u.fill(0);
dropLive.fill(0);
flying = 0;
asleep = true;
quiet = 0;
const waveSpeed = Math.sqrt(GRAVITY * Math.max(rest, 1));
maxSub = (colW * CFL_SAFETY) / waveSpeed;
}
function freeze() {
h.fill(rest);
u.fill(0);
dropLive.fill(0);
flying = 0;
asleep = true;
quiet = 0;
}
function drive(dt) {
const reach = CURSOR_REACH;
const kx = clampKick(pointer.vx) * 60;
const ky = clampKick(pointer.vy) * 60;
const first = Math.max(1, Math.floor((pointer.x - reach) / colW));
const lastCol = Math.min(COLUMNS - 1, Math.ceil((pointer.x + reach) / colW));
for (let i = first; i <= lastCol; i++) {
const dx = i * colW - pointer.x;
const fall = 1 - Math.abs(dx) / reach;
if (fall <= 0) continue;
if (pointer.y < H - h[i] - reach) continue;
const bite = fall * ripple * dt;
u[i] += kx * SWEEP_FORCE * bite;
u[i] += Math.sign(dx) * ky * DIP_FORCE * bite;
}
}
function splash(px) {
const reach = CURSOR_REACH;
const first = Math.max(1, Math.floor((px - reach) / colW));
const lastCol = Math.min(COLUMNS - 1, Math.ceil((px + reach) / colW));
for (let i = first; i <= lastCol; i++) {
const dx = i * colW - px;
const fall = 1 - Math.abs(dx) / reach;
if (fall <= 0) continue;
u[i] += Math.sign(dx || 1) * CLICK_FORCE * fall * ripple;
}
}
function tear(i, vx, vy) {
if (h[i] <= MIN_SPRAY_DEPTH) return;
let slot = -1;
for (let d = 0; d < MAX_DROPS; d++) {
if (!dropLive[d]) {
slot = d;
break;
}
}
if (slot < 0) return;
dropLive[slot] = 1;
flying++;
dropX[slot] = colX(i);
dropY[slot] = H - h[i] - DROP_RADIUS;
dropVX[slot] = vx;
dropVY[slot] = vy;
h[i] -= DROP_VOLUME / colW;
}
function spray(dt) {
if (hovering) {
const kx = clampKick(pointer.vx);
const ky = clampKick(pointer.vy);
const speed = Math.abs(kx) + Math.abs(ky);
if (speed > SPRAY_THRESHOLD) {
const want = Math.min(
SPRAY_PER_FRAME,
Math.round((speed - SPRAY_THRESHOLD) * SPRAY_RATE * ripple),
);
for (let k = 0; k < want; k++) {
const x = pointer.x + (Math.random() - 0.5) * CURSOR_REACH * 0.7;
const i = Math.max(0, Math.min(COLUMNS - 1, Math.floor(x / colW)));
if (pointer.y < H - h[i] - CURSOR_REACH * 0.5) continue;
tear(
i,
kx * 60 * 0.45 + (Math.random() - 0.5) * 70,
-(SPRAY_LIFT * (0.6 + Math.random() * 0.9)) + ky * 60 * 0.25,
);
}
}
}
for (let i = 1; i < COLUMNS - 1; i++) {
const flow = Math.abs(u[i]);
if (flow < BREAK_SPEED) continue;
if (Math.random() > (flow - BREAK_SPEED) * 0.004 * ripple * dt * 60) continue;
tear(i, u[i] * 0.5 + (Math.random() - 0.5) * 40, -(SPRAY_LIFT * 0.5 * (0.5 + Math.random())));
}
}
function fall(dt) {
if (flying === 0) return;
for (let d = 0; d < MAX_DROPS; d++) {
if (!dropLive[d]) continue;
dropVY[d] += DROP_GRAVITY * dt;
dropVX[d] -= dropVX[d] * Math.min(1, DROP_DRAG * dt);
dropX[d] += dropVX[d] * dt;
dropY[d] += dropVY[d] * dt;
if (dropX[d] < 0) {
dropX[d] = 0;
dropVX[d] = -dropVX[d] * 0.35;
} else if (dropX[d] > W) {
dropX[d] = W;
dropVX[d] = -dropVX[d] * 0.35;
}
const i = Math.max(0, Math.min(COLUMNS - 1, Math.floor(dropX[d] / colW)));
if (dropVY[d] > 0 && dropY[d] >= H - h[i]) {
h[i] += DROP_VOLUME / colW;
if (i > 0 && i < COLUMNS) u[i] += dropVX[d] * 0.25;
dropLive[d] = 0;
flying--;
}
}
}
function step(dt) {
spray(dt);
fall(dt);
const subSteps = Math.min(MAX_SUBSTEPS, Math.max(SUBSTEPS, Math.ceil(dt / maxSub)));
const sub = dt / subSteps;
for (let s = 0; s < subSteps; s++) {
if (hovering) drive(sub);
for (let i = 1; i < COLUMNS; i++) {
u[i] += ((-GRAVITY * (h[i] - h[i - 1])) / colW) * sub;
u[i] -= u[i] * Math.min(1, VISCOSITY * sub);
}
u[0] = 0;
u[COLUMNS] = 0;
for (let i = 1; i < COLUMNS; i++) {
flux[i] = (u[i] > 0 ? h[i - 1] : h[i]) * u[i];
}
flux[0] = 0;
flux[COLUMNS] = 0;
for (let i = 0; i < COLUMNS; i++) {
h[i] -= ((flux[i + 1] - flux[i]) / colW) * sub;
if (h[i] < 0) h[i] = 0;
}
}
for (let i = 0; i < COLUMNS; i++) {
if (!Number.isFinite(h[i]) || !Number.isFinite(u[i])) {
freeze();
return 0;
}
}
let worst = 0;
for (let i = 0; i < COLUMNS; i++) {
const off = Math.abs(h[i] - rest);
if (off > worst) worst = off;
}
return worst;
}
function draw() {
ctx.clearRect(0, 0, W, H);
ctx.save();
roundRectPath(ctx, 0, 0, W, H, rounded);
ctx.clip();
const surfaceAt = (i) => H - h[i];
ctx.beginPath();
ctx.moveTo(0, surfaceAt(0));
for (let i = 0; i < COLUMNS; i++) ctx.lineTo(colX(i), surfaceAt(i));
ctx.lineTo(W, surfaceAt(COLUMNS - 1));
ctx.lineTo(W, H);
ctx.lineTo(0, H);
ctx.closePath();
const grad = ctx.createLinearGradient(0, H - rest, 0, H);
grad.addColorStop(0, surfaceTone);
grad.addColorStop(0.35, bodyTone);
grad.addColorStop(1, depthTone);
ctx.fillStyle = grad;
ctx.fill();
ctx.beginPath();
ctx.moveTo(0, surfaceAt(0));
for (let i = 0; i < COLUMNS; i++) ctx.lineTo(colX(i), surfaceAt(i));
ctx.lineTo(W, surfaceAt(COLUMNS - 1));
ctx.strokeStyle = glintTone;
ctx.lineWidth = 1.5;
ctx.stroke();
if (flying > 0) {
ctx.beginPath();
for (let d = 0; d < MAX_DROPS; d++) {
if (!dropLive[d]) continue;
ctx.moveTo(dropX[d] + DROP_RADIUS, dropY[d]);
ctx.arc(dropX[d], dropY[d], DROP_RADIUS, 0, Math.PI * 2);
}
ctx.fillStyle = surfaceTone;
ctx.fill();
}
ctx.restore();
if (BORDER_STROKE <= 0) return;
const half = BORDER_STROKE / 2;
roundRectPath(ctx, half, half, W - BORDER_STROKE, H - BORDER_STROKE, rounded - half);
ctx.lineWidth = BORDER_STROKE;
ctx.strokeStyle = borderBase;
ctx.stroke();
const rim = ctx.createLinearGradient(W, H, 0, 0);
rim.addColorStop(0, borderTone);
rim.addColorStop(0.3, borderFade);
rim.addColorStop(0.7, borderFade);
rim.addColorStop(1, borderTone);
ctx.strokeStyle = rim;
ctx.stroke();
}
function loop(now) {
const dt = last ? Math.min((now - last) / 1000, 1 / 30) : 1 / 60;
last = now;
pointer.vx *= 0.8;
pointer.vy *= 0.8;
if (!asleep) {
const worst = step(dt);
draw();
if (!hovering && flying === 0 && worst < STILL) quiet++;
else quiet = 0;
if (quiet > SLEEP_FRAMES) {
freeze();
draw();
}
}
requestAnimationFrame(loop);
}
const wake = () => {
asleep = false;
quiet = 0;
};
const toLocal = (e) => {
const rect = root.getBoundingClientRect();
const sx = rect.width > 0 ? W / rect.width : 1;
const sy = rect.height > 0 ? H / rect.height : 1;
return { x: (e.clientX - rect.left) * sx, y: (e.clientY - rect.top) * sy };
};
root.addEventListener('mouseenter', (e) => {
hovering = true;
const p = toLocal(e);
pointer.x = p.x;
pointer.y = p.y;
pointer.vx = 0;
pointer.vy = 0;
wake();
});
root.addEventListener('mousemove', (e) => {
const p = toLocal(e);
pointer.vx = p.x - pointer.x;
pointer.vy = p.y - pointer.y;
pointer.x = p.x;
pointer.y = p.y;
wake();
});
root.addEventListener('mouseleave', () => {
hovering = false;
pointer.x = -9999;
pointer.y = -9999;
pointer.vx = 0;
pointer.vy = 0;
wake();
});
root.addEventListener('pointerdown', (e) => {
const p = toLocal(e);
pointer.x = p.x;
pointer.y = p.y;
splash(p.x);
wake();
});
const observer = new ResizeObserver(() => {
build();
draw();
});
observer.observe(root);
setInterval(() => {
if (Math.abs(pixelScale() - pixScale) < 0.02) return;
raster();
draw();
}, 300);
build();
draw();
requestAnimationFrame(loop);
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.water-button').forEach(mountWaterButton);
});
Friend Loop Marquee
There is also the marquee tag as the og method, this a slightly more polished version.
HTML
<div class="marquee">
<div class="marquee-track">
<img class="marquee-item" src="button1.gif" width="88" height="31" alt="Friend 1" />
<img class="marquee-item" src="button2.gif" width="88" height="31" alt="Friend 2" />
<img class="marquee-item" src="button3.gif" width="88" height="31" alt="Friend 3" />
<img class="marquee-item" src="button1.gif" width="88" height="31" alt="" aria-hidden="true" />
<img class="marquee-item" src="button2.gif" width="88" height="31" alt="" aria-hidden="true" />
<img class="marquee-item" src="button3.gif" width="88" height="31" alt="" aria-hidden="true" />
</div>
</div>
CSS
.marquee {
overflow: hidden;
}
.marquee-track {
display: flex;
width: max-content;
gap: 8px;
animation: marquee-scroll 24s linear infinite;
}
.marquee:hover .marquee-track {
animation-play-state: paused;
}
@keyframes marquee-scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
.marquee-item {
display: block;
flex-shrink: 0;
}
Glassmorphism - Original from w3schools
Visit w3schools glassmorphism generator!
WHUAU! I'm frosted!
HTML
<div class="glass-panel">
<p>WHUAU! I'm frosted!</p>
</div>
CSS
.glass-panel {
position: relative;
border-radius: 0;
padding: 20px;
background: rgba(255, 255, 255, 0.48);
border: 1px solid rgba(255, 255, 255, 0.30);
backdrop-filter: blur(13px) saturate(208%);
-webkit-backdrop-filter: blur(13px) saturate(208%);
}
Gradient Text
For headings you want filled with a color gradient.
Gradient Text oooOOoo
HTML
<h1 class="gradient-text">Gradient Text oooOOoo</h1>
CSS
.gradient-text {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-weight: 700;
font-size: 2.8rem;
margin: 0;
line-height: 1.25;
background: linear-gradient(180deg, #ffb3d1 0%, #ff6fa5 45%, #e14f86 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
Dark / Light Theme Toggle
Remembers what you picked (localStorage) and falls back to your OS setting if you haven't picked anything yet.
This card has its own light/dark state, click me!
HTML
<button class="theme-toggle" id="theme-toggle" type="button" aria-label="Toggle dark mode">
<span class="theme-icon sun-icon" aria-hidden="true">☀️</span>
<span class="theme-icon moon-icon" aria-hidden="true">🌙</span>
</button>
CSS
:root {
--bg: #fdf6f0;
--fg: #1a3f7a;
}
:root[data-theme="dark"] {
--bg: #0a1522;
--fg: #e9f1fb;
}
body {
background: var(--bg);
color: var(--fg);
transition: background 0.3s ease, color 0.3s ease;
}
.theme-toggle {
position: relative;
width: 34px;
height: 34px;
border-radius: 50%;
border: 1px solid rgba(128, 128, 128, 0.35);
background: rgba(255, 255, 255, 0.6);
cursor: pointer;
}
:root[data-theme="dark"] .theme-toggle {
background: rgba(255, 255, 255, 0.1);
}
.theme-icon {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
transition: opacity 0.4s ease, transform 0.4s ease;
}
:root[data-theme="light"] .sun-icon {
opacity: 1;
transform: rotate(0deg) scale(1);
}
:root[data-theme="light"] .moon-icon {
opacity: 0;
transform: rotate(-90deg) scale(0.6);
}
:root[data-theme="dark"] .sun-icon {
opacity: 0;
transform: rotate(90deg) scale(0.6);
}
:root[data-theme="dark"] .moon-icon {
opacity: 1;
transform: rotate(0deg) scale(1);
}
JavaScript
// Run this part as early as possible, ideally inline in <head>, before any
// CSS paints, so the page never flashes the wrong theme on first load.
(function () {
var stored = localStorage.getItem('theme');
var theme = stored || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
})();
// This part can run anywhere after the <button id="theme-toggle"> exists in your code.
document.addEventListener('DOMContentLoaded', () => {
const themeToggle = document.getElementById('theme-toggle');
if (!themeToggle) return;
themeToggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
});
Bubble Dividers
My deviders for organisation.
HTML
<!-- normal -->
<div class="bubble-divider"></div>
<!-- dashed -->
<div class="bubble-divider bubble-divider-dashed"></div>
CSS
.bubble-divider {
width: 100%;
height: 6px;
margin: 26px 0;
border-radius: 999px;
background: linear-gradient(90deg, #9fe0ea 0%, #3f7fc9 55%, #1a3f66 100%);
}
/* -------------------------------- */
/* remove this if you dont wanna use dark mode */
:root[data-theme="dark"] .bubble-divider {
background: linear-gradient(90deg, #e4c770 0%, #e69c2c 55%, #a86539 100%);
}
/* -------------------------------- */
.bubble-divider-dashed {
height: 0;
border-radius: 0;
background: none;
box-shadow: none;
border-top: 2px dashed var(--card-border-dashed, rgba(26, 63, 102, 0.4));
}
/* -------------------------------- */
/* remove this if you dont wanna use dark mode */
:root[data-theme="dark"] .bubble-divider-dashed {
border-top-color: rgba(204, 154, 82, 0.4);
}
/* -------------------------------- */


Main Page
About Me
Blog
Music
Guestbook
Art
Commissions
Shop
Space Cadet Pinball
Spider Solitaire
Paint
Metal Slug X
SCGMD4
Links