Currently there is one pattern that is extremely repetitive in your code: "if (some condition), add this class". You could probably instead do something like this:
var classes = [
i < s && 'before',
i > s && 'after',
i === s && 'active',
i === s - 1 && 'previous',
i === s + 1 && 'next'
].filter(x => x)
The first part of that code, var classes = [...], creates an array that looks something like [false, false, 'active', false, false], by conditionally adding items. For example, suppose i is 3 and s is also 3. When JavaScript evaluates i === s && 'active', it sees that i (3) === s (3), so it moves on to the next value, which is 'active', and sticks that inside the array. When it evaluates i === s + 1 && 'next', it sees that i (3) is not the same as s + 1 (4), so it immediately takes false and adds it to the list.
Then, we use filter to get rid of those false items, so we get an array more like ['active']. Basically, filter takes a function and passes each in the array item one by one to it; when the function returns a truthy value, it keeps the item, and otherwise, it removes the item. The function we passed is an arrow function; it just returns the value that is passed to it. You could replace it with function(x) { return x } and it would be functionally the same.
(I sometimes use .filter(Boolean) instead of .filter(x => x). Boolean can be used as a function to convert a value into a boolean (according to the "truthy" link above) - if you've seen !!x syntax before, it does the same thing. I find .filter(Boolean) more syntactic: "keep items that are truthy (like a boolean)". But it's entirely unnecessary because filter itself automatically checks if your function's returned value is truthy. So if you use it is just a matter of taste!)
Another option is to just get rid of the unnecessary braces and semicolons, like this:
if (i < s) classes.push('before')
if (i > s) classes.push('after')
// ..and so on.
It's certainly a bit more verbose, but you might prefer it anyways!