<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>Overshoot 2027 · Design and motion conference, Rotterdam</title>
<meta name="description" content="Overshoot is a two-day design and motion conference at Seinhuis, Rotterdam, on 14 and 15 April 2027.">
<meta name="theme-color" content="#f3efe6">
<script>document.documentElement.classList.add('js')</script>
<style>
:root{
--paper:#f3efe6;--card:#fffdf8;--ink:#16130f;--mute:#4d463c;--tomato:#ff4f2b;--cobalt:#2b3fff;--sun:#ffc61a;--mint:#8be6c1;
--pad:clamp(16px,4.4vw,40px);--max:1240px;--bw:3px;
--display:"Arial Black","Helvetica Neue",Helvetica,Arial,sans-serif;
--body:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
--mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
--spring:cubic-bezier(.34,1.56,.64,1);--out:cubic-bezier(.16,1,.3,1);
}
*,::before,::after{box-sizing:border-box}
*{margin:0}
html{-webkit-text-size-adjust:100%;scroll-padding-top:72px}
html.lock{overflow:hidden}
body{background:var(--paper);color:var(--ink);font:400 17px/1.55 var(--body);-webkit-font-smoothing:antialiased;overflow-x:clip}
button,input{font:inherit;color:inherit}
button{background:none;border:0;padding:0;cursor:pointer;-webkit-tap-highlight-color:transparent}
a{color:inherit}
:focus-visible{outline:3px solid var(--cobalt);outline-offset:3px;border-radius:6px}
.tix :focus-visible,footer :focus-visible{outline-color:var(--ink)}
footer :focus-visible{outline-color:var(--sun)}
.wrap{width:100%;max-width:calc(var(--max) + 2*var(--pad));margin:0 auto;padding:0 var(--pad)}
h1,h2,h3{font-family:var(--display);font-weight:900;font-synthesis:none;line-height:.92;letter-spacing:-.025em;text-transform:uppercase}
h2{font-size:clamp(2.5rem,9.4vw,6rem);text-wrap:balance}
.eyebrow{font:700 12px/1.3 var(--mono);letter-spacing:.1em;text-transform:uppercase;margin-bottom:16px}
.lede{font-size:clamp(1.05rem,2.3vw,1.28rem);max-width:31em;margin-top:22px}
.sec{padding:clamp(60px,9vw,120px) 0;border-bottom:var(--bw) solid var(--ink)}
.card{border:var(--bw) solid var(--ink);border-radius:22px;background:var(--card);box-shadow:6px 6px 0 var(--ink)}
.btn{--sh:6px;display:inline-flex;align-items:center;justify-content:center;gap:10px;min-height:52px;padding:0 24px;border:var(--bw) solid var(--ink);border-radius:99px;background:var(--bg,var(--tomato));color:var(--ink);font:800 16px/1 var(--body);text-decoration:none;white-space:nowrap;box-shadow:var(--sh) var(--sh) 0 var(--ink);transition:transform .35s var(--spring),box-shadow .35s var(--spring)}
.btn.ghost{--bg:var(--card)}
.btn.sm{--sh:4px;min-height:44px;padding:0 18px;font-size:15px}
.btn.dark{--bg:var(--ink);color:var(--paper)}
@media(hover:hover){.btn:hover{transform:translate(-3px,-3px);box-shadow:calc(var(--sh) + 3px) calc(var(--sh) + 3px) 0 var(--ink)}}
.btn:active{transform:translate(var(--sh),var(--sh));box-shadow:0 0 0 var(--ink);transition-duration:.08s}
/* bar */
.bar{position:sticky;top:0;z-index:30;background:rgba(243,239,230,.94);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-bottom:var(--bw) solid var(--ink)}
.bar .wrap{display:flex;align-items:center;gap:14px;height:56px}
.logo{display:flex;align-items:center;gap:10px;font:900 18px/1 var(--display);font-synthesis:none;text-transform:uppercase;letter-spacing:-.02em;text-decoration:none}
.logo svg{width:26px;height:30px;overflow:visible}
.logo .lc{transform-origin:13px 2px;transition:transform .5s var(--spring)}
.logo .lb{transition:transform .5s var(--spring)}
@media(hover:hover){.logo:hover .lc{transform:scaleY(1.25)}.logo:hover .lb{transform:translateY(5px)}}
.bar nav{display:none;margin-left:auto;gap:2px}
.bar nav a{padding:12px 14px;border-radius:99px;font:700 14px/1 var(--body);text-decoration:none;transition:background .2s,color .2s}
.bar nav a:hover{background:var(--ink);color:var(--paper)}
.bar .btn{margin-left:auto}
@media(min-width:860px){.bar nav{display:flex}.bar .btn{margin-left:10px}}
.prog{position:absolute;left:0;right:0;bottom:-3px;height:3px;background:var(--tomato);transform-origin:0 50%;transform:scaleX(var(--p,0))}
/* hero */
.hero{position:relative;overflow:hidden}
.hero::before{content:"";position:absolute;inset:0;background-image:linear-gradient(rgba(22,19,15,.085) 1px,transparent 1px),linear-gradient(90deg,rgba(22,19,15,.085) 1px,transparent 1px);background-size:32px 32px;-webkit-mask-image:linear-gradient(#000 35%,transparent 96%);mask-image:linear-gradient(#000 35%,transparent 96%);pointer-events:none}
.hero-in{position:relative;display:flex;flex-direction:column;min-height:calc(var(--vh,100vh) - 59px);padding-top:clamp(12px,2.4vw,26px);padding-bottom:clamp(14px,2.6vw,30px)}
.hero .eyebrow{margin:0 0 clamp(6px,1.4vw,14px)}
.mega{display:flex;flex-direction:column;align-items:flex-start;font-size:80px;line-height:.84;letter-spacing:-.035em;white-space:nowrap}
@media(min-width:720px){.mega{flex-direction:row;line-height:.86}}
.ln{display:flex}
.ch{display:inline-block;translate:0 calc(var(--k,0) * -.14em);transition:translate .5s var(--spring)}
.ch.c1{color:var(--cobalt)}
.ch.c2{color:#f84420}
.js .ch{opacity:0}
.go .ch{opacity:1;animation:drop .95s var(--spring) backwards;animation-delay:calc(.25s + var(--i)*55ms)}
@keyframes drop{from{opacity:0;transform:translateY(-1.1em) rotate(calc(var(--r)*1deg)) scale(.6)}18%{opacity:1}}
.js .e{opacity:0;transform:translateY(16px)}
.go .e{opacity:1;transform:none;transition:opacity .6s calc(var(--n)*90ms + .1s),transform .8s var(--spring) calc(var(--n)*90ms + .1s)}
.chart{position:relative;flex:1 1 auto;min-height:56px;max-height:calc(var(--vh,700px)*.8);margin-top:6px}
.chart svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.cv-ax{stroke:var(--ink);stroke-width:2}
.cv-tg{stroke:var(--ink);stroke-width:1.5;stroke-dasharray:5 6;opacity:.6}
.cv-ln{fill:none;stroke:var(--ink);stroke-width:4;stroke-linecap:round;stroke-linejoin:round}
.cv-bl{fill:var(--tomato);stroke:var(--ink);stroke-width:3}
.cv-tx{font:700 11.5px var(--mono);letter-spacing:.08em;text-transform:uppercase;fill:var(--ink)}
.cv-os{fill:var(--tomato);stroke:none}
.cv-br{stroke:var(--ink);stroke-width:2.5}
.hb{position:absolute;left:0;top:0;width:var(--d,40px);height:var(--d,40px);border-radius:50%;background:var(--tomato);border:var(--bw) solid var(--ink);box-shadow:4px 4px 0 var(--ink);transform-origin:50% 100%;animation:bounce 1.7s infinite}
.js .hb{opacity:0}
.go .hb{opacity:1;transition:opacity .2s 1.3s}
@keyframes bounce{
0%{transform:translateY(0) scale(1.18,.82);animation-timing-function:cubic-bezier(.1,.6,.3,1)}
38%{transform:translateY(calc(var(--bh,30px)*-1)) scale(.94,1.08);animation-timing-function:cubic-bezier(.6,0,.9,.4)}
78%{transform:translateY(0) scale(.96,1.05)}
86%{transform:translateY(0) scale(1.22,.78);animation-timing-function:ease-out}
100%{transform:translateY(0) scale(1.18,.82)}
}
.chart .badge{display:none;inset:auto 6px 20px auto;width:clamp(96px,10vw,132px);height:auto;animation:spin 22s linear infinite}
@media(min-width:900px){.chart .badge{display:block}}
.chart .badge.off{display:none!important}
.bt{font:700 11.5px var(--mono);letter-spacing:.09em;text-transform:uppercase;fill:var(--ink)}
@keyframes spin{to{transform:rotate(360deg)}}
.meta{display:flex;align-items:flex-end;justify-content:space-between;gap:12px 24px;margin-top:10px}
.when{font:800 14px/1.3 var(--body)}
.when b{display:block;font-weight:800}
.when span{display:block;font:700 11px/1.4 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.cta{display:flex;flex-wrap:wrap;gap:12px 16px}
.cta .ghost{display:none}
@media(min-width:560px){.cta .ghost{display:inline-flex}.when{font-size:clamp(16px,2vw,20px)}.when span{font-size:12px}}
@media(max-width:479px){.hero .btn{min-height:48px;padding:0 18px;font-size:15px}}
/* band */
.bandw{position:relative;z-index:2;margin:-22px 0;padding:22px 0;overflow:hidden;pointer-events:none}
.band{position:relative;margin:0 -20px;transform:rotate(-1.4deg);background:var(--cobalt);color:var(--paper);border-block:var(--bw) solid var(--ink);overflow:hidden;white-space:nowrap}
.band .tr{display:inline-flex;animation:tick 40s linear infinite}
.band span{display:inline-flex;align-items:center;gap:22px;padding:13px 0 13px 22px;font:900 clamp(18px,3.4vw,28px)/1 var(--display);font-synthesis:none;text-transform:uppercase;letter-spacing:-.01em}
.band span::after{content:"";width:.5em;height:.5em;border-radius:50%;background:var(--sun);border:2px solid var(--ink)}
@keyframes tick{to{transform:translateX(-50%)}}
/* lab */
.lab-g{display:grid;gap:34px;align-items:start}
@media(min-width:900px){.lab-g{grid-template-columns:1fr 1.05fr;gap:64px}}
.facts{display:flex;flex-wrap:wrap;gap:10px;margin-top:26px;list-style:none;padding:0}
.facts li{padding:10px 16px;border:var(--bw) solid var(--ink);border-radius:99px;background:var(--card);font:700 15px/1 var(--body);box-shadow:3px 3px 0 var(--ink)}
.facts b{font:900 17px/1 var(--display);font-synthesis:none}
.labc{padding:clamp(16px,3vw,26px)}
.top{display:flex;justify-content:space-between;gap:12px;font:700 11.5px/1.3 var(--mono);letter-spacing:.08em;text-transform:uppercase}
.lchart{position:relative;height:clamp(150px,34vw,200px);margin:14px 0 8px;border:2px solid var(--ink);border-radius:14px;background-image:linear-gradient(rgba(22,19,15,.09) 1px,transparent 1px),linear-gradient(90deg,rgba(22,19,15,.09) 1px,transparent 1px);background-size:24px 24px;overflow:hidden}
.lchart svg{position:absolute;inset:0;width:100%;height:100%}
.track{position:relative;height:48px;margin:12px 0 0;border:2px solid var(--ink);border-radius:99px;background:repeating-linear-gradient(90deg,transparent 0 17px,rgba(22,19,15,.14) 17px 18px)}
.track .mk{position:absolute;top:-2px;bottom:-2px;border-left:3px dashed var(--ink)}
.track .tb{position:absolute;top:50%;left:0;width:30px;height:30px;margin-top:-15px;border-radius:50%;background:var(--tomato);border:var(--bw) solid var(--ink)}
.lrow{display:flex;justify-content:space-between;margin-top:16px;font:700 12px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase}
.rng{display:block;width:100%;height:36px;margin-top:6px;-webkit-appearance:none;appearance:none;background:transparent;cursor:pointer}
.rng::-webkit-slider-runnable-track{height:12px;border-radius:99px;border:var(--bw) solid var(--ink);background:linear-gradient(90deg,var(--mint),var(--sun) 55%,var(--tomato))}
.rng::-webkit-slider-thumb{-webkit-appearance:none;width:34px;height:34px;margin-top:-13px;border-radius:50%;background:var(--card);border:var(--bw) solid var(--ink);box-shadow:3px 3px 0 var(--ink);transition:transform .3s var(--spring)}
.rng::-moz-range-track{height:12px;border-radius:99px;border:var(--bw) solid var(--ink);background:linear-gradient(90deg,var(--mint),var(--sun) 55%,var(--tomato))}
.rng::-moz-range-thumb{width:28px;height:28px;border-radius:50%;background:var(--card);border:var(--bw) solid var(--ink);box-shadow:3px 3px 0 var(--ink)}
.rng:active::-webkit-slider-thumb{transform:scale(1.15)}
.lout{margin-top:12px;font:800 16px/1.3 var(--body)}
.lout b{background:var(--tomato);color:var(--ink);padding:2px 7px;border-radius:7px;font-family:var(--display);font-weight:900;font-synthesis:none}
.rep{margin-top:12px}
/* speakers */
.spk{background:var(--sun)}
.cards{display:grid;gap:18px;margin-top:40px;list-style:none;padding:0;grid-template-columns:minmax(0,1fr)}
@media(min-width:640px){.cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px}}
@media(min-width:1040px){.cards{grid-template-columns:repeat(4,minmax(0,1fr))}}
.sp{position:relative;display:grid;grid-template-columns:104px minmax(0,1fr);overflow:hidden;cursor:pointer;transition:transform .45s var(--spring),box-shadow .45s var(--spring)}
.sp .art{display:block;width:100%;height:100%;min-height:132px;background:var(--bg);border-right:var(--bw) solid var(--ink)}
.sp .tx{padding:14px 16px 16px;display:flex;flex-direction:column;gap:4px;min-width:0}
.sp h3{font-size:clamp(1.15rem,2.6vw,1.4rem);line-height:1.02;letter-spacing:-.02em;text-transform:none}
.sp .role{font:600 12px/1.4 var(--mono);color:var(--mute)}
.sp .talk{font:800 16px/1.25 var(--body);margin-top:6px}
.sp .mt{margin-top:auto;padding-top:8px;font:700 11px/1.4 var(--mono);letter-spacing:.06em;text-transform:uppercase}
@media(min-width:640px){.sp{grid-template-columns:1fr;grid-template-rows:auto 1fr}.sp .art{border-right:0;border-bottom:var(--bw) solid var(--ink);aspect-ratio:1;height:auto;min-height:0}}
@media(hover:hover){.sp:hover{transform:translate(-4px,-4px) rotate(-.7deg);box-shadow:11px 11px 0 var(--ink)}}
.sp:active{transform:scale(.97)}
.sp:not(.live) .art *{animation-play-state:paused}
.a3{vector-effect:non-scaling-stroke}
.a-l{stroke:var(--ink);stroke-width:2.5}
.a-t{font:700 7px var(--mono);letter-spacing:.1em;fill:var(--ink);text-anchor:middle}
.a1{fill:var(--card);stroke:var(--ink);stroke-width:3;animation:slide 1.6s cubic-bezier(.65,0,.35,1) infinite alternate}
@keyframes slide{to{transform:translateX(56px)}}
.a2c{fill:none;stroke:var(--card);stroke-width:4;stroke-linejoin:round;stroke-linecap:round;transform-origin:50px 6px;animation:coil 1.5s var(--spring) infinite alternate}
@keyframes coil{from{transform:scaleY(.72)}to{transform:scaleY(1.18)}}
.a2b{fill:var(--sun);stroke:var(--ink);stroke-width:3;animation:cball 1.5s var(--spring) infinite alternate}
@keyframes cball{from{transform:translateY(-17.9px)}to{transform:translateY(11.5px)}}
.a3{fill:none;stroke:var(--ink);stroke-width:3;transform-box:fill-box;transform-origin:center;animation:ring 2.4s cubic-bezier(.2,.6,.3,1) infinite}
.a3.r2{animation-delay:.8s}.a3.r3{animation-delay:1.6s}
@keyframes ring{from{transform:scale(.5);opacity:1}to{transform:scale(4);opacity:0}}
.gh{fill:none;stroke:var(--ink);stroke-width:1.5;stroke-dasharray:3 3;opacity:.45}
.a4{fill:var(--tomato);stroke:var(--ink);stroke-width:3;animation:flip 1.5s steps(1,end) infinite alternate}
@keyframes flip{
0%{transform:translate(20px,76px) scale(1.3,.7)}
20%{transform:translate(35px,46px) scale(.9,1.1)}
40%{transform:translate(50px,30px)}
60%{transform:translate(65px,46px) scale(.9,1.1)}
80%{transform:translate(80px,76px) scale(1.3,.7)}
}
.a5{fill:var(--sun);transform-box:fill-box;transform-origin:center;animation:breathe 6s ease-in-out infinite}
@keyframes breathe{50%{transform:scale(1.7)}}
.a-q{stroke:var(--paper);stroke-width:2;opacity:.4}
.a6{transform-origin:50px 10px;animation:swing 1.5s cubic-bezier(.37,0,.63,1) infinite alternate}
@keyframes swing{from{transform:rotate(-32deg)}to{transform:rotate(32deg)}}
.a6b{fill:var(--cobalt);stroke:var(--ink);stroke-width:3}
.a7{fill:var(--card);stroke:var(--ink);stroke-width:2.5;transform-box:fill-box;transform-origin:center;animation:cell 2.2s var(--spring) infinite;animation-delay:calc(var(--i)*.14s)}
@keyframes cell{0%,100%{transform:scale(1)}30%{transform:scale(.45) rotate(45deg)}60%{transform:scale(1.15)}}
.a8{fill:var(--sun);stroke:var(--ink);stroke-width:3;transform-box:fill-box;transform-origin:center;animation:nudge 5s ease-in-out infinite}
@keyframes nudge{0%,86%{transform:none}91%{transform:translateX(7px) rotate(7deg)}95%{transform:translateX(-2px) rotate(-2deg)}100%{transform:none}}
.a-p{fill:var(--paper);font:700 6.5px var(--mono);letter-spacing:.08em;text-anchor:middle}
.sp.boing .art{animation:boing .6s var(--spring)}
@keyframes boing{30%{transform:scale(1.06,.94)}60%{transform:scale(.98,1.03)}}
/* programme */
.days{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:34px;max-width:640px}
.day{display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:14px 16px;border:var(--bw) solid var(--ink);border-radius:18px;background:var(--card);text-align:left;box-shadow:4px 4px 0 var(--ink);transition:transform .35s var(--spring),box-shadow .35s var(--spring),background .2s,color .2s}
.day small{font:700 11px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase}
.day b{font:900 clamp(17px,4.4vw,26px)/1 var(--display);font-synthesis:none;text-transform:uppercase;letter-spacing:-.02em}
.day[aria-selected=true]{background:var(--ink);color:var(--paper);transform:translate(4px,4px);box-shadow:0 0 0 var(--ink)}
.chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:22px}
.chip{display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:0 16px;border:var(--bw) solid var(--ink);border-radius:99px;background:var(--card);font:800 14px/1 var(--body);transition:transform .3s var(--spring),background .2s,color .2s}
.chip i{width:12px;height:12px;border-radius:50%;border:2px solid var(--ink);background:var(--c,var(--card))}
.chip[aria-pressed=true]{background:var(--ink);color:var(--paper)}
.chip[aria-pressed=true] i{border-color:var(--paper)}
@media(hover:hover){.chip:hover{transform:translateY(-2px) rotate(-1.5deg)}}
.rows{list-style:none;padding:0;margin-top:26px;border-top:var(--bw) solid var(--ink)}
.rows[hidden]{display:none}
.row{display:grid;grid-template-rows:1fr;transition:grid-template-rows .45s var(--out),opacity .3s}
.row.off{grid-template-rows:0fr;opacity:0}
.row>.in{min-height:0;overflow:hidden;border-bottom:2px solid var(--ink)}
.row.off>.in{border-bottom-width:0;visibility:hidden;transition:visibility 0s .45s}
.js .rows.play .row{animation:rowin .6s var(--spring) backwards;animation-delay:calc(var(--i)*45ms)}
@keyframes rowin{from{opacity:0;transform:translateY(22px)}}
.rbtn{display:grid;grid-template-columns:62px minmax(0,1fr) 36px;gap:2px 14px;align-items:start;width:100%;padding:18px 0;text-align:left}
.rbtn .t{font:800 15px/1.4 var(--mono)}
.rbtn .m b{display:block;font:800 clamp(17px,4.2vw,22px)/1.2 var(--body)}
.rbtn .m em{display:block;margin-top:2px;font:500 14px/1.4 var(--body);font-style:normal;color:var(--mute)}
.tags{grid-column:2;display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.tag{padding:5px 9px;border:2px solid var(--ink);border-radius:99px;background:var(--c,var(--card));font:700 11px/1 var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--ink)}
.pm{grid-row:1;grid-column:3;position:relative;width:34px;height:34px;border:var(--bw) solid var(--ink);border-radius:50%;transition:transform .45s var(--spring),background .2s}
.pm::before,.pm::after{content:"";position:absolute;left:7px;right:7px;top:50%;height:3px;margin-top:-1.5px;border-radius:2px;background:var(--ink)}
.pm::after{transform:rotate(90deg);transition:transform .45s var(--spring)}
.row.open .pm{transform:rotate(180deg);background:var(--sun)}
.row.open .pm::after{transform:rotate(0)}
@media(hover:hover){.rbtn:hover .pm{background:var(--sun)}}
.more{display:grid;grid-template-rows:0fr;transition:grid-template-rows .5s var(--out)}
.row.open .more{grid-template-rows:1fr}
.more>div{min-height:0;overflow:hidden;visibility:hidden;transition:visibility 0s .5s}
.row.open .more>div{visibility:visible;transition-delay:0s}
.more p{padding:0 0 22px 76px;max-width:44em;color:var(--mute)}
@media(min-width:760px){
.rbtn{grid-template-columns:96px minmax(0,1fr) 250px 40px;gap:2px 20px;align-items:center;padding:22px 0}
.tags{grid-column:auto;margin-top:0}
.pm{grid-row:auto;grid-column:auto}
.more p{padding-left:116px}
}
/* venue */
.ven{background:var(--mint)}
.ven-g{display:grid;gap:30px;margin-top:40px}
@media(min-width:960px){.ven-g{grid-template-columns:1.4fr 1fr;gap:48px;align-items:start}}
.plan{padding:12px}
.plan svg{display:block;width:100%;height:auto;overflow:visible}
.room{cursor:pointer;transition:opacity .3s}
.room rect{stroke:var(--ink);stroke-width:4;transform-box:fill-box;transform-origin:center;transition:transform .45s var(--spring)}
.room text{font-family:var(--display);font-weight:900;text-transform:uppercase;fill:var(--ink);letter-spacing:-.02em;pointer-events:none}
.room .sm{font:700 13px var(--mono);letter-spacing:.06em;fill:var(--ink)}
@media(max-width:639px){.room .sm{display:none}}
.plan.touched .room:not(.sel){opacity:.5}
.room.sel rect{stroke-width:6;transform:scale(1.025)}
.room:focus-visible{outline:none}
.room:focus-visible rect{stroke:var(--cobalt);stroke-width:7}
.pl-w{fill:none;stroke:var(--ink);stroke-width:5}
.pl-t{font:700 9px var(--mono);letter-spacing:.08em;text-transform:uppercase;fill:var(--ink)}
.rt{fill:none;stroke:var(--ink);stroke-width:2;stroke-dasharray:2 7;stroke-linecap:round;opacity:.6}
.you{fill:var(--tomato);stroke:var(--ink);stroke-width:3}
.you-r{fill:none;stroke:var(--tomato);stroke-width:2.5;transform-box:fill-box;transform-origin:center;animation:ring 2s ease-out infinite}
.rbts{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.rb{display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:0 14px;border:var(--bw) solid var(--ink);border-radius:99px;background:var(--card);font:800 14px/1 var(--body);transition:transform .3s var(--spring),background .2s,color .2s}
.rb i{width:14px;height:14px;border-radius:4px;border:2px solid var(--ink);background:var(--c)}
.rb[aria-pressed=true]{background:var(--ink);color:var(--paper)}
.rb[aria-pressed=true] i{border-color:var(--paper)}
.info{padding:22px}
.info h3{font-size:clamp(1.8rem,5vw,2.6rem)}
.info .sub{margin-top:10px}
.info ul{list-style:none;padding:0;margin-top:16px;display:grid;gap:10px}
.info li{display:grid;grid-template-columns:84px minmax(0,1fr);gap:10px;align-items:baseline;font-size:15px;line-height:1.35}
.info li b{font:800 12px/1.5 var(--mono);letter-spacing:.04em}
.info.swap{animation:swap .5s var(--spring)}
@keyframes swap{from{opacity:0;transform:translateY(14px)}}
.facts2{display:grid;gap:8px;margin-top:22px;padding:0;list-style:none;font-size:15px}
.facts2 li{display:flex;gap:10px}
.facts2 li::before{content:"";flex:none;width:.65em;height:.65em;margin-top:.5em;border-radius:50%;background:var(--ink)}
/* tickets */
.tix{background:var(--tomato)}
.tiers{display:grid;gap:26px;margin-top:46px}
@media(min-width:900px){.tiers{grid-template-columns:repeat(3,1fr);gap:26px;align-items:stretch}}
.tier{position:relative;display:flex;flex-direction:column;gap:10px;padding:26px 24px 24px;transition:transform .45s var(--spring),box-shadow .45s var(--spring)}
.tier.best{background:var(--sun)}
@media(hover:hover){.tier:hover{transform:translate(-4px,-4px) rotate(-.6deg);box-shadow:11px 11px 0 var(--ink)}}
.tier h3{font-size:1.5rem;letter-spacing:-.02em}
.tier .dt{font:700 12px/1.3 var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--mute)}
.price{font:900 clamp(3.4rem,10vw,4.8rem)/.9 var(--display);font-synthesis:none;letter-spacing:-.04em;margin:6px 0 4px}
.tier ul{list-style:none;padding:0;display:grid;gap:8px;margin:6px 0 14px;font-size:15.5px}
.tier li{display:flex;gap:10px}
.tier li::before{content:"";flex:none;width:.65em;height:.65em;margin-top:.5em;border-radius:50%;background:var(--ink)}
.tier .btn{margin-top:auto;--bg:var(--card)}
.tier.best .btn{--bg:var(--tomato)}
.stamp{position:absolute;top:-16px;right:18px;transform:rotate(5deg);padding:8px 12px;border-radius:8px;background:var(--ink);color:var(--sun);font:800 12px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;animation:stamp 3s var(--spring) infinite}
@keyframes stamp{0%,70%,100%{transform:rotate(5deg)}80%{transform:rotate(-3deg) scale(1.08)}90%{transform:rotate(7deg)}}
.tnote{margin-top:30px;max-width:40em;font-weight:600}
/* dialog + ticket */
dialog{margin:auto;padding:0;border:var(--bw) solid var(--ink);border-radius:24px;background:var(--paper);color:var(--ink);width:min(560px,calc(100% - 24px));max-height:calc(100% - 24px);overflow:auto;box-shadow:10px 10px 0 var(--ink)}
dialog::backdrop{background:rgba(22,19,15,.6)}
dialog[open]{animation:sheet .55s var(--spring)}
@keyframes sheet{from{opacity:0;transform:translateY(44px) scale(.95)}}
@media(max-width:639px){dialog{margin:auto auto 0;width:100%;max-width:none;max-height:92%;border-radius:24px 24px 0 0;border-bottom:0;box-shadow:none}}
.dlg{padding:20px clamp(16px,4vw,28px) 24px}
.dh{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.dh h2{font-size:clamp(1.8rem,7vw,2.5rem)}
.x{flex:none;width:44px;height:44px;border:var(--bw) solid var(--ink);border-radius:50%;background:var(--card);display:grid;place-items:center;transition:transform .35s var(--spring)}
.x svg{width:16px;height:16px}
@media(hover:hover){.x:hover{transform:rotate(90deg) scale(1.08)}}
.fld{display:grid;gap:8px;margin-top:18px}
.fld>label,.lb2{font:700 12px/1.3 var(--mono);letter-spacing:.1em;text-transform:uppercase}
.inp{width:100%;min-height:54px;padding:0 16px;border:var(--bw) solid var(--ink);border-radius:14px;background:var(--card);font:700 18px/1 var(--body)}
.inp[aria-invalid=true]{animation:shake .4s;border-color:var(--tomato)}
.ferr{display:none;align-items:center;gap:8px;font:700 14px/1.3 var(--body)}
.ferr::before{content:"!";flex:none;display:grid;place-items:center;width:20px;height:20px;border:2px solid var(--ink);border-radius:50%;background:var(--tomato);font:800 12px/1 var(--mono)}
.inp[aria-invalid=true]~.ferr{display:flex}
@keyframes shake{25%{transform:translateX(-8px)}50%{transform:translateX(8px)}75%{transform:translateX(-4px)}}
.opts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.opt{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:6px;min-height:64px;padding:8px 12px;border:var(--bw) solid var(--ink);border-radius:14px;background:var(--card);text-align:left;transition:transform .3s var(--spring),background .2s,color .2s}
.opt b{font:800 14px/1.1 var(--body)}
.opt span{font:900 20px/1 var(--display);font-synthesis:none;letter-spacing:-.03em}
.opt[aria-checked=true]{background:var(--ink);color:var(--paper);transform:translateY(-3px)}
.dlg .go2{margin-top:20px;width:100%}
.fine{margin-top:16px;font:600 12px/1.4 var(--mono);color:var(--mute)}
.prt{margin-top:18px}
.prt[hidden],.form[hidden]{display:none}
.slot{position:relative;z-index:2;height:18px;border:var(--bw) solid var(--ink);border-radius:99px;background:var(--ink)}
.tkw{position:relative;z-index:1;margin:-9px 10px 0;overflow:hidden;padding:0 0 16px}
.ticket{position:relative;display:grid;grid-template-columns:1fr;border:var(--bw) solid var(--ink);border-radius:16px;background:var(--tc,var(--sun));color:var(--tk,var(--ink));box-shadow:5px 5px 0 var(--ink);overflow:hidden}
.ticket.print{animation:feed 1.1s var(--spring) backwards}
@keyframes feed{from{transform:translateY(-104%)}}
.ticket::after{content:"";position:absolute;inset:0;background:linear-gradient(105deg,transparent 35%,rgba(255,255,255,.55) 50%,transparent 65%);transform:translateX(-120%);pointer-events:none}
.ticket.print::after{animation:sheen 1.1s .9s ease-out}
@keyframes sheen{to{transform:translateX(120%)}}
.tk-m{padding:18px 18px 16px;display:flex;flex-direction:column;gap:8px}
.tk-m .mono{font:700 11px/1.3 var(--mono);letter-spacing:.1em;text-transform:uppercase}
.tk-n{font:900 clamp(1.7rem,8vw,2.4rem)/.95 var(--display);font-synthesis:none;letter-spacing:-.03em;text-transform:uppercase;overflow-wrap:anywhere}
.tk-t{font:800 16px/1.2 var(--body)}
.tk-s{display:flex;align-items:center;gap:12px;padding:12px 18px;border-top:3px dashed var(--ink);background:rgba(255,255,255,.28)}
.tk-s svg{flex:1;min-width:0;height:44px}
.tk-s .mono{font:700 11px/1.2 var(--mono);letter-spacing:.06em}
.bar-r{transform-box:fill-box;transform-origin:50% 100%;animation:grow .35s var(--out) backwards;animation-delay:calc(.9s + var(--i)*16ms)}
@keyframes grow{from{transform:scaleY(0)}}
@media(min-width:480px){
.ticket{grid-template-columns:1fr 150px}
.tk-s{flex-direction:column;align-items:stretch;justify-content:center;border-top:0;border-left:3px dashed var(--ink)}
.tk-s svg{flex:none;width:100%}
}
.acts{display:flex;flex-wrap:wrap;gap:12px;margin-top:14px}
/* footer */
footer{position:relative;background:var(--ink);color:var(--paper);padding:clamp(70px,10vw,120px) 0 36px;overflow:hidden}
footer h2{font-size:clamp(2.6rem,10.4vw,9rem);line-height:.9;color:var(--paper)}
footer h2 .ln2{display:block}
footer h2 .ln2:last-child{color:var(--sun)}
.fl{display:flex;flex-wrap:wrap;gap:6px 22px;margin-top:34px;font:700 15px/1 var(--body)}
.fl a{display:inline-flex;align-items:center;min-height:44px;text-decoration:none;border-bottom:2px solid transparent;transition:border-color .2s}
.fl a:hover{border-color:var(--sun)}
.fine2{margin-top:30px;padding-top:22px;border-top:1px solid rgba(243,239,230,.25);font-size:13.5px;color:#b9b2a4;max-width:48em}
.fb{position:absolute;right:max(20px,7vw);bottom:clamp(120px,16vw,210px);width:clamp(54px,10vw,120px);aspect-ratio:1;pointer-events:none}
.fb i{display:block;width:100%;height:100%;border-radius:50%;background:var(--tomato);border:var(--bw) solid var(--paper);transform-origin:50% 100%;animation:drop2 3.4s infinite}
.fb::after{content:"";position:absolute;left:10%;right:10%;bottom:-8px;height:8px;border-radius:50%;background:rgba(243,239,230,.25);animation:shadow 3.4s infinite}
@keyframes drop2{
0%{transform:translateY(-170px) scale(.94,1.08);animation-timing-function:cubic-bezier(.55,0,.9,.5)}
40%{transform:translateY(0) scale(.94,1.08)}
45%{transform:translateY(0) scale(1.3,.68);animation-timing-function:cubic-bezier(.1,.5,.3,1)}
56%{transform:translateY(-64px) scale(.95,1.06);animation-timing-function:cubic-bezier(.55,0,.9,.5)}
70%{transform:translateY(0) scale(1.16,.84)}
76%{transform:translateY(-16px) scale(1);animation-timing-function:cubic-bezier(.55,0,.9,.5)}
84%,100%{transform:translateY(0) scale(1)}
}
@keyframes shadow{0%,40%,70%,84%,100%{transform:scaleX(1);opacity:1}0%{transform:scaleX(.3);opacity:.3}56%{transform:scaleX(.55);opacity:.5}76%{transform:scaleX(.8)}}
/* motion */
.js .rv{opacity:0;transform:translateY(40px) scale(.97)}
.js .rv.in{opacity:1;transform:none;transition:opacity .5s calc(var(--rd,0)*90ms),transform .9s var(--spring) calc(var(--rd,0)*90ms)}
@media(prefers-reduced-motion:reduce){
*,*::before,*::after{animation-duration:.001ms!important;animation-delay:0s!important;animation-iteration-count:1!important;transition-duration:.001ms!important;transition-delay:0s!important}
.js .e,.js .rv,.js .ch{opacity:1!important;transform:none!important}
.band .tr,.badge{animation:none!important}
}
</style>
</head>
<body>
<header class="bar"><div class="wrap">
<a class="logo" href="#top" aria-label="Overshoot 2027, back to top"><svg viewBox="0 0 26 30" aria-hidden="true"><g class="lc"><path d="M13 2v3L6 8l14 5L6 18l7 3" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></g><circle class="lb" cx="13" cy="25" r="4.5" fill="#ff4f2b" stroke="currentColor" stroke-width="2.4"/></svg>Overshoot</a>
<nav aria-label="Sections"><a href="#about">The idea</a><a href="#speakers">Speakers</a><a href="#programme">Programme</a><a href="#venue">Venue</a><a href="#tickets">Tickets</a></nav>
<button class="btn sm" data-ticket>Get a ticket</button>
</div><i class="prog" aria-hidden="true"></i></header>
<main>
<section class="hero" id="top"><div class="wrap hero-in">
<p class="eyebrow e" style="--n:0">Design & motion · Rotterdam</p>
<h1 class="mega" id="h1" aria-label="Overshoot"><span class="ln" aria-hidden="true">Over</span><span class="ln" aria-hidden="true">shoot</span></h1>
<i class="hb" aria-hidden="true"></i>
<div class="chart e" id="chart" style="--n:1"><svg id="hsvg" aria-hidden="true"></svg><svg class="badge" id="badge" viewBox="0 0 120 120" aria-hidden="true"><defs><path id="bp" d="M60 60m-45 0a45 45 0 1 1 90 0a45 45 0 1 1-90 0"/></defs><circle cx="60" cy="60" r="58" fill="#ffc61a" stroke="#16130f" stroke-width="3"/><text class="bt"><textPath href="#bp" textLength="268" lengthAdjust="spacing">Early prices · until 28 Feb ·</textPath></text><polygon points="60,43 63.44,51.69 72.02,47.98 68.31,56.56 77,60 68.31,63.44 72.02,72.02 63.44,68.31 60,77 56.56,68.31 47.98,72.02 51.69,63.44 43,60 51.69,56.56 47.98,47.98 56.56,51.69" fill="#ff4f2b" stroke="#16130f" stroke-width="3" stroke-linejoin="round"/></svg></div>
<div class="meta e" style="--n:2"><p class="when"><b>14–15 April 2027</b><span>Seinhuis, Rotterdam</span></p><div class="cta"><button class="btn" data-ticket>Get a ticket</button><a class="btn ghost" href="#programme">See the programme</a></div></div>
</div></section>
<div class="bandw" aria-hidden="true"><div class="band"><div class="tr"><span>Easing</span><span>Springs</span><span>Anticipation</span><span>Squash & stretch</span><span>Timing</span><span>Follow-through</span><span>Weight</span><span>Arcs</span><span>Settling</span><span>Overshoot</span></div></div></div>
<section class="sec" id="about"><div class="wrap lab-g">
<div class="rv"><p class="eyebrow">The idea</p><h2>Go past the target. Then settle.</h2>
<p class="lede">Every good animation overshoots. The button goes a little too far, hangs for a beat, then settles. That small mistake is what gives a screen weight. Overshoot is two days about getting it right.</p>
<ul class="facts"><li><b>2</b> days</li><li><b>8</b> speakers</li><li><b>3</b> rooms</li><li><b>400</b> seats</li></ul></div>
<div class="card labc rv" style="--rd:1"><div class="top"><span>Spring lab</span><span>Drag the slider</span></div>
<div class="lchart"><svg id="lsvg" aria-hidden="true"></svg></div>
<div class="track" id="track" aria-hidden="true"><i class="mk" id="tmk"></i><i class="tb" id="tb"></i></div>
<div class="lrow"><span>Stiff</span><span>Bouncy</span></div>
<input class="rng" id="bounce" type="range" min="0" max="100" value="66" aria-label="Bounciness, from stiff to bouncy">
<p class="lout" id="lout" aria-live="polite"></p>
<button class="btn sm ghost rep" id="rep">Replay</button></div>
</div></section>
<section class="sec spk" id="speakers"><div class="wrap"><div class="rv"><p class="eyebrow">Speakers</p><h2>Eight people, eight ways to move.</h2></div>
<ul class="cards" id="cards"></ul></div></section>
<section class="sec" id="programme"><div class="wrap"><div class="rv"><p class="eyebrow">Programme</p><h2>Two days. One talk at a time.</h2></div>
<div class="days" role="tablist" aria-label="Day"><button class="day" role="tab" id="d0" aria-controls="p0" aria-selected="true"><small>Wednesday</small><b>14 April</b></button><button class="day" role="tab" id="d1" aria-controls="p1" aria-selected="false" tabindex="-1"><small>Thursday</small><b>15 April</b></button></div>
<div class="chips" role="group" aria-label="Filter the programme"><button class="chip" aria-pressed="false" data-f="t"><i style="--c:var(--tomato)"></i>Talks</button><button class="chip" aria-pressed="false" data-f="w"><i style="--c:var(--mint)"></i>Workshops</button><button class="chip" aria-pressed="false" data-f="b"><i style="--c:var(--sun)"></i>Breaks</button></div>
<div id="p0" role="tabpanel" aria-labelledby="d0"></div><div id="p1" role="tabpanel" aria-labelledby="d1" hidden></div>
</div></section>
<section class="sec ven" id="venue"><div class="wrap"><div class="rv"><p class="eyebrow">Venue</p><h2>Where it happens.</h2>
<p class="lede">Seinhuis is a former signal works on the river: high ceilings, thick walls, one very big door. Tap a room and the red dot walks you there from the front.</p></div>
<div class="ven-g"><div><div class="card plan rv" id="plan"><svg viewBox="0 0 640 400" role="group" aria-label="Floor plan of Seinhuis">
<path class="pl-w" d="M92 386H628V12H12V386H48" fill="none" stroke-linejoin="round"/>
<g class="room" data-room="hall" role="button" tabindex="0" aria-label="Hall, 400 seats" aria-pressed="false"><rect x="28" y="28" width="272" height="202" rx="8" fill="#ff4f2b"/><text x="164" y="124" text-anchor="middle" font-size="40">Hall</text><text class="sm" x="164" y="152" text-anchor="middle">400 seats</text></g>
<g class="room" data-room="studio" role="button" tabindex="0" aria-label="Studio, 60 seats" aria-pressed="false"><rect x="312" y="28" width="164" height="202" rx="8" fill="#ffc61a"/><text x="394" y="124" text-anchor="middle" font-size="30">Studio</text><text class="sm" x="394" y="152" text-anchor="middle">60 seats</text></g>
<g class="room" data-room="lab" role="button" tabindex="0" aria-label="Lab, 30 seats" aria-pressed="false"><rect x="488" y="28" width="124" height="202" rx="8" fill="#8be6c1"/><text x="550" y="124" text-anchor="middle" font-size="30">Lab</text><text class="sm" x="550" y="152" text-anchor="middle">30 seats</text></g>
<g class="room" data-room="foyer" role="button" tabindex="0" aria-label="Foyer and café" aria-pressed="false"><rect x="28" y="242" width="584" height="130" rx="8" fill="#f3efe6"/><text x="278" y="300" text-anchor="middle" font-size="30">Foyer</text><text class="sm" x="278" y="322" text-anchor="middle">café · lunch · badges</text></g>
<path class="rt" id="route" d="M70 392V330H165V236V330H392V236V330H547V236"/>
<g id="you"><circle class="you-r" r="9"/><circle class="you" r="9"/></g>
</svg></div>
<div class="rbts" role="group" aria-label="Choose a room"><button class="rb" aria-pressed="false" data-room="hall"><i style="--c:var(--tomato)"></i>Hall</button><button class="rb" aria-pressed="false" data-room="studio"><i style="--c:var(--sun)"></i>Studio</button><button class="rb" aria-pressed="false" data-room="lab"><i style="--c:var(--mint)"></i>Lab</button><button class="rb" aria-pressed="false" data-room="foyer"><i style="--c:var(--paper)"></i>Foyer</button></div></div>
<div class="card info rv" id="info" style="--rd:1"><div id="ib" aria-live="polite"></div>
<ul class="facts2"><li>Step-free from the street to every room.</li><li>A quiet room is open on both days, upstairs.</li><li>Lunch is vegetarian, with vegan and gluten-free options.</li></ul></div>
</div></div></section>
<section class="sec tix" id="tickets"><div class="wrap"><div class="rv"><p class="eyebrow">Tickets</p><h2>Pick your pass.</h2></div>
<div class="tiers">
<article class="tier card rv"><h3>Day 1</h3><p class="dt">Wednesday 14 April</p><p class="price">€190</p><ul><li>Four talks in the Hall</li><li>A seat in one Wednesday workshop</li><li>Lunch, coffee and drinks on the quay</li></ul><button class="btn" data-ticket="0">Pick Day 1</button></article>
<article class="tier card best rv" style="--rd:1"><span class="stamp">Best value</span><h3>Both days</h3><p class="dt">Wed 14 + Thu 15 April</p><p class="price">€320</p><ul><li>Every talk and the panel</li><li>First pick of workshop seats</li><li>Lunch, coffee and drinks on the quay</li><li>Recordings sent in May</li></ul><button class="btn" data-ticket="1">Pick both days</button></article>
<article class="tier card rv" style="--rd:2"><h3>Student / unwaged</h3><p class="dt">Wed 14 + Thu 15 April</p><p class="price">€90</p><ul><li>Everything in the both-days pass</li><li>Show a student card or just tell us</li><li>40 passes, first come first served</li></ul><button class="btn" data-ticket="2">Pick this pass</button></article>
</div>
<p class="tnote">Early prices run until 28 February. After that every pass costs €40 more. Prices include VAT. You can hand your pass to a colleague until 1 April.</p></div></section>
</main>
<footer><i class="fb" aria-hidden="true"><i></i></i><div class="wrap">
<h2><span class="ln2">See you in</span><span class="ln2">Rotterdam.</span></h2>
<p class="lede">Doors open at 09:00 on Wednesday 14 April. Bring comfortable shoes and one thing you made that moves.</p>
<nav class="fl" aria-label="Footer"><a href="#top">Back to top</a><a href="#programme">Programme</a><a href="#venue">Venue</a><a href="#tickets">Tickets</a></nav>
<p class="fine2">Example page. Overshoot, Seinhuis and every speaker, company and number on it are made up. Nothing you type or click here is sent or saved.</p>
</div></footer>
<dialog id="dlg" aria-labelledby="dh"><div class="dlg">
<div class="dh"><h2 id="dh">Get your pass</h2><button class="x" id="dx" aria-label="Close"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2 2l12 12M14 2L2 14" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg></button></div>
<div class="form" id="frm">
<div class="fld"><label for="nm">Name on the pass</label><input class="inp" id="nm" maxlength="32" autocomplete="off" placeholder="Your name" aria-describedby="ferr"><p class="ferr" id="ferr" role="alert">Add a name for the pass.</p></div>
<div class="fld"><span class="lb2" id="pl">Pass</span><div class="opts" role="radiogroup" aria-labelledby="pl"><button class="opt" role="radio" aria-checked="false"><b>Day 1</b><span>€190</span></button><button class="opt" role="radio" aria-checked="true"><b>2 days</b><span>€320</span></button><button class="opt" role="radio" aria-checked="false"><b>Student</b><span>€90</span></button></div></div>
<button class="btn go2" id="pr">Make my pass</button></div>
<div class="prt" id="prt" hidden><div class="slot"></div><div class="tkw"><div class="ticket" id="tk"><div class="tk-m"><span class="mono">Overshoot 2027 · admit one</span><b class="tk-n" id="tkN"></b><span class="tk-t" id="tkT"></span><span class="mono" id="tkD"></span></div><div class="tk-s"><svg id="tkB" viewBox="0 0 96 44" preserveAspectRatio="none" aria-hidden="true"></svg><span class="mono" id="tkNo"></span></div></div></div>
<div class="acts"><button class="btn ghost" id="again">Make another</button><button class="btn dark" id="cl">Close</button></div></div>
<p class="fine">Example page: nothing is sent or saved, and the pass is gone when you close this.</p>
</div></dialog>
<script>
(()=>{
const $=(s,r=document)=>r.querySelector(s),$$=(s,r=document)=>[...r.querySelectorAll(s)];
const root=document.documentElement,RM=matchMedia('(prefers-reduced-motion:reduce)').matches,NS='http://www.w3.org/2000/svg';
const sv=(t,a,p)=>{const e=document.createElementNS(NS,t);for(const k in a)e.setAttribute(k,a[k]);if(p)p.append(e);return e};
const clamp=(v,a,b)=>Math.min(b,Math.max(a,v)),vis={};
const watch=(el,k)=>new IntersectionObserver(([e])=>{vis[k]=e.isIntersecting}).observe(el);
/* data */
const SP=[
['Ines Calderón','Motion lead, Studio Kade','200 milliseconds','Wed 10:00 · Hall','var(--tomato)'],
['Tobias Wennerström','Interaction engineer, Fjordline','Springs, not curves','Wed 10:50 · Hall','var(--cobalt)'],
['Amara Okonkwo-Reid','Design director, Halo & Finch','Ripples that answer back','Wed 12:10 · Hall','var(--mint)'],
['Jun Takeda','Animator, Tsuki Works','Frame by frame (workshop)','Wed 14:15 · Studio','var(--card)'],
['Rafael Duarte','Physics programmer, Pêndulo','Momentum is a feeling','Wed 16:30 · Hall','var(--card)'],
['Mette Aaberg','Sound designer, Stille','The quiet interface','Thu 10:00 · Hall','var(--ink)'],
['Dilnoza Karimova','Design systems lead, Kuvert','A grid for time','Thu 10:50 · Hall','var(--tomato)'],
['Pieter van der Laan','Product designer, Zeeuws Blauw','The talk where nothing moves','Thu 16:00 · Hall','var(--cobalt)']];
const ART=[
'<path class="a-l" fill="none" d="M10 78H90"/><rect class="gh" x="66" y="36" width="24" height="26" rx="6"/><rect class="a1" x="10" y="36" width="24" height="26" rx="6"/><text class="a-t" x="50" y="94">ease-in-out</text>',
'<path class="a-l" fill="none" d="M30 6H70"/><path class="a2c" d="M50 6V14L32 20L68 32L32 44L68 56L32 62L50 70"/><circle class="a2b" cx="50" cy="70" r="11"/>',
'<circle class="a3" cx="50" cy="50" r="12"/><circle class="a3 r2" cx="50" cy="50" r="12"/><circle class="a3 r3" cx="50" cy="50" r="12"/><circle cx="50" cy="50" r="7" fill="#16130f"/>',
'<circle class="gh" cx="20" cy="76" r="9"/><circle class="gh" cx="35" cy="46" r="9"/><circle class="gh" cx="50" cy="30" r="9"/><circle class="gh" cx="65" cy="46" r="9"/><circle class="gh" cx="80" cy="76" r="9"/><circle class="a4" r="9"/><text class="a-t" x="50" y="95">12 fps</text>',
'<path class="a-l" fill="none" d="M34 10H66"/><g class="a6"><path class="a-l" fill="none" d="M50 10V62"/><circle class="a6b" cx="50" cy="68" r="12"/></g>',
'<path class="a-q" d="M14 22H86M14 78H86"/><circle class="a5" cx="50" cy="50" r="12"/>',
[0,1,2].flatMap(r=>[0,1,2].map(c=>`<rect class="a7" x="${14+c*28}" y="${14+r*28}" width="22" height="22" rx="5" style="--i:${r+c}"/>`)).join(''),
'<rect class="a8" x="34" y="30" width="32" height="32" rx="8"/><text class="a-p" x="50" y="86">wait for it</text>'];
const TY={t:['Talk','var(--tomato)'],w:['Workshop','var(--mint)'],b:['Break','var(--sun)']};
const S=[
[0,'09:00','b','Doors open','Coffee, stroopwafels and name tags','Foyer','Pick up your badge, find the coffee, find a stranger to sit next to. The first talk starts on time, with or without you.'],
[0,'10:00','t','200 milliseconds','Ines Calderón','Hall','Why interface motion feels slow at 300 ms and rushed at 150. A year of timing tests on real phones, with the clips to prove it.'],
[0,'10:50','t','Springs, not curves','Tobias Wennerström','Hall','Swap your easing curves for two numbers, stiffness and damping, and watch the handoff arguments get shorter.'],
[0,'11:40','b','Coffee break','Pastries from the bakery next door','Foyer','Twenty-five minutes. The queue for the good coffee is shorter at the far end.'],
[0,'12:10','t','Ripples that answer back','Amara Okonkwo-Reid','Hall','How a touch ripple tells a button it has been heard, and why the best ones are almost too small to see.'],
[0,'13:00','b','Lunch on long tables','Seinhuis kitchen','Foyer','Vegetarian by default, vegan and gluten-free on request. Sit with someone you do not know.'],
[0,'14:15','w','Springs in an afternoon','Tobias Wennerström','Lab','Ninety minutes, one laptop, one button that bounces. You leave with a spring you wrote yourself.'],
[0,'14:15','w','Frame by frame','Jun Takeda','Studio','Twelve drawings, one bouncing ball and a lot of paper. Pencil animation for people who only ever used keyframes.'],
[0,'16:30','t','Momentum is a feeling','Rafael Duarte','Hall','What a pendulum teaches you about scroll physics, drag to dismiss, and the half second after you let go.'],
[0,'17:30','b','Drinks on the quay','The Overshoot social','Foyer','The first day ends with one drink, then probably three. Find someone you met at lunch.'],
[1,'10:00','t','The quiet interface','Mette Aaberg','Hall','Sound design for screens that should not make a sound. Small silences, loud absences and one very gentle click.'],
[1,'10:50','t','A grid for time','Dilnoza Karimova','Hall','Motion tokens for a hundred designers: one clock, three durations and a rule about when to break them.'],
[1,'11:40','b','Coffee break','Same coffee, new conversations','Foyer','Twenty-five minutes. The Studio doors are open if you want to watch the next workshop being set up.'],
[1,'12:10','w','Bring a broken animation','Amara Okonkwo-Reid and Jun Takeda','Studio','Bring a motion that feels wrong and you cannot say why. Two people who can will take it apart with you, live.'],
[1,'13:00','b','Lunch on long tables','Seinhuis kitchen','Foyer','Same tables, different soup.'],
[1,'14:30','t','Panel: who owns the motion?','Ines, Dilnoza and Rafael','Hall','Design says feel, engineering says frames, product says ship. Three people who have been all of them argue it out.'],
[1,'16:00','t','The talk where nothing moves','Pieter van der Laan','Hall','Thirty minutes, one slide. It moves exactly once, and you will remember when.']];
const R={
hall:['Hall','The main stage','Every talk happens here, one at a time, so nobody has to choose. Live captions on the big screen.',[['Seats','400'],['Sessions','7 talks and 1 panel'],['Doors','09:00, both days']]],
studio:['Studio','The workshop room','Tables, power and a lot of paper. Bring a laptop, or just a pencil.',[['Seats','60'],['Sessions','Frame by frame (Wed), Bring a broken animation (Thu)'],['Doors','30 minutes before each session']]],
lab:['Lab','The spring lab','Open all day, both days. Tinker with the stiff and the bouncy, borrow a tablet, corner a speaker.',[['Seats','30'],['Sessions','Springs in an afternoon (Wed), plus drop-in hours'],['Doors','09:00 to 18:00']]],
foyer:['Foyer','Badges, coffee, lunch','The one room where talking is the programme. Long tables for lunch, and the good coffee at the far end.',[['Space','Standing room and long tables'],['Serves','Coffee, lunch on both days, drinks on Wednesday'],['Opens','09:00 for badges']]]};
const D={hall:251,studio:666,lab:1009,foyer:110};
const T=[['Day 1','Wednesday 14 April','var(--mint)'],['Both days','Wed 14 + Thu 15 April','var(--sun)'],['Student','Wed 14 + Thu 15 April','var(--tomato)']];
/* build */
$('#cards').innerHTML=SP.map((s,i)=>`<li class="sp card rv" style="--bg:${s[4]};--rd:${i%4}"><svg class="art" viewBox="0 0 100 100" aria-hidden="true">${ART[i]}</svg><div class="tx"><h3>${s[0]}</h3><p class="role">${s[1]}</p><p class="talk">${s[2]}</p><p class="mt">${s[3]}</p></div></li>`).join('');
[0,1].forEach(d=>{$('#p'+d).innerHTML='<ul class="rows">'+S.filter(s=>s[0]==d).map((s,i)=>`<li class="row" data-t="${s[2]}" style="--i:${i}"><div class="in"><button class="rbtn" aria-expanded="false"><span class="t">${s[1]}</span><span class="m"><b>${s[3]}</b><em>${s[4]}</em></span><span class="tags"><span class="tag" style="--c:${TY[s[2]][1]}">${TY[s[2]][0]}</span><span class="tag">${s[5]}</span></span><i class="pm"></i></button><div class="more"><div><p>${s[6]}</p></div></div></div></li>`).join('')+'</ul>'});
const band=$('.tr');band.innerHTML+=band.innerHTML;
/* hero headline */
const hero=$('.hero'),h=$('#h1'),L=[];
$$('.ln',h).forEach((l,li)=>{const t=l.textContent;l.textContent='';[...t].forEach((c,i)=>{const s=document.createElement('span');s.className='ch'+(li==0&&i==0?' c1':li==1&&(i==2||i==3)?' c2':'');s.textContent=c;s.style.setProperty('--i',L.length);s.style.setProperty('--r',(L.length*53)%61-30);l.append(s);L.push(s)})});
function wave(from){if(RM)return;L.forEach((c,i)=>c.animate([{transform:'none'},{transform:'scale(1.1,.86) translateY(-.1em)',offset:.35},{transform:'scale(.96,1.06)',offset:.7},{transform:'none'}],{duration:800,delay:Math.abs(i-from)*60,easing:'cubic-bezier(.34,1.56,.64,1)'}))}
h.addEventListener('click',e=>{const c=e.target.closest('.ch');wave(c?L.indexOf(c):0)});
setInterval(()=>{if(vis.hero&&!document.hidden)wave(0)},6500);
if(matchMedia('(hover:hover) and (pointer:fine)').matches&&!RM){
hero.addEventListener('pointermove',e=>L.forEach(c=>{const r=c.getBoundingClientRect(),d=Math.hypot(e.clientX-r.left-r.width/2,e.clientY-r.top-r.height/2)/(r.width*2.4);c.style.setProperty('--k',Math.max(0,1-d)**2)}));
hero.addEventListener('pointerleave',()=>L.forEach(c=>c.style.setProperty('--k',0)))}
/* spring maths and plots */
function plot(s,id,z,tm,o){
s.textContent='';
const W=s.clientWidth,H=s.clientHeight;
s.setAttribute('viewBox','0 0 '+W+' '+H);
const w=Math.sqrt(1-z*z),ov=Math.exp(-Math.PI*z/w),vm=Math.max(1.2,(1+ov)*1.12),yb=H-o.b,x0=o.l,x1=W-o.r,
X=t=>x0+t/tm*(x1-x0),Y=v=>yb-v/vm*(yb-o.t),F=t=>1-Math.exp(-z*t)*(Math.cos(w*t)+z/w*Math.sin(w*t));
const cr=sv('rect',{y:-60,height:H+120,width:0},sv('clipPath',{id},sv('defs',{},s)));
sv('path',{class:'cv-ax',fill:'none',d:`M${x0} ${o.t-8}V${yb}H${x1+2}`},s);
sv('path',{class:'cv-tg',d:`M${x0} ${Y(1)}H${x1}`},s);
const g=sv('g',{'clip-path':`url(#${id})`},s),n=Math.max(90,W>>1),P=[];
let a=-1,b=-1,d='';
for(let i=0;i<=n;i++){const t=tm*i/n,v=F(t),x=X(t),y=Y(v);P.push([x,y]);d+=(i?'L':'M')+x.toFixed(1)+' '+y.toFixed(1);if(a<0&&v>1.0005)a=i;else if(a>=0&&b<0&&v<1)b=i}
if(a>0&&b>0)sv('path',{class:'cv-os',d:`M${P[a][0]} ${Y(1)}`+P.slice(a,b).map(p=>'L'+p.join(' ')).join('')+`L${P[b][0]} ${Y(1)}Z`},g);
sv('path',{class:'cv-ln',d},g);
if(ov>.03){const x=X(Math.PI/w),y1=Y(1),y2=Y(1+ov);
sv('path',{class:'cv-br',fill:'none',d:`M${x} ${y1}V${y2}M${x-5} ${y1}H${x+5}M${x-5} ${y2}H${x+5}`},g);
if(o.lab)sv('text',{class:'cv-tx',x:x+11,y:y2-1},g).textContent='Overshoot +'+Math.round(ov*100)+'%'}
if(o.lab){sv('text',{class:'cv-tx',x:x0+10,y:Y(1)-8},s).textContent='Target';
if(!o.tg)sv('text',{class:'cv-tx','text-anchor':'end',x:x1,y:H-6},s).textContent='Time →'}
const bl=sv('circle',{class:'cv-bl',r:o.br,cx:x0,cy:Y(0)},s);
return{vm,set:p=>{const t=tm*p,x=X(t),v=F(t);cr.setAttribute('width',x);bl.setAttribute('cx',x);bl.setAttribute('cy',Y(v));return v/vm}}}
let VH,lastW,hP,hp=RM?1:0,h0=0,LP,lz,ldur=1500,lt0=-1,tw=0;
const bn=$('#bounce'),track=$('#track'),tmk=$('#tmk'),tb=$('#tb'),out=$('#lout'),rep=$('#rep');
function springLab(){
lz=.98-bn.value*.009;
const tm=clamp(4.2/lz,4.5,40),ov=Math.round(100*Math.exp(-Math.PI*lz/Math.sqrt(1-lz*lz)));
ldur=clamp(tm/13*1.7,1.3,5)*1000;
LP=plot($('#lsvg'),'lc',lz,tm,{l:10,r:10,t:24,b:12,br:7,lab:1,tg:1});
tw=track.clientWidth-30;tmk.style.left=15+tw/LP.vm-1.5+'px';
out.innerHTML=ov?`<b>${ov}%</b> overshoot. Settles in ${(4/(lz*13)).toFixed(1)} s. ${ov<10?'Crisp.':ov<35?'Springy.':ov<60?'Playful.':'Wobbly.'}`:'<b>0%</b> overshoot. It just stops.';
lt0=-1;if(RM)tb.style.transform=`translateX(${LP.set(1)*tw}px)`}
bn.addEventListener('input',springLab);
rep.onclick=()=>{lt0=-1};
if(RM)rep.style.display='none';
function charts(){
const c=$('#chart'),H=c.clientHeight,lab=H>=130&&c.clientWidth>=340;
hP=plot($('#hsvg'),'hc',.34,11,{l:6,r:6,t:lab?36:10,b:lab?26:8,br:H<90?6:9,lab});
const bw=Math.min(132,Math.max(96,innerWidth/10));// the badge's CSS width, clamp(96px,10vw,132px)
$('#badge').classList.toggle('off',(H-(lab?62:18))/hP.vm<bw+(lab?12:30));// hidden where it would touch the curve's end ball
hP.set(hp);springLab()}
function fit(){
const row=innerWidth>=720,lns=$$('.ln',h),box=h.parentNode,cs=getComputedStyle(box),eb=$('.eyebrow',box);
h.style.fontSize='100px';
const w=lns.map(l=>l.getBoundingClientRect().width),need=row?w[0]+w[1]:Math.max(w[0],w[1]);
const res=eb.offsetHeight+parseFloat(getComputedStyle(eb).marginBottom)+$('.meta',box).offsetHeight+72+parseFloat(cs.paddingTop)+parseFloat(cs.paddingBottom);
h.style.fontSize=Math.min(100*h.clientWidth/need,Math.max(48,(VH-59-res)/(row?.86:1.68)),360).toFixed(1)+'px'}
function ball(){
const T=L[L.length-1].getBoundingClientRect(),B=h.parentNode.getBoundingClientRect(),hb=$('.hb'),fs=parseFloat(h.style.fontSize),d=clamp(fs*.22,26,60),top=T.top-B.top+fs*.05-d;
hb.style.setProperty('--d',d+'px');hb.style.setProperty('--bh',clamp(d*.55,8,Math.max(8,top-2))+'px');
hb.style.left=T.left-B.left+T.width/2-d/2+'px';hb.style.top=top+'px'}
function layout(){lastW=innerWidth;VH=innerHeight;root.style.setProperty('--vh',VH+'px');fit();ball();charts()}
let rt;addEventListener('resize',()=>{clearTimeout(rt);rt=setTimeout(()=>{if(innerWidth!==lastW||Math.abs(innerHeight-VH)>120)layout()},120)});
/* floor plan */
const plan=$('#plan'),info=$('#info'),route=$('#route'),you=$('#you');
let cur=0,tgt=0,wl=0,walking=false;
function put(){const p=route.getPointAtLength(cur);you.setAttribute('transform',`translate(${p.x} ${p.y})`)}
function walk(now){const dt=Math.min(.05,(now-wl)/1000),d=tgt-cur;wl=now;cur+=Math.sign(d)*Math.min(Math.abs(d),dt*330);put();if(cur===tgt)walking=false}
function selRoom(k,auto){
$$('.room,.rb').forEach(e=>{const on=e.dataset.room==k;e.classList.toggle('sel',on);e.setAttribute('aria-pressed',on)});
if(!auto)plan.classList.add('touched');
const r=R[k];
$('#ib').innerHTML=`<h3>${r[0]}</h3><p class="sub"><b>${r[1]}</b></p><p class="sub">${r[2]}</p><ul>${r[3].map(l=>`<li><b>${l[0]}</b><span>${l[1]}</span></li>`).join('')}</ul>`;
info.classList.remove('swap');void info.offsetWidth;info.classList.add('swap');
tgt=D[k];if(RM){cur=tgt;put()}else if(!walking){walking=true;wl=performance.now()}}
$$('.room').forEach(g=>{g.onclick=()=>selRoom(g.dataset.room);g.onkeydown=e=>{if(e.key=='Enter'||e.key==' '){e.preventDefault();selRoom(g.dataset.room)}}});
$$('.rb').forEach(b=>b.onclick=()=>selRoom(b.dataset.room));
/* programme */
const tabs=$$('.day'),on=new Set();
function day(i,focus){
tabs.forEach((t,j)=>{t.setAttribute('aria-selected',i==j);t.tabIndex=i==j?0:-1;$('#p'+j).hidden=i!=j});
const r=$('#p'+i+' .rows');r.classList.remove('play');void r.offsetWidth;r.classList.add('play');if(focus)tabs[i].focus()}
tabs.forEach((t,i)=>{t.onclick=()=>day(i);t.onkeydown=e=>{const j={ArrowRight:1-i,ArrowLeft:1-i,Home:0,End:1}[e.key];if(j!=null){e.preventDefault();day(j,1)}}});
$$('.chip').forEach(c=>c.onclick=()=>{const f=c.dataset.f,x=!on.has(f);x?on.add(f):on.delete(f);c.setAttribute('aria-pressed',x);$$('.row').forEach(r=>r.classList.toggle('off',on.size>0&&!on.has(r.dataset.t)))});
$('#programme').addEventListener('click',e=>{const b=e.target.closest('.rbtn');if(b)b.setAttribute('aria-expanded',b.parentNode.parentNode.classList.toggle('open'))});
/* tickets */
const dlg=$('#dlg'),nm=$('#nm'),frm=$('#frm'),prt=$('#prt'),tk=$('#tk'),opts=$$('.opt'),dh=$('#dh');
let pick=1;
const fnv=s=>{let x=2166136261;for(const c of s){x^=c.charCodeAt(0);x=Math.imul(x,16777619)}return x>>>0};
const mul=a=>()=>{a=a+1831565813|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296};
const choose=i=>{pick=i;opts.forEach((o,j)=>{o.setAttribute('aria-checked',j==i);o.tabIndex=j==i?0:-1})};
opts.forEach((o,i)=>{o.onclick=()=>choose(i);o.onkeydown=e=>{const d={ArrowRight:1,ArrowDown:1,ArrowLeft:-1,ArrowUp:-1}[e.key];if(d){e.preventDefault();const j=(i+d+3)%3;choose(j);opts[j].focus()}}});
function show(f){frm.hidden=!f;prt.hidden=f;dh.textContent=f?'Get your pass':'Here you go.'}
function openDlg(i){choose(i===''?1:+i);show(true);nm.value='';nm.removeAttribute('aria-invalid');dlg.showModal();root.classList.add('lock');nm.focus()}
dlg.addEventListener('close',()=>root.classList.remove('lock'));
dlg.addEventListener('click',e=>{if(e.target===dlg)dlg.close()});
$('#dx').onclick=$('#cl').onclick=()=>dlg.close();
$('#again').onclick=()=>{show(true);nm.value='';nm.focus()};
nm.addEventListener('keydown',e=>{if(e.key=='Enter'){e.preventDefault();$('#pr').click()}});
nm.addEventListener('input',()=>nm.removeAttribute('aria-invalid'));
$('#pr').onclick=()=>{
const n=nm.value.trim();
if(!n){nm.removeAttribute('aria-invalid');void nm.offsetWidth;nm.setAttribute('aria-invalid','true');nm.focus();return}
const t=T[pick],k=fnv(n+pick),r=mul(k),b=$('#tkB');
tk.style.setProperty('--tc',t[2]);
$('#tkN').textContent=n;$('#tkT').textContent=t[0]+' pass';$('#tkD').textContent=t[1]+' · Seinhuis, Rotterdam';
$('#tkNo').textContent='No. OV27-'+(k%1679616).toString(36).toUpperCase().padStart(4,'0');
b.textContent='';let x=0,i=0;
while(x<94){const w=1+(r()*3|0);sv('rect',{class:'bar-r',x,y:0,width:w,height:44,fill:'currentColor',style:'--i:'+i++},b);x+=w+1+(r()*2|0)}
show(false);tk.classList.remove('print');void tk.offsetWidth;tk.classList.add('print');$('#again').focus()};
document.addEventListener('click',e=>{
const b=e.target.closest('[data-ticket]');if(b){openDlg(b.getAttribute('data-ticket'));return}
const a=e.target.closest('a[href^="#"]');if(!a)return;
e.preventDefault();const id=a.getAttribute('href'),t=id=='#top'?null:$(id),o={behavior:RM?'auto':'smooth'};
t?t.scrollIntoView(o):scrollTo({top:0,...o})});
/* observers */
const rio=new IntersectionObserver(es=>es.forEach(e=>{if(!e.isIntersecting)return;const t=e.target;rio.unobserve(t);t.classList.add('in');setTimeout(()=>t.classList.remove('rv','in'),1300+(+getComputedStyle(t).getPropertyValue('--rd')||0)*90)}),{threshold:.1,rootMargin:'0px 0px -6% 0px'});
$$('.rv').forEach(e=>rio.observe(e));
const lio=new IntersectionObserver(es=>es.forEach(e=>e.target.classList.toggle('live',e.isIntersecting)));
$$('.sp').forEach(c=>{lio.observe(c);c.onclick=()=>{c.classList.remove('boing');void c.offsetWidth;c.classList.add('boing')}});
new IntersectionObserver((es,o)=>{if(es[0].isIntersecting){o.disconnect();$('#p0 .rows').classList.add('play')}}).observe($('.days'));
new IntersectionObserver((es,o)=>{if(es[0].isIntersecting){o.disconnect();setTimeout(()=>{if(!plan.classList.contains('touched'))selRoom('hall',true)},500)}},{threshold:.4}).observe(plan);
watch(hero,'hero');watch($('.lchart'),'lab');
const prog=$('.prog'),sc=()=>prog.style.setProperty('--p',(scrollY/Math.max(1,root.scrollHeight-innerHeight)).toFixed(4));
addEventListener('scroll',sc,{passive:true});sc();
/* go */
choose(1);put();layout();
function frame(now){
if(vis.hero&&h0&&hP){hp=clamp(((now-h0)%7500-450)/1700,0,1);hP.set(hp)}
if(vis.lab&&LP){if(lt0<0||now-lt0>ldur+1100)lt0=now;tb.style.transform=`translateX(${LP.set(clamp((now-lt0)/ldur,0,1))*tw}px)`}
if(walking)walk(now);
requestAnimationFrame(frame)}
requestAnimationFrame(t=>{root.classList.add('go');h0=t;if(!RM)requestAnimationFrame(frame)});
})();
</script>
</body>
</html>