Website for a Music Festival for Creators

Design filed:
Web design // branding // p5.js
Year:
2026
Context:
Academic Project
Team:
Tahir Shvartz

About

OFF RECORD is an international music festival that puts the creative process at its center. It brings together musicians, producers and creators from around the world, creating a space where music is made, developed and shared — from the first idea to the stage.

Create · Connect · Perform

Visual System

The visual language of OFF RECORD was inspired by sound, movement and the unpredictable nature of the creative process.
Using p5.js, I built a generative system of shifting lines and forms, creating visuals that are constantly in motion.

Colors

F1F1F5
013CA6
3E2324
5C82C1
FCC538

Typography

Advent Pro
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Logo

Off Record logo mark

Generative Visuals

function setup() {
  createCanvas(500, 700);
  noFill();
  strokeWeight(3);
}

function draw() {
  background('#F1F1F5');

  let blobW = map(mouseX, 0, width, 180, 360);
  let blobH = map(mouseY, 0, height, 220, 460);

  drawBlob(width * 0.32, height * 0.42, blobW, blobH, 34);
}

function drawBlob(cx, cy, w, h, count) {
  let c1 = color('#3E2324');
  let c2 = color('#5C82C1');
  let c3 = color('#FCC538');

  let distortion = map(mouseX, 0, width, 5, 45);
  let movement = map(mouseY, 0, height, 5, 35);

  for (let i = 0; i < count; i++) {
    let t = i / (count - 1);

    let col;
    if (t < 0.5) {
      col = lerpColor(c1, c2, t * 2);
    } else {
      col = lerpColor(c2, c3, (t - 0.5) * 2);
    }

    stroke(col);

    let scale = map(i, 0, count - 1, 1, 0.18);

    beginShape();

    for (let a = 0; a < TWO_PI; a += 0.04) {
      let wave = sin(frameCount * 0.03 + a * 3 + i * 0.2) * movement;

      let rX =
        w * scale +
        sin(a * 2) * distortion +
        sin(a * 5) * (distortion * 0.5) +
        wave;

      let rY =
        h * scale +
        cos(a * 3) * distortion +
        wave;

      let x = cos(a) * rX;
      let y = sin(a) * rY;

      x += sin(y * 0.018) * 45;
      y += cos(x * 0.012) * 25;

      vertex(cx + x, cy + y);
    }

    endShape(CLOSE);
  }
}
function setup() {
  createCanvas(500, 700);
}

function draw() {
  background('#F1F1F5');
  noFill();
  strokeCap(ROUND);

  let focusX = 355;
  let focusY = 335;
  let linesCount = 115;

  let time = frameCount * 0.035;

  for (let i = 0; i < linesCount; i++) {
    let t = i / (linesCount - 1);
    let d = abs(t - 0.5) * 2;

    let startX = -560;
    let endX = width + 170;

    let startCenterY = focusY - 310;
    let endCenterY = focusY + 170;

    let leftSpread = pow(d, 0.7) * 1050;
    let rightSpread = pow(d, 0.65) * 360;

    let startY = startCenterY + (t - 0.5) * leftSpread;
    let endY = endCenterY + (t - 0.5) * rightSpread;

    startY += sin(t * TWO_PI * 1.1 + time) * 70 * d;
    endY += sin(t * TWO_PI * 1.4 + time * 0.8) * 55 * d;

    let move =
      sin(time * 1.4 + t * TWO_PI) * 42 +
      sin(time * 0.7 + t * TWO_PI * 2.3) * 16;

    let cp1X = 120;
    let cp1Y = startY + move;

    let cp2X = focusX - 250;
    let cp2Y = focusY + (startY - focusY) * 0.1 + move;

    let cp3X = focusX + 80;
    let cp3Y = focusY + (endY - focusY) * 0.18;

    let cp4X = width - 10;
    let cp4Y = endY + 60;

    let prev = getPointOnFullCurve(
      startX, startY,
      cp1X, cp1Y,
      cp2X, cp2Y,
      focusX, focusY,
      cp3X, cp3Y,
      cp4X, cp4Y,
      endX, endY,
      0
    );

    for (let s = 1; s <= 120; s++) {
      let p = s / 120;
      let pt = getPointOnFullCurve(
        startX, startY,
        cp1X, cp1Y,
        cp2X, cp2Y,
        focusX, focusY,
        cp3X, cp3Y,
        cp4X, cp4Y,
        endX, endY,
        p
      );

      let colorMix = t * 0.65 + p * 0.35;
      colorMix += sin((t * 5 + p * 2) * TWO_PI) * 0.06;
      colorMix = constrain(colorMix, 0, 1);

      stroke(getSoftColor(colorMix));
      strokeWeight(2);
      line(prev.x, prev.y, pt.x, pt.y);
      prev = pt;
    }
  }
}

function getSoftColor(v) {
  let c1 = color("#3E2324");
  let c2 = color("#4A72B8");
  let c3 = color("#8FA0A0");
  let c4 = color("#FCC538");

  if (v < 0.33) return lerpColor(c1, c2, v / 0.33);
  if (v < 0.66) return lerpColor(c2, c3, (v - 0.33) / 0.33);
  return lerpColor(c3, c4, (v - 0.66) / 0.34);
}

function getPointOnFullCurve(
  x1, y1,
  cp1X, cp1Y,
  cp2X, cp2Y,
  focusX, focusY,
  cp3X, cp3Y,
  cp4X, cp4Y,
  x2, y2,
  p
) {
  if (p < 0.5) {
    let q = p / 0.5;
    return createVector(
      bezierPoint(x1, cp1X, cp2X, focusX, q),
      bezierPoint(y1, cp1Y, cp2Y, focusY, q)
    );
  } else {
    let q = (p - 0.5) / 0.5;
    return createVector(
      bezierPoint(focusX, cp3X, cp4X, x2, q),
      bezierPoint(focusY, cp3Y, cp4Y, y2, q)
    );
  }
}
function setup() {
  createCanvas(500, 700);
  noStroke();
}

function draw() {
  background('#F1F1F5');

  let c1 = color('#3E2324');
  let c2 = color('#5C82C1');
  let c3 = color('#FCC538');

  let barW = 4;
  let gap = 8;
  let centerY = height * 0.55;

  for (let x = 40; x < width - 40; x += barW + gap) {
    let t = map(x, 40, width - 40, 0, 1);

    let col;
    if (t < 0.5) {
      col = lerpColor(c1, c2, t * 2);
    } else {
      col = lerpColor(c2, c3, (t - 0.5) * 2);
    }

    let wave =
      sin(x * 0.025 + frameCount * 0.03) * 90 +
      sin(x * 0.012 + mouseX * 0.02) * 120;

    let mouseShape = map(mouseY, 0, height, 80, 260);

    let h = 220 + wave + mouseShape;

    let edgeFade = sin(t * PI);
    h *= edgeFade;

    fill(col);

    fill(red(col), green(col), blue(col), alpha);

    rect(
      x,
      centerY - h / 2,
      barW,
      h,
      20
    );
  }
}

The Website

Festival Merch

OFF RECORD lanyard badges, printed with the generative visual system