let n = 32
let scrollbarLength = 700 * 0.99

let soundtrack = "demo3.mp3"

let sin = Math.sin
let cos = Math.cos
let tan = Math.tan
let abs = Math.abs
let sqrt = Math.sqrt
let floor = Math.floor
let ceil = Math.ceil
let pow = Math.pow
let atan = Math.atan
let atan2 = Math.atan2
let random = Math.random
let max = Math.max
let min = Math.min
let asin = Math.asin
function frac(x) {
    return x - Math.floor(x)
}
let PI = Math.PI

// return value between 0 and 1
function smoothstep(min, max, v) {
    let x = Math.max(0, Math.min(1, (v - min) / (max - min)))
    return x * x * (3 - 2 * x)
}

// map v from range a, b to range c, d
function map(a, b, c, d, v) {
    return 0
}

function noise(x) {
    return frac(23423.23234 + sin(x) * 437588123)
}

function sliderhelper(p, h) {
    let y = h / 2 + p * (1 - h)
    return [y - h / 2, y + h / 2]
}

//////////////////////////// The animations!

function rain(t, x, i) {
    let h = 0.07 + 0.05 * noise(2 * x)
    //let v = x //noise(i)
    t = 2 * t
    let v = 2 - (2 * t * (1 + 0.5 * noise(x)) + noise(i))
    while (v < -h) {
        v += 1.2
    }

    let y = h / 2 + v * (1 - h)
    return [y - h / 2, y + h / 2]
}

// all sliders wiggle independently along a sine curve
function wiggle(t, x, i) {
    let h = 0.15

    let max_amplitude = 0.2
    let base_y = max_amplitude / 2 + noise(i) * (1 - max_amplitude)
    let amplitude = max_amplitude * noise(x)
    let offset = noise(5 * i + 0.5)
    let v = base_y + sin(2 * PI * offset + t * 2 * PI * 2) * amplitude

    let y = h / 2 + v * (1 - h)
    return [y - h / 2, y + h / 2]
}

function triangle(t, x, i) {
    return [1, x]
}

// return pair of two values between 0 and 1. they signify the range that the scrollbar occupies. 0 is bottom, 1 is top.
function data(t, x, i) {
    //return [0.5, i / (n - 1)]
    //return [0, 1]
    //return [0.5, 0.51]
    return [
        0.5 + 0.2 * Math.sin(2 * t + i / 4),
        0.5 + 0.1 * Math.sin(t + i / 4),
    ]
}

function single(t, x, i) {
    if (i == n - 1) {
        let w = 0.15
        t = max(0, t - 0.2)
        let p = 1 - (1 + cos(t * 2.8 * PI)) / 2
        return sliderhelper(p, w)
    } else {
        return undefined
    }
}

function fallhelper(t, x, i) {
    return abs(((1 - t) / (20 * t)) * sin(4 * t * 2 * PI))
}

function singlefall(t, x, i) {
    t = max(0, t)
    if (i == n - 1) {
        let w = 0.15
        let p = fallhelper(t, x, i)
        return sliderhelper(p, w)
    } else {
        return undefined
    }
}

function singleresize(t, x, i) {
    t = max(0, t)
    if (i == n - 1) {
        let w = 0.3 - 0.15 * cos(t * 2.5 * PI)
        let p = 1 - (1 + cos(0.1 + t * 4 * PI)) / 2
        return sliderhelper(p, w)
    } else {
        return undefined
    }
}

function allfall(t, x, i) {
    let v = noise(i) + 0.1
    let p
    let t2 = t * 1.5
    if (t2 > v) {
        p = 1 * fallhelper(t2 - v, x, i)
    } else {
        p = 1
    }
    return sliderhelper(p, 0.15)
}

function allresize(t, x, i) {
    let w =
        0.15 + ((1 + sin(t * 4 * PI + noise(i)) + t * noise(i)) / 2) * 0.15 * t
    let p = 0.5
    return sliderhelper(p, w)
}

function allsine(t, x, i) {
    let p = 0.5 + sin(2 * t * PI + x * 8 + 10 * t) * 0.1 * t
    let w = 0

    if (i == 0) {
        analyser.getByteFrequencyData(dataArray)
    }
    let v = dataArray[i] / 1200
    w += v

    return sliderhelper(p, w)
}

function loading(t, x, i) {
    t = max(1 / n, t)
    let t2 = t * 1.1
    let r = 0.05
    let w2 = 0.15 * (1 - smoothstep(t2 - r, t2 + r, 1 - x))
    return [0.5 - w2 / 2, 0.5 + w2 / 2]
}

function test(t, x, i) {
    return [x, x + 0.01]
}

function spectrogram(t, x, i) {
    if (i == 0) {
        analyser.getByteFrequencyData(dataArray)
    }
    let v = dataArray[i] / 1200
    return [0.5 - v, 0.5 + v]
}

function ball(t, x, i) {
    let br = 0.2

    t = 1.6 * t

    //let bx = 0.5 + (1 - br * 2) * 0.5 * sin(2 * PI * t)
    let bx = 0.8 * br + (1 - 2 * 0.8 * br) * abs(((2 * t) % 2) - 1)
    let by = br + abs(0.5 * cos(2 * PI * t * 1.5))

    let d = abs(bx - x)
    if (d > br) {
        return [by, by]
    } else {
        // intersect line and circle
        // sqrt(d**2 + v**2) == br
        // sqrt(br**2 - d**2) = v
        let v = sqrt(Math.pow(br, 2) - Math.pow(d, 2))
        return [by - v, by + v]
    }
}

function pong(t, x, i) {
    let br = 0.06

    t = 2 * t

    //let bx = 0.5 + (1 - br * 2) * 0.5 * sin(2 * PI * t)
    let bx =
        0.05 + 0.9 * (0.8 * br + (1 - 2 * 0.8 * br) * abs(((4 * t) % 2) - 1))
    let by = 0.8 * br + (1 - 2 * 0.8 * br) * abs(((3 * t) % 2) - 1)
    //br + abs(0.5 * cos(2 * PI * t * 1.5))

    let pw = 0.4
    if (i < 2) {
        let p = 0.5 + 0.2 * sin(2 * PI * t)
        return [p - pw / 2, p + pw / 2]
    } else if (i > n - 3) {
        let p = 0.5 + 0.2 * sin(3 * PI * t)
        return [p - pw / 2, p + pw / 2]
    } else {
        let d = abs(bx - x)
        if (d > br) {
            return [by, by]
        } else {
            // intersect line and circle
            // sqrt(d**2 + v**2) == br
            // sqrt(br**2 - d**2) = v
            let v = sqrt(Math.pow(br, 2) - Math.pow(d, 2))
            return [by - v, by + v]
        }
    }
}

function heart(t, x, i) {
    let val1 = 0
    let val2 = 0

    let j = x + 0.01

    let r = 0.15 + 0.06 * (sin(t * Math.PI * 0.5) * 0.5 + 0.5)

    x = 0.5 - r
    let y = 0.43 + r

    if (Math.abs(x - j) <= r) {
        let a = Math.acos(Math.abs(x - j) / r)
        let offset = Math.sin(a) * r
        val1 = y + offset
    }

    x = 0.5 + r

    if (Math.abs(x - j) <= r) {
        let a = Math.acos(Math.abs(x - j) / r)
        let offset = Math.sin(a) * r
        val1 = y + offset
    }

    r = 1.99 * r
    dist = Math.abs(0.5 - j)
    if (dist <= r) {
        val2 = dist + 0.43 - r / sqrt(2)
    }

    if (val1 == 0 && val2 != 0) {
        val1 = val2
    }
    if (val2 == 0 && val1 != 0) {
        val2 = val1
    }

    return [val1, val2]
}

function empty(t, x, i) {
    let v = noise(i)
    return [v, v]
}

function wave(t, x, i) {
    let p = 0.5 + 0.1 * sin(t * 2 * PI + x * 5)
    return [p, p + 0.037]
}

function wavewithboat(t, x, i) {
    let amp = 0.05
    let boat_x = 0.5 + sin(t * 2 * PI) * 0.2
    let boat_y = 0.47 + amp * sin(t * 2 * PI + boat_x * 5)

    let boat_w = 0.5
    if (x > boat_x - boat_w / 2 && x < boat_x + boat_w / 2) {
        let boat_offset = 0.8 * abs(x - boat_x) - 0.08
        if (abs(x - boat_x) < boat_w / 4) {
            boat_offset = 0.02
        }
        let top_boat_offset = 0
        if (abs(x - boat_x) < boat_w / 6) {
            top_boat_offset = 0.08 - abs(x - boat_x)
        }
        return [boat_y - 0.05 + boat_offset, boat_y + 0.1 + top_boat_offset]
    } else {
        let p = 0.5 + amp * sin(t * 2 * PI + x * 5)
        return [p, p + 0.037]
    }
}

function twister(t, x, i) {
    return [
        0.5 + 0.2 * sin(2 * PI * t + x * PI),
        0.5 + 0.3 * sin(4 * PI * t + x * PI),
    ]
}

function munch(t, x, i) {
    //let h = 0.037
    t /= 2
    let h = 0.05
    let p = (i ^ ((1 + t) * 100) % n) / (n - 1)
    //return [0, p]
    return sliderhelper(p, h)
}

function fire(t, x, i) {
    let v = 0.1 * Math.random()
    let v2 = 0.7 * Math.random() * (0.1 + (1 + sin(t * PI)) / 2)
    return [v, sin(x * PI) * v2]
}

function winston(t, x, i) {
    return [abs(cos(i + t)), abs(sin(i + t))]
}

function text(t, x, i) {
    let p = 0.1
    let h = 0.3

    let a = 0.5
    let b = 0.5

    x = (x + (t + 1) * 4) % 2

    if ((x > p && x < 2 * p) || (x > 3 * p && x < 4 * p)) {
        a = 0.5 - h
        b = 0.5 + h
    } else if (x > 2 * p && x < 3 * p) {
        a = 0.5 - p / 2
        b = 0.5 + p / 2
    } else if (x > 5 * p && x < 6 * p) {
        a = 0.5 - h
        b = 0.5 + h
    } else if (x > 8 * p && x < 12 * p) {
        a = 0.5 - h
        b = 0.65 + h - 3.5 * abs(x - 10 * p)
        if (x > 9.5 * p && x < 10.5 * p) {
            b = 0.5 + h
        }
    } else if (x > 13 * p && x < 14 * p) {
        a = 0.5 - h
        b = 0.5 + h
    } else if (x > 14 * p && x < 15 * p) {
        a = 0.5 - h
        b = 0.5 - h + p
    } else if (x > 16 * p && x < 17 * p) {
        a = 0.5 - h
        b = 0.5 + h
    } else if (x > 17 * p && x < 18 * p) {
        a = 0.5 - h
        b = 0.5 - h + p
    }

    return [a, b]
}

function vertical(t, x, i) {
    let slider_there = 0
    let slider_x = 0.1 + (0.8 * (1 + sin(t * 10))) / 2

    let [a, b] = sliderhelper(slider_x, 0.3)
    if (x > a && x < b) {
        slider_there = 1
    }

    let h = 0.08
    //return [1, 1 - h * slider_there]
    let slider_y = 0.05

    if (i == 0 || i == n - 1) {
        return [0.02, 0.08]
    }
    if (i == 1 || i == n - 2) {
        return [0, 0.1]
    }

    return [
        slider_y - (h / 2) * slider_there,
        slider_y + (h / 2) * slider_there,
    ]
}

function mandelbrot(t, x, i) {
    let min_y = null
    let max_y = null

    function is_inside_mandelbrot(x, y) {
        let a = x
        let b = y
        for (let i = 0; i < 1000; i++) {
            let a2 = a * a - b * b + x
            let b2 = 2 * a * b + y
            a = a2
            b = b2
            if (a * a + b * b > 4) {
                return false
            }
        }
        return true
    }

    let mx = -0.373127
    let my = -0.626316

    t *= 2
    let zoom = 0.3 + 100 * pow(Math.max(0, t - 0.2), 3)

    //let zoom = 0.1 + t * 5

    //zoom = 0.5
    //zoom = 0.01 + t * 100
    //zoom = 0.5

    x -= 0.5
    x /= zoom

    x += mx

    for (let y = 0; y < 1; y += 1 / (n - 1)) {
        let y2 = y
        y2 -= 0.5
        y2 /= zoom
        y2 -= my

        if (is_inside_mandelbrot(x, y2)) {
            if (min_y == null) {
                min_y = y
            }
            max_y = y
        }
    }
    return [min_y, max_y]
}

function square(t, x, i) {
    let br = 0.2

    //let bx = 0.5 + (1 - br * 2) * 0.5 * sin(2 * PI * t)
    //let bx = 0.8 * br + (1 - 2 * 0.8 * br) * abs(((2 * t) % 2) - 1)
    //let by = br + abs(0.5 * cos(2 * PI * t * 1.5))

    let bx = 0.5 + 0.31 * cos((6 * t * 3 * PI) / 4)
    let by = 0.5 + 0.31 * sin((6 * t * 2 * PI) / 4 + PI)

    let d = abs(bx - x)
    if (d > br) {
        return [by, by]
    } else {
        // intersect line and circle
        // sqrt(d**2 + v**2) == br
        // sqrt(br**2 - d**2) = v
        let v = br
        return [by - v, by + v]
    }
}

// Intersect a square centered at sx,sy, side width w, rotated by r radians
// with a vertical line at x, and return the two intersection y coordinates.
// If there is no intersection, return null.
function squareintersect(sx, sy, w, r, x) {
    let c = cos(r)
    let s = sin(r)

    let x2 = x - sx
    let y2 = 0

    let x3 = c * x2 + s * y2
    let y3 = -s * x2 + c * y2

    if (abs(x3) > w / 2) {
        return null
    }

    let y = sy + y3
    return [y - w / 2, y + w / 2]
}

function rotatingsquare2(t, x, i) {
    let br = 0.2
    let angle = t * 2 * PI
    angle %= PI / 2

    let cx = 0.8 * br + (1 - 2 * 0.8 * br) * abs(((2 * t) % 2) - 1)
    let cy = br + abs(0.5 * cos(2 * PI * t * 1.5))
    cx = 0.5
    cy = 0.5
    r = PI / 4

    // calculate one of the four corners, after rotation by angle
    let corner1_x = cx + cos(angle + r) * br
    let corner1_y = cy + sin(angle + r) * br

    // diagonally opposite corner
    let corner2_x = cx + cos(angle - r) * br
    let corner2_y = cy + sin(angle - r) * br

    let slope = tan(angle)
    let y = slope * (x - corner1_x) + corner1_y

    let otherslope = tan(angle + PI / 2)
    let y2 = otherslope * (x - corner1_x) + corner1_y

    let y3 = otherslope * (x - corner2_x) + corner2_y
    let y4 = slope * (x - corner2_x) + corner2_y

    //y = Math.max(y3, y4)

    return [0, y]
}

// return f(probe_x) for line f that goes through (x, y) with slope slope
function linethroughpoint(x, y, slope, probe_x) {
    return slope * (probe_x - x) + y
}

function rotatingsquare(t, x, i) {
    let w = 0.3 + 0.1 * sin(t * 2 * PI)
    let angle = t * 2 * PI
    angle %= PI / 2

    let cx = 0.5 + 0.15 * cos((6 * t * 3 * PI) / 4)
    let cy = 0.5 + 0.15 * sin((6 * t * 2 * PI) / 4 + PI)

    let corner1_x = cx + sin(PI / 4 - angle) * w
    let corner1_y = cy + cos(PI / 4 - angle) * w

    let corner4_x = cx + sin(PI / 4 + angle) * w

    let corner3_x = cx + sin(PI / 4 - angle + PI) * w
    let corner3_y = cy + cos(PI / 4 - angle + PI) * w

    let corner2_x = cx + sin(PI / 4 + angle + PI) * w

    let upper, lower

    let slope = tan(angle)
    if (x < corner2_x || x > corner4_x) {
        upper = cx
    } else if (x < corner1_x) {
        upper = linethroughpoint(corner1_x, corner1_y, slope, x)
    } else {
        upper = linethroughpoint(corner1_x, corner1_y, -1 / slope, x)
    }

    if (x < corner2_x || x > corner4_x) {
        lower = cx
    } else if (x < corner3_x) {
        lower = linethroughpoint(corner3_x, corner3_y, -1 / slope, x)
    } else {
        lower = linethroughpoint(corner3_x, corner3_y, slope, x)
    }

    return [lower, upper]
}

function cube(t, x, i) {
    let w = 0.3 + 0.1 * sin(t * 2 * PI)
    let angle = t * 2 * PI
    angle %= PI / 2

    let cx = 0.5 + 0.15 * cos((6 * t * 3 * PI) / 4)
    let cy = 0.5 + 0.15 * sin((6 * t * 2 * PI) / 4 + PI)

    let corner1_x = cx + sin(PI / 4 - angle) * w
    let corner1_y = cy + cos(PI / 4 - angle) * w

    let corner4_x = cx + sin(PI / 4 + angle) * w

    let corner3_x = cx + sin(PI / 4 - angle + PI) * w
    let corner3_y = cy + cos(PI / 4 - angle + PI) * w

    let corner2_x = cx + sin(PI / 4 + angle + PI) * w

    let upper, lower

    let slope = tan(angle)
    if (x < corner2_x || x > corner4_x) {
        upper = cx
    } else if (x < corner1_x) {
        upper = linethroughpoint(corner1_x, corner1_y, slope, x)
    } else {
        upper = linethroughpoint(corner1_x, corner1_y, -1 / slope, x)
    }

    if (x < corner2_x || x > corner4_x) {
        lower = cx
    } else if (x < corner3_x) {
        lower = linethroughpoint(corner3_x, corner3_y, -1 / slope, x)
    } else {
        lower = linethroughpoint(corner3_x, corner3_y, slope, x)
    }

    // top
    let squish = 2.5
    let lift = w * 1.5
    let top = [lift + lower / squish, lift + upper / squish]

    // bottom
    lift = 0
    squish = 2
    let bottom = [lift + lower / squish, lift + upper / squish]

    let bb, tt
    if (i % 2) {
        bb = bottom[0]
        tt = top[1]
    } else {
        bb = bottom[0]
        tt = top[0]
    }

    if (x < corner2_x || x > corner4_x) {
        bb = cy
        tt = cy
    }

    //return [bottom[0], top[1]]
    //return [bottom[0], 1]
    //return [0, top[0]]
    return [bb, tt]
}

function rotator(t, x, i) {
    let speed = 5 * sin(t * PI)
    t /= 2
    t *= speed
    t %= 1
    let dx = 0.5 + sin(-2 * PI * t + PI) * 0.3
    let dy = 0.5 + cos(-2 * PI * t + PI) * 0.3

    let a = (2 * PI * (t + 1)) % (2 * PI) //sin(t * Math.PI * 4 + 0.5) * PI
    //x = (i - dx) / 64 - 0.5
    let y = ((x - dx) / cos(a)) * sin(a) + dy

    let other = 0
    console.log(t)
    if (t > 0.25 && t < 0.75) {
        other = 1
    }

    return [other, y]
}

function doom(t, x, i) {
    let cam = [5.5 + 7.7, -11.0]
    let r = 11
    //cam[0] -= r * cos(t * 2 * PI)
    t = t - 2 * PI
    cam[1] -= r * sin(t * 2 * PI)
    //dir = typeof dir === "undefined" ? -PI / 2 : dir
    //dir = -PI / 2 //-t * 0.5 * PI - PI / 2
    //dir = -(t / 2) * 1 * PI
    dir = -PI / 2 + t * 2 * PI
    enemies =
        typeof enemies === "undefined"
            ? [
                  [29, -27],
                  [35, -19],
                  [24, -22],
              ]
            : enemies

    function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
        if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return false
        let d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
        if (d === 0) return false
        let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d
        let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d
        if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return false
        return [x1 + ua * (x2 - x1), y1 + ua * (y2 - y1)]
    }
    function intersect2(x1, y1, x2, y2, cx, cy, r) {
        v1x = x2 - x1
        v1y = y2 - y1
        v2x = x1 - cx
        v2y = y1 - cy
        let b = -2 * (v1x * v2x + v1y * v2y)
        let c = 2 * (v1x * v1x + v1y * v1y)
        let d = sqrt(b * b - 2 * c * (v2x * v2x + v2y * v2y - r * r))
        if (isNaN(d)) return false
        u1 = (b - d) / c
        if (u1 <= 1 && u1 >= 0) {
            return [x1 + v1x * u1, y1 + v1y * u1]
        }
        return false
    }

    walls = [
        [
            [0, 0],
            [2.8, 0],
        ],
        [
            [2.8, 0],
            [5.6, 2.1],
        ],
        [
            [5.6, 2.1],
            [8.4, 2.1],
        ],
        [
            [8.4, 2.1],
            [9.1, 2.1],
        ],
        [
            [9.1, 2.1],
            [12.5, 0],
        ],
        [
            [12.5, 0],
            [14, 0],
        ],
        [
            [14, 0],
            [14, -14.3],
        ],
        [
            [14, -14.3],
            [14.9, -20.7],
        ],
        [
            [14.9, -20.7],
            [16.7, -21.5],
        ],
        [
            [16.7, -21.5],
            [20.8, -21.5],
        ],
        [
            [20.8, -21.5],
            [21.1, -15.8],
        ],
        [
            [21.1, -15.8],
            [38.7, -15.8],
        ],
        [
            [38.7, -15.8],
            [38.7, -32],
        ],
        [
            [38.7, -32],
            [21, -32],
        ],
        [
            [21, -32],
            [21, -24.2],
        ],
        [
            [21, -24.2],
            [16.8, -24],
        ],
        [
            [16.8, -24],
            [11.9, -22],
        ],
        [
            [11.9, -22],
            [11.1, -14.5],
        ],
        [
            [11.1, -14.5],
            [5.7, -14.4],
        ],
        [
            [5.7, -14.4],
            [2.9, -13.1],
        ],
        [
            [2.9, -13.1],
            [0, -13],
        ],
        [
            [0, -13],
            [0, -9.7],
        ],
        [
            [0, -9.7],
            [-4.2, -8.7],
        ],
        [
            [-4.2, -8.7],
            [-5.3, -11.5],
        ],
        [
            [-5.3, -11.5],
            [-12.3, -11.5],
        ],
        [
            [-12.3, -11.5],
            [-12.3, -2.1],
        ],
        [
            [-12.3, -2.1],
            [-5.4, -2.1],
        ],
        [
            [-5.4, -2.1],
            [-4.2, -4.9],
        ],
        [
            [-4.2, -4.9],
            [0, -4],
        ],
        [
            [0, -4],
            [0, 0],
        ],
    ]

    function addColumn(cx, cy) {
        let cr = 0.3
        walls.push(
            ...[
                [
                    [cx - cr, cy - cr],
                    [cx - cr, cy + cr],
                ],
                [
                    [cx - cr, cy + cr],
                    [cx + cr, cy + cr],
                ],
                [
                    [cx + cr, cy + cr],
                    [cx + cr, cy - cr],
                ],
                [
                    [cx + cr, cy - cr],
                    [cx - cr, cy - cr],
                ],
            ],
        )
    }
    addColumn(4.6, -3.7)
    addColumn(10.8, -3.7)
    addColumn(10.8, -10)
    addColumn(4.6, -10)

    aa = atan((x - 0.5) * 1.5)
    a = dir + aa
    end = [cam[0] + cos(a) * 999, cam[1] + sin(a) * 999]
    xs = walls
        .map((w) =>
            intersect(
                cam[0],
                cam[1],
                end[0],
                end[1],
                w[0][0],
                w[0][1],
                w[1][0],
                w[1][1],
            ),
        )
        .filter((p) => p !== false)
        .map((p) => Math.sqrt((p[0] - cam[0]) ** 2 + (p[1] - cam[1]) ** 2))
    nearestWall = Math.min(9999, ...xs)

    xs2 = enemies
        .map((e) => intersect2(cam[0], cam[1], end[0], end[1], e[0], e[1], 0.3))
        .filter((p) => p !== false && p.length > 0)
        .map((p) => Math.sqrt((p[0] - cam[0]) ** 2 + (p[1] - cam[1]) ** 2))
    nearestEnemy = Math.min(99999, ...xs2)

    let v
    if (nearestWall < nearestEnemy) {
        v = 0.5 + (1 / (cos(aa) * nearestWall)) * 2 * (0 - 0.5)
    } else {
        v =
            0.5 +
            (1 / (cos(aa) * nearestEnemy)) * 1 * (0 - 0.75) -
            random() * 0.1 * (1 / nearestEnemy)
    }
    return [v, 1 - v]
}

function island(t, x, i) {
    let flood = 0.3 + 0.4 * t
    let water = flood + 0.005 * sin(10 * t * 2 * PI + 30 * x)
    let island = 0.25 + 0.3 * sin(x * PI)
    let upper = max(island, water)
    let lower = -(0.25 + 0.3 * sin(x * PI) - flood) + flood + water - flood

    //0.25 + 0.3 * sin (x * PI) = flood
    //-0.25 + flood = 0.3 * sin(x*PI)
    //(-0.25 + flood) / 0.3 = sin(x*PI)
    //asin((-0.25 + flood) / 0.3)/PI = x

    let islandwidth_x = asin((-0.25 + flood) / 0.3) / PI

    let islandwidth = islandwidth_x
    if (abs(0.5 - x) > 0.5 - islandwidth) {
        lower = upper - 0.035
    }
    if (flood > 0.55) {
        upper = water
        lower = water - 0.035
    }
    return [lower, upper]
}

function sunset(t, x, i) {
    let flood = 0.3 + 0.4 * t
    let water = flood + 0.005 * sin(10 * t * 2 * PI + 30 * x)
    let island = 0.25 + 0.3 * sin(x * PI)
    let upper = max(island, water)
    let lower = -(0.25 + 0.3 * sin(x * PI) - flood) + flood + water - flood

    //0.25 + 0.3 * sin (x * PI) = flood
    //-0.25 + flood = 0.3 * sin(x*PI)
    //(-0.25 + flood) / 0.3 = sin(x*PI)
    //asin((-0.25 + flood) / 0.3)/PI = x

    let islandwidth_x = asin((-0.25 + flood) / 0.3) / PI

    let islandwidth = islandwidth_x
    if (abs(0.5 - x) > 0.5 - islandwidth) {
        lower = upper - 0.035
    }
    if (flood > 0.55) {
        upper = water
        lower = water - 0.035
    }
    return [lower - flood + 0.3, upper - flood + 0.3]
}
//////// FRAMEWORK

let animations = [
    single,
    //singlefall,
    singleresize,
    loading,
    //allfall,
    //allresize,
    //spectrogram,
    allsine,
    wiggle,
    vertical,
    rain,
    //fire,
    //twister,
    //winston,
    //island,
    mandelbrot, // nicht spannend am ende
    //wave,
    sunset, // echter kreis?
    wavewithboat,
    munch,
    rotator,
    ball,
    pong,
    square,
    rotatingsquare,
    cube,
    doom,
    text, // A schöner?
    allfall,
    heart,
    empty,
    empty,
]

// create n scrollbar elements with an inner and put them into container
let firstScrollbar
function createScrollbars() {
    for (let i = 0; i < n; i++) {
        const scrollbar = document.createElement("div")
        scrollbar.classList.add("scrollbar")
        const inner = document.createElement("div")
        inner.classList.add("inner")
        inner.style.height = `0px`
        scrollbar.appendChild(inner)
        if (i < n - 1) {
            scrollbar.style.opacity = 0
        }
        container.appendChild(scrollbar)
    }

    // first is actually last
    firstScrollbar = document.querySelectorAll(".scrollbar").item(n - 1)
    let inner = firstScrollbar.querySelector(".inner")
    let innerHeight = 4600
    inner.style.height = `${innerHeight}px`
    //firstScrollbar.scrollTop = innerHeight * (1 - b)
}

let animationLength = 12.8 / 2 // seconds
let fadeDuration = 1 // seconds

function clamp(x, a, b) {
    return Math.max(a, Math.min(b, x))
}

function postprocess(result) {
    let a, b
    if (result) {
        ;[a, b] = result
    } else {
        a = 0
        b = 0
    }

    a = clamp(a, 0, 1)
    b = clamp(b, 0, 1)

    // swap so that a is always smaller than b
    if (a > b) {
        const temp = a
        a = b
        b = temp
    }
    return [a, b]
}

function animate() {
    //let t = performance.now() / 1000
    let t = audio.currentTime

    let currentAnimation = floor(t / animationLength) % animations.length
    let f = animations[currentAnimation]
    let f2 = animations[(currentAnimation + 1) % animations.length]

    const scrollbars = document.querySelectorAll(".scrollbar")

    let revealCutoffPhases = 0
    let loadingIndex = animations.indexOf(loading)
    if (loadingIndex != undefined) {
        revealCutoffPhases = loadingIndex
    }

    let revealFactor = 5
    ;[...scrollbars].slice(0, n - 1).forEach((scrollbar, i) => {
        i = n - 1 - i
        scrollbar.style.opacity = smoothstep(
            i / n / revealFactor + revealCutoffPhases,
            i / n / revealFactor + revealCutoffPhases + 0.1,
            t / animationLength,
        )
        //if (t > revealCutoffPhases * animationLength) {
        //} else {
        //    scrollbar.style.opa
        //}
    })

    scrollbars.forEach((scrollbar, i) => {
        let x = i / (n - 1)
        // animation progress between 0 and 1
        let tt = frac((t % animationLength) / animationLength)

        let result = f(tt, x, i)
        let [a, b] = postprocess(result)

        let result2 = f2(tt - 1, x, i)
        let [a2, b2] = postprocess(result2)

        if (tt > 1 - fadeDuration / animationLength) {
            let fadeProgress = smoothstep(
                1 - fadeDuration / animationLength,
                1,
                tt,
            )
            a = a * (1 - fadeProgress) + a2 * fadeProgress
            b = b * (1 - fadeProgress) + b2 * fadeProgress
        }

        let len
        if (a == b) {
            len = 1.1
        } else if (a == 0 && b == 1) {
            len = 0.999
        } else {
            len = Math.abs(b - a)
        }
        const inner = scrollbar.querySelector(".inner")
        let innerHeight = scrollbarLength / len
        inner.style.height = `${innerHeight}px`
        scrollbar.scrollTop = innerHeight * (1 - b)
    })

    animationRequest = requestAnimationFrame(animate)
}

let animationRequest = null

let analyser
const audio = document.querySelector("audio")
let dataArray

let set_up = false
function setup() {
    if (!set_up) {
        const audioCtx = new (window.AudioContext ||
            window.webkitAudioContext)()

        const source = audioCtx.createMediaElementSource(audio)
        // Create an analyser
        analyser = audioCtx.createAnalyser()
        analyser.fftSize = 64 * 2 * 8
        const bufferLength = analyser.frequencyBinCount
        dataArray = new Uint8Array(analyser.frequencyBinCount)
        // Connect parts
        source.connect(analyser)
        analyser.connect(audioCtx.destination)
        set_up = true
    }
}

function start() {
    setup()
    animate()

    setTimeout(() => {
        audio.play()
    }, 0)

    document.getElementById("instruction2").style.opacity = 0
}

//createScrollbars()

/*
document.onclick = () => {
    start()
}
*/

// on mouse wheel, move scrollbar
document.onwheel = (e) => {
    firstScrollbar.scrollTop += e.deltaY / 2
}

audio.onplay = () => {
    start()
}

let instruction = document.getElementById("instruction")
let instruction2 = document.getElementById("instruction2")
let clicked = false
document.onclick = () => {
    if (!clicked) {
        createScrollbars()
        //instruction.innerHTML = "Scroll down to start →"
        instruction.style.opacity = 0
        instruction2.style.opacity = 1
        // fullscreen
        document.documentElement.requestFullscreen()
        // start when scrolled to the bottom
        firstScrollbar.onscroll = (e) => {
            // scroll instruction2 up a bit
            //let currentScroll = firstScrollbar.scrollTop
            //instruction2.style.top = `calc(50%-${-currentScroll}px)`
            if (
                firstScrollbar.scrollTop + firstScrollbar.clientHeight >=
                firstScrollbar.scrollHeight
            ) {
                start()
            }
        }
        clicked = true
    }
}
