<!DOCTYPE html>
<html>
<head>
<style>
body
{
    margin:0;
}
section
{
    position:relative;
    height: 100vh;
    box-sizing: border-box;
}
nav
{
    position:fixed;
    right:50px;
    bottom:50px;
    z-index:10;
}
h2
{
    width:500px;
    margin:0 auto;
    text-align:center;
}
h2[data-spy='true']
{
    background:black;
    color: white;;
}
</style>
</head>
<body>

    <nav>
        <a href="#top"  >Top</a>
        <a href="#order">Order</a>
        <a href="#video">Video</a>
        <a href="#about">About</a>
        <a href="#specs">Specs</a>
        <a href="#email">Sign-up</a>
    </nav>
    <section id="video">
        <h2 data-spy="0.2|0.8">Videos</h2>
    </section>
    <section id="about">
        <h2 data-spy="0.2|0.8">About</h2>
    </section>
    <section id="specs">
        <h2 data-spy="0.2|0.8">Specs</h2>
    </section>
<script>
var Time =
{
    Jobs:[],
    Stamp:false,
    Queue:false,
    Loop:false,
    Add:function(inJob)
    {
        if(Time.Loop)
        {
            console.log("cannot modify queue while processing");
            return;
        }
        if(!Time.Queue)
        {
            window.requestAnimationFrame(Time.Update);
        }
        Time.Queue = true;
        Time.Jobs.push(inJob);
    },
    Remove:function(inJob)
    {
        if(Time.Loop)
        {
            console.log("cannot modify queue while processing");
            return;
        }
        var index = Time.Jobs.indexOf(inJob);
        if(index > -1)
        {
            Time.Jobs.splice(index, 1);
        }
    },
    Update:function(inTimestamp)
    {
        var delta;
        var i;

        if(!Time.Stamp)
        {
            Time.Stamp = inTimestamp;
        }
        delta = inTimestamp - Time.Stamp;
        Time.Stamp = inTimestamp;

        Time.Loop = true;
        for(i=0; i<Time.Jobs.length; i++)
        {
            if(Time.Jobs[i](delta) === false)
            {
                Time.Jobs.splice(i, 1);
                i--;
            }
        }
        Time.Loop = false;

        if(Time.Jobs.length > 0)
        {
            window.requestAnimationFrame(Time.Update);
        }
        else
        {
            Time.Stamp = false;
            Time.Queue = false;
        }
    }
};

function JobDuration(inDuration, inHandler, inDone)
{
    var timeCurrent = 0;
    var timeLimit = inDuration*1000;
    var timeRelative = 0;
    var timeMaxed = false;
    return function(inDelta)
    {
        timeCurrent += inDelta;
        timeMaxed = timeCurrent > timeLimit;
        if(timeMaxed)
        {
            inHandler(1);
            inDone();
            return false;
        }
        return inHandler(timeCurrent / timeLimit);
    };
}


document.querySelector("nav").addEventListener("click", function(inEvent)
{
    var domHtml = document.querySelector("html");
    var domGoal = document.querySelector(inEvent.target.getAttribute("href"));
    var posStart = domHtml.scrollTop - 200;
    var posRange = domGoal.getBoundingClientRect().top;
    var evtTick = function(inProgress){ domHtml.scrollTop = posStart + posRange*Math.sqrt(1 - Math.pow(1-(inProgress), 2)); };
    var evtDone = function(){ Spy.Resume(); };
    
    Spy.Suspend();
    Time.Add( JobDuration(0.4, evtTick, evtDone) );
    inEvent.preventDefault();
});

</script>
<script>
var Spy =
{
    Attribute:"data-spy",
    Members:[],
    Defaults:[0, 1, 0, 1],
    Disabled:false,
    Suspend:function()
    {
        Spy.Disabled = true;
    },
    Resume:function()
    {
        Spy.Disabled = false;
        Spy.UpdateAll();
    },
    UpdateAll:function()
    {
        var i, member, aabb, top, bottom, left, right;
        var visible;

        if(Spy.Disabled){ return; }

        for(i=0; i<Spy.Members.length; i++)
        {
            member = Spy.Members[i];

            top =    window.innerHeight * member.Bounds[0];
            bottom = window.innerHeight * member.Bounds[1];
            left =   window.innerWidth *  member.Bounds[2];
            right =  window.innerWidth *  member.Bounds[3];

            aabb = member.Element.getBoundingClientRect();
            visible = (aabb.top < bottom && aabb.bottom > top) && (aabb.left < right && aabb.right > left);
            if(visible != member.Visible)
            {
                member.Element.setAttribute(Spy.Attribute, visible);
                member.Visible = visible;
                member.Change(visible);
            }
        }
    },
    Create:function(inElement)
    {
        var j, bounds;
        var attr;
        var obj;
        attr = inElement.getAttribute(Spy.Attribute)||"";
        inElement.removeAttribute(Spy.Attribute);
        bounds = attr.split("|");
        for(j=0; j<Spy.Defaults.length; j++)
        {
            if(bounds[j])
            {
                bounds[j] = parseFloat(bounds[j]);
            }
            else
            {
                bounds[j] = Spy.Defaults[j];
            }
        }
        obj = {
            Element:inElement,
            Bounds:bounds,
            Visible:undefined,
            Change:function(){}
        };
        Spy.Members.push(obj);
        return obj;
    },
    CreateAll:function()
    {
        var i, elements;
        elements = document.querySelectorAll("*["+Spy.Attribute+"]");

        for(i=0; i<elements.length; i++)
        {
            Spy.Create(elements[i]);
        }
    },
    Init:function()
    {
        Spy.CreateAll();
        Spy.UpdateAll();
        document.addEventListener("scroll", Spy.UpdateAll, {passive:true});
        window.addEventListener("resize", Spy.UpdateAll, {passive:true});
    }
};
Spy.Init();
</script>
</body>
</html>