repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

3.4 KB · 125 lines · JavaScript Raw History
  1// Three star fields at different depths, offset against the pointer for
  2// parallax and twinkling on a four-step cycle. Everything is a 5px square.
  3
  4const COLORS = ["white", "yellow", "red", "green", "blue"];
  5
  6export const retrostars = (cvs, { numStars = 50 } = {}) => {
  7  const ctx = cvs.getContext("2d");
  8
  9  const resize = () => {
 10    cvs.width = cvs.offsetWidth;
 11    cvs.height = cvs.offsetHeight;
 12  };
 13  resize();
 14  window.addEventListener("resize", resize);
 15
 16  const mouse = { x: 640, y: 400 };
 17  let starSize = 0;
 18
 19  window.addEventListener(
 20    "mousemove",
 21    (e) => {
 22      mouse.x = e.clientX;
 23      mouse.y = e.clientY;
 24    },
 25    { passive: true }
 26  );
 27
 28  const offsetStar = (maxOffset) => ({
 29    x: -Math.floor((mouse.x / window.innerWidth) * maxOffset),
 30    y: -Math.floor((mouse.y / window.innerHeight) * maxOffset),
 31  });
 32
 33  const makeStar = () => ({
 34    loc: [cvs.width * Math.random(), cvs.height * Math.random()],
 35    color: COLORS[Math.floor(Math.random() * COLORS.length)],
 36  });
 37
 38  const smallStars = [];
 39  const mediumStars = [];
 40  const largeStars = [];
 41  for (let i = 0; i < numStars; i++) {
 42    smallStars.push(makeStar());
 43    if (mediumStars.length < i / 4) mediumStars.push(makeStar());
 44    if (largeStars.length < i / 8) largeStars.push(makeStar());
 45  }
 46
 47  const square = (color, x, y, w = 5, h = 5) => {
 48    ctx.beginPath();
 49    ctx.fillStyle = color;
 50    ctx.fillRect(x, y, w, h);
 51    ctx.closePath();
 52  };
 53
 54  // A plus sign of 5px squares, the twinkling state for medium and large stars.
 55  const plus = (color, x, y, arm = 5) => {
 56    square(color, x, y);
 57    square(color, x - arm, y);
 58    square(color, x, y - arm);
 59    square(color, x + arm, y);
 60    square(color, x, y + arm);
 61  };
 62
 63  let frame = null;
 64  let running = false;
 65  let twinkle = null;
 66
 67  const draw = () => {
 68    ctx.clearRect(0, 0, cvs.width, cvs.height);
 69
 70    const smallOffset = offsetStar(25);
 71    const mediumOffset = offsetStar(75);
 72    const largeOffset = offsetStar(125);
 73
 74    smallStars.forEach(({ loc, color }) => {
 75      square(color, loc[0] + smallOffset.x, loc[1] + smallOffset.y);
 76    });
 77
 78    mediumStars.forEach(({ loc, color }) => {
 79      const x = loc[0] + mediumOffset.x;
 80      const y = loc[1] + mediumOffset.y;
 81      if (starSize === 0 || starSize === 2) square(color, x, y);
 82      else plus(color, x, y);
 83    });
 84
 85    largeStars.forEach(({ loc, color }) => {
 86      const x = loc[0] + largeOffset.x;
 87      const y = loc[1] + largeOffset.y;
 88      if (starSize === 0) {
 89        square(color, x, y);
 90      } else if (starSize === 1 || starSize === 3) {
 91        plus(color, x, y);
 92      } else {
 93        // Peak twinkle. The hole punched out of the middle is what makes it
 94        // read as a flare instead of just a bigger dot.
 95        square(color, x - 5, y - 5, 15, 15);
 96        square(color, x - 10, y);
 97        square(color, x, y - 10);
 98        square(color, x + 10, y);
 99        square(color, x, y + 10);
100        square("black", x, y);
101      }
102    });
103
104    if (running) frame = window.requestAnimationFrame(draw);
105  };
106
107  return {
108    start() {
109      if (running) return;
110      running = true;
111      twinkle = window.setInterval(() => {
112        starSize = (starSize + 1) % 4;
113      }, 500);
114      frame = window.requestAnimationFrame(draw);
115    },
116    stop() {
117      running = false;
118      if (frame) window.cancelAnimationFrame(frame);
119      if (twinkle) window.clearInterval(twinkle);
120      frame = null;
121      twinkle = null;
122    },
123  };
124};