diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..fadd49a Binary files /dev/null and b/.DS_Store differ diff --git a/app/.DS_Store b/app/.DS_Store new file mode 100644 index 0000000..219c73f Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/html/.DS_Store b/app/html/.DS_Store new file mode 100644 index 0000000..e980360 Binary files /dev/null and b/app/html/.DS_Store differ diff --git a/app/html/akamai/sureroute-test-object.html b/app/html/akamai/sureroute-test-object.html new file mode 100644 index 0000000..73bf29c --- /dev/null +++ b/app/html/akamai/sureroute-test-object.html @@ -0,0 +1,105 @@ + + + + + + Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit + + + + + + +

Lorem Ipsum

+ +

Ipsum, Lorem

+ +
+

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam massa enim, tincidunt non hendrerit eget, malesuada et nisi. In hac habitasse platea dictumst. Praesent nec laoreet ante. Aenean tempus nisi in erat tempus tempus. Vestibulum imperdiet lobortis sapien eu tempus. Vivamus volutpat quam sed eros molestie vitae dignissim nulla ultricies. Vivamus dictum elit velit. Pellentesque pellentesque ornare ornare. Mauris vel gravida sapien. Praesent eleifend tristique ipsum nec tempor. Vestibulum cursus eleifend tellus, a egestas lectus euismod sed. +

+

+Duis nec massa quam. Nulla porta, enim ut consequat tincidunt, quam tortor consequat enim, eu interdum eros lorem eu turpis. Cras vestibulum orci quis felis tristique quis semper sem imperdiet. Sed mattis tincidunt risus scelerisque scelerisque. Aliquam nisl quam, bibendum quis luctus eu, sodales ut felis. Integer id turpis nisi. Phasellus mattis nulla eu odio faucibus a auctor orci tristique. Nulla ullamcorper, risus nec semper accumsan, libero lacus aliquet elit, quis lacinia metus nunc vestibulum turpis. Suspendisse vel sapien vel magna auctor aliquam. Aenean fringilla fringilla metus non imperdiet. Aliquam nisl lacus, tempus vitae commodo non, accumsan ut lectus. Nam in urna eu neque pretium aliquam. Maecenas sit amet urna lectus. Donec vitae metus enim. +

+

+Sed lacus nulla, faucibus eget ullamcorper ut, mollis at metus. Vivamus tortor felis, tincidunt at tristique ut, tincidunt feugiat velit. Ut euismod felis non urna luctus luctus. Integer nec urna massa. Mauris vestibulum hendrerit auctor. Morbi at tellus nec arcu scelerisque rhoncus. Phasellus facilisis interdum lorem vulputate posuere. Nullam quis felis est. Aenean metus augue, tempus non ultricies et, dapibus vel felis. Pellentesque at augue velit. Nulla erat nisi, posuere eu pellentesque id, pretium ac libero. Phasellus tincidunt sollicitudin sapien at mollis. Nullam et libero velit, nec tincidunt eros. Aliquam et sem elit. Quisque suscipit orci enim, vel aliquam nisi. Suspendisse in enim a ligula blandit volutpat in id velit. +

+

+Nam tempor neque nec ligula sollicitudin rhoncus. Etiam et lorem vel odio pharetra interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In imperdiet nisi sed diam rutrum gravida in vel massa. Nam ullamcorper ultrices diam, vitae consequat lacus consequat consequat. Curabitur laoreet leo sed tortor fringilla nec euismod libero lobortis. Donec non enim lectus. Suspendisse potenti. In hac habitasse platea dictumst. Fusce semper auctor neque nec lobortis. Praesent vitae mauris turpis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin sed pharetra odio. Suspendisse potenti. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis eget odio purus, quis dapibus massa. +

+

+Curabitur ut dapibus eros. Donec tempor, felis ac facilisis bibendum, nisi purus pellentesque sem, sollicitudin tempor lectus nulla at mi. Maecenas quis urna ut ante pulvinar pellentesque. Duis auctor imperdiet suscipit. Pellentesque dui nulla, volutpat quis posuere a, gravida ornare augue. Proin nec felis pharetra magna pellentesque facilisis. Curabitur lacus libero, malesuada sed tincidunt ac, aliquet ut tortor. Etiam gravida lorem nulla, consectetur eleifend risus. Donec facilisis, turpis laoreet imperdiet laoreet, purus justo egestas nulla, et hendrerit leo eros at orci. Nunc vulputate mauris sit amet sapien accumsan nec euismod orci volutpat. Sed ultricies velit ut lorem venenatis in convallis tellus imperdiet. Aenean auctor ultrices est ultricies rhoncus. Phasellus non magna a leo luctus fermentum nec fermentum erat. +

+

+ +Sed faucibus nisl quis diam mollis quis varius tortor tincidunt. Phasellus in turpis in tellus consectetur mollis. Donec a neque id metus condimentum dignissim. In hac habitasse platea dictumst. Pellentesque sem nisi, pulvinar nec sagittis vitae, lacinia non tellus. Aliquam dignissim dignissim volutpat. Pellentesque ut quam et mi tincidunt varius id vel quam. Duis consectetur elit ac ligula fringilla elementum. In elementum tellus viverra mi vehicula vitae tempus lectus laoreet. Nullam diam nibh, tincidunt vitae imperdiet a, luctus a felis. In posuere pulvinar volutpat. Pellentesque eget viverra justo. +

+

+Nullam nec sapien at felis molestie auctor. Sed dignissim erat eu nulla ullamcorper mattis. Curabitur felis sem, feugiat non semper ut, sollicitudin sed ipsum. Quisque cursus laoreet turpis, sit amet molestie neque consequat at. Vestibulum eu ligula quis nisl pulvinar rhoncus. Praesent faucibus, dolor in elementum ullamcorper, tellus ante mattis risus, ac imperdiet eros eros quis risus. Praesent luctus libero a diam pharetra eget placerat risus pulvinar. Donec sollicitudin pulvinar velit vel pellentesque. Quisque sagittis leo ac mauris congue adipiscing. In tempus facilisis facilisis. Aliquam erat volutpat. Suspendisse sagittis libero ipsum. +

+

+Aliquam at cursus ipsum. Vivamus purus mi, pretium at molestie id, dictum in quam. Proin egestas auctor iaculis. Maecenas sodales facilisis tellus eu bibendum. Vestibulum varius vehicula scelerisque. Praesent condimentum varius commodo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec sem nisl, sagittis eu euismod non, tempor nec magna. Fusce sed auctor nisl. Phasellus porttitor sagittis est, sit amet eleifend elit dignissim et. Nam consectetur elementum elit non egestas. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a ultricies neque. Integer hendrerit nisi id dolor porta quis venenatis lacus dignissim. In vitae fringilla magna. +

+

+Fusce ultrices scelerisque felis, id semper quam posuere a. Sed nec erat eget velit euismod condimentum a in enim. Maecenas bibendum aliquam tincidunt. Mauris vestibulum neque at nulla sagittis id lacinia enim fermentum. Quisque adipiscing risus nec massa auctor condimentum. Mauris venenatis lacus justo, eu varius odio. Fusce commodo luctus felis, vitae lobortis lectus facilisis id. Nunc faucibus vestibulum urna et lacinia. Cras ornare quam neque, non gravida sapien. Cras porta, diam sit amet laoreet rutrum, massa erat commodo diam, eu rhoncus nisl massa ac metus. In sem mauris, venenatis nec euismod ac, suscipit condimentum neque. Quisque pretium blandit lectus, ut aliquet neque rhoncus eu. Vivamus ultrices porttitor tincidunt. Curabitur ut ipsum non ipsum ultrices tincidunt. Integer scelerisque augue nec nisl varius tristique. Morbi condimentum rutrum sodales. Pellentesque odio mauris, porttitor ac sollicitudin in, ultrices ut diam. +

+

+Sed congue adipiscing orci a pellentesque. Etiam quis neque eu nulla viverra egestas. Ut ultricies dui non enim rhoncus laoreet. Nulla molestie nibh non erat venenatis gravida. Pellentesque faucibus sem sit amet risus tincidunt non ultrices diam auctor. Praesent quis libero et tellus tempor molestie. Mauris ullamcorper feugiat libero sed elementum. Donec eget nunc eget diam hendrerit pulvinar. Ut ut imperdiet enim. Vestibulum sed quam lorem. Nunc ipsum massa, venenatis eget condimentum at, ornare id ante. Vestibulum ornare volutpat tincidunt. Etiam a eros erat. Curabitur lobortis, nisi a malesuada tincidunt, nisi enim congue eros, in dictum elit odio at nunc. Nam hendrerit porta velit a viverra. +

+

+Etiam vel velit urna. Donec commodo aliquet magna rhoncus pretium. Donec fermentum orci in diam dictum non pulvinar mi tristique. Morbi urna libero, sagittis vel facilisis nec, ornare vitae nunc. Pellentesque laoreet mi a mi condimentum sagittis. Donec eleifend, nisi sit amet tincidunt sollicitudin, leo magna accumsan elit, at adipiscing velit lacus id purus. Aenean nunc sapien, egestas vitae pretium viverra, bibendum vel tellus. Maecenas mattis dui ac justo facilisis sollicitudin. Proin in mi ac lacus hendrerit congue ac vitae elit. Aliquam erat volutpat. In hac habitasse platea dictumst. Phasellus dapibus diam vel velit consectetur tempor. Maecenas viverra suscipit bibendum. Sed non enim neque. +

+ +

+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Phasellus at odio et odio volutpat egestas. Fusce non pellentesque felis. Nunc fermentum posuere sem quis egestas. Integer nec orci vel eros fringilla bibendum. Praesent placerat molestie elit at mattis. Nunc rutrum faucibus arcu non bibendum. Vestibulum at sapien sit amet sem iaculis congue. Morbi tempus, libero vitae interdum suscipit, lacus ipsum suscipit quam, non pretium nulla orci eget dui. Praesent et nisl turpis, ultricies convallis quam. In tempor urna et eros aliquet accumsan. Phasellus lobortis bibendum libero sit amet viverra. Aenean consectetur, neque eu cursus posuere, est leo molestie dui, sit amet vulputate mi erat eu tortor. Suspendisse arcu velit, porta sit amet adipiscing sed, ultrices id urna. In hendrerit iaculis massa in pretium. Vivamus eros augue, venenatis non hendrerit a, bibendum in tortor. Fusce et mauris lorem, vitae semper ligula. Nam iaculis, eros eu varius varius, orci sapien rhoncus arcu, et luctus urna lectus non quam. Donec gravida convallis justo at bibendum. Quisque non est velit, sed laoreet augue. +

+
+ + + diff --git a/app/html/css/akamai-viewer.css b/app/html/css/akamai-viewer.css new file mode 100644 index 0000000..5715c4c --- /dev/null +++ b/app/html/css/akamai-viewer.css @@ -0,0 +1,892 @@ +/*! Copyright 2019, Akamai Technologies, Inc. All Rights Reserved. akamai-viewer-0.7.2 */.enlarge_pane { + position: relative; +} +.enlarge_btn { + position: absolute; + top: 8px; + left: 8px; + font-size: 12px; + overflow: hidden; + background-color: rgba(255,255,255,.8); + color: #000; + text-decoration: none; + padding: .5em; +} +.enlarge_btn:hover, +.enlarge_btn:focus { + +} +.enlarge_btn:active { + +} +.enlarge_disabled .enlarge_btn { + display: none; +} +.zoom-out { + +} +.enlarge-locked .enlarge_btn { + background-color: #333; + color: #fff; + border-color: #333; + background-image: url(/img/zoom-out-light.svg); +} +.enlarge-zoomed { + overflow: hidden; +} +.enlarge_contain { + overflow: hidden; + -webkit-overflow-scrolling: touch; + display: block; + position: relative; + height: 100%; +} +.enlarge_contain img { + width: 100%; + -webkit-touch-callout: none; /* image share menus get in the way of the interaction */ +} +.enlarge-zoomed .enlarge_contain img, +.enlarge-zoomed.enlarge_flyout .enlarge_contain img { + max-width: none; +} +.enlarge-locked .enlarge_contain { + overflow: scroll; +} +.enlarge_loader { + top: 0; + left: 0; + width: 100%; + height: 5px; + position: absolute; + z-index: 30; + background: #eee; + opacity: 0; + display: none; +} +.enlarge-delay .enlarge_loader { + /* opacity: .4; disable loader for now */ +} +.enlarge_loader * { + width: 0; + display: block; + height: 5px; + background: #4fa4c0; + -webkit-transition: width .6s linear; + transition: width .6s linear; +} + +.enlarge_img-loading { + position: absolute; + left: -9999px; + opacity: 0; +} +.enlarge_flyout { + overflow: hidden; + opacity: 0; + transform: scale(0); + transition: transform .3s; + position: absolute; + z-index: 9999; +} +.enlarge_flyout-flyoutloupe { + border-radius: 100%; + border: 5px solid #fff; + box-shadow: 0 0 5px #333; + pointer-events: none; +} +.enlarge_flyout-flyouttopleft, +.enlarge_flyout-flyouttopright { + top: 0; +} +.enlarge_flyout-flyoutbottomleft, +.enlarge_flyout-flyoutbottomright { + bottom: 0; +} +.enlarge_flyout-flyouttopright, +.enlarge_flyout-flyoutbottomright { + transform-origin: left; +} +.enlarge_flyout-flyouttopleft, +.enlarge_flyout-flyoutbottomleft { + transform-origin: right; +} +.enlarge-zoomed.enlarge_flyout { + transform: scale(1); + opacity: 1; + overflow: hidden; +} +.enlarge-zoomed.enlarge_flyout .enlarge_contain { + overflow: visible; +} +.enlarge-locked.enlarge_flyout { + overflow: auto; +} + +.snapper:before, +.snapper_nextprev_contain:before, .snapper_items:before, .snapper_nav:before, .snapper:after, +.snapper_nextprev_contain:after, .snapper_items:after, .snapper_nav:after { + content: " "; + display: table; } + +.snapper:after, +.snapper_nextprev_contain:after, .snapper_items:after, .snapper_nav:after { + clear: both; } + +.snapper, +.snapper_nextprev_contain, .snapper_items, .snapper_nav { + *zoom: 1; } + +.snapper * { + box-sizing: border-box; } + +.snapper, +.snapper_nextprev_contain { + position: relative; } + +.snapper:focus { + /* snapper div receives a tabindex to allow focus for keyboard arrow control */ + outline: none; } + +/* to hide the scrollbar handles while still allowing touch and gesture scrolling, wrap the snapper pane in a div with a snapper_pane_crop class +note: only use this if you have thumbnails or some other means of advancing slides for non-touchscreen devices */ +.snapper-enhanced .snapper_pane_crop, +.enhanced .snapper_pane_crop { + overflow: hidden; } + +.snapper-enhanced .snapper_pane_crop .snapper_pane, +.enhanced .snapper_pane_crop .snapper_pane { + /* IE and edge */ + -ms-overflow-style: none; + /* Firefox */ + scrollbar-width: none; } + +.snapper-enhanced .snapper_pane_crop .snapper_pane::-webkit-scrollbar, +.enhanced .snapper_pane_crop .snapper_pane::-webkit-scrollbar { + display: none; } + +.snapper_pane { + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; + width: 100%; + /* snap to points */ + scroll-snap-type: mandatory; + scroll-snap-type: x mandatory; + /* x interval for snapping (100% of container width) */ + scroll-snap-points-x: repeat(100%); + position: relative; + z-index: 0; } + +.snapper_item { + width: 100%; + position: relative; + white-space: normal; + scroll-snap-align: start; + box-sizing: border-box; + padding-right: 1px; + padding-left: 1px; } + +.enhanced .snapper_item, +.snapper-enhanced .snapper_item { + display: none; } + +.enhanced .snapper_item:first-child, +.snapper-enhanced .snapper_item:first-child, +.snapper-enhanced .snapper_item[style] { + display: block; } + +.snapper-enhanced .snapper_item:first-child, +.snapper-enhanced .snapper_item[style] { + float: left !important; + /* important to ensure default float nones don't leak through */ + clear: none !important; + /* important to ensure default clears don't leak through */ } + +/* next prev arrow selectors */ +.snapper_item img { + width: 100%; } + +.snapper_nav { + position: relative; + margin: 1em 0; + overflow: hidden; } + +.snapper_nav_inner { + overflow: auto; + -webkit-overflow-scrolling: touch; + white-space: nowrap; + padding: 2px 1px 30px; + margin-bottom: -30px; + /* leave room for thumb outline to show up.. don't fully offset */ + height: 84px; } + +.snapper_nav a { + overflow: hidden; + border: 1px solid #ddd; + white-space: normal; + display: inline-block; + float: none; + vertical-align: middle; + height: 50px; + width: auto; + margin: 0 5px 0 0; } + +.snapper_nav a.snapper_nav_item-selected { + /* selected styles here */ + outline: 1px solid black; } + +.snapper_nav img { + display: block; + height: 100%; + width: auto; + max-width: 100%; } + +.tau { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} + +/* loading should be hidden to start */ +.tau .loading { + display: none; +} + +/* assist in preventing a drag */ +.tau img { + /* hide all images, see below for exceptions */ + display: none; + pointer-events: none; + + -moz-user-select: -moz-none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/* display the first image until enhancement when .focused should take over */ +.tau > img:nth-child(1) { + display: block; +} + +/* make sure that an enhanced component hides the first image unless overriden + by .focused */ +.tau.tau-enhanced > img:nth-child(1) { + display: none; +} + +/* display focused image */ +.tau.tau-enhanced > img.focused { + display: block +} + +.tau.tau-enhanced.tau-canvas > img { + position: absolute; + top: 0; + left: 0; + opacity: 0; + display: block; +} + +.tau .render img { + width: 1px; + height: 1px; + opacity: 0.00000001; +} + +/* grabbing cursor for all elements on mousedown */ +html.grabbing * { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; +} + +.snapper_nav a.snapper_nav_item-selected { + outline: 1px solid #000; + border: 1px solid #000; +} +.snapper_nav a.snapper_nav_item-selected:focus { + outline: 1px dotted #bbb; +} + +[data-akamai-carousel-thumbnail-type="none"] .snapper_nav { + display: none; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav_inner, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav_inner { + overflow: auto; + -webkit-overflow-scrolling: touch; + padding: 1px 30px 1px 1px; + white-space: normal; + margin-bottom: 0; + margin-right: -26px; /* leave room for thumb outline to show up.. don't fully offset */ + height: 100%; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav a, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav a { + height: auto; + display: block; + margin: 0 10px 10px 0; +} +.snapper_nav .snapper_nav_inner img { + width: auto; + display: inline-block; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav img, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav img { + height: auto; + display: block; +} + +/* thumbnails left and right */ +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nextprev_contain, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nextprev_contain { + width: 88%; + float: right; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav.snapper_nav, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav.snapper_nav { + position: absolute; + width: 10%; + margin: 0; + height: 100%; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav a, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav a { + width: 100%; +} + +/* thumbnails right */ +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nextprev_contain { + float: left; +} +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav.snapper_nav { + right: 0; +} + +/* next prev arrow selectors */ +/* if there's only one item, arrows should be hidden */ +[data-akamai-carousel-item-count="1"][data-akamai-carousel-arrows="true"] .snapper_nextprev { + display: none; +} +.snapper_nextprev, +.snapper_nextprev_item { + list-style: none; + margin: 0; + padding: 0; +} +.snapper_nextprev_next, +.snapper_nextprev_prev { + position: absolute; + top: 45%; + width: 34px; + height: 34px; + margin-top: -17px; + background-color: #fff; + border-radius: 100%; + overflow: hidden; + text-indent: -9999px; + cursor: pointer; + background-position: 52% 50%; + background-size: 16px auto; + transition: transform .1s linear; +} +.snapper_nextprev_next { + background-position: 54% 50%; + right: 8px; + background-image: url(../img/arrow-forward.svg); + background-position: 56% 50%; + background-repeat: no-repeat; + background-color: rgba(255,255,255,.3); +} +.snapper_nextprev_prev { + left: 8px; + background-image: url(../img/arrow-back.svg); + background-position: 44% 50%; + background-repeat: no-repeat; + background-color: rgba(255,255,255,.3); +} +.snapper_nextprev_next:hover, +.snapper_nextprev_prev:hover, +.snapper_nextprev_next:focus, +.snapper_nextprev_prev:focus { + transform: scale(1.1); + background-color: rgba(255,255,255,.5); +} +.snapper_nextprev_prev:active { + transform: scale(1); +} +@media (min-width: 40em) { + .snapper_nextprev_next, + .snapper_nextprev_prev { + width: 34px; + height: 34px; + margin-top: -17px; + background-size: 16px auto; + } +} + +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav { + display: block; + text-align: center; +} +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a { + display: inline-block; + float: none; + width: 10px; + height: 10px; + margin: 0 6px; + background: #ddd; + border-radius: 100%; + border: 0; + overflow: hidden; + text-indent: -9999px; + cursor: pointer; +} +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a img { + position: absolute; + left: -999px; +} +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a { + float: none; +} +[data-akamai-carousel-thumbnail-type="dots"][data-akamai-carousel-thumbnail-placement="left"] .snapper_nav a, +[data-akamai-carousel-thumbnail-type="dots"][data-akamai-carousel-thumbnail-placement="right"] .snapper_nav a { + display: block; + margin: 0 auto 6px; +} +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a.snapper_nav_item-selected, +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a.snapper_nav_item-selected:focus, +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a.snapper_nav_item-selected:active { + background: #000; + box-shadow: none; + border-color: transparent; + outline: none; +} +[data-akamai-carousel-thumb-type="video"], +[data-akamai-carousel-thumb-type="spin360"], +[data-akamai-carousel-thumb-type="image"] { + position: relative; +} +[data-akamai-carousel-thumb-type="spin360"]:after, +[data-akamai-carousel-thumb-type="video"]:after { + content: "\20"; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: rgba(0,0,0,.4) url(../img/video-light.svg) 50% 50% no-repeat; + background-size: 30px; + z-index: 2; +} +[data-akamai-carousel-thumbnail-type="dots"] [data-akamai-carousel-thumb-type="spin360"]:after, +[data-akamai-carousel-thumbnail-type="dots"] [data-akamai-carousel-thumb-type="video"]:after { + display: none; +} +[data-akamai-carousel-thumb-type="spin360"]:after { + background-image: url(../img/360-light.svg); + background-size: auto 45px; +} +[data-akamai-carousel-thumb-type="video"][data-akamai-carousel-thumb-noposter] img { + visibility: hidden; +} + +[data-akamai-carousel] .snapper_pane.no-scroll { + overflow-x: hidden; +} +.akamai-fullscreen-active { + max-width: 100% !important; +} +.akamai-fullscreen-active [data-akamai-fullscreen-width] { + background-color: #fff; + height: 100%; + margin: 0 auto; +} +::backdrop { + background-color: #fff; +} +/* Opera */ +:fullscreen-ancestor { + background-color: #fff; +} + +/* Fullscreen plugin Buttons */ +.akamai-fullscreen-btncontainer { + position: relative; +} +.akamai-fullscreen-btn { + position: absolute; + top: .5em; + right: .5em; + text-indent: -9999px; + width: 34px; + height: 34px; + background-size: 16px auto; + border: none; + border-radius: 50%; + cursor: pointer; +} +.akamai-fullscreen-btn-enter { + background-color: rgba(255,255,255,.3); +} +.akamai-fullscreen-btn-exit { + display: none; +} +.akamai-fullscreen-active .akamai-fullscreen-btn-enter { + display: none; +} +.akamai-fullscreen-active .akamai-fullscreen-btn-exit { + display: block; + background-color: rgba(0,0,0,.4); +} + +/* Fullscreen plugin fallback behavior */ +.akamai-fullscreen-fallback { + position: fixed !important; + left: 0; + top: 0; + background-color: #fff; + width: 100%; + height: 100%; + max-width: 100%; + margin: 0 !important; + z-index: 100000; +} +.akamai-fullscreen-fallback [data-akamai-fullscreen-btn] { + display: none; +} +.akamai-fullscreen-fallback [data-akamai-fullscreen-exit-btn] { + display: block; +} + +.icon-360 { background: url(../img/360.svg) 50% no-repeat; } +.icon-arrow-back { background: url(../img/arrow-back.svg) 50% no-repeat; } +.icon-arrow-forward { background: url(../img/arrow-forward.svg) 50% no-repeat; } +.icon-close { background: url(../img/close.svg) 50% no-repeat; } +.icon-muted { background: url(../img/muted.svg) 50% no-repeat; } +.icon-pause { background: url(../img/pause.svg) 50% no-repeat; } +.icon-play { background: url(../img/play.svg) 50% no-repeat; } +.icon-video { background: url(../img/video.svg) 50% no-repeat; } +.icon-volume { background: url(../img/volume.svg) 50% no-repeat; } +.icon-zoom-in { background: url(../img/zoom-in.svg) 50% no-repeat; } +.icon-zoom-out { background: url(../img/zoom-out.svg) 50% no-repeat; } +.icon-fullscreen { background: url(../img/fullscreen.svg) 50% no-repeat; } + + +.icon-360-light { background: url(../img/360-light.svg) 50% no-repeat; } +.icon-arrow-back-light { background: url(../img/arrow-back-light.svg) 50% no-repeat; } +.icon-arrow-forward-light { background: url(../img/arrow-forward-light.svg) 50% no-repeat; } +.icon-close-light { background: url(../img/close-light.svg) 50% no-repeat; } +.icon-muted-light { background: url(../img/muted-light.svg) 50% no-repeat; } +.icon-pause-light { background: url(../img/pause-light.svg) 50% no-repeat; } +.icon-play-light { background: url(../img/play-light.svg) 50% no-repeat; } +.icon-video-light { background: url(../img/video-light.svg) 50% no-repeat; } +.icon-volume-light { background: url(../img/volume-light.svg) 50% no-repeat; } +.icon-zoom-in-light { background: url(../img/zoom-in-light.svg) 50% no-repeat; } +.icon-zoom-out-light { background: url(../img/zoom-out-light.svg) 50% no-repeat; } +.icon-fullscreen-light { background: url(../img/fullscreen-light.svg) 50% no-repeat; } +.enlarge_btn { + width: 34px; + text-indent: -9999px; + overflow: hidden; + background-color: rgba(255,255,255,.3); + border-radius: 100%; + padding: 0; + height: 34px; + background-position: 50%; + background-repeat: no-repeat; + background-image: url(../img/zoom-in.svg); +} +.enlarge_btn:hover, +.enlarge_btn:focus { + transform: scale(1.1); + background-color: rgba(255,255,255,.5); +} +.enlarge_btn:active { + transform: scale(1); +} +.zoom-out { + background-image: url(../img/zoom-out-light.svg); +} +.enlarge-locked .enlarge_btn { + background-color: #333; + color: #fff; + border-color: #333; + background-image: url(../img/zoom-out-light.svg); +} +[data-akamai-magnifier-button="false"] .enlarge_btn, +[data-akamai-magnifier-placement*="flyout"] .enlarge_btn { + display: none; + opacity: 0; +} +[data-akamai-carousel-item] .enlarge_btn { + visibility: hidden; +} +[data-akamai-carousel-item-active] .enlarge_btn { + visibility: visible; +} +[data-akamai-magnifier-button="false"] .enlarge_btn:focus, +[data-akamai-magnifier-placement*="flyout"] .enlarge_btn:focus { + opacity: 1; +} +.enlarge-locked .enlarge_btn { + opacity: 1; +} +[data-akamai-magnifier-hover-zoom-without-click="false"] { + cursor: zoom-in; +} +[data-akamai-magnifier-placement="inline"] .enlarge_flyout { + display: none; +} +[data-akamai-magnifier-disabled="true"] .enlarge_btn { + display: none; +} +[data-akamai-magnifier-mode="animatedzoom"] { + display: inline-block; + cursor: default; + overflow: hidden; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +[data-akamai-magnifier-mode="animatedzoom"] > div { + background: white; + height: 100%; + + -webkit-transform-origin: left top; + -webkit-transform: translateZ(0); + -moz-transform-origin: left top; + -moz-transform: translateZ(0); + -ms-transform-origin: left top; + -ms-transform: translateZ(0); + -o-transform-origin: left top; + -o-transform: translateZ(0); + transform-origin: left top; + transform: translateZ(0); +} +[data-akamai-magnifier-mode="animatedzoom"] > div > div { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; +} +[data-akamai-magnifier-mode="animatedzoom"] .scroller-zoom-in { + top: 8px; + background-image: url(../img/zoom-in.svg); +} +[data-akamai-magnifier-mode="animatedzoom"] .scroller-zoom-out { + top: 50px; + background-image: url(../img/zoom-out.svg); +} +[data-akamai-magnifier-mode="animatedzoom"] a { + position: absolute; + top: 8px; + left: 8px; + font-size: 12px; + color: #000; + text-decoration: none; + width: 34px; + height: 34px; + text-indent: -9999px; + overflow: hidden; + background-color: rgba(255,255,255,.3); + border-radius: 100%; + padding: 0; + background-position: 50%; + background-repeat: no-repeat; +} +[data-akamai-magnifier-mode="animatedzoom"] a:hover, +[data-akamai-magnifier-mode="animatedzoom"] a:focus { + transform: scale(1.1); + background-color: rgba(255,255,255,.5); +} +[data-akamai-magnifier-mode="animatedzoom"] a:active { + transform: scale(1); +} +[data-akamai-magnifier-button="false"] .scroller-zoom-in, +[data-akamai-magnifier-button="false"] .scroller-zoom-out { + display: none; +} +.scroller-zoom-in-max .scroller-zoom-in, +.scroller-zoom-in-max .scroller-zoom-in:hover, +.scroller-zoom-in-max .scroller-zoom-in:focus, +.scroller-zoom-in-max .scroller-zoom-in:active { + background-color: #333; + color: #fff; + border-color: #333; +} +.scroller-zoom-out-max .scroller-zoom-out, +.scroller-zoom-out-max .scroller-zoom-out:hover, +.scroller-zoom-out-max .scroller-zoom-out:focus, +.scroller-zoom-out-max .scroller-zoom-out:active { + background-color: #333; + color: #fff; + border-color: #333; +} + +[data-akamai-spin360-cue="visible"]:before { + content: attr(title); + text-indent: -9999px; + overflow: hidden; + color: #fff; + position: absolute; + z-index: 9999; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,.5) url(../img/360-light.svg) 50% 50% no-repeat; + background-size: 10% auto; +} +.tau-controls { + position: absolute; + bottom: 20px; + width: 100%; + text-align: center; +} +[data-tau-controls] { + display: inline-block; + background-color: #fff; + background-image: none; + width: 40px; + height: 40px; + margin: 0 5px; + background-position: 50%; + border-radius: 0; + background-repeat: no-repeat; + background-size: auto; + opacity: .4; + overflow: hidden; + text-indent: -999px; +} +[data-tau-controls]:focus, +[data-tau-controls]:hover { + opacity: .8; +} +[data-tau-controls="left"] { + background-image: url(../img/arrow-back.svg); +} +[data-tau-controls="right"] { + background-image: url(../img/arrow-forward.svg); +} +.tau.spinning [data-tau-controls="play"] { + background-image: url(../img/pause.svg); +} +[data-tau-controls="play"], +.tau.spinning.control-left-down [data-tau-controls="play"], +.tau.spinning.control-right-down [data-tau-controls="play"] { + background-image: url(../img/play.svg); +} + +[data-akamai-viewer] .tau > img { + /* hide all images, see below for exceptions */ + display: none; +} +[data-akamai-video] { + position: relative; +} +[data-akamai-video] > video { + width: 100%; + vertical-align: middle; +} + +[data-akamai-video] svg { + box-sizing: border-box; + width: 100%; + height: 100%; + padding: 10px calc(50% - 50px); + position: absolute; + top: 0; + left: 0; + display: block; + opacity: 0.8; + cursor: pointer; + background-color: rgba(0,0,0,0.2); + transition: opacity 150ms; +} + +[data-akamai-video] svg:hover { + opacity: 1; +} + +[data-akamai-video] svg.is-hidden { + display: none; +} + +/* necessary for several child component positioning contexts */ +[data-akamai-viewer] { + position: relative; +} +[data-akamai-viewer] * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +[data-akamai-viewer] img, +[data-akamai-viewer] video, +[data-akamai-viewer] canvas { + max-width: 100%; + width: 100%; + display: block; +} +/* aspect ratio selector looks for number-based attr value */ +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-carousel-item] { + position: relative; + height: 0; + /* padding-top: x% goes on the wrapper as an inline style */ +} + +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-magnifier], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-spin360], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-video] { + position: absolute; + top: 0; + width: 100%; + left: 0; + height: 100%; +} + +/* if there's an aspect ratio, all img, canvas, and video height should be 100% of the container - which should have an aspect ratio top padding set by the JS */ +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-carousel-item] img, +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-carousel-item] video, +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-carousel-item] canvas { + max-height: 100%; + display: block; + margin: 0 auto; + width: auto; + height: auto; + max-width: 100%; + position: relative; + top: 50%; + transform: translateY(-50%); +} +/* once loaded, media will get an attribute for landscape or portrait, depending on the longer dimension. this is used to set one direction to 100% */ +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) img[data-akamai-carousel-aspectratio-orientation="landscape"], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) video[data-akamai-carousel-aspectratio-orientation="landscape"], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) canvas[data-akamai-carousel-aspectratio-orientation="landscape"] { + width: 100%; +} +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) img[data-akamai-carousel-aspectratio-orientation="portrait"], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) video[data-akamai-carousel-aspectratio-orientation="portrait"], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) canvas[data-akamai-carousel-aspectratio-orientation="portrait"] { + height: 100%; +} +/* when zoomed, we need to reset a lot of those values temporily */ +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) .enlarge-zoomed img { + max-height: none; + max-width: none; + transform: none; + top: auto; + width: auto; + height: auto; +} +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) .tau.tau-enhanced.tau-canvas > img { + display: none !important; +} diff --git a/app/html/css/docs.css b/app/html/css/docs.css new file mode 100644 index 0000000..fc54bab --- /dev/null +++ b/app/html/css/docs.css @@ -0,0 +1,1841 @@ +/*! Copyright 2019, Akamai Technologies, Inc. All Rights Reserved. akamai-viewer-0.7.2 */.enlarge_pane { + position: relative; +} +.enlarge_btn { + position: absolute; + top: 8px; + left: 8px; + font-size: 12px; + overflow: hidden; + background-color: rgba(255,255,255,.8); + color: #000; + text-decoration: none; + padding: .5em; +} +.enlarge_btn:hover, +.enlarge_btn:focus { + +} +.enlarge_btn:active { + +} +.enlarge_disabled .enlarge_btn { + display: none; +} +.zoom-out { + +} +.enlarge-locked .enlarge_btn { + background-color: #333; + color: #fff; + border-color: #333; + background-image: url(/img/zoom-out-light.svg); +} +.enlarge-zoomed { + overflow: hidden; +} +.enlarge_contain { + overflow: hidden; + -webkit-overflow-scrolling: touch; + display: block; + position: relative; + height: 100%; +} +.enlarge_contain img { + width: 100%; + -webkit-touch-callout: none; /* image share menus get in the way of the interaction */ +} +.enlarge-zoomed .enlarge_contain img, +.enlarge-zoomed.enlarge_flyout .enlarge_contain img { + max-width: none; +} +.enlarge-locked .enlarge_contain { + overflow: scroll; +} +.enlarge_loader { + top: 0; + left: 0; + width: 100%; + height: 5px; + position: absolute; + z-index: 30; + background: #eee; + opacity: 0; + display: none; +} +.enlarge-delay .enlarge_loader { + /* opacity: .4; disable loader for now */ +} +.enlarge_loader * { + width: 0; + display: block; + height: 5px; + background: #4fa4c0; + -webkit-transition: width .6s linear; + transition: width .6s linear; +} + +.enlarge_img-loading { + position: absolute; + left: -9999px; + opacity: 0; +} +.enlarge_flyout { + overflow: hidden; + opacity: 0; + transform: scale(0); + transition: transform .3s; + position: absolute; + z-index: 9999; +} +.enlarge_flyout-flyoutloupe { + border-radius: 100%; + border: 5px solid #fff; + box-shadow: 0 0 5px #333; + pointer-events: none; +} +.enlarge_flyout-flyouttopleft, +.enlarge_flyout-flyouttopright { + top: 0; +} +.enlarge_flyout-flyoutbottomleft, +.enlarge_flyout-flyoutbottomright { + bottom: 0; +} +.enlarge_flyout-flyouttopright, +.enlarge_flyout-flyoutbottomright { + transform-origin: left; +} +.enlarge_flyout-flyouttopleft, +.enlarge_flyout-flyoutbottomleft { + transform-origin: right; +} +.enlarge-zoomed.enlarge_flyout { + transform: scale(1); + opacity: 1; + overflow: hidden; +} +.enlarge-zoomed.enlarge_flyout .enlarge_contain { + overflow: visible; +} +.enlarge-locked.enlarge_flyout { + overflow: auto; +} + +.snapper:before, +.snapper_nextprev_contain:before, .snapper_items:before, .snapper_nav:before, .snapper:after, +.snapper_nextprev_contain:after, .snapper_items:after, .snapper_nav:after { + content: " "; + display: table; } + +.snapper:after, +.snapper_nextprev_contain:after, .snapper_items:after, .snapper_nav:after { + clear: both; } + +.snapper, +.snapper_nextprev_contain, .snapper_items, .snapper_nav { + *zoom: 1; } + +.snapper * { + box-sizing: border-box; } + +.snapper, +.snapper_nextprev_contain { + position: relative; } + +.snapper:focus { + /* snapper div receives a tabindex to allow focus for keyboard arrow control */ + outline: none; } + +/* to hide the scrollbar handles while still allowing touch and gesture scrolling, wrap the snapper pane in a div with a snapper_pane_crop class +note: only use this if you have thumbnails or some other means of advancing slides for non-touchscreen devices */ +.snapper-enhanced .snapper_pane_crop, +.enhanced .snapper_pane_crop { + overflow: hidden; } + +.snapper-enhanced .snapper_pane_crop .snapper_pane, +.enhanced .snapper_pane_crop .snapper_pane { + /* IE and edge */ + -ms-overflow-style: none; + /* Firefox */ + scrollbar-width: none; } + +.snapper-enhanced .snapper_pane_crop .snapper_pane::-webkit-scrollbar, +.enhanced .snapper_pane_crop .snapper_pane::-webkit-scrollbar { + display: none; } + +.snapper_pane { + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; + width: 100%; + /* snap to points */ + scroll-snap-type: mandatory; + scroll-snap-type: x mandatory; + /* x interval for snapping (100% of container width) */ + scroll-snap-points-x: repeat(100%); + position: relative; + z-index: 0; } + +.snapper_item { + width: 100%; + position: relative; + white-space: normal; + scroll-snap-align: start; + box-sizing: border-box; + padding-right: 1px; + padding-left: 1px; } + +.enhanced .snapper_item, +.snapper-enhanced .snapper_item { + display: none; } + +.enhanced .snapper_item:first-child, +.snapper-enhanced .snapper_item:first-child, +.snapper-enhanced .snapper_item[style] { + display: block; } + +.snapper-enhanced .snapper_item:first-child, +.snapper-enhanced .snapper_item[style] { + float: left !important; + /* important to ensure default float nones don't leak through */ + clear: none !important; + /* important to ensure default clears don't leak through */ } + +/* next prev arrow selectors */ +.snapper_item img { + width: 100%; } + +.snapper_nav { + position: relative; + margin: 1em 0; + overflow: hidden; } + +.snapper_nav_inner { + overflow: auto; + -webkit-overflow-scrolling: touch; + white-space: nowrap; + padding: 2px 1px 30px; + margin-bottom: -30px; + /* leave room for thumb outline to show up.. don't fully offset */ + height: 84px; } + +.snapper_nav a { + overflow: hidden; + border: 1px solid #ddd; + white-space: normal; + display: inline-block; + float: none; + vertical-align: middle; + height: 50px; + width: auto; + margin: 0 5px 0 0; } + +.snapper_nav a.snapper_nav_item-selected { + /* selected styles here */ + outline: 1px solid black; } + +.snapper_nav img { + display: block; + height: 100%; + width: auto; + max-width: 100%; } + +.tau { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} + +/* loading should be hidden to start */ +.tau .loading { + display: none; +} + +/* assist in preventing a drag */ +.tau img { + /* hide all images, see below for exceptions */ + display: none; + pointer-events: none; + + -moz-user-select: -moz-none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/* display the first image until enhancement when .focused should take over */ +.tau > img:nth-child(1) { + display: block; +} + +/* make sure that an enhanced component hides the first image unless overriden + by .focused */ +.tau.tau-enhanced > img:nth-child(1) { + display: none; +} + +/* display focused image */ +.tau.tau-enhanced > img.focused { + display: block +} + +.tau.tau-enhanced.tau-canvas > img { + position: absolute; + top: 0; + left: 0; + opacity: 0; + display: block; +} + +.tau .render img { + width: 1px; + height: 1px; + opacity: 0.00000001; +} + +/* grabbing cursor for all elements on mousedown */ +html.grabbing * { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; +} + +.snapper_nav a.snapper_nav_item-selected { + outline: 1px solid #000; + border: 1px solid #000; +} +.snapper_nav a.snapper_nav_item-selected:focus { + outline: 1px dotted #bbb; +} + +[data-akamai-carousel-thumbnail-type="none"] .snapper_nav { + display: none; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav_inner, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav_inner { + overflow: auto; + -webkit-overflow-scrolling: touch; + padding: 1px 30px 1px 1px; + white-space: normal; + margin-bottom: 0; + margin-right: -26px; /* leave room for thumb outline to show up.. don't fully offset */ + height: 100%; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav a, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav a { + height: auto; + display: block; + margin: 0 10px 10px 0; +} +.snapper_nav .snapper_nav_inner img { + width: auto; + display: inline-block; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav img, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav img { + height: auto; + display: block; +} + +/* thumbnails left and right */ +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nextprev_contain, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nextprev_contain { + width: 88%; + float: right; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav.snapper_nav, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav.snapper_nav { + position: absolute; + width: 10%; + margin: 0; + height: 100%; +} +[data-akamai-carousel-thumbnail-placement="left"] .snapper_nav a, +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav a { + width: 100%; +} + +/* thumbnails right */ +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nextprev_contain { + float: left; +} +[data-akamai-carousel-thumbnail-placement="right"] .snapper_nav.snapper_nav { + right: 0; +} + +/* next prev arrow selectors */ +/* if there's only one item, arrows should be hidden */ +[data-akamai-carousel-item-count="1"][data-akamai-carousel-arrows="true"] .snapper_nextprev { + display: none; +} +.snapper_nextprev, +.snapper_nextprev_item { + list-style: none; + margin: 0; + padding: 0; +} +.snapper_nextprev_next, +.snapper_nextprev_prev { + position: absolute; + top: 45%; + width: 34px; + height: 34px; + margin-top: -17px; + background-color: #fff; + border-radius: 100%; + overflow: hidden; + text-indent: -9999px; + cursor: pointer; + background-position: 52% 50%; + background-size: 16px auto; + transition: transform .1s linear; +} +.snapper_nextprev_next { + background-position: 54% 50%; + right: 8px; + background-image: url(../img/arrow-forward.svg); + background-position: 56% 50%; + background-repeat: no-repeat; + background-color: rgba(255,255,255,.3); +} +.snapper_nextprev_prev { + left: 8px; + background-image: url(../img/arrow-back.svg); + background-position: 44% 50%; + background-repeat: no-repeat; + background-color: rgba(255,255,255,.3); +} +.snapper_nextprev_next:hover, +.snapper_nextprev_prev:hover, +.snapper_nextprev_next:focus, +.snapper_nextprev_prev:focus { + transform: scale(1.1); + background-color: rgba(255,255,255,.5); +} +.snapper_nextprev_prev:active { + transform: scale(1); +} +@media (min-width: 40em) { + .snapper_nextprev_next, + .snapper_nextprev_prev { + width: 34px; + height: 34px; + margin-top: -17px; + background-size: 16px auto; + } +} + +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav { + display: block; + text-align: center; +} +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a { + display: inline-block; + float: none; + width: 10px; + height: 10px; + margin: 0 6px; + background: #ddd; + border-radius: 100%; + border: 0; + overflow: hidden; + text-indent: -9999px; + cursor: pointer; +} +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a img { + position: absolute; + left: -999px; +} +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a { + float: none; +} +[data-akamai-carousel-thumbnail-type="dots"][data-akamai-carousel-thumbnail-placement="left"] .snapper_nav a, +[data-akamai-carousel-thumbnail-type="dots"][data-akamai-carousel-thumbnail-placement="right"] .snapper_nav a { + display: block; + margin: 0 auto 6px; +} +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a.snapper_nav_item-selected, +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a.snapper_nav_item-selected:focus, +[data-akamai-carousel-thumbnail-type="dots"] .snapper_nav a.snapper_nav_item-selected:active { + background: #000; + box-shadow: none; + border-color: transparent; + outline: none; +} +[data-akamai-carousel-thumb-type="video"], +[data-akamai-carousel-thumb-type="spin360"], +[data-akamai-carousel-thumb-type="image"] { + position: relative; +} +[data-akamai-carousel-thumb-type="spin360"]:after, +[data-akamai-carousel-thumb-type="video"]:after { + content: "\20"; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: rgba(0,0,0,.4) url(../img/video-light.svg) 50% 50% no-repeat; + background-size: 30px; + z-index: 2; +} +[data-akamai-carousel-thumbnail-type="dots"] [data-akamai-carousel-thumb-type="spin360"]:after, +[data-akamai-carousel-thumbnail-type="dots"] [data-akamai-carousel-thumb-type="video"]:after { + display: none; +} +[data-akamai-carousel-thumb-type="spin360"]:after { + background-image: url(../img/360-light.svg); + background-size: auto 45px; +} +[data-akamai-carousel-thumb-type="video"][data-akamai-carousel-thumb-noposter] img { + visibility: hidden; +} + +[data-akamai-carousel] .snapper_pane.no-scroll { + overflow-x: hidden; +} +.akamai-fullscreen-active { + max-width: 100% !important; +} +.akamai-fullscreen-active [data-akamai-fullscreen-width] { + background-color: #fff; + height: 100%; + margin: 0 auto; +} +::backdrop { + background-color: #fff; +} +/* Opera */ +:fullscreen-ancestor { + background-color: #fff; +} + +/* Fullscreen plugin Buttons */ +.akamai-fullscreen-btncontainer { + position: relative; +} +.akamai-fullscreen-btn { + position: absolute; + top: .5em; + right: .5em; + text-indent: -9999px; + width: 34px; + height: 34px; + background-size: 16px auto; + border: none; + border-radius: 50%; + cursor: pointer; +} +.akamai-fullscreen-btn-enter { + background-color: rgba(255,255,255,.3); +} +.akamai-fullscreen-btn-exit { + display: none; +} +.akamai-fullscreen-active .akamai-fullscreen-btn-enter { + display: none; +} +.akamai-fullscreen-active .akamai-fullscreen-btn-exit { + display: block; + background-color: rgba(0,0,0,.4); +} + +/* Fullscreen plugin fallback behavior */ +.akamai-fullscreen-fallback { + position: fixed !important; + left: 0; + top: 0; + background-color: #fff; + width: 100%; + height: 100%; + max-width: 100%; + margin: 0 !important; + z-index: 100000; +} +.akamai-fullscreen-fallback [data-akamai-fullscreen-btn] { + display: none; +} +.akamai-fullscreen-fallback [data-akamai-fullscreen-exit-btn] { + display: block; +} + +.icon-360 { background: url(../img/360.svg) 50% no-repeat; } +.icon-arrow-back { background: url(../img/arrow-back.svg) 50% no-repeat; } +.icon-arrow-forward { background: url(../img/arrow-forward.svg) 50% no-repeat; } +.icon-close { background: url(../img/close.svg) 50% no-repeat; } +.icon-muted { background: url(../img/muted.svg) 50% no-repeat; } +.icon-pause { background: url(../img/pause.svg) 50% no-repeat; } +.icon-play { background: url(../img/play.svg) 50% no-repeat; } +.icon-video { background: url(../img/video.svg) 50% no-repeat; } +.icon-volume { background: url(../img/volume.svg) 50% no-repeat; } +.icon-zoom-in { background: url(../img/zoom-in.svg) 50% no-repeat; } +.icon-zoom-out { background: url(../img/zoom-out.svg) 50% no-repeat; } +.icon-fullscreen { background: url(../img/fullscreen.svg) 50% no-repeat; } + + +.icon-360-light { background: url(../img/360-light.svg) 50% no-repeat; } +.icon-arrow-back-light { background: url(../img/arrow-back-light.svg) 50% no-repeat; } +.icon-arrow-forward-light { background: url(../img/arrow-forward-light.svg) 50% no-repeat; } +.icon-close-light { background: url(../img/close-light.svg) 50% no-repeat; } +.icon-muted-light { background: url(../img/muted-light.svg) 50% no-repeat; } +.icon-pause-light { background: url(../img/pause-light.svg) 50% no-repeat; } +.icon-play-light { background: url(../img/play-light.svg) 50% no-repeat; } +.icon-video-light { background: url(../img/video-light.svg) 50% no-repeat; } +.icon-volume-light { background: url(../img/volume-light.svg) 50% no-repeat; } +.icon-zoom-in-light { background: url(../img/zoom-in-light.svg) 50% no-repeat; } +.icon-zoom-out-light { background: url(../img/zoom-out-light.svg) 50% no-repeat; } +.icon-fullscreen-light { background: url(../img/fullscreen-light.svg) 50% no-repeat; } +.enlarge_btn { + width: 34px; + text-indent: -9999px; + overflow: hidden; + background-color: rgba(255,255,255,.3); + border-radius: 100%; + padding: 0; + height: 34px; + background-position: 50%; + background-repeat: no-repeat; + background-image: url(../img/zoom-in.svg); +} +.enlarge_btn:hover, +.enlarge_btn:focus { + transform: scale(1.1); + background-color: rgba(255,255,255,.5); +} +.enlarge_btn:active { + transform: scale(1); +} +.zoom-out { + background-image: url(../img/zoom-out-light.svg); +} +.enlarge-locked .enlarge_btn { + background-color: #333; + color: #fff; + border-color: #333; + background-image: url(../img/zoom-out-light.svg); +} +[data-akamai-magnifier-button="false"] .enlarge_btn, +[data-akamai-magnifier-placement*="flyout"] .enlarge_btn { + display: none; + opacity: 0; +} +[data-akamai-carousel-item] .enlarge_btn { + visibility: hidden; +} +[data-akamai-carousel-item-active] .enlarge_btn { + visibility: visible; +} +[data-akamai-magnifier-button="false"] .enlarge_btn:focus, +[data-akamai-magnifier-placement*="flyout"] .enlarge_btn:focus { + opacity: 1; +} +.enlarge-locked .enlarge_btn { + opacity: 1; +} +[data-akamai-magnifier-hover-zoom-without-click="false"] { + cursor: zoom-in; +} +[data-akamai-magnifier-placement="inline"] .enlarge_flyout { + display: none; +} +[data-akamai-magnifier-disabled="true"] .enlarge_btn { + display: none; +} +[data-akamai-magnifier-mode="animatedzoom"] { + display: inline-block; + cursor: default; + overflow: hidden; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +[data-akamai-magnifier-mode="animatedzoom"] > div { + background: white; + height: 100%; + + -webkit-transform-origin: left top; + -webkit-transform: translateZ(0); + -moz-transform-origin: left top; + -moz-transform: translateZ(0); + -ms-transform-origin: left top; + -ms-transform: translateZ(0); + -o-transform-origin: left top; + -o-transform: translateZ(0); + transform-origin: left top; + transform: translateZ(0); +} +[data-akamai-magnifier-mode="animatedzoom"] > div > div { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; +} +[data-akamai-magnifier-mode="animatedzoom"] .scroller-zoom-in { + top: 8px; + background-image: url(../img/zoom-in.svg); +} +[data-akamai-magnifier-mode="animatedzoom"] .scroller-zoom-out { + top: 50px; + background-image: url(../img/zoom-out.svg); +} +[data-akamai-magnifier-mode="animatedzoom"] a { + position: absolute; + top: 8px; + left: 8px; + font-size: 12px; + color: #000; + text-decoration: none; + width: 34px; + height: 34px; + text-indent: -9999px; + overflow: hidden; + background-color: rgba(255,255,255,.3); + border-radius: 100%; + padding: 0; + background-position: 50%; + background-repeat: no-repeat; +} +[data-akamai-magnifier-mode="animatedzoom"] a:hover, +[data-akamai-magnifier-mode="animatedzoom"] a:focus { + transform: scale(1.1); + background-color: rgba(255,255,255,.5); +} +[data-akamai-magnifier-mode="animatedzoom"] a:active { + transform: scale(1); +} +[data-akamai-magnifier-button="false"] .scroller-zoom-in, +[data-akamai-magnifier-button="false"] .scroller-zoom-out { + display: none; +} +.scroller-zoom-in-max .scroller-zoom-in, +.scroller-zoom-in-max .scroller-zoom-in:hover, +.scroller-zoom-in-max .scroller-zoom-in:focus, +.scroller-zoom-in-max .scroller-zoom-in:active { + background-color: #333; + color: #fff; + border-color: #333; +} +.scroller-zoom-out-max .scroller-zoom-out, +.scroller-zoom-out-max .scroller-zoom-out:hover, +.scroller-zoom-out-max .scroller-zoom-out:focus, +.scroller-zoom-out-max .scroller-zoom-out:active { + background-color: #333; + color: #fff; + border-color: #333; +} + +[data-akamai-spin360-cue="visible"]:before { + content: attr(title); + text-indent: -9999px; + overflow: hidden; + color: #fff; + position: absolute; + z-index: 9999; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,.5) url(../img/360-light.svg) 50% 50% no-repeat; + background-size: 10% auto; +} +.tau-controls { + position: absolute; + bottom: 20px; + width: 100%; + text-align: center; +} +[data-tau-controls] { + display: inline-block; + background-color: #fff; + background-image: none; + width: 40px; + height: 40px; + margin: 0 5px; + background-position: 50%; + border-radius: 0; + background-repeat: no-repeat; + background-size: auto; + opacity: .4; + overflow: hidden; + text-indent: -999px; +} +[data-tau-controls]:focus, +[data-tau-controls]:hover { + opacity: .8; +} +[data-tau-controls="left"] { + background-image: url(../img/arrow-back.svg); +} +[data-tau-controls="right"] { + background-image: url(../img/arrow-forward.svg); +} +.tau.spinning [data-tau-controls="play"] { + background-image: url(../img/pause.svg); +} +[data-tau-controls="play"], +.tau.spinning.control-left-down [data-tau-controls="play"], +.tau.spinning.control-right-down [data-tau-controls="play"] { + background-image: url(../img/play.svg); +} + +[data-akamai-viewer] .tau > img { + /* hide all images, see below for exceptions */ + display: none; +} +[data-akamai-video] { + position: relative; +} +[data-akamai-video] > video { + width: 100%; + vertical-align: middle; +} + +[data-akamai-video] svg { + box-sizing: border-box; + width: 100%; + height: 100%; + padding: 10px calc(50% - 50px); + position: absolute; + top: 0; + left: 0; + display: block; + opacity: 0.8; + cursor: pointer; + background-color: rgba(0,0,0,0.2); + transition: opacity 150ms; +} + +[data-akamai-video] svg:hover { + opacity: 1; +} + +[data-akamai-video] svg.is-hidden { + display: none; +} + +/* necessary for several child component positioning contexts */ +[data-akamai-viewer] { + position: relative; +} +[data-akamai-viewer] * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +[data-akamai-viewer] img, +[data-akamai-viewer] video, +[data-akamai-viewer] canvas { + max-width: 100%; + width: 100%; + display: block; +} +/* aspect ratio selector looks for number-based attr value */ +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-carousel-item] { + position: relative; + height: 0; + /* padding-top: x% goes on the wrapper as an inline style */ +} + +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-magnifier], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-spin360], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-video] { + position: absolute; + top: 0; + width: 100%; + left: 0; + height: 100%; +} + +/* if there's an aspect ratio, all img, canvas, and video height should be 100% of the container - which should have an aspect ratio top padding set by the JS */ +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-carousel-item] img, +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-carousel-item] video, +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) [data-akamai-carousel-item] canvas { + max-height: 100%; + display: block; + margin: 0 auto; + width: auto; + height: auto; + max-width: 100%; + position: relative; + top: 50%; + transform: translateY(-50%); +} +/* once loaded, media will get an attribute for landscape or portrait, depending on the longer dimension. this is used to set one direction to 100% */ +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) img[data-akamai-carousel-aspectratio-orientation="landscape"], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) video[data-akamai-carousel-aspectratio-orientation="landscape"], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) canvas[data-akamai-carousel-aspectratio-orientation="landscape"] { + width: 100%; +} +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) img[data-akamai-carousel-aspectratio-orientation="portrait"], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) video[data-akamai-carousel-aspectratio-orientation="portrait"], +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) canvas[data-akamai-carousel-aspectratio-orientation="portrait"] { + height: 100%; +} +/* when zoomed, we need to reset a lot of those values temporily */ +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) .enlarge-zoomed img { + max-height: none; + max-width: none; + transform: none; + top: auto; + width: auto; + height: auto; +} +[data-akamai-carousel]:not([data-akamai-carousel-aspectratio="false"]) .tau.tau-enhanced.tau-canvas > img { + display: none !important; +} + +.xrayhtml { + border: 1px solid rgba(0,0,0,.1); + border-radius: .3em; + margin: 1.5em 0 2.5em 0; + padding: 1em 1em 2em; +} +.xrayhtml.xray-copy { + position: relative; +} +.xrayhtml .xraytitle { + text-transform: uppercase; + letter-spacing: 1px; + font: .75em sans-serif; + color: rgba(0,0,0,.5); + background-color: #fff; + border-radius: 3px; + display: inline-block; + position: relative; + top: -2.166666667em; /* 26px */ + padding-left: .1em; + padding-right: .1em; + z-index: 3; + margin: 0; +} +.xrayhtml.method-flip:before { + background-color: rgba(255,255,255,.6); +} +.xrayhtml .source-panel { + background: #f7f7f7; + margin-top: 2em; + tab-size: 2; +} +.xrayhtml .source-panel pre { + margin: 0; + padding: 16px; + border-radius: 0 0 .3em .3em; +} +.xrayhtml .source-panel code { + white-space: pre-wrap; +} +.xrayhtml.method-flip .source-panel { + margin-top: 0; + border-radius: 0.3em; +} +.xrayhtml.method-inline .source-panel { + margin: 2em -1em -2em -1em !important; /* Prism override. */ + border-top: 1px solid rgba(0,0,0,.1); + border-radius: 0 0 .3em .3em; +} +/* Prism override. */ +.xrayhtml .source-panel code.language-markup { + white-space: pre-wrap !important; +} + +.xrayhtml.antipattern { + border-color: #C9282D; +} +.xrayhtml.antipattern .xraytitle { + color: #d75e72; + font-weight: 700; +} + +/* Flip Animation */ +.xrayhtml.method-flip { + padding: 0; +} +.method-flip { + -webkit-perspective: 2500px; + -moz-perspective: 2500px; + perspective: 2500px; +} +.method-flip .source-panel { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow-x: auto; +} +.method-flip .snippet { + margin: 0; + position: relative; + top: 0; + left: 0; + z-index: 2; + min-height: 100%; + background-color: #fff; + padding: 1em; + + -webkit-transform: rotateY(0deg); + -webkit-transform-style: preserve-3d; + -webkit-backface-visibility: hidden; + + -moz-transform: rotateY(0deg); + -moz-transform-style: preserve-3d; + -moz-backface-visibility: hidden; + + -webkit-transition: -webkit-transform .4s ease-in-out; + -moz-transition: -moz-transform .4s ease-in-out; +} +.method-flip.view-source .snippet { + z-index: 1; + -webkit-transform: rotateY(180deg); + -moz-transform: rotateY(180deg); +} +.method-flip .source-panel { + -webkit-transform: rotateY(-180deg); + -webkit-backface-visibility: hidden; + + -moz-transform: rotateY(-180deg); + -moz-backface-visibility: hidden; + + -moz-transition: all .4s ease-in-out; + -webkit-transition: all .4s ease-in-out; +} +.method-flip.view-source .source-panel { + z-index: 2; + -webkit-transform: rotateY(0deg); + -moz-transform: rotateY(0deg); +} + +.method-flip.view-source .xraytitle { + background-color: transparent; + background-image: linear-gradient( + to bottom, + transparent, + transparent 40%, + #ffffff 40%, + transparent); +} + +iframe.xray-iframe { + border: 0; + width: 100% +} +body.docs { + padding: 0; + margin: 0; + background-color: transparent; +} +.docbody { + clear: both; + padding: 1.5em 30px; + position: relative; + border-top: 1px solid #eee; +} +.docs-masthead, +.docbody { + box-sizing: border-box; +} +.docs-logo { + max-width: 100px; +} + + + +/* ---------- MASTHEAD ---------- */ + +.docs-masthead { + overflow: hidden; + position: relative; + padding: 20px 30px; + border-bottom: 1px solid #eee; + font-family: Proxima Nova, sans-serif; +} +.docs-masthead h1 { + font-size: 1em; + color: #bbb; + font-family: inherit; +} +.docs-logo { + margin: 1em 0; +} +.docs-logo img { + width: 100%; + max-width: 100px; +} +.docs-help a { + text-decoration: none; + text-transform: uppercase; + font-size: 13px; +} + +.docs-footer { + clear: both; + background-color: #F9F9F9; + padding: 1.5em; + color: #888; + font-family: Proxima Nova, sans-serif; +} +.docs-footer p { + margin: 0; + font-size: 13px; + font-family: inherit; + max-width: none; +} + + + +/* ---------- NAV ---------- */ + +.docs-nav { + width: 230px; + position: relative; + z-index: 10000; + font-family: Proxima Nova, sans-serif; +} +.docs-nav-content { + display: none; + position: absolute; + background-color: #fff; + border: 1px solid #ddd; + border-width: 0 1px 1px 0; + box-shadow: 0 2px 1px rgba( 0,0,0 , .1 ); + padding: 15px 0 0; +} +.docs-nav-open .docs-nav-content { + display: block; + width: 100%; +margin-left: -1px; +} +.docs-nav a { + text-decoration: none; +} +.docs-nav-toggle { + background: url(../img/menubars.png) 25px 12px no-repeat; + padding: 12px 0 12px 60px; + margin: 0; + color: #888; + cursor: pointer; +} +.docs-nav-open .docs-nav-toggle { + background-color: #f6f6f6; + border-right: 1px solid #ddd; +} +.docs-nav-list { + list-style-type: none; + margin: 0 0 1.5em; + padding: 0; + font-family: Proxima Nova, sans-serif; +} +.docs-nav-list a { + font-family: Proxima Nova, sans-serif; + color: #777; +} +.docs-nav-list a, +.docs-nav-title { + font-size: 15px; + display: block; + margin: 0 0 4px; + padding: 4px 20px; + box-shadow: none; +} +.docs-nav-title, +.docs-nav-list a.docs-nav-title { + font-family: Proxima Nova, sans-serif; + color: #002b5c; + text-transform: uppercase; + font-weight: bold; +} +.docs-nav-list a:hover { + background-color: #eee; + text-decoration: none; +} +.docs-nav-list a.docs-nav-active, +.docs-nav-list a.docs-nav-active:hover, +.docs-nav-list a.docs-nav-active:active { + background-color: #CAE7F8; + color: #000; +} + +.docs-pagenav { + padding: 1em 0 0; + font-family: Proxima Nova, sans-serif; +} + + + +/* ---------- Color palette ---------- */ + +.docs-palette { + overflow: hidden; + font-size: 12px; +} +.docs-palette .color { + width: 120px; + height: 50px; + float: left; + padding: 15px; + margin: 15px; + font-family: Courier, serif; +} +.docs-palette .border { + border-style: solid; + border-width: 1px; +} + + +/* ---------- Content styles ---------- */ + +.docs-md > h1, +.docs-api h1.page-title, +.docs-md > h2, +.docs-md > h3, +.docs-md > h4 { + position: relative; + margin: 1.2em 0 .5em; + text-transform: none; + color: #000; + clear: both; + font-weight: bold; + font-family: Proxima Nova, sans-serif; +} + +.docs-md > h1, +.docs-api h1.page-title { font-size: 2.2em; margin: 0; padding-bottom: .3em; border-bottom: 1px solid #ddd; } +.docs-md > h2 { font-size: 1.8em; } +.docs-md > h3 { font-size: 1.4em; } +.docs-md > h4 { font-size: 1.1em; } + +h2 > .deep-link, +h3 > .deep-link, +h4 > .deep-link { + position: absolute; + top: -2px; + left: -22px; + width: 100%; + opacity: .5; +} +.deep-link-icon { + visibility: hidden; +} +.deep-link:hover .deep-link-icon { + visibility: visible; +} +/* hide links on top-level headings, not necessary */ +h1 > .deep-link { + display: none; +} +.xrayhtml + h2, +.xrayhtml + h3 { + margin-top: 0; +} + +.docs-md > ul, +.docs-md > ol, +.docs-md > p, +.docs-md > table th, +.docs-md > table td { + margin: 1em 0; + font-family: Proxima Nova, sans-serif; +} +.docs-md > p code, +.docs-md > li code { + font-size: 1.1em; + color: inherit; + font-weight: bold; +} +.docs-md > pre code { + font-size: .85em; +} + +.docs-md > ul, +.docs-md > ol { + padding-bottom: .5em; +} +.docs-md > ul li, +.docs-md > ol li { + margin-bottom: 8px; +} +.docs-md > ul ul, +.docs-md > ul ol { + padding-top: 8px; +} +.docs-md > ul ul li { + font-size: 1em; +} +.docs-md .docs-toc { + font-weight: bold; + list-style-type: none; + padding-left: 0; + margin-bottom:8px; +} +.docs-md .docs-toc li { + margin-top:16px; +} +.docs-md li.docs-toc-l2 { + margin-left: 20px; + font-weight: normal; + list-style-type: square; +} +.docs-toc a:link, +.docs-toc a:visited { + text-decoration: none; + color: #0085cd; +} +.docs-md > p strong, +.docs-md > ul strong, +.docs-table strong, +.docs-table b { + font-family: Proxima Nova, sans-serif; + font-weight: bold; +} + +.docs-md > table { + width: 100%; + padding: 1em 0; + margin-top: 2em; + font-family: Proxima Nova, sans-serif; + border-collapse: collapse; +} +.docs-md > table th { + text-align: left; +} +.docs-md > table td, +.docs-md > table thead th { + padding: 10px; + vertical-align: top; + font-family: Proxima Nova, sans-serif; +} +.docs-md > table thead th { + font-weight: normal; + text-transform: uppercase; + color: #aaa; + font-size: .8em; + padding: 6px 10px 6px; +} +.docs-md > table td { + border-top: 1px solid #bbb; +} +.docs-md > table td p { + margin: 0 0 .5em; +} +.docs-md > table td p a { + font-weight: bold; + font-size: 1.4em; +} +.docs-md > table code { + white-space: nowrap; +} + + +/* Older markup styles (dependent on .docs class) */ +h1.docs, +h2.docs, +h3.docs, +h4.docs, +h5.docs { + margin: 1.2em 0 .5em; + text-transform: none; + color: #000; + clear: both; + font-weight: bold; + font-family: Proxima Nova, sans-serif; +} + +h1.docs { font-size: 2.2em; margin-top: 0; margin-bottom: .8em; padding-bottom: .3em; border-bottom: 1px solid #ddd; } +h2.docs { font-size: 1.8em; } +h3.docs { font-size: 1.4em; } +h4.docs { font-size: 1.1em; } + +h1.docs + p.docs, +h2.docs + p.docs { + margin-top: -.5em; +} +h3.docs + p.docs, +h4.docs + p.docs { + margin-top: 0; +} + +p.docs, +ol.docs, +ul.docs, +p.docs-note, +dl.docs { + margin: 1em 0; + font-family: Proxima Nova, sans-serif; +} +h1.docs + ul.docs { + margin-top: 2em; +} +ul.docs, +ol.docs { + padding-bottom: .5em; +} +ol.docs li, +ul.docs li { + margin-bottom: 8px; +} +ul.docs ul, +ol.docs ul { + padding-top: 8px; +} +ul.docs li li { + font-size: 1em; +} +.docs code { + font-size: 1.1em; + color: inherit; + font-weight: bold; +} +.docs-note { + background-color: #FFFAA4; + padding: .5em; +} + +.docs-table { + width: 100%; + padding: 1em 0; + margin-top: 2em; + font-family: Proxima Nova, sans-serif; + border-collapse: collapse; +} +.docs-table th { + text-align: left; +} +.docs-table td, +.docs-table thead th { + padding: 10px; + vertical-align: top; + font-family: Proxima Nova, sans-serif; +} +.docs-table thead th { + font-weight: normal; + text-transform: uppercase; + color: #aaa; + font-size: .8em; + padding: 6px 10px 6px; +} +.docs-table td { + border-top: 1px solid #bbb; +} +.docs-table td p { + margin: 0 0 .5em; +} +.docs-table td p a { + font-weight: bold; + font-size: 1.4em; +} +.docs-table-sect { + background-color: #f6f6f6; +} +.docs-table code { + white-space: nowrap; +} + +@-webkit-keyframes code-highlight { from { margin-left:-20%; } to { margin-left:100%; } } + @-moz-keyframes code-highlight { from { margin-left:-20%; } to { margin-left:100%; } } + @-o-keyframes code-highlight { from { margin-left:-20%; } to { margin-left:100%; } } + @keyframes code-highlight { from { margin-left:-20%; } to { margin-left:100%; } } + +.xrayhtml > .snippet:before, +.xrayhtml > .snippet:after, +.docs-example-fullwidth-wrap:before, +.docs-example-fullwidth-wrap:after { + content: " "; + display: table; +} +.xrayhtml > .snippet:after, +.docs-example-fullwidth-wrap:after { + clear: both; +} +.docs .xrayhtml { + padding-top: 1em; + margin-top: 2.5em; + position: relative; + z-index: 9999; +} +@media (max-width: 500px) { + .docs .xrayhtml { + border-radius: 0; + margin-left: -30px; + margin-right: -30px; + padding-left: 0; + padding-right: 0; + } + .docs .xraytitle { + left: 1em; + } +} +.docs .xrayhtml .source-panel { + max-height: none; + overflow: auto; + position: absolute; +} +.docs .xrayhtml.method-flip .source-panel { + overflow: hidden; +} +.docs .xrayhtml.method-flip.view-source .source-panel { + overflow: auto; +} +/*.enhanced-copy.source-panel:after { + display: block; + content: "click to copy"; + position: absolute; + top: 20px; + right: 10px; + color: #bbb; + font-size: 12px; +}*/ +.xrayhtml .source-panel.enhanced-copy-active { + background-color: #FFEA60; + transition: background-color .5s ease-in-out; +} +.docs .xrayhtml .source-panel code, +.prism code { + color: purple; + font-weight: normal; + font-size: 13px; +} +.docs .xrayhtml .source-panel pre { + padding-top: 0; +} +.xrayhtml .source-panel code.language-markup { + white-space: pre !important; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +.xrayviewsource { + font-size: 11px; + float: right; + margin-right: 10px; + margin-top: 13px; + position: relative; + background: #fff; + padding: 7px 10px; + border: #aaa; + border-radius: 5px; + color: #333; + text-decoration: none; + font-family: sans-serif; + min-width: 6em; + box-shadow: 0 0 2px #aaa; + z-index: 10000; + text-align: center; +} + +@media ( max-width: 25em ) { + .docs .xrayhtml.method-inline .source-panel { + margin-left: 0 !important; + margin-right: 0 !important; + } +} + +@media ( min-width: 28em ) { + .docs-help { + position: absolute; + top: 27px; + right: 20px; + } +} + +@media ( min-width: 37.5em ) { + .docs-masthead { + display: table; + width: 100%; + padding-top: 20px; + } + .docs-masthead h1, + .docs-logo, + .docs-help { + position: static; + display: table-cell; + vertical-align: middle; + } + .docs-masthead h1 { + width: 50%; + padding-right: 1em; + } + .docs-logo { + padding: 0; + width: 25%; + } + .docs-help { + width: 25%; + text-align: right; + float: none; + } +} + +@media ( min-width: 56.25em ) { + body.docs { + max-width: 125em; + margin: 0 auto; + } +} + +@media ( min-width: 75em ) { + .docs-masthead { + padding-top: 30px; + padding-bottom: 30px; + } + .docs-nav { + float: left; + margin-right: -230px; + } + .docs-nav-toggle { + display: none; + } + .docs-nav-content { + position: static; + display: block; + } + .docbody { + clear: none; + float: right; + width: 100%; + padding-left: 270px; + padding-right: 310px; + border-top: 0; + } + .docs-api .docbody { + padding-right: 0; + } + + .docs-pagenav { + width: 200px; + background: #eee; + position: absolute; + z-index: 9999; + top: 80px; + right: 20px; + padding: 20px; + } + .docs-pagenav a { + color: #222; + text-decoration: none; + } + .docs-pagenav a:hover { + text-decoration: underline; + } + .docs-pagenav-fixed .docs-pagenav { + position: fixed; + top: 25px; + } + .docs-pagenav ul { + padding: 0; + margin: 0; + list-style: none; + } + .docs-pagenav li { + font-size: 14px; + margin: 0 0 1em; + font-weight: bold; + } + .docs-pagenav li.docs-toc-l2 { + font-weight: normal; + } + .docs-pagenav h4 { + display: block; + margin: 0 0 1em; + color: #888; + font-size: 14px; + text-transform: uppercase; + } +} + + + +/* akamai docs */ +.demo-icons ul, +.demo-icons li { + list-style: none; + margin: 0; + padding: 0; +} +.demo-icons ul { + padding: 10px 0; +} +.demo-icons li { + display: block; + margin: 0; + padding: 15px 30px; + background-position: left 50%; +} +.icons-light { + background: #333; + color: #fff; +} + + +html { + font-size: 1em; +} +.docs-api { + margin: 0; + font-family: Proxima Nova, sans-serif; + line-height: inherit; +} +.docs-api * { + letter-spacing: normal; + box-sizing: border-box; +} +.docs-api h2, +.docs-api h3.subsection-title { + margin-top: 1em; +} +.docs-api .docs-masthead h1 { + margin: .67em 0; + font-weight: bold; + font-family: inherit; +} +.docs-api #main, +.docs-api nav, +.docs-api footer { + float: none; + width: auto; +} +.docs-api footer { + padding: 1.5em 30px; +} +.docs-api #main > section { + padding: 15px 0; + margin: 0; +} +.docs-api #main > .page-title + section { + padding-top: 0; +} +.docs-api nav { + padding-top: 0; + border: 0; +} +.docs-api table { + margin: 0 0 .3em; +} +.docs-api thead tr { + background-color: #fff; +} +.docs-api th, +.docs-api td { + border: 0; + border-bottom: 1px solid #bbb; + background-color: transparent; +} +.docs-api thead th { + font-weight: normal; + text-transform: uppercase; + color: #aaa; + font-size: .8em; + padding: 6px 10px 6px; +} +.docs-api tr > th:last-child { + border-right: 0; +} +.docs-api td { + padding: 10px; + vertical-align: top; + font-family: Proxima Nova, sans-serif; +} +.docs-api th:first-child, +.docs-api td:first-child { + padding-left: 0; +} +.docs-api td code { + font-size: .9em; +} +.docs-api .prettyprint { + border: 0; + margin: 15px 0 20px; +} +.docs-api .prettyprint code { + background: #f5f2f0; + padding: 1em; + margin: 0; + overflow: auto; + font-size: .8em; +} +.docs-api h4.name { + border-left: 5px solid orange; + padding: 0 0 0 1em; + margin-top: 40px; +} +.docs-api .subsection-title + h4.name { + margin-top: 20px; +} +.docs-api h5, +.docs-api .container-overview .subsection-title { + font-size: 1em; +} +.docs-api dl { + display: block; + overflow: hidden; + margin: 0 0 1em; +} +.docs-api dt { + clear: left; + float: left; + width: 120px; +} +.docs-api dd { + float: left; + margin: 0; +} +.docs-api .details { + font-size: .9em; + color: #aaa; + border-left: 0; +} +.docs-api .details li { + margin-left: 0; +} +.docs-api .details ul, +.docs-api .details dt { + padding-left: 0; +} +.docs-api .details dt { + width: auto; + padding-right: 1em; +} +.docs-api h5 + ul { + margin: .3em 0 1.5em; + padding: 0 0 0 1.1em; +} +.docs-api .container-overview { + border-bottom: 1px solid #ddd; + margin-bottom: 30px; + padding-bottom: 20px; +} +.docs-api .container-overview .name:first-child { + margin-top: 30px; +} + +@media ( min-width: 48em ) { + .docs-api #main { + float: left; + padding-right: 310px; + width: 100%; + } + .docs-api nav { + float: right; + width: 270px; + margin-left: -270px; + font-size: .8em; + padding-left: 0; + padding-top: 1em; + } + .docs-api nav p:first-child { + font-weight: bold; + font-size: 14px; + } +} +.snippet { + background: #fff; +} +.demo-viewer-portrait { + max-width: 40em; +} diff --git a/app/html/css/spin360.html b/app/html/css/spin360.html new file mode 100644 index 0000000..39e5264 --- /dev/null +++ b/app/html/css/spin360.html @@ -0,0 +1,239 @@ + + + + + + Akamai Viewer Documentation + + + + + + + +
+

Akamai Viewer Library

+ +
+
+ +
+

Menu

+
+

Overview

+ + +

Customization

+ + +

API Docs

+ + +

Standalone Benchmark Pages

+ +
+
+ +
+ +

Spin360

+

The spin360 component consists of a series of images that iterate on drag or with arrow keys to produce a 360 degree view of a subject.

+

Basic example

+

This is a fairly typical 360 viewer with default options. Autoplay is enabled and there are no controls. Drag with your finger or mouse to spin the ring.

+
+ +
+ + + +
+

Reverse image order

+

If the images seem to spin in the opposite direction than the drag order, use the reverse: true option to flip the sequence. In this car example, the 360 needs this option set. Autoplay is disabled and there are no controls.

+
+ +
+ + + +
+

Longer play time, simple controls

+

This example has an added animation at the end so it's less of a pure 360 rotation and more of an animation you can scrub through. A custom interval is used because this is a long animation.

+
+
+ + + +
+

Controls: Left, Right, Play/Pause

+

This example turns on the manual controls to rotate left/right and play/pause but doesn't autoplay. It also adds a longer interval to give the animation longer to run.

+
+ +
+ + + +
+

Controls with custom control labels

+

A typical 360 view for a shoe without autoplay with 5 second autoplay delay, controls with custom labels, and faster interval.

+
+
+ + + +
+

Image Configuration

+

The Spin360 images sources can be configured with a policy and widths.

+
+ +
+ + + +
+ +
+ + + + + diff --git a/app/html/example.html b/app/html/example.html new file mode 100644 index 0000000..1b1582f --- /dev/null +++ b/app/html/example.html @@ -0,0 +1,318 @@ + + + + + + Akamai Viewer Documentation + + + + + + + +
+

Akamai Viewer Library

+ +
+
+ +
+

Menu

+
+

Overview

+ + +

Customization

+ + +

API Docs

+ + +

Standalone Benchmark Pages

+ +
+
+ +
+ +

Viewer

+

The viewer is a wrapper component designed to contain a carousel, which itself can contain magnify-able images, +videos, and spin360 viewers. Here are some viewer examples.

+

Viewer with portrait-style media

+

Uses tags and a variety of breakpoint configurations for magnification and thumbnail styles.

+
+ +
+
+ +

Viewer with square images and dot thumbnails on the bottom

+
+ + +
+
+ +

Viewer with portrait style images and right thumbnails with a loupe magnifier at wider breakpoints

+
+ + + +
+
+

Car Photos & Spin360

+
+ +
+ + + +
+

Tag switcher demo

+
+ + + + +
+ green + navy + pink + red +
+
+
+
+ +
+ + + + + diff --git a/app/html/img/360-light.svg b/app/html/img/360-light.svg new file mode 100644 index 0000000..30ec88d --- /dev/null +++ b/app/html/img/360-light.svg @@ -0,0 +1 @@ +360-light \ No newline at end of file diff --git a/app/html/img/360.svg b/app/html/img/360.svg new file mode 100644 index 0000000..d72f69c --- /dev/null +++ b/app/html/img/360.svg @@ -0,0 +1 @@ +360 \ No newline at end of file diff --git a/app/html/img/akamai-logo.png b/app/html/img/akamai-logo.png new file mode 100644 index 0000000..a79dc65 Binary files /dev/null and b/app/html/img/akamai-logo.png differ diff --git a/app/html/img/arrow-back-circle.svg b/app/html/img/arrow-back-circle.svg new file mode 100644 index 0000000..8015bfa --- /dev/null +++ b/app/html/img/arrow-back-circle.svg @@ -0,0 +1 @@ +arrow-back-circle \ No newline at end of file diff --git a/app/html/img/arrow-back-light.svg b/app/html/img/arrow-back-light.svg new file mode 100644 index 0000000..a398546 --- /dev/null +++ b/app/html/img/arrow-back-light.svg @@ -0,0 +1 @@ +arrow-back-light \ No newline at end of file diff --git a/app/html/img/arrow-back.svg b/app/html/img/arrow-back.svg new file mode 100644 index 0000000..38005b8 --- /dev/null +++ b/app/html/img/arrow-back.svg @@ -0,0 +1 @@ +arrow-back \ No newline at end of file diff --git a/app/html/img/arrow-forward-circle.svg b/app/html/img/arrow-forward-circle.svg new file mode 100644 index 0000000..e28b5af --- /dev/null +++ b/app/html/img/arrow-forward-circle.svg @@ -0,0 +1 @@ +arrow-forward-circle \ No newline at end of file diff --git a/app/html/img/arrow-forward-light.svg b/app/html/img/arrow-forward-light.svg new file mode 100644 index 0000000..83494d1 --- /dev/null +++ b/app/html/img/arrow-forward-light.svg @@ -0,0 +1 @@ +arrow-forward-light \ No newline at end of file diff --git a/app/html/img/arrow-forward.svg b/app/html/img/arrow-forward.svg new file mode 100644 index 0000000..9950e34 --- /dev/null +++ b/app/html/img/arrow-forward.svg @@ -0,0 +1 @@ +arrow-forward \ No newline at end of file diff --git a/app/html/img/close-light.svg b/app/html/img/close-light.svg new file mode 100644 index 0000000..66b5006 --- /dev/null +++ b/app/html/img/close-light.svg @@ -0,0 +1 @@ +close-light \ No newline at end of file diff --git a/app/html/img/close.svg b/app/html/img/close.svg new file mode 100644 index 0000000..4d6841f --- /dev/null +++ b/app/html/img/close.svg @@ -0,0 +1 @@ +close \ No newline at end of file diff --git a/app/html/img/fullscreen-light.svg b/app/html/img/fullscreen-light.svg new file mode 100644 index 0000000..705b5ca --- /dev/null +++ b/app/html/img/fullscreen-light.svg @@ -0,0 +1 @@ +fullscreen-light \ No newline at end of file diff --git a/app/html/img/fullscreen.svg b/app/html/img/fullscreen.svg new file mode 100644 index 0000000..b66ed43 --- /dev/null +++ b/app/html/img/fullscreen.svg @@ -0,0 +1 @@ +fullscreen \ No newline at end of file diff --git a/app/html/img/icon-zoom-in.svg b/app/html/img/icon-zoom-in.svg new file mode 100644 index 0000000..eaec8f5 --- /dev/null +++ b/app/html/img/icon-zoom-in.svg @@ -0,0 +1,18 @@ + + + + zoom-in + Created with sketchtool. + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/html/img/icon-zoom-out-white.svg b/app/html/img/icon-zoom-out-white.svg new file mode 100644 index 0000000..9339d02 --- /dev/null +++ b/app/html/img/icon-zoom-out-white.svg @@ -0,0 +1,16 @@ + + + + zoom-out-white + Created with sketchtool. + + + + + + + + + + + \ No newline at end of file diff --git a/app/html/img/menubars.png b/app/html/img/menubars.png new file mode 100644 index 0000000..c047972 Binary files /dev/null and b/app/html/img/menubars.png differ diff --git a/app/html/img/muted-light.svg b/app/html/img/muted-light.svg new file mode 100644 index 0000000..3a68471 --- /dev/null +++ b/app/html/img/muted-light.svg @@ -0,0 +1 @@ +muted-light \ No newline at end of file diff --git a/app/html/img/muted.svg b/app/html/img/muted.svg new file mode 100644 index 0000000..1a597fa --- /dev/null +++ b/app/html/img/muted.svg @@ -0,0 +1 @@ +muted \ No newline at end of file diff --git a/app/html/img/pause-light.svg b/app/html/img/pause-light.svg new file mode 100644 index 0000000..e9cb7ae --- /dev/null +++ b/app/html/img/pause-light.svg @@ -0,0 +1 @@ +pause-light \ No newline at end of file diff --git a/app/html/img/pause.svg b/app/html/img/pause.svg new file mode 100644 index 0000000..fe18f77 --- /dev/null +++ b/app/html/img/pause.svg @@ -0,0 +1 @@ +pause \ No newline at end of file diff --git a/app/html/img/play-light.svg b/app/html/img/play-light.svg new file mode 100644 index 0000000..25c6a8a --- /dev/null +++ b/app/html/img/play-light.svg @@ -0,0 +1 @@ +play-light \ No newline at end of file diff --git a/app/html/img/play.svg b/app/html/img/play.svg new file mode 100644 index 0000000..a95a834 --- /dev/null +++ b/app/html/img/play.svg @@ -0,0 +1 @@ +play \ No newline at end of file diff --git a/app/html/img/video-light.svg b/app/html/img/video-light.svg new file mode 100644 index 0000000..10dd058 --- /dev/null +++ b/app/html/img/video-light.svg @@ -0,0 +1 @@ +video-light \ No newline at end of file diff --git a/app/html/img/video-poster.png b/app/html/img/video-poster.png new file mode 100644 index 0000000..2dcef41 Binary files /dev/null and b/app/html/img/video-poster.png differ diff --git a/app/html/img/video.svg b/app/html/img/video.svg new file mode 100644 index 0000000..a05d210 --- /dev/null +++ b/app/html/img/video.svg @@ -0,0 +1 @@ +video \ No newline at end of file diff --git a/app/html/img/volume-light.svg b/app/html/img/volume-light.svg new file mode 100644 index 0000000..0116836 --- /dev/null +++ b/app/html/img/volume-light.svg @@ -0,0 +1 @@ +volume-light \ No newline at end of file diff --git a/app/html/img/volume.svg b/app/html/img/volume.svg new file mode 100644 index 0000000..4cbc40c --- /dev/null +++ b/app/html/img/volume.svg @@ -0,0 +1 @@ +volume \ No newline at end of file diff --git a/app/html/img/zoom-in-light.svg b/app/html/img/zoom-in-light.svg new file mode 100644 index 0000000..6aa019c --- /dev/null +++ b/app/html/img/zoom-in-light.svg @@ -0,0 +1 @@ +zoom-in-light \ No newline at end of file diff --git a/app/html/img/zoom-in.svg b/app/html/img/zoom-in.svg new file mode 100644 index 0000000..ddbaefb --- /dev/null +++ b/app/html/img/zoom-in.svg @@ -0,0 +1 @@ +zoom-in \ No newline at end of file diff --git a/app/html/img/zoom-out-light.svg b/app/html/img/zoom-out-light.svg new file mode 100644 index 0000000..82dd2a9 --- /dev/null +++ b/app/html/img/zoom-out-light.svg @@ -0,0 +1 @@ +zoom-out-light \ No newline at end of file diff --git a/app/html/img/zoom-out.svg b/app/html/img/zoom-out.svg new file mode 100644 index 0000000..8ed611f --- /dev/null +++ b/app/html/img/zoom-out.svg @@ -0,0 +1 @@ +zoom-out \ No newline at end of file diff --git a/app/html/index.html b/app/html/index.html new file mode 100644 index 0000000..fb1f44d --- /dev/null +++ b/app/html/index.html @@ -0,0 +1,134 @@ + + + + + + Thirty-Three + + + + + +
+ +
+ +
+
+ +
+ + + + +

Men's Jackets - Video

+

Demo Video Optimization

+
+ + + +
+ + + +

Men's Jackets

+

Auto policy

+
+ +
+

Women's Tops

+

Demo Media viewer

+
+ +
+

Accessories and Jewelry

+

Demo 360 Media viewer

+
+ +
+

Men's Essentials

+

srcset with imwidth

+
+ +
+

Denim

+

srcset with imwidth + policy

+
+ +
+ + + + + + + +

Leather Collection

+

Art direction with dynamic policies

+
+ +
+ +
+ +
+
+
+ Contact Us + +
+
+ Departments + +
+
+ About Us + +
+
+
+ + + \ No newline at end of file diff --git a/app/html/index.php b/app/html/index.php new file mode 100644 index 0000000..95908a6 --- /dev/null +++ b/app/html/index.php @@ -0,0 +1,187 @@ + + + + + + + + Hi, Akamai! + + Thirty-Three + + + + + + + +
+ +
+ +
+
+ +
+ + + + + +

Men's Jackets - Video

+

Demo Video Optimization

+ +
+ + + +
+ + + +

Men's Jackets

+

Auto policy

+
+ +
+

Women's Tops

+

Demo Media viewer

+
+ +
+

Accessories and Jewelry

+

Demo 360 Media viewer

+
+ +
+

Men's Essentials

+

srcset with imwidth

+
+ +
+

Denim

+

srcset with imwidth + policy

+
+ +
+ + + + + + + +

Leather Collection

+

Art direction with dynamic policies

+
+ +
+ +
+ +
+
+
+ Contact Us + +
+
+ Departments + +
+
+ About Us + +
+
+
+ + + diff --git a/app/html/jacket.html b/app/html/jacket.html new file mode 100644 index 0000000..0c7be8c --- /dev/null +++ b/app/html/jacket.html @@ -0,0 +1,185 @@ + + + + + + Thirty-Three + + + + + + +
+ +
+ +
+
+ + + +
+
+
+

IM Optimized

+ + +
+ +
+

Original (Pristine)

+ + +
+
+ + +
+ + + +
 
+
+ +
+ +
+ +
+
+
+ Contact Us + +
+
+ Departments + +
+
+ About Us + +
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/app/html/jewelry.html b/app/html/jewelry.html new file mode 100644 index 0000000..2102664 --- /dev/null +++ b/app/html/jewelry.html @@ -0,0 +1,162 @@ + + + + + Thirty-Three + + + + + + + + + + +
+ +
+ +
+
+ + + + +
+ +
+ Women's Ring £2000

+ +
* NEW IN * +

+
18 Reviews + +
+ +

+


+
+ + +
+
+
+
+
+ +
+ Details +

+ Since its creation over a century ago, the deep ring setting has been the world’s favorite engagement ring. The ring of rings, as it has been called, is the most brilliant ring ever. It is also the most beautiful. +

+ Charles Michael Hughes recognized that an exceptional diamond should be heralded rather than hidden. So we handcrafted a ring with a diamond so rare, it deserved to be lifted above the band. In so doing, we created something not just brilliant, but legendary: the engagement ring as we know it today. +

+ 1.0 ct shown +

+ Different variations of diamond availabe on request. +
+ + + +
+ +
+ +
+
+
+ Contact Us + +
+
+ Departments + +
+
+ About Us + +
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/app/html/js/akamai-async-loader.js b/app/html/js/akamai-async-loader.js new file mode 100644 index 0000000..e1ae555 --- /dev/null +++ b/app/html/js/akamai-async-loader.js @@ -0,0 +1,2 @@ +/*! Copyright 2019, Akamai Technologies, Inc. All Rights Reserved. akamai-viewer-0.7.2 */ +!function(a){var b=function(a,c){this._element=a,this._options=c,this._bindings=[],b.queue.push(this),b.runQueue()};b.queue=[],b.deps=0,b.depsNeeded=2,b.ready=function(){b.deps++,b.runQueue()},b.runQueue=function(){window.shoestring;b.depsInstantiation + * var element = document.querySelector( "[data-akamai-viewer]" ); + * var viewer = Akamai.AsyncViewer( element, options ); + * @returns AsyncViewer instance + */ + var AsyncViewer = function( elem, options ){ + this._element = elem; + this._options = options; + this._bindings = []; + AsyncViewer.queue.push( this ); + AsyncViewer.runQueue(); + }; + + AsyncViewer.queue = []; + AsyncViewer.deps = 0; + AsyncViewer.depsNeeded = 2; + + AsyncViewer.ready = function(){ + AsyncViewer.deps++; + AsyncViewer.runQueue(); + }; + + AsyncViewer.runQueue = function(){ + var domLib = window.shoestring; + + if( AsyncViewer.deps < AsyncViewer.depsNeeded ){ + return; + } + + AsyncViewer.queue.map(function(viewer){ + return viewer.instantiate(); + }); + }; + + /** + * onInitialize callback - access the viewer instance + * + * + * @callback + * @example Callback function usage + * var asyncViewer = new Akamai.AsyncViewer( element, options ); + * asyncViewer.onInitialize(function(viewer){ + * console.log(viewer.getCarousels()); + * }); + */ + AsyncViewer.prototype.onInitialize = function(binding){ + if(this._instantiated){ + binding(this._viewer); + return; + } + + this._bindings.push(binding); + }; + + AsyncViewer.prototype.instantiate = function(){ + this._instantiated = true; + var domLib = window.shoestring; + var $element = domLib(this._element); + + $element.bind( "akamai-carousel-first-media-load", function(){ + domLib( $element.attr("data-akamai-viewer-placeholder") ).remove(); + }); + + $element.bind("akamai-viewer-init", function(){ + this._bindings.forEach(function(binding){ + binding(this._viewer); + }.bind(this)); + }.bind(this)); + + this._viewer = new Akamai.Viewer(this._element, this._options); + this._instantiated = true; + return this._viewer; + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.AsyncViewer = AsyncViewer; +})(typeof exports === 'undefined'? window : exports); diff --git a/app/html/js/akamai-viewer.es2015 b/app/html/js/akamai-viewer.es2015 new file mode 100644 index 0000000..edb3ea8 --- /dev/null +++ b/app/html/js/akamai-viewer.es2015 @@ -0,0 +1,8476 @@ +/*! Copyright 2019, Akamai Technologies, Inc. All Rights Reserved. akamai-viewer-0.7.2 *//** + * Generic animation class with support for dropped frames both optional easing and duration. + * + * Optional duration is useful when the lifetime is defined by another condition than time + * e.g. speed of an animating object, etc. + * + * Dropped frame logic allows to keep using the same updater logic independent from the actual + * rendering. This eases a lot of cases where it might be pretty complex to break down a state + * based on the pure time difference. + */ +(function(global) { + var time = Date.now || function() { + return +new Date(); + }; + var desiredFrames = 60; + var millisecondsPerSecond = 1000; + var running = {}; + var counter = 1; + + // Create namespaces + if (!global.core) { + global.core = { effect : {} }; + + } else if (!core.effect) { + core.effect = {}; + } + + core.effect.Animate = { + + /** + * A requestAnimationFrame wrapper / polyfill. + * + * @param callback {Function} The callback to be invoked before the next repaint. + * @param root {HTMLElement} The root element for the repaint + */ + requestAnimationFrame: (function() { + + // Check for request animation Frame support + var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame; + var isNative = !!requestFrame; + + if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) { + isNative = false; + } + + if (isNative) { + return function(callback, root) { + requestFrame(callback, root) + }; + } + + var TARGET_FPS = 60; + var requests = {}; + var requestCount = 0; + var rafHandle = 1; + var intervalHandle = null; + var lastActive = +new Date(); + + return function(callback, root) { + var callbackHandle = rafHandle++; + + // Store callback + requests[callbackHandle] = callback; + requestCount++; + + // Create timeout at first request + if (intervalHandle === null) { + + intervalHandle = setInterval(function() { + + var time = +new Date(); + var currentRequests = requests; + + // Reset data structure before executing callbacks + requests = {}; + requestCount = 0; + + for(var key in currentRequests) { + if (currentRequests.hasOwnProperty(key)) { + currentRequests[key](time); + lastActive = time; + } + } + + // Disable the timeout when nothing happens for a certain + // period of time + if (time - lastActive > 2500) { + clearInterval(intervalHandle); + intervalHandle = null; + } + + }, 1000 / TARGET_FPS); + } + + return callbackHandle; + }; + + })(), + + + /** + * Stops the given animation. + * + * @param id {Integer} Unique animation ID + * @return {Boolean} Whether the animation was stopped (aka, was running before) + */ + stop: function(id) { + var cleared = running[id] != null; + if (cleared) { + running[id] = null; + } + + return cleared; + }, + + + /** + * Whether the given animation is still running. + * + * @param id {Integer} Unique animation ID + * @return {Boolean} Whether the animation is still running + */ + isRunning: function(id) { + return running[id] != null; + }, + + + /** + * Start the animation. + * + * @param stepCallback {Function} Pointer to function which is executed on every step. + * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }` + * @param verifyCallback {Function} Executed before every animation step. + * Signature of the method should be `function() { return continueWithAnimation; }` + * @param completedCallback {Function} + * Signature of the method should be `function(droppedFrames, finishedAnimation) {}` + * @param duration {Integer} Milliseconds to run the animation + * @param easingMethod {Function} Pointer to easing function + * Signature of the method should be `function(percent) { return modifiedValue; }` + * @param root {Element ? document.body} Render root, when available. Used for internal + * usage of requestAnimationFrame. + * @return {Integer} Identifier of animation. Can be used to stop it any time. + */ + start: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) { + + var start = time(); + var lastFrame = start; + var percent = 0; + var dropCounter = 0; + var id = counter++; + + if (!root) { + root = document.body; + } + + // Compacting running db automatically every few new animations + if (id % 20 === 0) { + var newRunning = {}; + for (var usedId in running) { + newRunning[usedId] = true; + } + running = newRunning; + } + + // This is the internal step method which is called every few milliseconds + var step = function(virtual) { + + // Normalize virtual value + var render = virtual !== true; + + // Get current time + var now = time(); + + // Verification is executed before next animation step + if (!running[id] || (verifyCallback && !verifyCallback(id))) { + + running[id] = null; + completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false); + return; + + } + + // For the current rendering to apply let's update omitted steps in memory. + // This is important to bring internal state variables up-to-date with progress in time. + if (render) { + + var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1; + for (var j = 0; j < Math.min(droppedFrames, 4); j++) { + step(true); + dropCounter++; + } + + } + + // Compute percent value + if (duration) { + percent = (now - start) / duration; + if (percent > 1) { + percent = 1; + } + } + + // Execute step callback, then... + var value = easingMethod ? easingMethod(percent) : percent; + if ((stepCallback(value, now, render) === false || percent === 1) && render) { + running[id] = null; + completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null); + } else if (render) { + lastFrame = now; + core.effect.Animate.requestAnimationFrame(step, root); + } + }; + + // Mark as running + running[id] = true; + + // Init first step + core.effect.Animate.requestAnimationFrame(step, root); + + // Return unique animation ID + return id; + } + }; +})(this); + + +var Scroller; + +(function() { + + /** + * A pure logic 'component' for 'virtual' scrolling/zooming. + */ + Scroller = function(callback, options) { + + this.__callback = callback; + + this.options = { + + /** Enable scrolling on x-axis */ + scrollingX: true, + + /** Enable scrolling on y-axis */ + scrollingY: true, + + /** Enable animations for deceleration, snap back, zooming and scrolling */ + animating: true, + + /** duration for animations triggered by scrollTo/zoomTo */ + animationDuration: 250, + + /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */ + bouncing: true, + + /** Enable locking to the main axis if user moves only slightly on one of them at start */ + locking: true, + + /** Enable pagination mode (switching between full page content panes) */ + paging: false, + + /** Enable snapping of content to a configured pixel grid */ + snapping: false, + + /** Enable zooming of content via API, fingers and mouse wheel */ + zooming: false, + + /** Minimum zoom level */ + minZoom: 0.5, + + /** Maximum zoom level */ + maxZoom: 3 + + }; + + for (var key in options) { + this.options[key] = options[key]; + } + + }; + + + // Easing Equations (c) 2003 Robert Penner, all rights reserved. + // Open source under the BSD License. + + /** + * @param pos {Number} position between 0 (start of effect) and 1 (end of effect) + **/ + var easeOutCubic = function(pos) { + return (Math.pow((pos - 1), 3) + 1); + }; + + /** + * @param pos {Number} position between 0 (start of effect) and 1 (end of effect) + **/ + var easeInOutCubic = function(pos) { + if ((pos /= 0.5) < 1) { + return 0.5 * Math.pow(pos, 3); + } + + return 0.5 * (Math.pow((pos - 2), 3) + 2); + }; + + + var members = { + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: STATUS + --------------------------------------------------------------------------- + */ + + /** {Boolean} Whether only a single finger is used in touch handling */ + __isSingleTouch: false, + + /** {Boolean} Whether a touch event sequence is in progress */ + __isTracking: false, + + /** + * {Boolean} Whether a gesture zoom/rotate event is in progress. Activates when + * a gesturestart event happens. This has higher priority than dragging. + */ + __isGesturing: false, + + /** + * {Boolean} Whether the user has moved by such a distance that we have enabled + * dragging mode. Hint: It's only enabled after some pixels of movement to + * not interrupt with clicks etc. + */ + __isDragging: false, + + /** + * {Boolean} Not touching and dragging anymore, and smoothly animating the + * touch sequence using deceleration. + */ + __isDecelerating: false, + + /** + * {Boolean} Smoothly animating the currently configured change + */ + __isAnimating: false, + + + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: DIMENSIONS + --------------------------------------------------------------------------- + */ + + /** {Integer} Available outer left position (from document perspective) */ + __clientLeft: 0, + + /** {Integer} Available outer top position (from document perspective) */ + __clientTop: 0, + + /** {Integer} Available outer width */ + __clientWidth: 0, + + /** {Integer} Available outer height */ + __clientHeight: 0, + + /** {Integer} Outer width of content */ + __contentWidth: 0, + + /** {Integer} Outer height of content */ + __contentHeight: 0, + + /** {Integer} Snapping width for content */ + __snapWidth: 100, + + /** {Integer} Snapping height for content */ + __snapHeight: 100, + + /** {Integer} Height to assign to refresh area */ + __refreshHeight: null, + + /** {Boolean} Whether the refresh process is enabled when the event is released now */ + __refreshActive: false, + + /** {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */ + __refreshActivate: null, + + /** {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */ + __refreshDeactivate: null, + + /** {Function} Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */ + __refreshStart: null, + + /** {Number} Zoom level */ + __zoomLevel: 1, + + /** {Number} Scroll position on x-axis */ + __scrollLeft: 0, + + /** {Number} Scroll position on y-axis */ + __scrollTop: 0, + + /** {Integer} Maximum allowed scroll position on x-axis */ + __maxScrollLeft: 0, + + /** {Integer} Maximum allowed scroll position on y-axis */ + __maxScrollTop: 0, + + /* {Number} Scheduled left position (final position when animating) */ + __scheduledLeft: 0, + + /* {Number} Scheduled top position (final position when animating) */ + __scheduledTop: 0, + + /* {Number} Scheduled zoom level (final scale when animating) */ + __scheduledZoom: 0, + + + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: LAST POSITIONS + --------------------------------------------------------------------------- + */ + + /** {Number} Left position of finger at start */ + __lastTouchLeft: null, + + /** {Number} Top position of finger at start */ + __lastTouchTop: null, + + /** {Date} Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */ + __lastTouchMove: null, + + /** {Array} List of positions, uses three indexes for each state: left, top, timestamp */ + __positions: null, + + + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: DECELERATION SUPPORT + --------------------------------------------------------------------------- + */ + + /** {Integer} Minimum left scroll position during deceleration */ + __minDecelerationScrollLeft: null, + + /** {Integer} Minimum top scroll position during deceleration */ + __minDecelerationScrollTop: null, + + /** {Integer} Maximum left scroll position during deceleration */ + __maxDecelerationScrollLeft: null, + + /** {Integer} Maximum top scroll position during deceleration */ + __maxDecelerationScrollTop: null, + + /** {Number} Current factor to modify horizontal scroll position with on every step */ + __decelerationVelocityX: null, + + /** {Number} Current factor to modify vertical scroll position with on every step */ + __decelerationVelocityY: null, + + + + /* + --------------------------------------------------------------------------- + PUBLIC API + --------------------------------------------------------------------------- + */ + + /** + * Configures the dimensions of the client (outer) and content (inner) elements. + * Requires the available space for the outer element and the outer size of the inner element. + * All values which are falsy (null or zero etc.) are ignored and the old value is kept. + * + * @param clientWidth {Integer ? null} Inner width of outer element + * @param clientHeight {Integer ? null} Inner height of outer element + * @param contentWidth {Integer ? null} Outer width of inner element + * @param contentHeight {Integer ? null} Outer height of inner element + */ + setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) { + + var self = this; + + // Only update values which are defined + if (clientWidth) { + self.__clientWidth = clientWidth; + } + + if (clientHeight) { + self.__clientHeight = clientHeight; + } + + if (contentWidth) { + self.__contentWidth = contentWidth; + } + + if (contentHeight) { + self.__contentHeight = contentHeight; + } + + // Refresh maximums + self.__computeScrollMax(); + + // Refresh scroll position + self.scrollTo(self.__scrollLeft, self.__scrollTop, true); + + }, + + + /** + * Sets the client coordinates in relation to the document. + * + * @param left {Integer ? 0} Left position of outer element + * @param top {Integer ? 0} Top position of outer element + */ + setPosition: function(left, top) { + + var self = this; + + self.__clientLeft = left || 0; + self.__clientTop = top || 0; + + }, + + + /** + * Configures the snapping (when snapping is active) + * + * @param width {Integer} Snapping width + * @param height {Integer} Snapping height + */ + setSnapSize: function(width, height) { + + var self = this; + + self.__snapWidth = width; + self.__snapHeight = height; + + }, + + + /** + * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever + * the user event is released during visibility of this zone. This was introduced by some apps on iOS like + * the official Twitter client. + * + * @param height {Integer} Height of pull-to-refresh zone on top of rendered list + * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release. + * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled. + * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh. + */ + activatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback) { + + var self = this; + + self.__refreshHeight = height; + self.__refreshActivate = activateCallback; + self.__refreshDeactivate = deactivateCallback; + self.__refreshStart = startCallback; + + }, + + + /** + * Signalizes that pull-to-refresh is finished. + */ + finishPullToRefresh: function() { + + var self = this; + + self.__refreshActive = false; + if (self.__refreshDeactivate) { + self.__refreshDeactivate(); + } + + self.scrollTo(self.__scrollLeft, self.__scrollTop, true); + + }, + + + /** + * Returns the scroll position and zooming values + * + * @return {Map} `left` and `top` scroll position and `zoom` level + */ + getValues: function() { + + var self = this; + + return { + left: self.__scrollLeft, + top: self.__scrollTop, + zoom: self.__zoomLevel + }; + + }, + + + /** + * Returns the maximum scroll values + * + * @return {Map} `left` and `top` maximum scroll values + */ + getScrollMax: function() { + + var self = this; + + return { + left: self.__maxScrollLeft, + top: self.__maxScrollTop + }; + + }, + + + /** + * Zooms to the given level. Supports optional animation. Zooms + * the center when no coordinates are given. + * + * @param level {Number} Level to zoom to + * @param animate {Boolean ? false} Whether to use animation + * @param originLeft {Number ? null} Zoom in at given left coordinate + * @param originTop {Number ? null} Zoom in at given top coordinate + */ + zoomTo: function(level, animate, originLeft, originTop) { + + var self = this; + + if (!self.options.zooming) { + throw new Error("Zooming is not enabled!"); + } + + // Stop deceleration + if (self.__isDecelerating) { + core.effect.Animate.stop(self.__isDecelerating); + self.__isDecelerating = false; + } + + var oldLevel = self.__zoomLevel; + + // Normalize input origin to center of viewport if not defined + if (originLeft == null) { + originLeft = self.__clientWidth / 2; + } + + if (originTop == null) { + originTop = self.__clientHeight / 2; + } + + // Limit level according to configuration + level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom); + + // Recompute maximum values while temporary tweaking maximum scroll ranges + self.__computeScrollMax(level); + + // Recompute left and top coordinates based on new zoom level + var left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft; + var top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop; + + // Limit x-axis + if (left > self.__maxScrollLeft) { + left = self.__maxScrollLeft; + } else if (left < 0) { + left = 0; + } + + // Limit y-axis + if (top > self.__maxScrollTop) { + top = self.__maxScrollTop; + } else if (top < 0) { + top = 0; + } + + // Push values out + self.__publish(left, top, level, animate); + + }, + + + /** + * Zooms the content by the given factor. + * + * @param factor {Number} Zoom by given factor + * @param animate {Boolean ? false} Whether to use animation + * @param originLeft {Number ? 0} Zoom in at given left coordinate + * @param originTop {Number ? 0} Zoom in at given top coordinate + */ + zoomBy: function(factor, animate, originLeft, originTop) { + + var self = this; + + self.zoomTo(self.__zoomLevel * factor, animate, originLeft, originTop); + + }, + + + /** + * Scrolls to the given position. Respect limitations and snapping automatically. + * + * @param left {Number?null} Horizontal scroll position, keeps current if value is null + * @param top {Number?null} Vertical scroll position, keeps current if value is null + * @param animate {Boolean?false} Whether the scrolling should happen using an animation + * @param zoom {Number?null} Zoom level to go to + */ + scrollTo: function(left, top, animate, zoom) { + + var self = this; + + // Stop deceleration + if (self.__isDecelerating) { + core.effect.Animate.stop(self.__isDecelerating); + self.__isDecelerating = false; + } + + // Correct coordinates based on new zoom level + if (zoom != null && zoom !== self.__zoomLevel) { + + if (!self.options.zooming) { + throw new Error("Zooming is not enabled!"); + } + + left *= zoom; + top *= zoom; + + // Recompute maximum values while temporary tweaking maximum scroll ranges + self.__computeScrollMax(zoom); + + } else { + + // Keep zoom when not defined + zoom = self.__zoomLevel; + + } + + if (!self.options.scrollingX) { + + left = self.__scrollLeft; + + } else { + + if (self.options.paging) { + left = Math.round(left / self.__clientWidth) * self.__clientWidth; + } else if (self.options.snapping) { + left = Math.round(left / self.__snapWidth) * self.__snapWidth; + } + + } + + if (!self.options.scrollingY) { + + top = self.__scrollTop; + + } else { + + if (self.options.paging) { + top = Math.round(top / self.__clientHeight) * self.__clientHeight; + } else if (self.options.snapping) { + top = Math.round(top / self.__snapHeight) * self.__snapHeight; + } + + } + + // Limit for allowed ranges + left = Math.max(Math.min(self.__maxScrollLeft, left), 0); + top = Math.max(Math.min(self.__maxScrollTop, top), 0); + + // Don't animate when no change detected, still call publish to make sure + // that rendered position is really in-sync with internal data + if (left === self.__scrollLeft && top === self.__scrollTop) { + animate = false; + } + + // Publish new values + self.__publish(left, top, zoom, animate); + + }, + + + /** + * Scroll by the given offset + * + * @param left {Number ? 0} Scroll x-axis by given offset + * @param top {Number ? 0} Scroll x-axis by given offset + * @param animate {Boolean ? false} Whether to animate the given change + */ + scrollBy: function(left, top, animate) { + + var self = this; + + var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft; + var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop; + + self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate); + + }, + + + + /* + --------------------------------------------------------------------------- + EVENT CALLBACKS + --------------------------------------------------------------------------- + */ + + /** + * Mouse wheel handler for zooming support + */ + doMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) { + + var self = this; + var change = wheelDelta > 0 ? 0.97 : 1.03; + + return self.zoomTo(self.__zoomLevel * change, false, pageX - self.__clientLeft, pageY - self.__clientTop); + + }, + + + /** + * Touch start handler for scrolling support + */ + doTouchStart: function(touches, timeStamp) { + + // Array-like check is enough here + if (touches.length == null) { + throw new Error("Invalid touch list: " + touches); + } + + if (timeStamp instanceof Date) { + timeStamp = timeStamp.valueOf(); + } + if (typeof timeStamp !== "number") { + throw new Error("Invalid timestamp value: " + timeStamp); + } + + var self = this; + + // Stop deceleration + if (self.__isDecelerating) { + core.effect.Animate.stop(self.__isDecelerating); + self.__isDecelerating = false; + } + + // Stop animation + if (self.__isAnimating) { + core.effect.Animate.stop(self.__isAnimating); + self.__isAnimating = false; + } + + // Use center point when dealing with two fingers + var currentTouchLeft, currentTouchTop; + var isSingleTouch = touches.length === 1; + if (isSingleTouch) { + currentTouchLeft = touches[0].pageX; + currentTouchTop = touches[0].pageY; + } else { + currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; + currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; + } + + // Store initial positions + self.__initialTouchLeft = currentTouchLeft; + self.__initialTouchTop = currentTouchTop; + + // Store current zoom level + self.__zoomLevelStart = self.__zoomLevel; + + // Store initial touch positions + self.__lastTouchLeft = currentTouchLeft; + self.__lastTouchTop = currentTouchTop; + + // Store initial move time stamp + self.__lastTouchMove = timeStamp; + + // Reset initial scale + self.__lastScale = 1; + + // Reset locking flags + self.__enableScrollX = !isSingleTouch && self.options.scrollingX; + self.__enableScrollY = !isSingleTouch && self.options.scrollingY; + + // Reset tracking flag + self.__isTracking = true; + + // Dragging starts directly with two fingers, otherwise lazy with an offset + self.__isDragging = !isSingleTouch; + + // Some features are disabled in multi touch scenarios + self.__isSingleTouch = isSingleTouch; + + // Clearing data structure + self.__positions = []; + + }, + + + /** + * Touch move handler for scrolling support + */ + doTouchMove: function(touches, timeStamp, scale) { + + // Array-like check is enough here + if (touches.length == null) { + throw new Error("Invalid touch list: " + touches); + } + + if (timeStamp instanceof Date) { + timeStamp = timeStamp.valueOf(); + } + if (typeof timeStamp !== "number") { + throw new Error("Invalid timestamp value: " + timeStamp); + } + + var self = this; + + // Ignore event when tracking is not enabled (event might be outside of element) + if (!self.__isTracking) { + return; + } + + + var currentTouchLeft, currentTouchTop; + + // Compute move based around of center of fingers + if (touches.length === 2) { + currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; + currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; + } else { + currentTouchLeft = touches[0].pageX; + currentTouchTop = touches[0].pageY; + } + + var positions = self.__positions; + + // Are we already is dragging mode? + if (self.__isDragging) { + + // Compute move distance + var moveX = currentTouchLeft - self.__lastTouchLeft; + var moveY = currentTouchTop - self.__lastTouchTop; + + // Read previous scroll position and zooming + var scrollLeft = self.__scrollLeft; + var scrollTop = self.__scrollTop; + var level = self.__zoomLevel; + + // Work with scaling + if (scale != null && self.options.zooming) { + + var oldLevel = level; + + // Recompute level based on previous scale and new scale + level = level / self.__lastScale * scale; + + // Limit level according to configuration + level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom); + + // Only do further compution when change happened + if (oldLevel !== level) { + + // Compute relative event position to container + var currentTouchLeftRel = currentTouchLeft - self.__clientLeft; + var currentTouchTopRel = currentTouchTop - self.__clientTop; + + // Recompute left and top coordinates based on new zoom level + scrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel; + scrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel; + + // Recompute max scroll values + self.__computeScrollMax(level); + + } + } + + if (self.__enableScrollX) { + + scrollLeft -= moveX; + var maxScrollLeft = self.__maxScrollLeft; + + if (scrollLeft > maxScrollLeft || scrollLeft < 0) { + + // Slow down on the edges + if (self.options.bouncing) { + + scrollLeft += (moveX / 2); + + } else if (scrollLeft > maxScrollLeft) { + + scrollLeft = maxScrollLeft; + + } else { + + scrollLeft = 0; + + } + } + } + + // Compute new vertical scroll position + if (self.__enableScrollY) { + + scrollTop -= moveY; + var maxScrollTop = self.__maxScrollTop; + + if (scrollTop > maxScrollTop || scrollTop < 0) { + + // Slow down on the edges + if (self.options.bouncing) { + + scrollTop += (moveY / 2); + + // Support pull-to-refresh (only when only y is scrollable) + if (!self.__enableScrollX && self.__refreshHeight != null) { + + if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) { + + self.__refreshActive = true; + if (self.__refreshActivate) { + self.__refreshActivate(); + } + + } else if (self.__refreshActive && scrollTop > -self.__refreshHeight) { + + self.__refreshActive = false; + if (self.__refreshDeactivate) { + self.__refreshDeactivate(); + } + + } + } + + } else if (scrollTop > maxScrollTop) { + + scrollTop = maxScrollTop; + + } else { + + scrollTop = 0; + + } + } + } + + // Keep list from growing infinitely (holding min 10, max 20 measure points) + if (positions.length > 60) { + positions.splice(0, 30); + } + + // Track scroll movement for decleration + positions.push(scrollLeft, scrollTop, timeStamp); + + // Sync scroll position + self.__publish(scrollLeft, scrollTop, level); + + // Otherwise figure out whether we are switching into dragging mode now. + } else { + + var minimumTrackingForScroll = self.options.locking ? 3 : 0; + var minimumTrackingForDrag = 5; + + var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft); + var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop); + + self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll; + self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll; + + positions.push(self.__scrollLeft, self.__scrollTop, timeStamp); + + self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag); + + } + + // Update last touch positions and time stamp for next event + self.__lastTouchLeft = currentTouchLeft; + self.__lastTouchTop = currentTouchTop; + self.__lastTouchMove = timeStamp; + self.__lastScale = scale; + + }, + + + /** + * Touch end handler for scrolling support + */ + doTouchEnd: function(timeStamp) { + + if (timeStamp instanceof Date) { + timeStamp = timeStamp.valueOf(); + } + if (typeof timeStamp !== "number") { + throw new Error("Invalid timestamp value: " + timeStamp); + } + + var self = this; + + // Ignore event when tracking is not enabled (no touchstart event on element) + // This is required as this listener ('touchmove') sits on the document and not on the element itself. + if (!self.__isTracking) { + return; + } + + // Not touching anymore (when two finger hit the screen there are two touch end events) + self.__isTracking = false; + + // Be sure to reset the dragging flag now. Here we also detect whether + // the finger has moved fast enough to switch into a deceleration animation. + if (self.__isDragging) { + + // Reset dragging flag + self.__isDragging = false; + + // Start deceleration + // Verify that the last move detected was in some relevant time frame + if (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) { + + // Then figure out what the scroll position was about 100ms ago + var positions = self.__positions; + var endPos = positions.length - 1; + var startPos = endPos; + + // Move pointer to position measured 100ms ago + for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) { + startPos = i; + } + + // If start and stop position is identical in a 100ms timeframe, + // we cannot compute any useful deceleration. + if (startPos !== endPos) { + + // Compute relative movement between these two points + var timeOffset = positions[endPos] - positions[startPos]; + var movedLeft = self.__scrollLeft - positions[startPos - 2]; + var movedTop = self.__scrollTop - positions[startPos - 1]; + + // Based on 50ms compute the movement to apply for each render step + self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60); + self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60); + + // How much velocity is required to start the deceleration + var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? 4 : 1; + + // Verify that we have enough velocity to start deceleration + if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) { + + // Deactivate pull-to-refresh when decelerating + if (!self.__refreshActive) { + + self.__startDeceleration(timeStamp); + + } + } + } + } + } + + // If this was a slower move it is per default non decelerated, but this + // still means that we want snap back to the bounds which is done here. + // This is placed outside the condition above to improve edge case stability + // e.g. touchend fired without enabled dragging. This should normally do not + // have modified the scroll positions or even showed the scrollbars though. + if (!self.__isDecelerating) { + + if (self.__refreshActive && self.__refreshStart) { + + // Use publish instead of scrollTo to allow scrolling to out of boundary position + // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled + self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true); + + if (self.__refreshStart) { + self.__refreshStart(); + } + + } else { + + self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel); + + // Directly signalize deactivation (nothing todo on refresh?) + if (self.__refreshActive) { + + self.__refreshActive = false; + if (self.__refreshDeactivate) { + self.__refreshDeactivate(); + } + + } + } + } + + // Fully cleanup list + self.__positions.length = 0; + + }, + + + + /* + --------------------------------------------------------------------------- + PRIVATE API + --------------------------------------------------------------------------- + */ + + /** + * Applies the scroll position to the content element + * + * @param left {Number} Left scroll position + * @param top {Number} Top scroll position + * @param animate {Boolean?false} Whether animation should be used to move to the new coordinates + */ + __publish: function(left, top, zoom, animate) { + + var self = this; + + // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation + var wasAnimating = self.__isAnimating; + if (wasAnimating) { + core.effect.Animate.stop(wasAnimating); + self.__isAnimating = false; + } + + if (animate && self.options.animating) { + + // Keep scheduled positions for scrollBy/zoomBy functionality + self.__scheduledLeft = left; + self.__scheduledTop = top; + self.__scheduledZoom = zoom; + + var oldLeft = self.__scrollLeft; + var oldTop = self.__scrollTop; + var oldZoom = self.__zoomLevel; + + var diffLeft = left - oldLeft; + var diffTop = top - oldTop; + var diffZoom = zoom - oldZoom; + + var step = function(percent, now, render) { + + if (render) { + + self.__scrollLeft = oldLeft + (diffLeft * percent); + self.__scrollTop = oldTop + (diffTop * percent); + self.__zoomLevel = oldZoom + (diffZoom * percent); + + // Push values out + if (self.__callback) { + self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel); + } + + } + }; + + var verify = function(id) { + return self.__isAnimating === id; + }; + + var completed = function(renderedFramesPerSecond, animationId, wasFinished) { + if (animationId === self.__isAnimating) { + self.__isAnimating = false; + } + + if (self.options.zooming) { + self.__computeScrollMax(); + } + }; + + // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out + self.__isAnimating = core.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic); + + } else { + + self.__scheduledLeft = self.__scrollLeft = left; + self.__scheduledTop = self.__scrollTop = top; + self.__scheduledZoom = self.__zoomLevel = zoom; + + // Push values out + if (self.__callback) { + self.__callback(left, top, zoom); + } + + // Fix max scroll ranges + if (self.options.zooming) { + self.__computeScrollMax(); + } + } + }, + + + /** + * Recomputes scroll minimum values based on client dimensions and content dimensions. + */ + __computeScrollMax: function(zoomLevel) { + + var self = this; + + if (zoomLevel == null) { + zoomLevel = self.__zoomLevel; + } + + self.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0); + self.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0); + + }, + + + + /* + --------------------------------------------------------------------------- + ANIMATION (DECELERATION) SUPPORT + --------------------------------------------------------------------------- + */ + + /** + * Called when a touch sequence end and the speed of the finger was high enough + * to switch into deceleration mode. + */ + __startDeceleration: function(timeStamp) { + + var self = this; + + if (self.options.paging) { + + var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0); + var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0); + var clientWidth = self.__clientWidth; + var clientHeight = self.__clientHeight; + + // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area. + // Each page should have exactly the size of the client area. + self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth; + self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight; + self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth; + self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight; + + } else { + + self.__minDecelerationScrollLeft = 0; + self.__minDecelerationScrollTop = 0; + self.__maxDecelerationScrollLeft = self.__maxScrollLeft; + self.__maxDecelerationScrollTop = self.__maxScrollTop; + + } + + // Wrap class method + var step = function(percent, now, render) { + self.__stepThroughDeceleration(render); + }; + + // How much velocity is required to keep the deceleration running + var minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1; + + // Detect whether it's still worth to continue animating steps + // If we are already slow enough to not being user perceivable anymore, we stop the whole process here. + var verify = function() { + return Math.abs(self.__decelerationVelocityX) >= minVelocityToKeepDecelerating || Math.abs(self.__decelerationVelocityY) >= minVelocityToKeepDecelerating; + }; + + var completed = function(renderedFramesPerSecond, animationId, wasFinished) { + self.__isDecelerating = false; + + // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions + self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping); + }; + + // Start animation and switch on flag + self.__isDecelerating = core.effect.Animate.start(step, verify, completed); + + }, + + + /** + * Called on every step of the animation + * + * @param inMemory {Boolean?false} Whether to not render the current step, but keep it in memory only. Used internally only! + */ + __stepThroughDeceleration: function(render) { + + var self = this; + + + // + // COMPUTE NEXT SCROLL POSITION + // + + // Add deceleration to scroll position + var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX; + var scrollTop = self.__scrollTop + self.__decelerationVelocityY; + + + // + // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE + // + + if (!self.options.bouncing) { + + var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft); + if (scrollLeftFixed !== scrollLeft) { + scrollLeft = scrollLeftFixed; + self.__decelerationVelocityX = 0; + } + + var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop); + if (scrollTopFixed !== scrollTop) { + scrollTop = scrollTopFixed; + self.__decelerationVelocityY = 0; + } + + } + + + // + // UPDATE SCROLL POSITION + // + + if (render) { + + self.__publish(scrollLeft, scrollTop, self.__zoomLevel); + + } else { + + self.__scrollLeft = scrollLeft; + self.__scrollTop = scrollTop; + + } + + + // + // SLOW DOWN + // + + // Slow down velocity on every iteration + if (!self.options.paging) { + + // This is the factor applied to every iteration of the animation + // to slow down the process. This should emulate natural behavior where + // objects slow down when the initiator of the movement is removed + var frictionFactor = 0.95; + + self.__decelerationVelocityX *= frictionFactor; + self.__decelerationVelocityY *= frictionFactor; + + } + + + // + // BOUNCING SUPPORT + // + + if (self.options.bouncing) { + + var scrollOutsideX = 0; + var scrollOutsideY = 0; + + // This configures the amount of change applied to deceleration/acceleration when reaching boundaries + var penetrationDeceleration = 0.03; + var penetrationAcceleration = 0.08; + + // Check limits + if (scrollLeft < self.__minDecelerationScrollLeft) { + scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft; + } else if (scrollLeft > self.__maxDecelerationScrollLeft) { + scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft; + } + + if (scrollTop < self.__minDecelerationScrollTop) { + scrollOutsideY = self.__minDecelerationScrollTop - scrollTop; + } else if (scrollTop > self.__maxDecelerationScrollTop) { + scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop; + } + + // Slow down until slow enough, then flip back to snap position + if (scrollOutsideX !== 0) { + if (scrollOutsideX * self.__decelerationVelocityX <= 0) { + self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration; + } else { + self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration; + } + } + + if (scrollOutsideY !== 0) { + if (scrollOutsideY * self.__decelerationVelocityY <= 0) { + self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration; + } else { + self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration; + } + } + } + } + }; + + // Copy over members to prototype + for (var key in members) { + Scroller.prototype[key] = members[key]; + } + +})(); + +(function( factory ) { + if( typeof define === 'function' && define.amd ) { + // AMD. Register as an anonymous module. + define( [ 'shoestring' ], factory ); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = factory(); + } else { + // Browser globals + factory(); + } +}(function () { + var win = typeof window !== "undefined" ? window : this; + var doc = win.document; + + + /** + * The shoestring object constructor. + * + * @param {string,object} prim The selector to find or element to wrap. + * @param {object} sec The context in which to match the `prim` selector. + * @returns shoestring + * @this window + */ + function shoestring( prim, sec ){ + var pType = typeof( prim ), + ret = [], + sel; + + // return an empty shoestring object + if( !prim ){ + return new Shoestring( ret ); + } + + // ready calls + if( prim.call ){ + return shoestring.ready( prim ); + } + + // handle re-wrapping shoestring objects + if( prim.constructor === Shoestring && !sec ){ + return prim; + } + + // if string starting with <, make html + if( pType === "string" && prim.indexOf( "<" ) === 0 ){ + var dfrag = doc.createElement( "div" ); + + dfrag.innerHTML = prim; + + // TODO depends on children (circular) + return shoestring( dfrag ).children().each(function(){ + dfrag.removeChild( this ); + }); + } + + // if string, it's a selector, use qsa + if( pType === "string" ){ + if( sec ){ + return shoestring( sec ).find( prim ); + } + + sel = doc.querySelectorAll( prim ); + + return new Shoestring( sel, prim ); + } + + // array like objects or node lists + if( Object.prototype.toString.call( pType ) === '[object Array]' || + (win.NodeList && prim instanceof win.NodeList) ){ + + return new Shoestring( prim, prim ); + } + + // if it's an array, use all the elements + if( prim.constructor === Array ){ + return new Shoestring( prim, prim ); + } + + // otherwise assume it's an object the we want at an index + return new Shoestring( [prim], prim ); + } + + var Shoestring = function( ret, prim ) { + this.length = 0; + this.selector = prim; + shoestring.merge(this, ret); + }; + + // TODO only required for tests + Shoestring.prototype.reverse = [].reverse; + + // For adding element set methods + shoestring.fn = Shoestring.prototype; + + shoestring.Shoestring = Shoestring; + + // For extending objects + // TODO move to separate module when we use prototypes + shoestring.extend = function( first, second ){ + for( var i in second ){ + if( second.hasOwnProperty( i ) ){ + first[ i ] = second[ i ]; + } + } + + return first; + }; + + // taken directly from jQuery + shoestring.merge = function( first, second ) { + var len, j, i; + + len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }; + + // expose + win.shoestring = shoestring; + + + + /** + * Make an HTTP request to a url. + * + * **NOTE** the following options are supported: + * + * - *method* - The HTTP method used with the request. Default: `GET`. + * - *data* - Raw object with keys and values to pass with request as query params. Default `null`. + * - *headers* - Set of request headers to add. Default `{}`. + * - *async* - Whether the opened request is asynchronouse. Default `true`. + * - *success* - Callback for successful request and response. Passed the response data. + * - *error* - Callback for failed request and response. + * - *cancel* - Callback for cancelled request and response. + * + * @param {string} url The url to request. + * @param {object} options The options object, see Notes. + * @return shoestring + * @this shoestring + */ + + shoestring.ajax = function( url, options ) { + var params = "", req = new XMLHttpRequest(), settings, key; + + settings = shoestring.extend( {}, shoestring.ajax.settings ); + + if( options ){ + shoestring.extend( settings, options ); + } + + if( !url ){ + url = settings.url; + } + + if( !req || !url ){ + return; + } + + // create parameter string from data object + if( settings.data ){ + for( key in settings.data ){ + if( settings.data.hasOwnProperty( key ) ){ + if( params !== "" ){ + params += "&"; + } + params += encodeURIComponent( key ) + "=" + + encodeURIComponent( settings.data[key] ); + } + } + } + + // append params to url for GET requests + if( settings.method === "GET" && params ){ + + url += "?" + params; + } + + req.open( settings.method, url, settings.async ); + + if( req.setRequestHeader ){ + req.setRequestHeader( "X-Requested-With", "XMLHttpRequest" ); + + // Set 'Content-type' header for POST requests + if( settings.method === "POST" && params ){ + req.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" ); + } + + for( key in settings.headers ){ + if( settings.headers.hasOwnProperty( key ) ){ + req.setRequestHeader(key, settings.headers[ key ]); + } + } + } + + req.onreadystatechange = function () { + if( req.readyState === 4 ){ + // Trim the whitespace so shoestring('
') works + var res = (req.responseText || '').replace(/^\s+|\s+$/g, ''); + if( req.status.toString().indexOf( "0" ) === 0 ){ + return settings.cancel( res, req.status, req ); + } + else if ( req.status.toString().match( /^(4|5)/ ) && RegExp.$1 ){ + return settings.error( res, req.status, req ); + } + else if (settings.success) { + return settings.success( res, req.status, req ); + } + } + }; + + if( req.readyState === 4 ){ + return req; + } + + // Send request + if( settings.method === "POST" && params ){ + req.send( params ); + } else { + req.send(); + } + + return req; + }; + + shoestring.ajax.settings = { + success: function(){}, + error: function(){}, + cancel: function(){}, + method: "GET", + async: true, + data: null, + headers: {} + }; + + + + /** + * Helper function wrapping a call to [ajax](ajax.js.html) using the `GET` method. + * + * @param {string} url The url to GET from. + * @param {function} callback Callback to invoke on success. + * @return shoestring + * @this shoestring + */ + shoestring.get = function( url, callback ){ + return shoestring.ajax( url, { success: callback } ); + }; + + + + /** + * Load the HTML response from `url` into the current set of elements. + * + * @param {string} url The url to GET from. + * @param {function} callback Callback to invoke after HTML is inserted. + * @return shoestring + * @this shoestring + */ + shoestring.fn.load = function( url, callback ){ + var self = this, + args = arguments, + intCB = function( data ){ + self.each(function(){ + shoestring( this ).html( data ); + }); + + if( callback ){ + callback.apply( self, args ); + } + }; + + shoestring.ajax( url, { success: intCB } ); + return this; + }; + + + + /** + * Helper function wrapping a call to [ajax](ajax.js.html) using the `POST` method. + * + * @param {string} url The url to POST to. + * @param {object} data The data to send. + * @param {function} callback Callback to invoke on success. + * @return shoestring + * @this shoestring + */ + shoestring.post = function( url, data, callback ){ + return shoestring.ajax( url, { data: data, method: "POST", success: callback } ); + }; + + + + /** + * Iterates over `shoestring` collections. + * + * @param {function} callback The callback to be invoked on each element and index + * @return shoestring + * @this shoestring + */ + shoestring.fn.each = function( callback ){ + return shoestring.each( this, callback ); + }; + + shoestring.each = function( collection, callback ) { + var val; + for( var i = 0, il = collection.length; i < il; i++ ){ + val = callback.call( collection[i], i, collection[i] ); + if( val === false ){ + break; + } + } + + return collection; + }; + + + + /** + * Check for array membership. + * + * @param {object} needle The thing to find. + * @param {object} haystack The thing to find the needle in. + * @return {boolean} + * @this window + */ + shoestring.inArray = function( needle, haystack ){ + var isin = -1; + for( var i = 0, il = haystack.length; i < il; i++ ){ + if( haystack.hasOwnProperty( i ) && haystack[ i ] === needle ){ + isin = i; + } + } + return isin; + }; + + + + /** + * Bind callbacks to be run when the DOM is "ready". + * + * @param {function} fn The callback to be run + * @return shoestring + * @this shoestring + */ + shoestring.ready = function( fn ){ + if( ready && fn ){ + fn.call( doc ); + } + else if( fn ){ + readyQueue.push( fn ); + } + else { + runReady(); + } + + return [doc]; + }; + + // TODO necessary? + shoestring.fn.ready = function( fn ){ + shoestring.ready( fn ); + return this; + }; + + // Empty and exec the ready queue + var ready = false, + readyQueue = [], + runReady = function(){ + if( !ready ){ + while( readyQueue.length ){ + readyQueue.shift().call( doc ); + } + ready = true; + } + }; + + // If DOM is already ready at exec time, depends on the browser. + // From: https://github.com/mobify/mobifyjs/blob/526841be5509e28fc949038021799e4223479f8d/src/capture.js#L128 + if (doc.attachEvent ? doc.readyState === "complete" : doc.readyState !== "loading") { + runReady(); + } else { + doc.addEventListener( "DOMContentLoaded", runReady, false ); + doc.addEventListener( "readystatechange", runReady, false ); + win.addEventListener( "load", runReady, false ); + } + + + + /** + * Checks the current set of elements against the selector, if one matches return `true`. + * + * @param {string} selector The selector to check. + * @return {boolean} + * @this {shoestring} + */ + shoestring.fn.is = function( selector ){ + var ret = false, self = this, parents, check; + + // assume a dom element + if( typeof selector !== "string" ){ + // array-like, ie shoestring objects or element arrays + if( selector.length && selector[0] ){ + check = selector; + } else { + check = [selector]; + } + + return _checkElements(this, check); + } + + parents = this.parent(); + + if( !parents.length ){ + parents = shoestring( doc ); + } + + parents.each(function( i, e ) { + var children; + + children = e.querySelectorAll( selector ); + + ret = _checkElements( self, children ); + }); + + return ret; + }; + + function _checkElements(needles, haystack){ + var ret = false; + + needles.each(function() { + var j = 0; + + while( j < haystack.length ){ + if( this === haystack[j] ){ + ret = true; + } + + j++; + } + }); + + return ret; + } + + + + /** + * Get data attached to the first element or set data values on all elements in the current set. + * + * @param {string} name The data attribute name. + * @param {any} value The value assigned to the data attribute. + * @return {any|shoestring} + * @this shoestring + */ + shoestring.fn.data = function( name, value ){ + if( name !== undefined ){ + if( value !== undefined ){ + return this.each(function(){ + if( !this.shoestringData ){ + this.shoestringData = {}; + } + + this.shoestringData[ name ] = value; + }); + } + else { + if( this[ 0 ] ) { + if( this[ 0 ].shoestringData ) { + return this[ 0 ].shoestringData[ name ]; + } + } + } + } + else { + return this[ 0 ] ? this[ 0 ].shoestringData || {} : undefined; + } + }; + + + /** + * Remove data associated with `name` or all the data, for each element in the current set. + * + * @param {string} name The data attribute name. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeData = function( name ){ + return this.each(function(){ + if( name !== undefined && this.shoestringData ){ + this.shoestringData[ name ] = undefined; + delete this.shoestringData[ name ]; + } else { + this[ 0 ].shoestringData = {}; + } + }); + }; + + + + /** + * An alias for the `shoestring` constructor. + */ + if (typeof(win.$) === "undefined") { win.$ = shoestring; } + + + + /** + * Add a class to each DOM element in the set of elements. + * + * @param {string} className The name of the class to be added. + * @return shoestring + * @this shoestring + */ + shoestring.fn.addClass = function( className ){ + var classes = className.replace(/^\s+|\s+$/g, '').split( " " ); + + return this.each(function(){ + for( var i = 0, il = classes.length; i < il; i++ ){ + if( this.className !== undefined && + (this.className === "" || + !this.className.match( new RegExp( "(^|\\s)" + classes[ i ] + "($|\\s)"))) ){ + this.className += " " + classes[ i ]; + } + } + }); + }; + + + + /** + * Add elements matching the selector to the current set. + * + * @param {string} selector The selector for the elements to add from the DOM + * @return shoestring + * @this shoestring + */ + shoestring.fn.add = function( selector ){ + var ret = []; + this.each(function(){ + ret.push( this ); + }); + + shoestring( selector ).each(function(){ + ret.push( this ); + }); + + return shoestring( ret ); + }; + + + + /** + * Insert an element or HTML string after each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.after = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + if( fragment.length > 1 ){ + fragment = fragment.reverse(); + } + return this.each(function( i ){ + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + var insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ]; + this.parentNode.insertBefore( insertEl, this.nextSibling ); + } + }); + }; + + + + /** + * Insert an element or HTML string as the last child of each element in the set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.append = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + return this.each(function( i ){ + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + this.appendChild( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ] ); + } + }); + }; + + + + /** + * Insert the current set as the last child of the elements matching the selector. + * + * @param {string} selector The selector after which to append the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.appendTo = function( selector ){ + return this.each(function(){ + shoestring( selector ).append( this ); + }); + }; + + + + /** + * Get the value of the first element of the set or set the value of all the elements in the set. + * + * @param {string} name The attribute name. + * @param {string} value The new value for the attribute. + * @return {shoestring|string|undefined} + * @this {shoestring} + */ + shoestring.fn.attr = function( name, value ){ + var nameStr = typeof( name ) === "string"; + + if( value !== undefined || !nameStr ){ + return this.each(function(){ + if( nameStr ){ + this.setAttribute( name, value ); + } else { + for( var i in name ){ + if( name.hasOwnProperty( i ) ){ + this.setAttribute( i, name[ i ] ); + } + } + } + }); + } else { + return this[ 0 ] ? this[ 0 ].getAttribute( name ) : undefined; + } + }; + + + + /** + * Insert an element or HTML string before each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.before = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + return this.each(function( i ){ + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + this.parentNode.insertBefore( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ], this ); + } + }); + }; + + + + /** + * Get the children of the current collection. + * @return shoestring + * @this shoestring + */ + shoestring.fn.children = function(){ + var ret = [], + childs, + j; + this.each(function(){ + childs = this.children; + j = -1; + + while( j++ < childs.length-1 ){ + if( shoestring.inArray( childs[ j ], ret ) === -1 ){ + ret.push( childs[ j ] ); + } + } + }); + return shoestring(ret); + }; + + + + /** + * Clone and return the current set of nodes into a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.clone = function() { + var ret = []; + + this.each(function() { + ret.push( this.cloneNode( true ) ); + }); + + return shoestring( ret ); + }; + + + + /** + * Find an element matching the selector in the set of the current element and its parents. + * + * @param {string} selector The selector used to identify the target element. + * @return shoestring + * @this shoestring + */ + shoestring.fn.closest = function( selector ){ + var ret = []; + + if( !selector ){ + return shoestring( ret ); + } + + this.each(function(){ + var element, $self = shoestring( element = this ); + + if( $self.is(selector) ){ + ret.push( this ); + return; + } + + while( element.parentElement ) { + if( shoestring(element.parentElement).is(selector) ){ + ret.push( element.parentElement ); + break; + } + + element = element.parentElement; + } + }); + + return shoestring( ret ); + }; + + + + shoestring.cssExceptions = { + 'float': [ 'cssFloat' ] + }; + + + + (function() { + var cssExceptions = shoestring.cssExceptions; + + // marginRight instead of margin-right + function convertPropertyName( str ) { + return str.replace( /\-([A-Za-z])/g, function ( match, character ) { + return character.toUpperCase(); + }); + } + + function _getStyle( element, property ) { + return win.getComputedStyle( element, null ).getPropertyValue( property ); + } + + var vendorPrefixes = [ '', '-webkit-', '-ms-', '-moz-', '-o-', '-khtml-' ]; + + /** + * Private function for getting the computed style of an element. + * + * **NOTE** Please use the [css](../css.js.html) method instead. + * + * @method _getStyle + * @param {HTMLElement} element The element we want the style property for. + * @param {string} property The css property we want the style for. + */ + shoestring._getStyle = function( element, property ) { + var convert, value, j, k; + + if( cssExceptions[ property ] ) { + for( j = 0, k = cssExceptions[ property ].length; j < k; j++ ) { + value = _getStyle( element, cssExceptions[ property ][ j ] ); + + if( value ) { + return value; + } + } + } + + for( j = 0, k = vendorPrefixes.length; j < k; j++ ) { + convert = convertPropertyName( vendorPrefixes[ j ] + property ); + + // VendorprefixKeyName || key-name + value = _getStyle( element, convert ); + + if( convert !== property ) { + value = value || _getStyle( element, property ); + } + + if( vendorPrefixes[ j ] ) { + // -vendorprefix-key-name + value = value || _getStyle( element, vendorPrefixes[ j ] + property ); + } + + if( value ) { + return value; + } + } + + return undefined; + }; + })(); + + + + (function() { + var cssExceptions = shoestring.cssExceptions; + + // marginRight instead of margin-right + function convertPropertyName( str ) { + return str.replace( /\-([A-Za-z])/g, function ( match, character ) { + return character.toUpperCase(); + }); + } + + /** + * Private function for setting the style of an element. + * + * **NOTE** Please use the [css](../css.js.html) method instead. + * + * @method _setStyle + * @param {HTMLElement} element The element we want to style. + * @param {string} property The property being used to style the element. + * @param {string} value The css value for the style property. + */ + shoestring._setStyle = function( element, property, value ) { + var convertedProperty = convertPropertyName(property); + + element.style[ property ] = value; + + if( convertedProperty !== property ) { + element.style[ convertedProperty ] = value; + } + + if( cssExceptions[ property ] ) { + for( var j = 0, k = cssExceptions[ property ].length; j -1 ){ + ret.push( this ); + } + } + }); + + return shoestring( ret ); + }; + + + + /** + * Find descendant elements of the current collection. + * + * @param {string} selector The selector used to find the children + * @return shoestring + * @this shoestring + */ + shoestring.fn.find = function( selector ){ + var ret = [], + finds; + this.each(function(){ + finds = this.querySelectorAll( selector ); + + for( var i = 0, il = finds.length; i < il; i++ ){ + ret = ret.concat( finds[i] ); + } + }); + return shoestring( ret ); + }; + + + + /** + * Returns the first element of the set wrapped in a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.first = function(){ + return this.eq( 0 ); + }; + + + + /** + * Returns the raw DOM node at the passed index. + * + * @param {integer} index The index of the element to wrap and return. + * @return {HTMLElement|undefined|array} + * @this shoestring + */ + shoestring.fn.get = function( index ){ + + // return an array of elements if index is undefined + if( index === undefined ){ + var elements = []; + + for( var i = 0; i < this.length; i++ ){ + elements.push( this[ i ] ); + } + + return elements; + } else { + return this[ index ]; + } + }; + + + + /** + * Private function for setting/getting the offset property for height/width. + * + * **NOTE** Please use the [width](width.js.html) or [height](height.js.html) methods instead. + * + * @param {shoestring} set The set of elements. + * @param {string} name The string "height" or "width". + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this window + */ + shoestring._dimension = function( set, name, value ){ + var offsetName; + + if( value === undefined ){ + offsetName = name.replace(/^[a-z]/, function( letter ) { + return letter.toUpperCase(); + }); + + return set[ 0 ][ "offset" + offsetName ]; + } else { + // support integer values as pixels + value = typeof value === "string" ? value : value + "px"; + + return set.each(function(){ + this.style[ name ] = value; + }); + } + }; + + + + /** + * Gets the height value of the first element or sets the height for the whole set. + * + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.height = function( value ){ + return shoestring._dimension( this, "height", value ); + }; + + + + var set = function( html ){ + if( typeof html === "string" || typeof html === "number" ){ + return this.each(function(){ + this.innerHTML = "" + html; + }); + } else { + var h = ""; + if( typeof html.length !== "undefined" ){ + for( var i = 0, l = html.length; i < l; i++ ){ + h += html[i].outerHTML; + } + } else { + h = html.outerHTML; + } + return this.each(function(){ + this.innerHTML = h; + }); + } + }; + /** + * Gets or sets the `innerHTML` from all the elements in the set. + * + * @param {string|undefined} html The html to assign + * @return {string|shoestring} + * @this shoestring + */ + shoestring.fn.html = function( html ){ + if( typeof html !== "undefined" ){ + return set.call( this, html ); + } else { // get + var pile = ""; + + this.each(function(){ + pile += this.innerHTML; + }); + + return pile; + } + }; + + + + (function() { + function _getIndex( set, test ) { + var i, result, element; + + for( i = result = 0; i < set.length; i++ ) { + element = set.item ? set.item(i) : set[i]; + + if( test(element) ){ + return result; + } + + // ignore text nodes, etc + // NOTE may need to be more permissive + if( element.nodeType === 1 ){ + result++; + } + } + + return -1; + } + + /** + * Find the index in the current set for the passed selector. + * Without a selector it returns the index of the first node within the array of its siblings. + * + * @param {string|undefined} selector The selector used to search for the index. + * @return {integer} + * @this {shoestring} + */ + shoestring.fn.index = function( selector ){ + var self, children; + + self = this; + + // no arg? check the children, otherwise check each element that matches + if( selector === undefined ){ + children = ( ( this[ 0 ] && this[0].parentNode ) || doc.documentElement).childNodes; + + // check if the element matches the first of the set + return _getIndex(children, function( element ) { + return self[0] === element; + }); + } else { + + // check if the element matches the first selected node from the parent + return _getIndex(self, function( element ) { + return element === (shoestring( selector, element.parentNode )[ 0 ]); + }); + } + }; + })(); + + + + /** + * Insert the current set after the elements matching the selector. + * + * @param {string} selector The selector after which to insert the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.insertAfter = function( selector ){ + return this.each(function(){ + shoestring( selector ).after( this ); + }); + }; + + + + /** + * Insert the current set before the elements matching the selector. + * + * @param {string} selector The selector before which to insert the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.insertBefore = function( selector ){ + return this.each(function(){ + shoestring( selector ).before( this ); + }); + }; + + + + /** + * Returns the last element of the set wrapped in a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.last = function(){ + return this.eq( this.length - 1 ); + }; + + + + /** + * Returns a `shoestring` object with the set of siblings of each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.next = function(){ + + var result = []; + + // TODO need to implement map + this.each(function() { + var children, item, found; + + // get the child nodes for this member of the set + children = shoestring( this.parentNode )[0].childNodes; + + for( var i = 0; i < children.length; i++ ){ + item = children.item( i ); + + // found the item we needed (found) which means current item value is + // the next node in the list, as long as it's viable grab it + // NOTE may need to be more permissive + if( found && item.nodeType === 1 ){ + result.push( item ); + break; + } + + // find the current item and mark it as found + if( item === this ){ + found = true; + } + } + }); + + return shoestring( result ); + }; + + + + /** + * Removes elements from the current set. + * + * @param {string} selector The selector to use when removing the elements. + * @return shoestring + * @this shoestring + */ + shoestring.fn.not = function( selector ){ + var ret = []; + + this.each(function(){ + var found = shoestring( selector, this.parentNode ); + + if( shoestring.inArray(this, found) === -1 ){ + ret.push( this ); + } + }); + + return shoestring( ret ); + }; + + + + /** + * Returns an object with the `top` and `left` properties corresponging to the first elements offsets. + * + * @return object + * @this shoestring + */ + shoestring.fn.offset = function(){ + return { + top: this[ 0 ].offsetTop, + left: this[ 0 ].offsetLeft + }; + }; + + + + /** + * Returns the set of first parents for each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.parent = function(){ + var ret = [], + parent; + + this.each(function(){ + // no parent node, assume top level + // jQuery parent: return the document object for or the parent node if it exists + parent = (this === doc.documentElement ? doc : this.parentNode); + + // if there is a parent and it's not a document fragment + if( parent && parent.nodeType !== 11 ){ + ret.push( parent ); + } + }); + + return shoestring(ret); + }; + + + + /** + * Returns the set of all parents matching the selector if provided for each element in the current set. + * + * @param {string} selector The selector to check the parents with. + * @return shoestring + * @this shoestring + */ + shoestring.fn.parents = function( selector ){ + var ret = []; + + this.each(function(){ + var curr = this, match; + + while( curr.parentElement && !match ){ + curr = curr.parentElement; + + if( selector ){ + if( curr === shoestring( selector )[0] ){ + match = true; + + if( shoestring.inArray( curr, ret ) === -1 ){ + ret.push( curr ); + } + } + } else { + if( shoestring.inArray( curr, ret ) === -1 ){ + ret.push( curr ); + } + } + } + }); + + return shoestring(ret); + }; + + + + /** + * Add an HTML string or element before the children of each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML string or element to add. + * @return shoestring + * @this shoestring + */ + shoestring.fn.prepend = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + return this.each(function( i ){ + + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + var insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ]; + if ( this.firstChild ){ + this.insertBefore( insertEl, this.firstChild ); + } else { + this.appendChild( insertEl ); + } + } + }); + }; + + + + /** + * Add each element of the current set before the children of the selected elements. + * + * @param {string} selector The selector for the elements to add the current set to.. + * @return shoestring + * @this shoestring + */ + shoestring.fn.prependTo = function( selector ){ + return this.each(function(){ + shoestring( selector ).prepend( this ); + }); + }; + + + + /** + * Returns a `shoestring` object with the set of *one* siblingx before each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.prev = function(){ + + var result = []; + + // TODO need to implement map + this.each(function() { + var children, item, found; + + // get the child nodes for this member of the set + children = shoestring( this.parentNode )[0].childNodes; + + for( var i = children.length -1; i >= 0; i-- ){ + item = children.item( i ); + + // found the item we needed (found) which means current item value is + // the next node in the list, as long as it's viable grab it + // NOTE may need to be more permissive + if( found && item.nodeType === 1 ){ + result.push( item ); + break; + } + + // find the current item and mark it as found + if( item === this ){ + found = true; + } + } + }); + + return shoestring( result ); + }; + + + + /** + * Returns a `shoestring` object with the set of *all* siblings before each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.prevAll = function(){ + + var result = []; + + this.each(function() { + var $previous = shoestring( this ).prev(); + + while( $previous.length ){ + result.push( $previous[0] ); + $previous = $previous.prev(); + } + }); + + return shoestring( result ); + }; + + + + // Property normalization, a subset taken from jQuery src + shoestring.propFix = { + "class": "className", + contenteditable: "contentEditable", + "for": "htmlFor", + readonly: "readOnly", + tabindex: "tabIndex" + }; + + + + /** + * Gets the property value from the first element or sets the property value on all elements of the currrent set. + * + * @param {string} name The property name. + * @param {any} value The property value. + * @return {any|shoestring} + * @this shoestring + */ + shoestring.fn.prop = function( name, value ){ + if( !this[0] ){ + return; + } + + name = shoestring.propFix[ name ] || name; + + if( value !== undefined ){ + return this.each(function(){ + this[ name ] = value; + }); + } else { + return this[ 0 ][ name ]; + } + }; + + + + /** + * Remove an attribute from each element in the current set. + * + * @param {string} name The name of the attribute. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeAttr = function( name ){ + return this.each(function(){ + this.removeAttribute( name ); + }); + }; + + + + /** + * Remove a class from each DOM element in the set of elements. + * + * @param {string} className The name of the class to be removed. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeClass = function( cname ){ + var classes = cname.replace(/^\s+|\s+$/g, '').split( " " ); + + return this.each(function(){ + var newClassName, regex; + + for( var i = 0, il = classes.length; i < il; i++ ){ + if( this.className !== undefined ){ + regex = new RegExp( "(^|\\s)" + classes[ i ] + "($|\\s)", "gmi" ); + newClassName = this.className.replace( regex, " " ); + + this.className = newClassName.replace(/^\s+|\s+$/g, ''); + } + } + }); + }; + + + + /** + * Remove the current set of elements from the DOM. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.remove = function(){ + return this.each(function(){ + if( this.parentNode ) { + this.parentNode.removeChild( this ); + } + }); + }; + + + + /** + * Remove a proprety from each element in the current set. + * + * @param {string} name The name of the property. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeProp = function( property ){ + var name = shoestring.propFix[ property ] || property; + + return this.each(function(){ + this[ name ] = undefined; + delete this[ name ]; + }); + }; + + + + /** + * Replace each element in the current set with that argument HTML string or HTMLElement. + * + * @param {string|HTMLElement} fragment The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.replaceWith = function( fragment ){ + if( typeof( fragment ) === "string" ){ + fragment = shoestring( fragment ); + } + + var ret = []; + + if( fragment.length > 1 ){ + fragment = fragment.reverse(); + } + this.each(function( i ){ + var clone = this.cloneNode( true ), + insertEl; + ret.push( clone ); + + // If there is no parentNode, this is pointless, drop it. + if( !this.parentNode ){ return; } + + if( fragment.length === 1 ){ + insertEl = i > 0 ? fragment[ 0 ].cloneNode( true ) : fragment[ 0 ]; + this.parentNode.replaceChild( insertEl, this ); + } else { + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ]; + this.parentNode.insertBefore( insertEl, this.nextSibling ); + } + this.parentNode.removeChild( this ); + } + }); + + return shoestring( ret ); + }; + + + + shoestring.inputTypes = [ + "text", + "hidden", + "password", + "color", + "date", + "datetime", + // "datetime\-local" matched by datetime + "email", + "month", + "number", + "range", + "search", + "tel", + "time", + "url", + "week" + ]; + + shoestring.inputTypeTest = new RegExp( shoestring.inputTypes.join( "|" ) ); + + + /** + * Serialize child input element values into an object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.serialize = function(){ + var data = {}; + + shoestring( "input, select", this ).each(function(){ + var type = this.type, name = this.name, value = this.value; + + if( shoestring.inputTypeTest.test( type ) || + ( type === "checkbox" || type === "radio" ) && + this.checked ){ + + data[ name ] = value; + } else if( this.nodeName === "SELECT" ){ + data[ name ] = this.options[ this.selectedIndex ].nodeValue; + } + }); + + return data; + }; + + + + /** + * Get all of the sibling elements for each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.siblings = function(){ + + if( !this.length ) { + return shoestring( [] ); + } + + var sibs = [], el = this[ 0 ].parentNode.firstChild; + + do { + if( el.nodeType === 1 && el !== this[ 0 ] ) { + sibs.push( el ); + } + + el = el.nextSibling; + } while( el ); + + return shoestring( sibs ); + }; + + + + var getText = function( elem ){ + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; + }; + + /** + * Recursively retrieve the text content of the each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.text = function() { + + return getText( this ); + }; + + + + + /** + * Get the value of the first element or set the value of all elements in the current set. + * + * @param {string} value The value to set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.val = function( value ){ + var el; + if( value !== undefined ){ + return this.each(function(){ + if( this.tagName === "SELECT" ){ + var optionSet, option, + options = this.options, + values = [], + i = options.length, + newIndex; + + values[0] = value; + while ( i-- ) { + option = options[ i ]; + if ( (option.selected = shoestring.inArray( option.value, values ) >= 0) ) { + optionSet = true; + newIndex = i; + } + } + // force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + this.selectedIndex = -1; + } else { + this.selectedIndex = newIndex; + } + } else { + this.value = value; + } + }); + } else { + el = this[0]; + + if( el.tagName === "SELECT" ){ + if( el.selectedIndex < 0 ){ return ""; } + return el.options[ el.selectedIndex ].value; + } else { + return el.value; + } + } + }; + + + + /** + * Gets the width value of the first element or sets the width for the whole set. + * + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.width = function( value ){ + return shoestring._dimension( this, "width", value ); + }; + + + + /** + * Wraps the child elements in the provided HTML. + * + * @param {string} html The wrapping HTML. + * @return shoestring + * @this shoestring + */ + shoestring.fn.wrapInner = function( html ){ + return this.each(function(){ + var inH = this.innerHTML; + + this.innerHTML = ""; + shoestring( this ).append( shoestring( html ).html( inH ) ); + }); + }; + + + + function initEventCache( el, evt ) { + if ( !el.shoestringData ) { + el.shoestringData = {}; + } + if ( !el.shoestringData.events ) { + el.shoestringData.events = {}; + } + if ( !el.shoestringData.loop ) { + el.shoestringData.loop = {}; + } + if ( !el.shoestringData.events[ evt ] ) { + el.shoestringData.events[ evt ] = []; + } + } + + function addToEventCache( el, evt, eventInfo ) { + var obj = {}; + obj.isCustomEvent = eventInfo.isCustomEvent; + obj.callback = eventInfo.callfunc; + obj.originalCallback = eventInfo.originalCallback; + obj.namespace = eventInfo.namespace; + + el.shoestringData.events[ evt ].push( obj ); + + if( eventInfo.customEventLoop ) { + el.shoestringData.loop[ evt ] = eventInfo.customEventLoop; + } + } + + /** + * Bind a callback to an event for the currrent set of elements. + * + * @param {string} evt The event(s) to watch for. + * @param {object,function} data Data to be included with each event or the callback. + * @param {function} originalCallback Callback to be invoked when data is define.d. + * @return shoestring + * @this shoestring + */ + shoestring.fn.bind = function( evt, data, originalCallback ){ + + if( typeof data === "function" ){ + originalCallback = data; + data = null; + } + + var evts = evt.split( " " ); + + // NOTE the `triggeredElement` is purely for custom events from IE + function encasedCallback( e, namespace, triggeredElement ){ + var result; + + if( e._namespace && e._namespace !== namespace ) { + return; + } + + e.data = data; + e.namespace = e._namespace; + + var returnTrue = function(){ + return true; + }; + + e.isDefaultPrevented = function(){ + return false; + }; + + var originalPreventDefault = e.preventDefault; + var preventDefaultConstructor = function(){ + if( originalPreventDefault ) { + return function(){ + e.isDefaultPrevented = returnTrue; + originalPreventDefault.call(e); + }; + } else { + return function(){ + e.isDefaultPrevented = returnTrue; + e.returnValue = false; + }; + } + }; + + // thanks https://github.com/jonathantneal/EventListener + e.target = triggeredElement || e.target || e.srcElement; + e.preventDefault = preventDefaultConstructor(); + e.stopPropagation = e.stopPropagation || function () { + e.cancelBubble = true; + }; + + result = originalCallback.apply(this, [ e ].concat( e._args ) ); + + if( result === false ){ + e.preventDefault(); + e.stopPropagation(); + } + + return result; + } + + return this.each(function(){ + var domEventCallback, + customEventCallback, + customEventLoop, + oEl = this; + + for( var i = 0, il = evts.length; i < il; i++ ){ + var split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + domEventCallback = function( originalEvent ) { + if( oEl.ssEventTrigger ) { + originalEvent._namespace = oEl.ssEventTrigger._namespace; + originalEvent._args = oEl.ssEventTrigger._args; + + oEl.ssEventTrigger = null; + } + return encasedCallback.call( oEl, originalEvent, namespace ); + }; + customEventCallback = null; + customEventLoop = null; + + initEventCache( this, evt ); + + this.addEventListener( evt, domEventCallback, false ); + + addToEventCache( this, evt, { + callfunc: customEventCallback || domEventCallback, + isCustomEvent: !!customEventCallback, + customEventLoop: customEventLoop, + originalCallback: originalCallback, + namespace: namespace + }); + } + }); + }; + + shoestring.fn.on = shoestring.fn.bind; + + + + + /** + * Unbind a previous bound callback for an event. + * + * @param {string} event The event(s) the callback was bound to.. + * @param {function} callback Callback to unbind. + * @return shoestring + * @this shoestring + */ + shoestring.fn.unbind = function( event, callback ){ + + + var evts = event ? event.split( " " ) : []; + + return this.each(function(){ + if( !this.shoestringData || !this.shoestringData.events ) { + return; + } + + if( !evts.length ) { + unbindAll.call( this ); + } else { + var split, evt, namespace; + for( var i = 0, il = evts.length; i < il; i++ ){ + split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + if( evt ) { + unbind.call( this, evt, namespace, callback ); + } else { + unbindAll.call( this, namespace, callback ); + } + } + } + }); + }; + + function unbind( evt, namespace, callback ) { + var bound = this.shoestringData.events[ evt ]; + if( !(bound && bound.length) ) { + return; + } + + var matched = [], j, jl; + for( j = 0, jl = bound.length; j < jl; j++ ) { + if( !namespace || namespace === bound[ j ].namespace ) { + if( callback === undefined || callback === bound[ j ].originalCallback ) { + this.removeEventListener( evt, bound[ j ].callback, false ); + matched.push( j ); + } + } + } + + for( j = 0, jl = matched.length; j < jl; j++ ) { + this.shoestringData.events[ evt ].splice( j, 1 ); + } + } + + function unbindAll( namespace, callback ) { + for( var evtKey in this.shoestringData.events ) { + unbind.call( this, evtKey, namespace, callback ); + } + } + + shoestring.fn.off = shoestring.fn.unbind; + + + /** + * Bind a callback to an event for the currrent set of elements, unbind after one occurence. + * + * @param {string} event The event(s) to watch for. + * @param {function} callback Callback to invoke on the event. + * @return shoestring + * @this shoestring + */ + shoestring.fn.one = function( event, callback ){ + var evts = event.split( " " ); + + return this.each(function(){ + var thisevt, cbs = {}, $t = shoestring( this ); + + for( var i = 0, il = evts.length; i < il; i++ ){ + thisevt = evts[ i ]; + + cbs[ thisevt ] = function( e ){ + var $t = shoestring( this ); + + for( var j in cbs ) { + $t.unbind( j, cbs[ j ] ); + } + + return callback.apply( this, [ e ].concat( e._args ) ); + }; + + $t.bind( thisevt, cbs[ thisevt ] ); + } + }); + }; + + + + /** + * Trigger an event on the first element in the set, no bubbling, no defaults. + * + * @param {string} event The event(s) to trigger. + * @param {object} args Arguments to append to callback invocations. + * @return shoestring + * @this shoestring + */ + shoestring.fn.triggerHandler = function( event, args ){ + var e = event.split( " " )[ 0 ], + el = this[ 0 ], + ret; + + // See this.fireEvent( 'on' + evts[ i ], document.createEventObject() ); instead of click() etc in trigger. + if( doc.createEvent && el.shoestringData && el.shoestringData.events && el.shoestringData.events[ e ] ){ + var bindings = el.shoestringData.events[ e ]; + for (var i in bindings ){ + if( bindings.hasOwnProperty( i ) ){ + event = doc.createEvent( "Event" ); + event.initEvent( e, true, true ); + event._args = args; + args.unshift( event ); + + ret = bindings[ i ].originalCallback.apply( event.target, args ); + } + } + } + + return ret; + }; + + + + /** + * Trigger an event on each of the DOM elements in the current set. + * + * @param {string} event The event(s) to trigger. + * @param {object} args Arguments to append to callback invocations. + * @return shoestring + * @this shoestring + */ + shoestring.fn.trigger = function( event, args ){ + var evts = event.split( " " ); + + return this.each(function(){ + var split, evt, namespace; + for( var i = 0, il = evts.length; i < il; i++ ){ + split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + if( evt === "click" ){ + if( this.tagName === "INPUT" && this.type === "checkbox" && this.click ){ + this.click(); + return false; + } + } + + if( doc.createEvent ){ + var event = doc.createEvent( "Event" ); + event.initEvent( evt, true, true ); + event._args = args; + event._namespace = namespace; + + this.dispatchEvent( event ); + } + } + }); + }; + + + + return shoestring; +})); + +if( !window.jQuery ){ + window.jQuery = window.jQuery || window.shoestring; +} + +(function(exports){ + /** + * The Akamai component namespace contains the {@link Akamai.Viewer}, {@link + * Akamai.Spin360}, {@link Akamai.Carousel}, and {@link Akamai.Zoom} components. + * The {@link Akamai.Viewer} component manages the other components depending on + * the state of the markup it is provided, start there for more. + * + * @example On-ready automatic initialization with jQuery + * $(function(){ + * $( "[data-akamai-viewer]" ).each(function(i, element){ + * var viewer = Akamai.Viewer( element ); + * ... + * // example API use, stoping autoration of first {@link Akamai.Spin360} + * viewer.getCarousels()[0].getSpin360s()[0].stopSpin(); + * }) + * }) + * + * @namespace Akamai + */ + exports.Akamai = exports.Akamai || {}; + window.componentNamespace = "Akamai"; +})(typeof exports === 'undefined'? window : exports); + +(function( window, $ ) { + var $window, $doc; + + $window = $(window); + $doc = $( document.documentElement ); + + var ns = window.componentNamespace = window.componentNamespace || "FG"; + window[ns] = window[ns] || {}; + + Function.prototype.bind = Function.prototype.bind || function( context ) { + var self = this; + + return function() { + self.apply( context, arguments ); + }; + }; + + var Tau = window[ns].Tau = function( element, options ) { + var startIndex, reducedStepSize; + + this.element = element; + this.options = options || {}; + this.$element = $( element ); + this.$initial = this.$element.find( "img" ); + this.$loading = this.$element.find( ".loading" ); + this.index = 0; + + // frame count by order of precendence + // 1. initial frames when they are specified explicitly + // 2. the data attribute on the initial image + // 3. the configured number of frames + this.frames = + this.$initial.length > 1 ? this.$initial.length : + parseInt( this.$initial.attr("data-frames"), 10 ) || + this.options.frames; + + // grab the user specified step size for when the browser is less-abled + reducedStepSize = parseInt( this.$initial.attr("data-reduced-step-size"), 10 ) || 4; + + // TODO sort out a better qualification for the full set of images? + this.stepSize = window.requestAnimationFrame ? 1 : reducedStepSize; + + // grab the user specified auto start delay + this.autoRotateStartDelay = + (this.options.autoplay || {}).delay || + parseInt( this.$initial.attr("data-auto-rotate-delay"), 10 ) || + Tau.autoRotateStartDelay; + + this.mouseMoveBinding = this.rotateEvent.bind(this); + this.touchMoveBinding = this.rotateEvent.bind(this); + + this.path = new Tau.Path(); + + // make sure the initial image stays visible after enhance + this.$initial.first().addClass( "focused" ); + + // hide all other images + this.$element.addClass( "tau-enhanced" ); + + // create a rendering spot to force decoding in IE and prevent blinking + this.$render = $( "
" ) + .css( "position", "absolute" ) + .css( "left", "0" ) + .css( "top", "0" ) + .prependTo( this.element ); + + if( this.options.canvas !== false ){ + this.canvas = $( "").prependTo( this.element )[0]; + + if(this.canvas.getContext ){ + this.canvasCtx = this.canvas.getContext("2d"); + this.$element.addClass( "tau-canvas" ); + + $(window).bind("resize", function(){ + clearTimeout(this.canvasResizeTimeout); + this.canvasResizeTimeout = setTimeout(this.renderCanvas.bind(this), 100); + }.bind(this)); + } + } + + if( this.options.controls ){ + this.options.controls.text = this.options.controls.text || { + play: "Spin Object", + left: "Rotate Left", + right: "Rotate Right" + }; + + this.createControls(); + } + + // create the rest of the images + this.createImages(); + + // set the initial index and image + if( this.options.autoplay && this.options.autoplay.enabled ){ + // start the automatic rotation + this.autostartTimeout = setTimeout(this.autoRotate.bind(this), this.autoRotateStartDelay); + } + + // setup the event bindings for touch drag and mouse drag rotation + this.bind(); + }; + + Tau.autoRotateTraversalTime = 4500; + Tau.autoRotateStartDelay = 100; + Tau.verticalScrollRatio = 4; + // Tau.decelTimeStep = Tau.autoRotateDelay / 2; + // Tau.decel = Tau.decelTimeStep / 8; + Tau.maxVelocity = 60; + + Tau.prototype.createControls = function(){ + this.$controls = $("
"); + + if(this.options.controls.play){ + this.$controls.append(this.controlAnchorMarkup("play")); + } + + if(this.options.controls.arrows){ + this.$controls + .prepend(this.controlAnchorMarkup("left")) + .append(this.controlAnchorMarkup("right")); + } + + this.$controls.bind("mousedown touchstart", this.onControlDown.bind(this)); + this.$controls.bind("mouseup", this.onControlUp.bind(this)); + + // prevent link clicks from bubbling + this.$controls.bind("click", function(event){ + if( $(event.target).is("a") ){ + event.preventDefault(); + } + }); + + this.$element.append(this.$controls); + }; + + Tau.prototype.controlAnchorMarkup = function(name){ + var text = this.options.controls.text[name]; + + return "" + text + + ""; + }; + + Tau.prototype.onControlDown = function(event){ + var $link = $(event.target).closest("a"); + + switch($link.attr("data-tau-controls")){ + case "left": + this.$element.addClass("control-left-down"); + this.stopAutoRotate(); + this.autoRotate(); + break; + case "right": + this.$element.addClass("control-right-down"); + this.stopAutoRotate(); + this.autoRotate(true); + break; + } + }; + + Tau.prototype.onControlUp = function(event){ + var $link = $(event.target).closest("a"); + + switch($link.attr("data-tau-controls")){ + case "left": + case "right": + this.$element.removeClass("control-left-down"); + this.$element.removeClass("control-right-down"); + this.stopAutoRotate(); + break; + case "play": + if( this.autoInterval ){ + this.stopAutoRotate(); + } else { + this.autoRotate(); + } + break; + } + }; + + Tau.prototype.change = function( delta ) { + this.goto( this.options.reverse ? this.index - delta : this.index + delta ); + }; + + Tau.prototype.goto = function( index ) { + var $next, normalizedIndex, imageCount = this.$images.length; + + index = index % imageCount; + + // stay within the bounds of the array + normalizedIndex = (imageCount + index) % imageCount; + + // set the next image that's going to be shown/focused + $next = this.$images.eq( normalizedIndex ); + + // skip this action if the desired image isn't loaded yet + // TODO do something fancier here instead of just throwing up hands + if( !$next[0].tauImageLoaded ) { + this.showLoading(); + return false; + } + + // hide any image that happens to be visible (initial image when canvas) + if( this.$current ) { + this.$current.removeClass( "focused" ); + } else { + this.$images.removeClass( "focused" ); + } + + // record the current focused image and make it visible + this.$current = $next; + + // record the updated index only after advancing is possible + this.index = normalizedIndex; + + if( this.canvasCtx ) { + return this.renderCanvas(); + } else { + // show the new focused image + this.$current.addClass( "focused" ); + return true; + } + }; + + Tau.prototype.renderCanvas = function() { + var $img = this.$current; + var img = $img[0]; + + var width = img.naturalWidth; + var height = img.naturalHeight; + + var parentWidth = this.element.clientWidth; + var calcHeight = (parentWidth/width) * height; + + if(!width || !height || !img.complete){ + return false; + } + + if( this.canvas.width !== parentWidth || + this.canvas.height !== calcHeight || (parentWidth && calcHeight) ) { + this.canvas.width = parentWidth; + this.canvas.height = calcHeight; + } + + this.canvasCtx.drawImage(img, 0, 0, parentWidth, calcHeight); + + return true; + }; + + // TODO transplant the attributes from the initial image + Tau.prototype.createImages = function() { + var src, frames, html, $new, boundImageLoaded; + + // if there are no image elements, raise an exception + if( this.$initial.length < 1 ){ + throw new Error( "At least one image required" ); + } + + this.loadedCount = 0; + + // if there is only one image element, assume it's a template + if( this.$initial.length == 1 ) { + this.markImageLoaded( this.$initial[0] ); + + src = + this.options.template || + this.$initial.attr( "data-src-template" ); + + var imgs = []; + for( var i = this.stepSize + 1; i <= this.frames; i+= this.stepSize ) { + html = ""; + $new = $( html ); + imgs.push($new); + } + + $.each(imgs, function(i, e){ + var $img = $(e); + + $img.bind("load error", function(e){ this.imageLoaded(i, e.target, e); }.bind(this)); + + this.$element.append( $img ); + this.$render.append( $img.html() ); + }.bind(this)); + + // take all the child images and use them as frames of the rotation + this.$images = this.$element.children().filter( "img" ); + this.$current = this.$images; + this.goto(0); + } else { + // take all the child images and use them as frames of the rotation + this.$images = this.$element.children().filter( "img" ); + + this.$images.each(function(i, e){ + // if the image height is greater than zero we assume the image is loaded + // otherwise we bind to onload and pray that we win the race + if( $(e).height() > 0 ){ + this.imageLoaded( i, e ); + } else { + $(e).bind("load error", function(event){ + this.imageLoaded( i, event.target, event ); + }.bind(this)); + } + }.bind(this)); + } + }; + + + Tau.prototype.imageLoaded = function( index, element, event ) { + var initTriggered = false; + this.markImageLoaded( element ); + + // if the isn't going to play automatically and the first image is + // loaded make sure to render it + if( this.$element.find("img")[0] == element && + (!event || event.type !== "error") && + (!this.options.autoplay || !this.options.autoplay.enabled) ){ + this.goto(0); + this.$element.trigger("tau.init"); + initTriggered = true; + } + + this.loadedCount++; + + if( this.loadedCount >= this.frames - 1) { + this.hideLoading(); + + if(!initTriggered) { + this.$element.trigger("tau.init"); + this.initialized = true; + } + } + }; + + Tau.prototype.markImageLoaded = function( element ) { + element.tauImageLoaded = true; + }; + + Tau.prototype.bind = function() { + this.$element.bind( "mousedown touchstart", this.track.bind(this) ); + }; + + Tau.prototype.autoRotate = function( right ) { + // already rotating + if( this.autoInterval ) { + return; + } + + this.$element.addClass("spinning"); + + // move once initially + this.change( right ? -1 : 1 ); + + // move after the interval + this.autoInterval = setInterval(function() { + this.change( right ? -1 : 1 ); + }.bind(this), this.autoRotateDelay() * this.stepSize); + + this.$element.trigger( "tau.auto-rotate-start" ); + }; + + Tau.prototype.autoRotateDelay = function(){ + return (this.options.interval || Tau.autoRotateTraversalTime) / this.frames; + }; + + Tau.prototype.stopAutoRotate = function() { + clearInterval( this.autoInterval ); + clearInterval( this.autostartTimeout ); + this.$element.removeClass("spinning"); + this.autoInterval = undefined; + this.$element.trigger( "tau.auto-rotate-stop" ); + }; + + Tau.prototype.track = function( event ) { + var point; + + // ignore tracking on control clicks + if( $(event.target).closest(".tau-controls").length ){ + return; + } + + // prevent dragging behavior for mousedown + if( event.type === "mousedown" ){ + event.preventDefault(); + } + + if( event.type === "touchstart" ) { + this.$element.trigger("tau.touch-tracking-start"); + } else { + this.$element.trigger("tau.mouse-tracking-start"); + } + + if( this.tracking ) { + return; + } + + $doc.one( "mouseup", this.release.bind(this) ); + $doc.one( "touchend", this.release.bind(this) ); + + this.tracking = true; + + // clean out the path since we'll need a new one for decel + this.path.reset(); + + // show the cursor as grabbing + this.cursorGrab(); + + // By default the number of pixels required to move the carousel by one + // frame is the ratio of the tau element width to the number of frames. That + // is, by default the user should be able to see the full rotation by moving + // their input device from one side of the tau element to the other. + var defaultThreshold = this.$element[0].clientWidth / this.frames ; + + // divide the default by the sensitivity. If the senstivity is greater than + // 1 it will require less effort (smaller distance) to advance the rotation + // by a single slide. If the sensitivity is less than 1 it will require more + // effort + this.rotateThreshold = defaultThreshold / (this.options.sensitivity || 1); + + // record the x for threshold calculations + point = this.getPoint( event ); + this.downX = point.x; + this.downY = point.y; + this.downIndex = this.index; + + $doc.bind( "mousemove", this.mouseMoveBinding ); + $doc.bind( "touchmove", this.touchMoveBinding ); + }; + + Tau.prototype.slow = function() { + // if the path gets broken during the decel just stop + if( !this.path.isSufficient() ) { + this.clearSlowInterval(); + return; + } + + this.rotate({ + x: this.path.last().x + this.velocity, + y: this.path.last().y + }); + + if( this.velocity > 0 ){ + this.velocity = this.velocity - this.decelVal(); + + if( this.velocity <= 0 ){ + this.clearSlowInterval(); + } + } else { + this.velocity = this.velocity + this.decelVal(); + + if( this.velocity >= 0 ){ + this.clearSlowInterval(); + } + } + }; + + Tau.prototype.decelVal = function(){ + return this.decelTimeStep() / 8; + }; + + Tau.prototype.clearSlowInterval = function() { + clearInterval(this.slowInterval); + this.velocity = 0; + this.slowInterval = undefined; + }; + + Tau.prototype.decel = function() { + var velocity, sign; + + // if we don't have two points of mouse or touch tracking this won't work + if( !this.path.isSufficient() ) { + return; + } + + // determine the starting velocity based on the traced path + velocity = this.path.velocity( this.decelTimeStep() ); + + // borrowed from http://stackoverflow.com/questions/7624920/number-sign-in-javascript + sign = velocity > 0 ? 1 : velocity < 0 ? -1 : 0; + + // keep a lid on how fast the rotation spins out + if( Math.abs(velocity) > Tau.maxVelocity ){ + velocity = sign * Tau.maxVelocity; + } + + this.velocity = velocity; + this.slowInterval = setInterval(this.slow.bind(this), this.decelTimeStep()); + }; + + Tau.prototype.decelTimeStep = function(){ + return this.autoRotateDelay() / 2; + }; + + Tau.prototype.release = function( event ) { + if( $(event.target).closest(".tau-controls").length ){ + return; + } + + if( !this.tracking ){ + return; + } + + if( event.type === "touchend" ) { + this.$element.trigger("tau.touch-tracking-stop"); + } else { + this.$element.trigger("tau.mouse-tracking-stop"); + } + + this.decel(); + + this.cursorRelease(); + + // TODO sort out why shoestring borks when unbinding with a string split list + $doc.unbind( "mousemove", this.mouseMoveBinding ); + $doc.unbind( "touchmove", this.touchMoveBinding ); + + this.tracking = false; + }; + + Tau.prototype.cursorGrab = function() { + $doc.addClass( "grabbing" ); + }; + + Tau.prototype.cursorRelease = function() { + $doc.removeClass( "grabbing" ); + }; + + Tau.prototype.showLoading = function() { + this.$loading.attr( "style" , "display: block" ); + }; + + Tau.prototype.hideLoading = function() { + this.$loading.attr( "style" , "display: none" ); + }; + + Tau.prototype.getPoint = function( event ) { + var touch = event.touches || (event.originalEvent && event.originalEvent.touches); + + if( touch ){ + return { + x: touch[0].pageX, + y: touch[0].pageY + }; + } + + return { + x: event.pageX || event.clientX, + y: event.pageY || event.clientY + }; + }; + + Tau.prototype.rotateEvent = function( event ) { + // NOTE it might be better to prevent when the rotation returns anything BUT false + // so that slow drags still get the scroll prevented + if( this.rotate(this.getPoint(event)) ){ + event.preventDefault(); + } + }; + + Tau.prototype.rotate = function( point ) { + var deltaX, deltaY; + + deltaX = point.x - this.downX; + deltaY = point.y - this.downY; + + // if the movement on the Y dominates X then skip and allow scroll + if( Math.abs(deltaY) / Math.abs(deltaX) >= Tau.verticalScrollRatio ) { + return false; + } + + // since we're rotating record the point for decel + this.path.record( point ); + + // NOTE to reverse the spin direction add the delta/thresh to the downIndex + if( Math.abs(deltaX) >= this.rotateThreshold ) { + // NOTE works better on mousedown, here allows autorotate to continue + this.stopAutoRotate(); + + var index; + + if( this.options.reverse ) { + index = this.downIndex + Math.round(deltaX / this.rotateThreshold); + } else { + index = this.downIndex - Math.round(deltaX / this.rotateThreshold); + } + + this.goto( index ); + + return true; + } + }; +})(this, jQuery); + +(function( window, $ ) { + var ns = window.componentNamespace = window.componentNamespace || "FG"; + window[ns] = window[ns] || {}; + + // IE 8 + Date.now = Date.now || function now() { + return new Date().getTime(); + }; + + var Path = window[ns].Tau.Path = function() { + this.reset(); + }; + + Path.prototype.isSufficient = function() { + return !!this.prevPoint && this.prevPrevPoint; + }; + + Path.prototype.distance = function() { + return this.prevPoint.x - this.prevPrevPoint.x; + }; + + Path.prototype.duration = function() { + return this.prevTime - this.prevPrevTime; + }; + + // TODO sort out variable names + Path.prototype.record = function( point ) { + this.prevPrevTime = this.prevTime; + this.prevPrevPoint = this.prevPoint; + + // record the most recent drag point for decel on release + this.prevTime = Date.now(); + this.prevPoint = point; + }; + + Path.prototype.velocity = function( timeStep ) { + var distance, time; + + distance = this.distance(); + time = this.duration(); + + return distance / ( time / timeStep ); + }; + + Path.prototype.reset = function() { + this.prevPoint = undefined; + this.prevTime = undefined; + + this.prevPrevTime = undefined; + this.prevPrevPoint = undefined; + }; + + Path.prototype.last = function() { + return this.prevPoint; + }; +})(this, jQuery); + +(function(exports, $){ + exports.Akamai = exports.Akamai || {}; + var Util = exports.Akamai.Util = {}; + + Util.preflight = function(context, element, options, name){ + name = name || context.constructor.name; + + context._$el = $(element); + + if( !context._$el.length ){ + throw new Error( name + " component requires element" ); + } + + // If the element has already been instantiated with this component, skip + if( context._$el.data(name) ) { + return context._$el.data(name); + } + + // Store the instance for access and to prevent double init above + context._$el.data( name, context ); + + // make sure the element has the data attribute for CSS that keys off it + context._$el.attr(context.constructor._dataAttr, true); + + context._options = + Util.options(context.constructor.defaultOptions || {}, options || {}, context._$el, name); + + // retain the options decided at instantiation for reseting the options + // when the breakpoint options don't apply (are "backed out") + context._originalOptions = Util.extend(true, {}, context._options); + + // map all the child component events to the wrapper component events + Util.mapEvents(context); + + return false; + }; + + Util.component = function(name, async, init){ + if( !init ){ + init = async; + async = false; + } + + var constr = function(element, options){ + // do the preflight + var existing = Util.preflight(this, element, options, name); + + // return on double init + if( existing ){ + this._trigger("init"); + return existing; + } + + // call the init code + init.apply(this, arguments); + + // trigger the init event + if( !async ) { this._trigger("init"); } + }; + + constr.name = name; + + constr._dataAttr = `data-akamai-${name.toLowerCase()}`; + + // static constructor for many possible objects + constr.createMany = Util.createMany(constr); + + // component prefixed event trigger + constr.prototype._trigger = Util.trigger(name); + + return constr; + }; + + Util.mapEvents = function(context){ + var mapping = context.constructor._componentEventMapping || {}; + + for( var event in mapping) { + if( mapping.hasOwnProperty(event) ){ + var mappedTo = mapping[event].to || mapping[event]; + var $el = + mapping[event].selector ? context._$el.find(mapping[event].selector) : undefined; + + Util.mapEvent(context, event, mappedTo, $el); + } + } + }; + + Util.mapEvent = function(context, from, to, $el){ + ($el || context._$el).bind(from, function(){ + context._trigger(to); + }.bind(context)); + }; + + Util.createMany = function(constructor, selector){ + selector = selector || `[${constructor._dataAttr}]`; + + return function(element, options){ + var $comps = $(element).find( selector ); + + // sigh, shoestring needs map + var objects = []; + + $comps.each(function(i, el){ + objects.push(new constructor(el, options)); + }); + + return objects; + }; + }; + + /** + * Helper for building assertions into methods based on property requirements + * @private + * @static + * @param {prop} prop - the property required for the function to operate correctly + * @param {fn} function - the function definition + */ + Util.propRequired = function(prop, msg, fn){ + return function(){ + var args = arguments; + + if( !this[prop] ){ + throw new Error( msg ); + } + + return fn.apply(this, args); + }; + }; + + Util.trigger = function(componentName){ + componentName = componentName.toLowerCase(); + return function(event) { + var fullEvent = "akamai-" + componentName + "-" + event; + + // NOTE we assume here that if jQuery is present we will only want to trigger + // with jQuery and that we are using the same DOM lib across the library + this._$el.trigger(fullEvent); + }; + }; + + + Util.camelToKabob = function(string){ + return string.replace(/([A-Z]+)/g, function(m){ + return "-" + m.toLowerCase(); + }); + }; + + Util.options = function(defaults, options, $element, name){ + var dataAttrOptions = {}; + // make sure to make a new copy of the defaults so we don't mess with the original + var cloneDefaults = Util.extend(true, {}, defaults); + + if( $element && name ) { + dataAttrOptions = + Util.getDataAttrOptions($element, defaults, "data-akamai-" + name.toLowerCase()); + } + + // use the following precedence, prefering settings further to the right + // defaults -> options -> datattributes + var finalOptions = + Util.extend(true, Util.extend(true, cloneDefaults, dataAttrOptions), options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + if( $element && name ){ + Util.setDataAttrOptions($element, defaults, finalOptions, name); + } + + return finalOptions; + }; + + Util.getDataAttrOptions = function($element, defaults, prefix){ + var dataAttrOptions = {}; + + for( var prop in defaults ) { + if( defaults.hasOwnProperty(prop) ){ + var dashedProp = Util.camelToKabob(prop); + var currentDataProp = prefix + "-" + dashedProp; + + if( /object/.test(typeof defaults[prop]) && !(defaults[prop] instanceof Array)){ + var subDefaults = defaults[prop]; + var newPrefix = currentDataProp; + + var subConfig = Util.getDataAttrOptions($element, subDefaults, newPrefix); + dataAttrOptions[prop] = subConfig; + } else { + var value = $element.attr(currentDataProp); + value = Util.coerceAttrVal(value); + + if( value !== undefined && value !== null ){ + // set the config + dataAttrOptions[prop] = value; + } + } + } + } + + return dataAttrOptions; + }; + + Util.setDataAttrOptions = function($element, defaults, options, name){ + Util.setDataAttrOptionsPrefix($element, defaults, options, "data-akamai-" + name.toLowerCase()); + }; + + + Util.setDataAttrOptionsPrefix = function($element, defaults, options, prefix){ + defaults = defaults || {}; + + for( var prop in options ) { + if( options.hasOwnProperty(prop) && prop !== "breakpoints" ){ + var dashedProp = Util.camelToKabob(prop); + var currentDataProp = prefix + "-" + dashedProp; + + if( /object/.test(typeof options[prop]) ){ + var subOptions = options[prop]; + var newPrefix = currentDataProp; + + Util.setDataAttrOptionsPrefix($element, defaults[prop], subOptions, newPrefix); + } else { + + // if the $element had the attribute already or the property exists + // as one of the defaults then we can write it to the element. + if( $element.attr(currentDataProp) || defaults[prop] !== undefined ) { + $element.attr(currentDataProp, options[prop]); + } + } + } + } + }; + + Util.coerceAttrVal = function(value){ + if( value === "true" ) { return true; } + if( value === "false" ) { return false; } + if( /^[0-9]+$/.test(value) ) { return parseInt(value, 10); } + if( /^[0-9]+.[0-9]+$/.test(value) ) { return parseFloat(value, 10); } + + return value; + }; + + // Due to jQuery, but with less argument handling + Util.extend = function( deep, target, options ) { + var name, src, copy, copyIsArray, clone; + + // Only deal with non-null/undefined values + if ( options != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && Util.isPlainObject( copy ) ) { + + clone = src && Util.isPlainObject( src ) ? src : {}; + + // Never move original objects, clone them + target[ name ] = Util.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + + // Return the modified object + return target; + }; + + // Due to jQuery + Util.isPlainObject = function( obj ) { + var proto, Ctor; + + // taken from https://github.com/jupiter/simple-mock/pull/3/files + if ( !obj || Object.prototype.toString.call(obj) !== "[object Object]" ) { + return false; + } + + return true; + }; + + // Due to jQuery + var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + Util.trim = function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }; + + Util.map = function(array, fn){ + return Util.reduce(array, function(acc, val, i){ + acc.push(fn(val, i)); + return acc; + }, []); + }; + + Util.reduce = function(array, fn, acc){ + for(var i = 0; i < array.length; i++){ + acc = fn(acc, array[i], i); + } + + return acc; + }; + + Util.log = function(msg, type){ + type = type || "log"; + + if( console ){ + console[type](msg); + } + }; + + Util.mapJSONVals = function(obj, callback, key){ + var result = {}; + + if(obj.map){ + return obj.map(function(o){ + return Util.mapJSONVals(o, callback); + }); + } + + if(["string", "number", "boolean"].indexOf( typeof obj ) >= 0 ){ + return callback(obj, key); + } + + for( var prop in obj ){ + if(obj.hasOwnProperty(prop)){ + result[prop] = Util.mapJSONVals(obj[prop], callback, prop); + } + } + + return result; + }; + + Util.escapeJSONVals = function(obj){ + return Util.mapJSONVals(obj, function(val){ + if(typeof val === "string" ) { + return escapeHTML(val); + } + + return val; + }); + }; + + // https://github.com/janl/mustache.js/blob/23beb3a8805c9a857e3ea777431481599fab503e/mustache.js#L60 + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + function escapeHTML(string) { + return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) { + return entityMap[s]; + }); + } +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + var Advanceable = { + extension: { + next: function(callback){ + this.goto(this.getIndex() + 1, function(){ + if( callback ) { callback(); }; + this._trigger("next"); + }.bind(this)); + }, + + previous: function(callback){ + this.goto(this.getIndex() - 1, function(){ + if( callback ) { callback(); }; + this._trigger("previous"); + }.bind(this)); + } + }, + + extend: function(constr) { + if(!constr.prototype.goto || !constr.prototype.getIndex) { + throw new Error("Advanceable mixin requires `goto` and `getIndex` methods"); + } + + $.extend(constr.prototype, this.extension); + } + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Advanceable = Advanceable; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + + +// TODO this should be handled in an image component +(function(exports, $){ + var Sourceable = { + extension: { + _srcArray: function(url, options, includeW ){ + var policy = options.policy || options.thumbnail && options.thumbnail.policy; + var widthParam = options.widthParam; + + return options.widths.map(function(w){ + var src = url + (url.indexOf("?") === -1 ? "?" : "&") + widthParam + "=" + w; + + if( policy ){ + src += "&impolicy=" + policy; + } + + //note src width for srcset (eg ' 500w') + if( includeW ){ + src += " " + w + "w"; + } + + return src; + }); + }, + + _srcset: function(url, options){ + return this._srcArray( url, options, true ).join(", "); + }, + + _fallbackSrc: function(url, options){ + return this._srcArray( url, options )[ 0 ]; + }, + + _largestSrc: function(url, options){ + return this._srcArray( url, options ).pop(); + } + }, + + extendStatic: function(constr) { + Akamai.Util.extend(false, constr, this.extension); + } + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Sourceable = Sourceable; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + + +(function( w, undefined ){ + + // requestAnimationFrame pfill + var raf = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + function( callback ){ + w.setTimeout(callback, 1000 / 60); + }; + })(); + + + + + /* toss scrolls and element with easing + + // elem is the element to scroll + // options hash: + * left is the desired horizontal scroll. Default is "+0". For relative distances, pass a string with "+" or "-" in front. + * top is the desired vertical scroll. Default is "+0". For relative distances, pass a string with "+" or "-" in front. + * duration is the number of milliseconds the throw will take. Default is 100. + * easing is an optional custom easing function. Default is w.toss.easing. Must follow the easing function signature + + */ + w.toss = function( elem, options ){ + toss.tossing( elem, false ); + var i = 0, + sLeft = elem.scrollLeft, + sTop = elem.scrollTop, + // Toss defaults + op = { + top: "+0", + left: "+0", + duration: 200, + easing: toss.easing, + finished: function() {} + }, + endLeft, endTop; + + // Mixin based on predefined defaults + if( options ){ + for( var j in op ){ + if( options[ j ] !== undefined ){ + op[ j ] = options[ j ]; + } + } + } + + // Convert relative values to ints + // First the left val + if( typeof op.left === "string" ){ + op.left = parseFloat( op.left ); + endLeft = op.left + sLeft; + } + else { + endLeft = op.left; + op.left = op.left - sLeft; + } + // Then the top val + if( typeof op.top === "string" ){ + + op.top = parseFloat( op.top ); + endTop = op.top + sTop; + } + else { + endTop = op.top; + op.top = op.top - sTop; + } + + toss.tossing( elem, true ); + var startTime = new Date().getTime(); + var endTime = startTime + op.duration; + var run = function(){ + var curTime = new Date().getTime(); + // if tossing is suddenly not true, return the callback + if( !toss.tossing( elem ) ){ + if( op.finished ){ + op.finished(); + } + } + // if the time is still less than the end of duration, keep scrolling + else if( curTime < endTime ){ + i = ( ( curTime - startTime ) / op.duration ) * op.duration; + elem.scrollLeft = op.easing( i, sLeft, op.left, op.duration ); + elem.scrollTop = op.easing( i, sTop, op.top, op.duration ); + return raf( run ); + } + // if time is up, + else{ + elem.scrollLeft = endLeft; + elem.scrollTop = endTop; + if( op.finished ){ + op.finished(); + } + toss.tossing( elem, false ); + } + }; + + raf( run ); + + // Return the values, post-mixin, with end values specified + return { top: endTop, left: endLeft, duration: op.duration, easing: op.easing }; + }; + + // tossing object keeps track of currently tossing elements. true during a programatic scroll + var tossingElems = {}; + toss.tossing = function( elem, state ){ + if( state !== undefined ){ + tossingElems[ elem ] = state; + } + return tossingElems[ elem ]; + }; + + // Easing can use any of Robert Penner's equations (http://www.robertpenner.com/easing_terms_of_use.html). By default, toss includes ease-out-cubic + // arguments: t = current iteration, b = initial value, c = end value, d = total iterations + // use w.toss.easing to provide a custom function externally, or pass an easing function as a callback to the toss method + toss.easing = function (t, b, c, d) { + return c*((t=t/d-1)*t*t + 1) + b; + }; + + //retain old api + toss.toss = toss; + +})( this ); + +;(function( w, $ ){ + var pluginName = "snapper"; + $.fn[ pluginName ] = function(optionsOrMethod){ + var pluginArgs = arguments; + var scrollListening = true; + + // css snap points feature test. + // even if this test passes, several behaviors will still be polyfilled, such as snapping after resize, and animated advancing of slides with anchor links or next/prev links + var testProp = "scroll-snap-type"; + // test latest spec first. then fallback older + var snapSupported = w.CSS && w.CSS.supports && (w.CSS.supports(testProp, "x mandatory") || w.CSS.supports(testProp, "mandatory") || w.CSS.supports("-webkit-" + testProp, "mandatory") || w.CSS.supports("-ms-" + testProp, "mandatory") ); + + // get the snapper_item elements whose left offsets fall within the scroll pane. Returns a wrapped array. + function itemsAtOffset( elem, offset ){ + var $childNodes = $( elem ).find( "." + pluginName + "_item" ); + var containWidth = $( elem ).width(); + var activeItems = []; + + $childNodes.each(function( i ){ + if( this.offsetLeft >= offset - 5 && this.offsetLeft < offset + containWidth - 5 ){ + activeItems.push( this ); + } + }); + return $( activeItems ); + } + + function outerWidth( $elem ){ + return $elem.width() + parseFloat( $elem.css( "margin-left" ) ) + parseFloat( $elem.css( "margin-right" ) ); + } + + function outerHeight( $elem ){ + return $elem.height() + parseFloat( $elem.css( "margin-bottom" ) ) + parseFloat( $elem.css( "margin-top" ) ); + } + + + // snapEvent dispatches the "snapper.snap" event. + // The snapper_item elements with left offsets that are inside the scroll viewport are listed in an array in the second callback argument's activeSlides property. + // use like this: $( ".snapper" ).bind( "snapper.snap", function( event, data ){ console.log( data.activeSlides ); } ); + function snapEvent( elem, x, prefix ){ + prefix = prefix ? prefix + "-" : ""; + var activeSlides = itemsAtOffset( elem, x ); + $( elem ).trigger( pluginName + "." + prefix + "snap", { activeSlides: activeSlides } ); + } + + // optional: include toss() in your page to get a smooth scroll, otherwise it'll just jump to the slide + function goto( elem, x, nothrow, callback ){ + scrollListening = false; + snapEvent( elem, x ); + + var after = function(){ + elem.scrollLeft = x; + $(elem).closest( "." + pluginName ).removeClass( pluginName + "-looping" ); + $( elem ).trigger( pluginName + ".after-goto", { + activeSlides: itemsAtOffset( elem, x ) + }); + + if( callback ){ callback(); }; + snapEvent( elem, x, "after" ); + scrollListening = true; + }; + + // backport to old toss for compat + if( !w.toss && w.overthrow ){ + w.toss = w.overthrow.toss; + } + if( typeof w.toss !== "undefined" && !nothrow ){ + w.toss( elem, { left: x, finished: after }); + } + else { + elem.scrollLeft = x; + after(); + } + } + + + var result, innerResult; + + // Loop through snapper elements and enhance/bind events + result = this.each(function(){ + if( innerResult !== undefined ){ + return; + } + + var self = this; + var $self = $( self ); + var addNextPrev = $self.is( "[data-" + pluginName + "-nextprev]" ); + var autoTimeout; + var $slider = $( "." + pluginName + "_pane", self ); + var enhancedClass = pluginName + "-enhanced"; + var $itemsContain = $slider.find( "." + pluginName + "_items" ); + var $items = $itemsContain.children(); + $items.addClass( pluginName + "_item" ); + var numItems = $items.length; + var $nav = $( "." + pluginName + "_nav", self ); + var navSelectedClass = pluginName + "_nav_item-selected"; + var useDeepLinking = $self.attr( "data-snapper-deeplinking" ) !== "false"; + + if( typeof optionsOrMethod === "string" ){ + var args = Array.prototype.slice.call(pluginArgs, 1); + var index; + var itemWidth = ( $itemsContain.width() / numItems ); + + switch(optionsOrMethod) { + case "goto": + index = args[0] % numItems; + + // width / items * index to make sure it goes + offset = itemWidth * index; + goto( $slider[ 0 ], offset, false, function(){ + // snap the scroll to the right position + snapScroll(); + + // invoke the callback if it was supplied + if( typeof args[1] === "function" ){ + args[1](); + } + }); + break; + case "getIndex": + // NOTE make the scroll left value large enough to overcome + // subpixel widths + innerResult = Math.floor(($slider[ 0 ].scrollLeft + 1)/ itemWidth); + break; + case "updateWidths": + updateWidths(); + break; + } + + return; + } + + // avoid double enhance activities + if( $self.attr("data-" + pluginName + "-enhanced") ) { + return; + } + + // NOTE all state manipulation has to come after method invocation to + // avoid monkeying with the DOM when it's unwarranted + var $navInner = $nav.find( "." + pluginName + "_nav_inner" ); + if( !$navInner.length ){ + $navInner = $( '
' ).append( $nav.children() ).appendTo( $nav ); + } + + // give the pane a tabindex for arrow key handling + $slider.attr("tabindex", "0"); + + function getAutoplayInterval() { + var autoTiming = $self.attr( "data-autoplay" ) || $self.attr( "data-snapper-autoplay" ); + var parseError = false; + + if( autoTiming ) { + try { + autoTiming = parseInt(autoTiming, 10); + } catch(e) { + parseError = true; + } + + // if NaN or there was an error throw an exception + if( !autoTiming || parseError ) { + var msg = "Snapper: `data-autoplay` must have an natural number value."; + throw new Error(msg); + } + } + + return autoTiming; + } + + // this function updates the widths of the items within the slider, and their container. + // It factors in margins and converts those to values that make sense when all items are placed in a long row + function updateWidths(){ + var itemsContainStyle = $itemsContain.attr( "style" ); + $itemsContain.attr( "style", "" ); + var itemStyle = $items.eq(0).attr( "style" ); + $items.eq(0).attr( "style", "" ); + var sliderWidth = $slider.width(); + var itemWidth = $items.eq(0).width(); + var computed = w.getComputedStyle( $items[ 0 ], null ); + var itemLeftMargin = parseFloat( computed.getPropertyValue( "margin-left" ) ); + var itemRightMargin = parseFloat( computed.getPropertyValue( "margin-right" ) ); + var outerItemWidth = itemWidth + itemLeftMargin + itemRightMargin; + $items.eq(0).attr( "style", itemStyle ); + $itemsContain.attr( "style", itemsContainStyle ); + var parentWidth = numItems / Math.round(sliderWidth / outerItemWidth) * 100; + var iPercentWidth = itemWidth / sliderWidth * 100; + var iPercentRightMargin = itemRightMargin / sliderWidth * 100; + var iPercentLeftMargin = itemLeftMargin / sliderWidth * 100; + var outerPercentWidth = iPercentWidth + iPercentLeftMargin + iPercentRightMargin; + var percentAsWidth = iPercentWidth / outerPercentWidth; + var percentAsRightMargin = iPercentRightMargin / outerPercentWidth; + var percentAsLeftMargin = iPercentLeftMargin / outerPercentWidth; + $itemsContain.css( "width", parentWidth + "%"); + $items.css( "width", 100 / numItems * percentAsWidth + "%" ); + $items.css( "margin-left", 100 / numItems * percentAsLeftMargin + "%" ); + $items.css( "margin-right", 100 / numItems * percentAsRightMargin + "%" ); + } + + updateWidths(); + $( self ).addClass( enhancedClass ); + + // if the nextprev option is set, add the nextprev nav + if( addNextPrev ){ + var $nextprev = $( '' ); + var $nextprevContain = $( ".snapper_nextprev_contain", self ); + if( !$nextprevContain.length ){ + $nextprevContain = $( self ); + } + $nextprev.appendTo( $nextprevContain ); + } + + // This click binding will allow deep-linking to slides without causing the page to scroll to the carousel container + // this also supports click handling for generated next/prev links + $( "a", this ).bind( "click", function( e ){ + clearTimeout(autoTimeout); + var slideID = $( this ).attr( "href" ); + + if( $( this ).is( ".snapper_nextprev_next" ) ){ + e.preventDefault(); + return arrowNavigate( true ); + } + else if( $( this ).is( ".snapper_nextprev_prev" ) ){ + e.preventDefault(); + return arrowNavigate( false ); + } + // internal links to slides + else if( slideID.indexOf( "#" ) === 0 && slideID.length > 1 ){ + e.preventDefault(); + + var $slide = $( slideID, self ); + if( $slide.length ){ + goto( $slider[ 0 ], $slide[ 0 ].offsetLeft ); + if( useDeepLinking && "replaceState" in w.history ){ + w.history.replaceState( {}, document.title, slideID ); + } + } + } + }); + + // arrow key bindings for next/prev + $( this ) + .bind( "keydown", function( e ){ + if( e.keyCode === 37 || e.keyCode === 38 ){ + clearTimeout(autoTimeout); + e.preventDefault(); + e.stopImmediatePropagation(); + arrowNavigate( false ); + } + if( e.keyCode === 39 || e.keyCode === 40 ){ + clearTimeout(autoTimeout); + e.preventDefault(); + e.stopImmediatePropagation(); + arrowNavigate( true ); + } + } ); + + var snapScrollCancelled = false; + + // snap to nearest slide. Useful after a scroll stops, for polyfilling snap points + function snapScroll(){ + if(isTouched){ + snapScrollCancelled = true; + return; + } + + var currScroll = $slider[ 0 ].scrollLeft; + var width = $itemsContain.width(); + var itemWidth = $items[ 1 ] ? $items[ 1 ].offsetLeft : outerWidth( $items.eq( 0 ) ); + var roundedScroll = Math.round(currScroll/itemWidth)*itemWidth; + var maxScroll = width - $slider.width(); + if( roundedScroll > maxScroll ){ + roundedScroll = maxScroll; + } + if( currScroll !== roundedScroll ){ + if( snapSupported ){ + snapEvent( $slider[ 0 ], roundedScroll ); + snapEvent( $slider[ 0 ], roundedScroll, "after" ); + } + else { + goto( $slider[ 0 ], roundedScroll ); + } + } else { + goto( $slider[ 0 ], roundedScroll ); + } + + snapScrollCancelled = false; + } + + // retain snapping on resize (necessary even in scroll-snap supporting browsers currently, unfortunately) + var startSlide; + var afterResize; + function snapStay(){ + var currScroll = $slider[ 0 ].scrollLeft; + var numItems = $items.length; + var width = $itemsContain.width(); + if( startSlide === undefined ){ + startSlide = Math.round( currScroll / width * numItems ); + } + if( afterResize ){ + clearTimeout( afterResize ); + } + afterResize = setTimeout( function(){ + updateWidths(); + goto( $slider[ 0 ], $items[ startSlide ].offsetLeft, true ); + startSlide = afterResize = undefined; + }, 50 ); + } + $( w ).bind( "resize", snapStay ); + + // next/prev links or arrows should loop back to the other end when an extreme is reached + function arrowNavigate( forward ){ + var currScroll = $slider[ 0 ].scrollLeft; + var width = $itemsContain.width(); + var itemWidth = outerWidth( $slider ); + var maxScroll = width - itemWidth - 5; + if( forward ){ + if( currScroll >= maxScroll ){ + $self.addClass( pluginName + "-looping" ); + return first(); + } + else { + return next(); + } + } + else { + if( currScroll === 0 ){ + $self.addClass( pluginName + "-looping" ); + return last(); + } + else { + return prev(); + } + } + } + + // advance slide one full scrollpane's width forward + function next(){ + goto( $slider[ 0 ], $slider[ 0 ].scrollLeft + ( $itemsContain.width() / numItems ), false, function(){ + $slider.trigger( pluginName + ".after-next" ); + }); + } + + // advance slide one full scrollpane's width backwards + function prev(){ + goto( $slider[ 0 ], $slider[ 0 ].scrollLeft - ( $itemsContain.width() / numItems ), false, function(){ + $slider.trigger( pluginName + ".after-prev" ); + }); + } + + // go to first slide + function first(){ + goto( $slider[ 0 ], 0 ); + } + + // go to last slide + function last(){ + goto( $slider[ 0 ], $itemsContain.width() - $slider.width() ); + } + + // update thumbnail state on pane scroll + if( $nav.length ){ + // function for scrolling to the xy of the active thumbnail + function scrollNav(elem, x, y){ + if( typeof w.toss !== "undefined" ){ + w.toss( elem, { left: x, top:y }); + } + else { + elem.scrollLeft = x; + elem.scrollTop = y; + } + } + var lastActiveItem; + function activeItem( force ){ + var currTime = new Date().getTime(); + if( !force && lastActiveItem && currTime - lastActiveItem < 200 ){ + return; + } + lastActiveItem = currTime; + var currScroll = $slider[ 0 ].scrollLeft; + var width = outerWidth( $itemsContain ); + var navWidth = outerWidth( $nav ); + var navHeight = outerHeight( $nav ); + var activeIndex = Math.round( currScroll / width * numItems ) || 0; + var childs = $nav.find( "a" ).removeClass( navSelectedClass ); + var activeChild = childs.eq( activeIndex ).addClass( navSelectedClass ); + + var thumbX = activeChild[ 0 ].offsetLeft - (navWidth/2); + var thumbY = activeChild[ 0 ].offsetTop - (navHeight/2); + + scrollNav( $navInner[ 0 ], thumbX, thumbY ); + } + + // set active item on init + activeItem(); + + $slider.bind( "scroll", activeItem ); + } + + // apply snapping after scroll, in browsers that don't support CSS scroll-snap + var scrollStop; + var scrolling; + var lastScroll = 0; + + + $slider.bind( "scroll", function(e){ + lastScroll = new Date().getTime(); + scrolling = true; + }); + + setInterval(function(){ + if( scrolling && lastScroll <= new Date().getTime() - 150) { + snapScroll(); + if( activeItem ){ + activeItem( true ); + } + scrolling = false; + } + }, 150); + + var isTouched = false; + + // if a touch event is fired on the snapper we know the user is trying to + // interact with it and we should disable the auto play + $slider.bind("touchstart", function(){ + clearTimeout(autoTimeout); + + isTouched = true; + }); + + $slider.bind("touchend", function(){ + isTouched = false; + + if(snapScrollCancelled && !scrolling){ + snapScroll(); + scrolling = false; + } + }); + + // if the `data-autoplay` attribute is assigned a natural number value + // use it to make the slides cycle until there is a user interaction + function autoplay( autoTiming ) { + if( autoTiming ){ + // autoTimeout is cleared in each user interaction binding + autoTimeout = setTimeout(function(){ + var timeout = getAutoplayInterval(); + if( timeout ) { + arrowNavigate(true); + autoplay( timeout ); + } + }, autoTiming); + } + } + + autoplay( getAutoplayInterval() ); + $self.attr("data-" + pluginName + "-enhanced", true); + }); + + return (innerResult !== undefined ? innerResult : result); + }; +}( this, jQuery )); + +;(function( w ){ + var enlarge = function(){ + var $ = w.jQuery; + var pluginName = "enlarge"; + $.fn[ pluginName ] = function( options ){ + var pluginArgs = arguments; + + // options + var o = $(this).data("options") || { + button: true, + hoverZoomWithoutClick: true, + delay: 300, + flyout: { + width: 200, + height: 200 + }, + placement: "inline", + magnification: 3 + }; + + if( typeof options !== "string" ) { + // extend with passed options + o = $.extend( o, options ); + $(this).data("options", o); + } + + var internalResult; + + var result = this.each(function(){ + var $element = $(this); + + var self = this; + var testimg = w.document.createElement( "img" ); + var srcsetSupported = "srcset" in testimg; + var srcsetSizesSupported = srcsetSupported && "sizes" in testimg; + var $anchor = $( this ).find( "a" ); + + if( !$anchor.length ){ + throw new Error(pluginName + ": requires an anchor element with `href` for the enlarged image source"); + } + + // find image within container + var initialImg = $element.find( "img" )[ 0 ]; + var targetImg = initialImg; + var imgOriginalSrc = targetImg.src; + var srcset = $( targetImg ).attr( "srcset" ); + var imgOriginalSizes = $( targetImg ).attr( "sizes" ); + var imgZoomSrc = $anchor[ 0 ].href; + var initialText = $anchor[ 0 ].innerText; + var zoomClass = pluginName + "-zoomed"; + var delayClass = pluginName + "-delay"; + var $contain = $( targetImg ).closest( ".enlarge_contain" ); + var $zoomContain = $contain; + var $parentPane = $( targetImg ).closest( ".enlarge_pane" ) || $element; + + var $zoomParent = $(this).data("zoomParent") || $parentPane; + $(this).data("zoomParent", $zoomParent); + + var zoomed = $element.data("zoomed") || false; + $element.data("zoomed", zoomed); + + $element.data("lockedZoom", $element.data("lockedZoom") || false); + + var lockZoomClass = pluginName + "-locked"; + + if( !$contain.length ){ + throw new Error(pluginName + ": requires an element above the image marked with the class `enlarge_contain`"); + } + + // this allows for methods and changing options in subsequent calls to the plugin + if( typeof options === "string" ) { + var args = Array.prototype.slice.call(pluginArgs, 1); + + switch(options){ + case "in": + if( !$element.data("zoomed") ){ + standardToggleZoom(); + } + break; + case "out": + if( $element.data("zoomed") ){ + standardToggleZoom(); + } + break; + case "isZoomed": + internalResult = $element.data("zoomed"); + break; + case "updateOptions": + $element.data( "updateOptions" )( args[ 0 ] ); + break; + } + return; + } + + // to toggle between inline and flyout modes, we change the elements that + // the targetImg, zoomParent, and zoomContain vars refer to + function updatePlacement(){ + if( o.placement === "inline" ){ + targetImg = initialImg; + $zoomParent = $parentPane; + $element.data("zoomParent", $zoomParent); + $zoomContain = $contain; + } else { + targetImg = $flyout.find( "img" )[ 0 ]; + $zoomParent = $zoomContain = $flyout; + $element.data("zoomParent", $zoomParent); + } + } + + // this positions the loupe or side flyout element either according to mouse/touch coordinates + // or the sides of the viewer specified + function positionFlyout(){ + + // set flyout width and height + $flyout.css({ + "width": o.flyout.width + "px", + "height": o.flyout.height + "px", + top: "", + left: "", + "margin-left": "", + "margin-top": "" + }); + + // set negative left or right value to match width + var flyoutSide = o.placement.match( /left|right/ ); + + if( flyoutSide ){ + $flyout.css( flyoutSide[ 0 ], -o.flyout.width + "px" ); + } + // if loupe mode, center offset + var loupe = o.placement.match( /loupe/ ); + + if( loupe ) { + // loupe + $flyout.css({ + "margin-left": ( -o.flyout.width / 2 ) + "px", + "margin-top": ( -o.flyout.height / 2 ) + "px" + }); + } + + // add class to specify positioning spot for static css to apply + $flyout[ 0 ].className = $flyout[ 0 ].className.replace( /enlarge_flyout\-[^$\s]+/, ' ' ); + $flyout.addClass( "enlarge_flyout-" + o.placement ); + } + + function disable(){ + if(o.disabled){ + $element.addClass("enlarge_disabled"); + } else { + $element.removeClass("enlarge_disabled"); + } + } + + disable(); + + // this allows for subsequent calls to the plugin to pass an updateOptions method and object, + // which will pass through to the existing viewer on that element + $element.data( "updateOptions", function( opts ){ + o = $.extend( o, opts ); + $(this).data("options", o); + + updatePlacement(); + positionFlyout(); + hoverEnabled = o.hoverZoomWithoutClick; + if( o.image && o.image.sizes ){ + imgOriginalSizes = o.image.sizes; + toggleImgSrc(); + } + + disable(); + + if( o.disabled && $element.data("zoomed") ) { + standardToggleZoom(); + } + }); + + // loader div holds a new image while its new source is loading + // we insert this into the dom so that srcset/sizes can calculate a best source + function addLoader(){ + $contain.append( '' ); + } + + // zoom state toggle boolean + function toggleZoomState(){ + zoomed = !$element.data("zoomed"); + $element.data("zoomed", zoomed); + } + + // toggle the image source bigger or smaller + // ideally, this toggles the sizes attribute and allows the browser to select a new source from srcset + // if srcset isn't supported or sizes attribute is not provided, the link href is used for the larger source + function toggleImgSrc(after){ + after = after || function(){}; + + if( !zoomed ){ + targetImg.sizes = imgOriginalSizes; + if( !srcsetSizesSupported ){ + targetImg.src = imgOriginalSrc; + } + after(); + } else { + // if the zooming is disabled do not replace with the larger source + // NOTE we don't prevent switching to the original source because we + // always want to allow the plugin to back out of the zoomed state + // when disabled + if( o.disabled ) { after(); return false; } + + var zoomimg = new Image(); + zoomimg.className = "enlarge_img-loading"; + zoomimg.onload = function(){ + targetImg.sizes = zoomimg.sizes; + if( !srcsetSizesSupported || !srcset ){ + targetImg.src = imgZoomSrc; + } + $( zoomimg ).remove(); + + after(); + }; + + zoomimg.sizes = imgZoomWidth() + "px"; + + if( !srcsetSizesSupported || !srcset ){ + zoomimg.src = imgZoomSrc; + } else if (srcset) { + zoomimg.srcset = srcset; + } + + $( zoomimg ).insertBefore( targetImg ); + } + } + + // scroll to the center of the zoomed image + function scrollToCenter(){ + var pw = $zoomContain.width(); + var ph = $zoomContain.height(); + var w = targetImg.offsetWidth; + var h = targetImg.offsetHeight; + $zoomContain[ 0 ].scrollLeft = ( w / 2 ) - ( pw / 2 ); + $zoomContain[ 0 ].scrollTop = ( h / 2 ) - ( ph / 2 ); + } + + // lock zoom mode allows for scrolling around normally without a cursor-follow behavior + function toggleLockZoom(){ + if( !$element.data("lockedZoom") ){ + // NOTE we allow the image to zoom out if functionality gets disabled + // when it's in a zoomed state + if(o.disabled) { return false; } + + $parentPane.add( $zoomParent ).addClass( lockZoomClass ); + $element.data("lockedZoom", lockedZoom = true); + $zoomContain.attr( "tabindex", "0" ); + $zoomContain[ 0 ].focus(); + } + else { + $parentPane.add( $zoomParent ).removeClass( lockZoomClass ); + $element.data("lockedZoom", lockedZoom = false); + $zoomContain.removeAttr( "tabindex" ); + } + } + + function imgZoomWidth(){ + return $parentPane[0].offsetWidth * o.magnification; + } + + // toggle magnification of image + function toggleImgZoom(){ + if( $element.data("zoomed") ){ + // NOTE we allow the image to zoom out if functionality gets disabled + // when it's in a zoomed state + if(o.disabled) { return false; } + + if( o.placement === "inline" ){ + $contain.add( $parentPane ).css( { "width": $parentPane[0].offsetWidth + "px", "height": parseFloat( getComputedStyle( $parentPane[0] ).height ) + "px" } ); + } + $zoomParent.addClass( zoomClass ); + $( targetImg ).css( "width", imgZoomWidth() + "px" ); + + $(self).trigger( pluginName + ".after-zoom-in"); + } + else{ + $zoomParent.removeClass( zoomClass ); + if( o.placement === "inline" ){ + $contain.add( $parentPane ).css( { "width": "", "height": "" } ); + } + $( targetImg ).css( "width", "" ); + + $(self).trigger( pluginName + ".after-zoom-out"); + } + } + + function forceInlineMode(){ + var oldO = o.placement; + if( oldO !== "inline" ){ + function resetPlacement(){ + o.placement = oldO; + updatePlacement(); + $(self).unbind( pluginName + ".after-zoom-out", resetPlacement ); + } + $(self).bind( pluginName + ".after-zoom-out", resetPlacement ); + o.placement = "inline"; + updatePlacement(); + } + } + + // lock zoom mode toggle + function standardToggleZoom(){ + // NOTE if the current is zoomed out and it's disabled prevent toggling + if(o.disabled && !$element.data("zoomed")) { return false; } + toggleZoomState(); + toggleImgSrc(function(){ + toggleLockZoom(); + toggleImgZoom(); + scrollToCenter(); + }); + } + + var trackingOn; + var trackingTimer; + var mouseEntered; + var touchStarted; + var hoverEnabled = o.hoverZoomWithoutClick; + var lastMouseMove; + + // mouseenter or touchstart handler for dragging image + function startTrackingDelay(e){ + if( e.type === "touchstart" ){ + touchStarted = true; + } + if( touchStarted && e.type === "mouseenter" || + e.type === "mouseenter" && !touchStarted && !hoverEnabled || + $element.data("lockedZoom") || + mouseEntered ){ + return; + } + mouseEntered = true; + $contain.addClass( delayClass ); + trackingTimer = setTimeout( function(){ + $contain.removeClass( delayClass ); + toggleZoomState(); + toggleImgSrc(function(){ + toggleImgZoom(); + trackingOn = true; + if (lastMouseMove) { + scrollWithMouse(lastMouseMove); + } else { + scrollWithMouse(e); + } + }); + }, o.delay ); + } + + // mouseleave or touchend after a drag + function stopTrackingDelay( e ){ + $contain.removeClass( delayClass ); + clearTimeout( trackingTimer ); + trackingOn = false; + if( o.hoverZoomWithoutClick === false && !touchStarted ){ + hoverEnabled = false; + } + if( touchStarted && e.type === "mouseleave" ){ + touchStarted = false; + } + } + + // mousemove or touch-drag image placement + function scrollWithMouse( e ){ + // if tracking's not on yet, ignore. This allows the delay to work + if( trackingOn ){ + // if the move was touch-started, and the event is mousemove, it can be ignored + // (mouse events fire along with touch events and we just want the touch) + if( touchStarted && e.type === "mousemove" ){ + return; + } + // normalize ev to touch or mouse + var ev = e.touches ? e.touches[ 0 ] : e; + e.preventDefault(); + var x = ev.clientX - $contain[ 0 ].getBoundingClientRect().left; + var y = ev.clientY - $contain[ 0 ].getBoundingClientRect().top; + + if( o.placement.match( /loupe/ ) ) { + // offset the loupe a little differently for touch so that it's not directly beneath a finger + var mLeft = ( e.touches ? -o.flyout.width / 1.3 : -o.flyout.width / 2 ) + "px"; + var mTop = ( e.touches ? -o.flyout.height / 1.3 : -o.flyout.height / 2 ) + "px"; + requestAnimationFrame(function(){ + $flyout.css( { + top: y + "px", + left: x + "px", + "margin-left": mLeft, + "margin-top": mTop + } ); + }); + } + + var containWidth = $contain[ 0 ].offsetWidth; + var containHeight = $contain[ 0 ].offsetHeight; + var containScrollWidth = targetImg.offsetWidth; + var containScrollHeight = targetImg.offsetHeight; + var zoomContainWidth = $zoomContain[ 0 ].offsetWidth; + var zoomContainHeight = $zoomContain[ 0 ].offsetHeight; + var widthFactor = containWidth / zoomContainWidth; + var heightFactor = containHeight / zoomContainHeight; + + $zoomContain[ 0 ].scrollLeft = (( x / containWidth ) * ( containScrollWidth - zoomContainWidth )); + $zoomContain[0].scrollLeft += ((x / containWidth) - 0.5) * zoomContainWidth; + $zoomContain[ 0 ].scrollTop = (( y / containHeight ) * ( containScrollHeight - zoomContainHeight )) ; + $zoomContain[0].scrollTop += ((y / containHeight) - 0.5) * zoomContainHeight; + } else { + lastMouseMove = e; + } + } + + // add flyout for flyout and loupe modes + // flyout is always present, yet hidden when inline mode is active + var $flyout = $( '
' ).append( $contain.clone() ); + $flyout.insertAfter( $parentPane ); + + // add loader div + addLoader(); + updatePlacement(); + positionFlyout(); + + // clicking the magnify anchor toggles lock-zoom mode + $anchor + .bind( "keydown", function(e){ + if( e.keyCode === 13 || e.keyCode === 32 ){ + forceInlineMode(); + } + // spacebar triggers click too + if( e.keyCode === 32 ){ + e.preventDefault(); // don't scroll the new focused area + $( this ).trigger( "click" ); + } + }) + .bind( "click", function( e ){ + e.preventDefault(); + standardToggleZoom(); + }); + + // on resize, if in lock zoom mode, un zoom + $( w ) + .bind( "resize", function( e ){ + if( $element.data("lockedZoom") ){ + standardToggleZoom(); + } + }); + + // on click-out on the page, if in locked zoom mode, zoom out + $( w.document ) + .bind( "mouseup", function( e ){ + if( $element.data("lockedZoom") && !$( e.target ).closest( $parentPane ).length ){ + standardToggleZoom(); + } + }); + + // mouse hover and touch-drag gestures for a cursor-tracked zoom behavior + $( initialImg ) + .bind( "mouseenter touchstart", startTrackingDelay ) + .bind( "mousemove touchmove", scrollWithMouse ) + .bind( "mouseleave touchend", function( e ){ + mouseEntered = false; + if( zoomed && !$element.data("lockedZoom") ){ + toggleZoomState(); + toggleImgSrc(function(){ + toggleImgZoom(); + }); + } + stopTrackingDelay( e ); + }) + // tapping the image should trigger a lock zoom + // click will not fire after a touch-drag so it works as a tap for our needs here + .bind( "click", function( e ){ + e.preventDefault(); + // if the click was started with a touchstart event, + // and placement is inline + // toggle the locked zoom mode + if( touchStarted && o.placement === "inline" ){ + standardToggleZoom(); + } + + if( o.hoverZoomWithoutClick === false && !touchStarted ){ + hoverEnabled = !hoverEnabled; + if( hoverEnabled ){ + $( this ).trigger( "mouseenter" ); + } + else { + $( this ).trigger( "mouseleave" ); + } + } + } ); + + // keyboard handling for arrows in zoom mode + $(this).bind( "keydown keyup", function( e ){ + if( e.keyCode === 37 || + e.keyCode === 38 || + e.keyCode === 39 || + e.keyCode === 40 ){ + e.stopImmediatePropagation(); + if( !$element.data("lockedZoom") ){ + e.preventDefault(); + } + } else if( e.type === "keyup" && $(this).data("lockedZoom") && e.keyCode === 27 ){ //esc or backspace closes zoom + standardToggleZoom(); + $anchor[0].focus(); + e.stopImmediatePropagation(); + } + }); + + // on scroll, zoom out + $parentPane.bind( "scroll", function(){ + if( $element.data("zoomed") ){ + toggleZoomState(); + toggleImgSrc(function(){ + if( $element.data("lockedZoom") ){ + toggleLockZoom(); + } + toggleImgZoom(); + }); + } + }); + }); + + return internalResult !== undefined ? internalResult : result; + }; + }; + + if( typeof module !== "undefined" ){ + module.exports = enlarge; + } else { + enlarge(); + } +}( typeof global !== "undefined" ? global : this )); + +(function(exports){ + var Image = { + defaultConfig: { + widths: [ + "320", + "640", + "800", + "1024", + "2048", + "5000" + ], + + sizes: "100vw", + policy: undefined, + widthParam: "imwidth" + } + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Image = Image; +})(typeof exports === 'undefined'? window : exports); + +(function(exports, $){ + /** + * 360 degree viewer + * @class + * @alias Akamai.Spin360 + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Spin360 = Akamai.Util.component("Spin360", function(element, options){ + this._comp = new Akamai.Tau(element, this._options); + }); + + // srcset stuff + Akamai.Sourceable.extendStatic(Spin360); + + // Used in preflight to "rename" events based on the child component events + Spin360._componentEventMapping = { + "tau.auto-rotate-start": "play", + "tau.auto-rotate-stop": "pause" + }; + + Spin360.prototype._updateOptions = function(options){ + if( !options ) { + this._options = this._originalOptions; + } + + this._options = Akamai.Util.extend(true, this._options, options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Spin360.defaultOptions, this._options, "Spin360"); + + // TODO actually update the options + }; + + Spin360._renderImg = function(url, options){ + return ` + + `; + }; + + Spin360.render = function(json, options){ + var finalOptions = Akamai.Util.options(Spin360.defaultOptions, options); + + // TODO fix the string problem in shoestring + return Akamai.Util.trim(` +
+ ${Akamai.Util + .map(json.urls, function(url) { return Spin360._renderImg(url, finalOptions); }) + .join("\n") + } +
+ `); + }; + + /** + * Defines the global default options for all Spin360s on the page + * @static + * @property {Boolean} autoplay.enabled - Enable autoplay (default: false) + * @property {Integer} autoplay.delay - Delay in milliseconds after initialization before spinning begins (default: 1000) + * @property {Boolean} controls.arrows - Render controls to spin left and right (default: false) + * @property {Boolean} controls.play - Render controls to enable and disable automatic spinning (default: false) + * @property {String} controls.text.left - Left rotation control title and text (default: "Rotate Left") + * @property {String} controls.text.right - Right rotation control title and text (default: "Rotate Right") + * @property {String} controls.text.play - Spin control title and text (default: "Spin Object") + * @property {Array} images.widths - list of available widths for an image (to be combined with image.widthParam), Default: ["320","640","800","1024","2048","5000"] + * @property {String} images.sizes - value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw`, `200px`, `(min-width:1000px) 500px, 100vw`. + * @property {String} images.policy - query param value for policy, appended to &policy= per image url when specified. Values: `foo`. Default: undefined. + * @property {String} images.widthParam - query string name to use for setting each url width. Default urls will be ?imwidth=320 for example. Values: `imwidth` (default), `w`, `width`, etc. + * @property {Integer} interval - The full rotation interval in milliseconds, determines physics, (default: 3000) + * @property {Boolean} reverse - Reverse the direction of the spin (default: false) + * @property {Float} sensitivity - The speed at which the object rotates relative to user input (default: 1). At the default value of `1` the object will complete a full 360 rotation when you drag across the entire width of the spin360 component. To require less effort to complete a rotation, change this value to a higher number. For example, setting `sensitivity: 2` would complete a full 360 rotation by dragging halfway (ex. from the center to the edge). + */ + Spin360.defaultOptions = { + autoplay: { + enabled: false, + delay: 1000 + }, + + controls: { + arrows: false, + play: false, + text: { + left: "Rotate Left", + right: "Rotate Right", + play: "Spin Object" + } + }, + + // unsupported, the number of frame images to create using the configured + // template + frames: 72, + images: Akamai.Image.defaultConfig, + interval: 3000, + reverse: false, + + sensitivity: 1, + + // unsupported, template used to generate urls when only one image is + // present in the 360 viewer + template: undefined + }; + + /** + * Goto to a particular frame of the spining image + * @method + * @param {Integer} index - the frame to advance to + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Spin360.prototype.goto = function(index, callback){ + this._comp.goto(index); + // NOTE goto must come before the callback because the callback will be + // used to trigger the `next` and `previous` events. The order should be + // maintained consistently as `goto` -> `next`/`previous` + this._trigger("goto"); + if(callback){ callback(); } + }; + + /** + * Return the current frame index + * @method + * @returns {Integer} + */ + Spin360.prototype.getIndex = function(){ + return this._comp.index; + }; + + // Extend Carousel with Advanceable interface + Akamai.Advanceable.extend(Spin360); + + /** + * Go to the next frame + * @method + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Spin360.prototype.next; + // required for JSDocs + + /** + * Go to the previous frame + * @method + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Spin360.prototype.previous; + // required for JSDocs + + /** + * Begin the automatic rotation of the images + * @todo support passing in an interval or speed? + * @method + * @fires Akamai.Spin360#akamai-spin360-play + * @returns {undefined} + */ + Spin360.prototype.play = function(){ + this._comp.autoRotate(); + }; + + /** + * Stop the automatic rotation of the images + * @method + * @fires Akamai.Spin360#akamai-spin360-pause + * @returns {undefined} + */ + Spin360.prototype.pause = function(){ + this._comp.stopAutoRotate(); + }; + + Spin360.prototype.getElement = function(){ + return this._$el[0]; + }; + + Spin360.States = { + Playing: 0, + Paused: 1 + }; + + /** + * Return the current state of the spin360 + * @example Spin360 states + * Akamai.Spin360.States = { + * Playing: 0, + * Paused: 1 + * }; + * + * @example Conditioning on states + * if( spin360.getState() === Akamai.Spin360.States.Paused ) { + * spin360.play() + * } + * @method + * @returns {Akamai.Spin360.State} + */ + Spin360.prototype.getState = function(){ + // TODO expose using method in Tau + return !!this._comp.autoInterval ? Spin360.States.Playing : Spin360.States.Paused; + }; + + /** + * Triggered when the spin360 has stoped automatically spinning + * {@link Akamai.Spin360#pause}. + * + * @event Akamai.Spin360#akamai-spin360-stop-spin + */ + + /** + * Triggered when the spin360 has started automatically spinning + * {@link Akamai.Spin360#play}. + * + * @event Akamai.Spin360#akamai-spin360-start-spin + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Spin360}. + * + * @event Akamai.Spin360#akamai-spin360-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Spin360 = Spin360; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(){ + function MagnifierImpl(element, options){ + throw new Error('Not allowed to instantiate MagnifierImpl'); + } + + MagnifierImpl.prototype.updateOptions = function(options){ + throw new Error('magnifier updateOptions not implemented'); + }; + + MagnifierImpl.prototype.zoomIn = function(){ + throw new Error('magnifier zoomIn not implemented'); + }; + + MagnifierImpl.prototype.zoomOut = function(){ + throw new Error('magnifier zoomOut not implemented'); + }; + + MagnifierImpl.prototype.isMagnified = function(){ + throw new Error('magnifier isMagnified not implemented'); + }; + + MagnifierImpl.prototype.toggleZoom = function(){ + throw new Error('magnifier toggleZoom not implemented'); + }; + + MagnifierImpl.prototype.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){ + throw new Error('magnifier render not implemented'); + }; + + var ex = (typeof exports === 'undefined') ? window : exports; + ex.Akamai = ex.Akamai || {}; + ex.Akamai.MagnifierImpl = MagnifierImpl; +})(); +(function(){ + function MagnifierImplEnlarge(element, options){ + this._$el = $(element); + this._$el.enlarge(options); + } + + MagnifierImplEnlarge.prototype = Object.create(Akamai.MagnifierImpl.prototype); + MagnifierImplEnlarge.prototype.constructor = MagnifierImplEnlarge; + + MagnifierImplEnlarge.prototype.updateOptions = function(options){ + this._$el.enlarge( "updateOptions", options ); + }; + + MagnifierImplEnlarge.prototype.zoomIn = function(){ + this._$el.enlarge( "in" ); + }; + + MagnifierImplEnlarge.prototype.zoomOut = function(){ + this._$el.enlarge( "out" ); + }; + + MagnifierImplEnlarge.prototype.isMagnified = function(){ + return this._$el.enlarge( "isZoomed" ); + }; + + MagnifierImplEnlarge.prototype.toggleZoom = function(){ + if(this.isMagnified()){ + this.zoomOut(); + } else { + this.zoomIn(); + } + }; + + MagnifierImplEnlarge.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){ + return ` +
+
+ +
+ + ${options.buttonText} + +
+ `; + }; + + var ex = (typeof exports === 'undefined') ? window : exports; + ex.Akamai = ex.Akamai || {}; + ex.Akamai.MagnifierImplEnlarge = MagnifierImplEnlarge; +})(); +(function(){ + function ClickTracker(element, handler){ + var self = this; + self.element = element; + self.handler = handler; + this.reset(); + } + + ClickTracker.prototype.onDown = function(e){ + if (e.target == this.element) { + if (this.isDown) { + this.reset(); + } else { + this.down.x = e.clientX; + this.down.y = e.clientY; + this.down.timestamp = new Date(); + this.isDown = true; + } + } + }; + + ClickTracker.prototype.onMove = function(){ + this.isMoved = true; + }; + + ClickTracker.prototype.onUp = function(e){ + if (this.isDown) { + this.up.x = e.clientX; + this.up.y = e.clientY; + this.up.timestamp = new Date(); + + if (!this.isMoved && (this.down.x === this.up.x && this.down.y === this.up.y && (this.up.timestamp.getTime() - this.down.timestamp.getTime() <= 500))) { + this.handler(); + } + } + + this.reset(); + }; + + ClickTracker.prototype.reset = function(x, y){ + this.down = { + x: -Infinity, + y: -Infinity, + timestamp: new Date(0) + }; + + this.isDown = false; + this.isMoved = false; + + this.up = { + x: Infinity, + y: Infinity, + timestamp: new Date() + }; + }; + + function MagnifierImplScroller(element, options){ + this._$el = $(element); + this._options = options; + this._container = this._$el.get(0); + this._content = this._$el.children().get(0); + this._imgCast = $(this._content).find('div').get(0); + this._zoomInBtn = this._$el.find("a.scroller-zoom-in").get(0); + this._zoomOutBtn = this._$el.find("a.scroller-zoom-out").get(0); + this._magnification = 1; + + var self = this; + + // Initialize Scroller + this.scroller = new Scroller(this._renderer(), { + zooming: options.enabled, + minZoom: 1, + maxZoom: options.magnification, + animationDuration: options.animationDuration + }); + + var rect = this._container.getBoundingClientRect(); + this.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop); + + this._installEventHandlers(); + this._updateButtonStates(); + setTimeout(function(){ + self._onResize(); + }); + } + + MagnifierImplScroller.prototype = Object.create(Akamai.MagnifierImpl.prototype); + MagnifierImplScroller.prototype.constructor = MagnifierImplScroller; + + MagnifierImplScroller.prototype.updateOptions = function(options){ + var rect = this._container.getBoundingClientRect(); + this.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop); + this._onResize(); + this._zoomBy(1 / this._options.magnification, false); + }; + + MagnifierImplScroller.prototype.zoomIn = function(){ + this._zoomBy(this._options.incrementalZoomFactor); + }; + + MagnifierImplScroller.prototype.cyclicZoom = function(){ + var self = this; + if (this._magnification >= this._options.magnification) { + this._zoomBy(1 / this._options.magnification); + } else { + this.zoomIn(); + } + }; + + MagnifierImplScroller.prototype.zoomOut = function(){ + this._zoomBy(1 / this._options.incrementalZoomFactor); + }; + + MagnifierImplScroller.prototype.isMagnified = function(){ + return this._magnification > 1; + }; + + MagnifierImplScroller.prototype.toggleZoom = function(){ + throw new Error('zoom toggle behavior is undefined in scroller mode'); + }; + + MagnifierImplScroller.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){ + return ` +
+
+ +
+
+ + ${options.zoomInBtnText} + + + ${options.zoomOutBtnText} + +
+ `; + }; + + MagnifierImplScroller.prototype._zoomBy = function(magnification, animate){ + var self = this, magnification = Number(magnification); + + if (typeof(animate) === "undefined") { + animate = this._options.animateZoom; + } + + if (magnification === 1 || // if we are magnifying by 1 OR + (this._magnification === 1 && magnification < 1) || // fully zoomed out and still attempting a zoom out OR + (this._magnification === this._options.magnification && magnification > 1)) { // fully zoomed in and still attempting a zoom in + return; + } + + this.scroller.zoomTo(this._magnification * magnification, this._options.animateZoom); + + setTimeout(function(){ + self._onZoom(); + }, this._options.animationDuration); + } + + MagnifierImplScroller.prototype._onZoom = function(){ + var oldMagnification = this._magnification; + + this._magnification = Number(this.scroller.getValues().zoom.toFixed(2)); + this._updateButtonStates(); + + if (oldMagnification > this._magnification) { + this._$el.trigger("scroller.after-zoom-out"); + } else if (oldMagnification < this._magnification) { + this._$el.trigger("scroller.after-zoom-in"); + } + }; + + MagnifierImplScroller.prototype._renderer = function(){ + var docStyle = document.documentElement.style, self = this; + + var engine; + if (window.opera && Object.prototype.toString.call(opera) === '[object Opera]') { + engine = 'presto'; + } else if ('MozAppearance' in docStyle) { + engine = 'gecko'; + } else if ('WebkitAppearance' in docStyle) { + engine = 'webkit'; + } else if (typeof navigator.cpuClass === 'string') { + engine = 'trident'; + } + + var vendorPrefix = { + trident: 'ms', + gecko: 'Moz', + webkit: 'Webkit', + presto: 'O' + }[engine]; + + var helperElem = document.createElement("div"); + var undef; + + var perspectiveProperty = vendorPrefix + "Perspective"; + var transformProperty = vendorPrefix + "Transform"; + + if (helperElem.style[perspectiveProperty] !== undef) { + return function(left, top, zoom) { + self._content.style[transformProperty] = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')'; + }; + + } else if (helperElem.style[transformProperty] !== undef) { + return function(left, top, zoom) { + self._content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')'; + }; + + } else { + return function(left, top, zoom) { + self._content.style.marginLeft = left ? (-left/zoom) + 'px' : ''; + self._content.style.marginTop = top ? (-top/zoom) + 'px' : ''; + self._content.style.zoom = zoom || ''; + }; + } + }; + + MagnifierImplScroller.prototype._onResize = function(){ + this.scroller.setDimensions(this._container.clientWidth, this._container.clientHeight, this._container.clientWidth, this._container.clientHeight); + }; + + MagnifierImplScroller.prototype._installEventHandlers = function(){ + var self = this, clickTracker = new ClickTracker(this._imgCast, function(){ + self.cyclicZoom(); + }); + + window.addEventListener("resize", function(e){ + self._onResize(e); + }, false); + + this._zoomInBtn.addEventListener("click", function(e) { + e.preventDefault(); + self.zoomIn(); + }, false); + + this._zoomOutBtn.addEventListener("click", function(e) { + e.preventDefault(); + self.zoomOut(); + }, false); + + if ('ontouchstart' in window) { + self._container.addEventListener("touchstart", function(e) { + // Don't react if initial down happens on one of the zoom buttons + if (e.touches[0].target == self._$el.find('a.scroller-zoom-in').get(0) || e.touches[0].target == self._$el.find('a.scroller-zoom-out').get(0)) { + return; + } + + // Don't react if initial down happens on a form element + if (e.touches[0] && e.touches[0].target && e.touches[0].target.tagName.match(/input|textarea|select/i)) { + return; + } + + self.scroller.doTouchStart(e.touches, e.timeStamp); + + for (var i = 0; i < e.touches.length; i++) { + clickTracker.onDown(e.touches[i]); + } + + e.preventDefault(); + }, false); + + document.addEventListener("touchmove", function(e) { + self.scroller.doTouchMove(e.touches, e.timeStamp, e.scale); + clickTracker.onMove(); + }, false); + + document.addEventListener("touchend", function(e) { + self.scroller.doTouchEnd(e.timeStamp); + + // handle any zoom that may have occured + self._onZoom(); + + for (var i = 0; i < e.changedTouches.length; i++) { + clickTracker.onUp(e.changedTouches[i]); + } + }, false); + + document.addEventListener("touchcancel", function(e) { + self.scroller.doTouchEnd(e.timeStamp); + // handle any zoom that may have occured + self._onZoom(); + + for (var i = 0; i < e.changedTouches.length; i++) { + clickTracker.onUp(e.changedTouches[i]); + } + }, false); + } else { + self._container.addEventListener("mousedown", function(e) { + if (e.target.tagName.match(/input|textarea|select/i)) { + return; + } + + if (self.scroller.__clientWidth === 0) { + self._onResize(); + } + self.scroller.doTouchStart([{ + pageX: e.pageX, + pageY: e.pageY + }], e.timeStamp); + + clickTracker.onDown(e); + }, false); + + document.addEventListener("mousemove", function(e) { + if (!clickTracker.isDown) { + return; + } + + self.scroller.doTouchMove([{ + pageX: e.pageX, + pageY: e.pageY + }], e.timeStamp); + + clickTracker.onMove(); + }, false); + + document.addEventListener("mouseup", function(e) { + if (!clickTracker.isDown) { + return; + } + + self.scroller.doTouchEnd(e.timeStamp); + clickTracker.onUp(e); + }, false); + + // self._container.addEventListener(navigator.userAgent.indexOf("Firefox") > -1 ? "DOMMouseScroll" : "mousewheel", function(e) { + // e.preventDefault(); + // self.scroller.doMouseZoom(e.detail ? (e.detail * -120) : e.wheelDelta, e.timeStamp, e.pageX, e.pageY); + // }, false); + } + }; + + MagnifierImplScroller.prototype._updateButtonStates = function(){ + if (this._magnification === 1) { + $(this._$el).addClass('scroller-zoom-out-max'); + $(this._$el).removeClass('scroller-zoom-in-max'); + } else if (this._magnification === this._options.magnification) { + $(this._$el).addClass('scroller-zoom-in-max'); + $(this._$el).removeClass('scroller-zoom-out-max'); + } else { + $(this._$el).removeClass('scroller-zoom-in-max'); + $(this._$el).removeClass('scroller-zoom-out-max'); + } + }; + + var ex = (typeof exports === 'undefined') ? window : exports; + ex.Akamai = ex.Akamai || {}; + ex.Akamai.MagnifierImplScroller = MagnifierImplScroller; +})(); +(function(exports, $){ + /** + * Image magnifier + * @class + * @alias Akamai.Magnifier + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Magnifier = Akamai.Util.component("Magnifier", function(element, options){ + // compat with enlarge `disabled` option + this._options.disabled = !this._options.enabled; + if (this._options.mode === Magnifier.MODE_ANIMATED_ZOOM) { + this._impl = new Akamai.MagnifierImplScroller(element, options); + } else { + this._impl = new Akamai.MagnifierImplEnlarge(element, options); + } + }); + + Magnifier.prototype._updateOptions = function(options){ + if( !options ) { + this._options = this._originalOptions; + } + + this._options = Akamai.Util.extend(true, this._options, options); + + // compat with enlarge `disabled` option + this._options.disabled = !this._options.enabled; + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Magnifier.defaultOptions, this._options, "Magnifier"); + + this._impl.updateOptions(this._options); + }; + + // Used in preflight to "rename" events based on the child component events + Magnifier._componentEventMapping = { + "enlarge.after-zoom-in": "in", + "enlarge.after-zoom-out": "out", + "scroller.after-zoom-in": "in", + "scroller.after-zoom-out": "out" + }; + + Magnifier.MODE_HOVER_ZOOM = 'hoverzoom'; + Magnifier.MODE_ANIMATED_ZOOM = 'animatedzoom'; + + /** + * Defines the global default options for all Magnifiers on the page + * @static + * @property {String} mode - Set what mode to run the magnifier in. There are two modes available: `hoverzoom` and `animatedzoom`. The `hoverzoom` mode provides the ability to magnify and pan the image by just hovering your mouse over the image. It also provides a `flyout` option where a clip of the zoomed in image is displayed on a separate widget floating somewhere around (configurable) the image. The `animatedzoom` mode only does inline magnification but provides smooth animation between magnification levels, it also allows for incremental zooming. + * @property {Boolean} button - Whether to show a button for toggling magnification (default: true) + * @property {Boolean} enabled - Enabled/disable magnification (default: true, breakpoints supported) + * @property {Integer} magnification - The scale factor to magnify the image: `2`, `3` (default), `4`, `4.5`, etc + + * @property {Array} image.widths - List of available widths for an image (to be combined with image.widthParam) - (default: ["320","640","800","1024","2048","5000"]) + * @property {String} image.sizes - Value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw` (default), `200px`, `(min-width:1000px) 500px, 100vw` - (default: `100vw`, breakpoints supported) + * @property {String} image.policy - Query param value for policy, appended to &policy= per image url when specified. Values: `foo`. (default: undefined) + + * @property {Integer} delay - Only applicable in `hoverzoom` mode. The time delay in milliseconds between mouse hover and magnification (default: 300, breakpoints supported) + * @property {String} buttonText - Only applicable in `hoverzoom` mode. Text for the zoom button. Also used for its title attribute. (default: "Toggle Image Magnification") + * @property {Integer} flyout.width - Only applicable in `hoverzoom` mode. Width of the flyout image (default: 200) + * @property {Integer} flyout.height - Only applicable in `hoverzoom` mode. Height of the flyout image (default: 200) + * @property {Boolean} hoverZoomWithoutClick - Only applicable in `hoverzoom` mode. Zoom starts on mouse hover with no click needed (default: true; false will require a click to hover-zoom) + * @property {String} placement - Only applicable in `hoverzoom` mode. Placement of the magnified image: `inline` , `flyoutloupe`, `flyouttopleft`,`flyoutbottomleft` ,`flyouttopright` and `flyoutbottomright` - (default: inline, breakpoins supported) + + * @property {Float} incrementalZoomFactor - Only applicable in `animatedzoom` mode. A number by which to incrementally zoom up until the specified `magnification`, default `3`. For example a `magnification` of `4` and an `incrementalZoomFactor` of `2` will zoom the image in `2` steps + * @property {Boolean} animateZoom - Only applicable in `animatedzoom` mode. Animates the magnification process. Default `true` + * @property {Integer} animationDuration - Only applicable in `animatedzoom` mode. If `animateZoom` is true, this specifies the length of the animation in milliseconds. Default `250` + * @property {String} zoomInBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom in button. Default `Zoom In` + * @property {String} zoomOutBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom out button. Default `Zoom Out` + */ + Magnifier.defaultOptions = { + // general options + mode: Magnifier.MODE_HOVER_ZOOM, + button: true, + enabled: true, + magnification: 3, + + // image options + image: Akamai.Image.defaultConfig, + + // hoverzoom options + delay: 300, + buttonText: "Toggle Image Magnification", + flyout: { + width: 200, + height: 200 + }, + hoverZoomWithoutClick: true, + placement: "inline", + + // animatedzoom options + incrementalZoomFactor: 3, + animateZoom: true, + animationDuration: 250, + zoomInBtnText: 'Zoom In', + zoomOutBtnText: 'Zoom Out' + }; + + // srcset stuff + Akamai.Sourceable.extendStatic(Magnifier); + + Magnifier.render = function(json, options){ + var finalOptions = Akamai.Util.options(Magnifier.defaultOptions, options); + var dataAttr = Magnifier._dataAttr; + var fallbackSrc = Magnifier._fallbackSrc(json.url, finalOptions.image); + var largestSrc = Magnifier._largestSrc(json.url, finalOptions.image); + var srcSet = Magnifier._srcset(json.url, finalOptions.image) + var sizes = finalOptions.image.sizes; + var impl = (finalOptions.mode === Magnifier.MODE_ANIMATED_ZOOM) ? Akamai.MagnifierImplScroller : Akamai.MagnifierImplEnlarge; + return impl.render(finalOptions, dataAttr, fallbackSrc, largestSrc, srcSet, sizes); + }; + + /** + * Enter magnifier mode + * @method + * @fires Akamai.Magnifier#akamai-magnifier-in + * @returns {undefined} + */ + Magnifier.prototype.in = function(){ + this._impl.zoomIn(); + }; + + /** + * Exit magnifier mode + * @method + * @fires Akamai.Magnifier#akamai-magnifier-out + * @returns {undefined} + */ + Magnifier.prototype.out = function(){ + this._impl.zoomOut(); + }; + + /** + * Return the current state of the magnifier + * @method + * @returns {Boolean} + */ + Magnifier.prototype.isMagnified = function(){ + return this._impl.isMagnified(); + }; + + /** + * Toggle the state of the magnifier + * @method + * @fires Akamai.Magnifier#akamai-magnifier-in + * @fires Akamai.Magnifier#akamai-magnifier-out + * @returns {undefined} + */ + Magnifier.prototype.toggle = function(){ + this._impl.toggleZoom(); + }; + + /** + * Triggered when the magnifier has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carouse#in}. + * + * @event Akamai.Magnifier#akamai-magnifier-in + */ + + /** + * Triggered when the magnifier has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carouse#out}. + * + * @event Akamai.Magnifier#akamai-magnifier-out + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Magnifier}. + * + * @event Akamai.Magnifier#akamai-magnifier-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Magnifier = Magnifier; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + var VideoImpl = Akamai.Util.component("VideoImpl", function(element, options){ + throw new Error('Not allowed to instantiate VideoImpl'); + }); + + VideoImpl.render = function(json, options){ + throw new Error('VideoImpl.render not implemented'); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.VideoImpl = VideoImpl; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + var VideoImplPristine = Akamai.Util.component("VideoImplPristine", function(element, options){ + }); + + VideoImplPristine.prototype = Object.create(Akamai.VideoImpl.prototype); + VideoImplPristine.prototype.constructor = VideoImplPristine; + + VideoImplPristine.render = function(json, options){ + var finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options); + + var url = json.url; + var poster = json.poster || ""; + var mime = (json.mime) ? 'type="' + json.mime + '"' : ''; + + var attrs = [ + finalOptions.loop ? "loop" : "", + finalOptions.autoplay ? "autoplay" : "", + finalOptions.controls ? "controls" : "", + finalOptions.muted ? "muted" : "", + "playsinline" + ]; + + return Akamai.Util.trim(` +
+ +
+ `); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.VideoImplPristine = VideoImplPristine; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + var VideoImplIm = Akamai.Util.component("VideoImplIm", function(element, options){ + var finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options); + VideoImplIm._validateOptions(finalOptions); + }); + + VideoImplIm.prototype = Object.create(Akamai.VideoImpl.prototype); + VideoImplIm.prototype.constructor = VideoImplIm; + + VideoImplIm._validateOptions = function(options){ + if (!Array.isArray(options.sizes) || options.sizes.length !== 3) { + throw new Error('Akamai.Video sizes must have three entries'); + } + + if (options.sizes.some(function (size) { + return isNaN(size); + })) { + throw new Error('Akamai.VideoImplIm all sizes must be numbers'); + } + } + + VideoImplIm.render = function(json, options){ + var finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options); + VideoImplIm._validateOptions(finalOptions); + + var url = json.url; + var poster = json.poster || ""; + var width; + + var viewPortWidth = window.innerWidth; + + if (viewPortWidth < 992) { + width = finalOptions.sizes[0]; + } else if (viewPortWidth < 1200) { + width = finalOptions.sizes[1]; + } else { + width = finalOptions.sizes[2]; + } + + var attrs = [ + finalOptions.loop ? "loop" : "", + finalOptions.autoplay ? "autoplay" : "", + finalOptions.controls ? "controls" : "", + finalOptions.muted ? "muted" : "", + "playsinline" + ]; + + var joiner = url.indexOf('?') === -1 ? '?' : '&'; + + return Akamai.Util.trim(` +
+ +
+ `); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.VideoImplIm = VideoImplIm; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + /** + * Video component + * @class + * @alias Akamai.Video + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Video = Akamai.Util.component("Video", function(element, options){ + // TODO + this._comp = this._$el.length && undefined; + this._$videoElement = this._$el.find("video"); + this._videoElement = this._$videoElement[0]; + + if( !this._videoElement ){ + throw new Error( "Akamai.Video requires a child HTML Video element" ); + } + + if( !this._videoElement.play || !this._videoElement.pause ){ + this._unsupportedAPI = true; + this._unsupported(); + return; + } + + // get the initial state (could be autoplaying on render) + this._setState(this._videoElement.paused ? Video.States.Paused : Video.States.Playing); + + // state bindings + this._$el + .bind("akamai-video-play", function(){ + this._setState(Video.States.Playing); + }.bind(this)) + .bind("akamai-video-pause", function(){ + this._setState(Video.States.Paused); + }.bind(this)); + + this._createPlayButton(); + }); + + Video.MODE_IM = 'im'; + Video.MODE_PRISTINE = 'pristine'; + + Video._componentEventMapping = { + "play": { + to: "play", + selector: "video" + }, + + "pause": { + to: "pause", + selector: "video" + }, + + "seeked": { + to: "seek", + selector: "video" + } + }; + + Video.prototype._updateOptions = function(options){ + if( !options ) { + this._options = this._originalOptions; + } + + this._options = Akamai.Util.extend(true, this._options, options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Video.defaultOptions, this._options, "Video"); + + // TODO see Magnifier for example + }; + + /** + * Defines the global default options for all Spin360s on the page + * @static + * @property {Boolean} autoplay - Automatically play the video on load (default: false) + * @property {Boolean} controls - Display the video controls (default: true) + * @property {Boolean} loop - Restart the video when it reaches the end (default: false) + * @property {Boolean} muted - Mute the video (default: true) + * @property {String} mode - What video component to use. `im` will let you use any size pristine video and will automatically request & generate the right size when the page is loaded. `pristine` just passes through the original video into the video player and this is what will always play on the users device. Default: `pristine` + * @property {Array} sizes - Video sizes (widths) to use for different screen widths. Defaults: [1920 (large screens), 1280 (tablet), 854 (mobile), ] + */ + Video.defaultOptions = { + autoplay: false, + controls: true, + loop: false, + muted: true, + mode: Video.MODE_PRISTINE, + sizes: [854, 1280, 1920] + }; + + Video.render = function(json, options){ + var impl = (options && options.mode === Video.MODE_IM) ? Akamai.VideoImplIm : Akamai.VideoImplPristine; + return impl.render(json, options); + }; + + Video.States = { + Playing: 0, + Paused: 1 + }; + + Video.prototype._unsupported = function(){ + if( this._unsupportedAPI ){ + Akamai.Util.log("Video: video API not supported", 'error'); + } + + return this._unsupportedAPI; + }; + + /** + * Play the video, idempotent + * @method + * @fires Akamai.Video#akamai-video-play + * @returns {undefined} + */ + Video.prototype.play = function(){ + if( this._unsupported() ) { return; } + this._videoElement.play(); + }; + + /** + * Pause the video + * @method + * @fires Akamai.Video#akamai-video-pause + * @returns {undefined} + */ + Video.prototype.pause = function(){ + if( this._unsupported() ) { return; } + this._videoElement.pause(); + }; + + /** + * Return the current state of the video + * @example Video states + * Akamai.Video.States = { + * Playing: 0, + * Paused: 1 + * }; + * + * @example Conditioning on states + * if( video.getState() === Akamai.Video.States.Paused ) { + * video.play() + * } + * @method + * @returns {Akamai.Video.State} + */ + Video.prototype.getState = function(){ + return this._state; + }; + + Video.prototype._setState = function(value){ + this._state = value; + }; + + /** + * Seek to the input percentage. + * @method + * @fires Akamai.Video#akamai-video-seek + * @param {Integer} percent - value between 0 and 100 percent for seeking + * @returns {undefined} + */ + Video.prototype.seek = function(percent){ + if( percent < 0 || 100 < percent ) { + throw new Error( "seek takes a an integer between 0 and 100" ); + } + + var newTime = (percent / 100) * (this._videoElement.duration || 1); + this._videoElement.currentTime = newTime; + }; + + /** + * Set whether the video should loop or not + * @method + * @param {Boolean} value - The value true or false + * @returns {undefined} + */ + Video.prototype.setLoop = function(value){ + this._videoElement.loop = value; + }; + + Video.prototype.getElement = function(){ + return this._$el[0]; + }; + + // Borrowed from https://codepen.io/chrisnager/pen/jPrJgQ + Video.prototype._createPlayButton = function(){ + var videoPlayButton; + var videoWrapper = this._$el[0]; + var video = this._videoElement; + + if(this._options.autoplay){ + return; + } + + // TODO move to render? + videoWrapper.insertAdjacentHTML('beforeend', ` + + + + + `); + + video.removeAttribute('controls'); + videoPlayButton = this._$el.find('svg')[0]; + + videoPlayButton.addEventListener('click', function(){ + video.play(); + videoPlayButton.classList.add('is-hidden'); + if( this._options.controls ) { + video.setAttribute('controls', 'controls'); + } + }.bind(this)); + }; + + /** + * Triggered when the video has been played. See {@link Akamai.Video#play}. + * + * @event Akamai.Video#akamai-video-play + */ + + /** + * Triggered when the video has been paused. See {@link Akamai.Video#pause}. + * + * @event Akamai.Video#akamai-video-pause + */ + + /** + * Triggered when the video has seeked to a position. See {@link Akamai.Video#seek}. + * + * @event Akamai.Video#akamai-video-seek + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Video}. + * + * @event Akamai.Video#akamai-video-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Video = Video; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + /** + * Image carousel + * @class + * @alias Akamai.Carousel + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Carousel = Akamai.Util.component("Carousel", function(element, options){ + this._spins = Akamai.Spin360.createMany(this._$el[0], this._options.spin360); + this._magnifiers = Akamai.Magnifier.createMany(this._$el[0], this._options.magnifier); + this._videos = Akamai.Video.createMany(this._$el[0], this._options.video); + + this._$el.snapper(this._options); + + this._setAspectRatio(); + this._bindAspectAttributes(); + + if( this._options.slideshow.autostart ){ + setTimeout(function(){ + this.startSlideshow(); + }.bind(this), this._options.slideshow.interval); + } + + // NOTE !! the following two bindings must happen in order, the + // autoplayables binding relies on the attribut + + // add item attrs and maintain the active item attributes to determine the + // active item state + this._activeItemAttributes(); + this._$el.bind( "akamai-carousel-goto", this._activeItemAttributes.bind(this)); + + // TODO pause autoplay videos that are not visible + this._handleAutoplayables(); + this._$el.bind( "akamai-carousel-goto", this._handleAutoplayables.bind(this)); + + this._$el + .bind( "tau.touch-tracking-start", function(){ + this._$el.find(".snapper_pane").addClass("no-scroll"); + }.bind(this)) + .bind( "tau.touch-tracking-stop", function(){ + this._$el.find(".snapper_pane").removeClass("no-scroll"); + }.bind(this)); + + // when child components are doing things, stop the carousel from + // automatically advancing + this._$el.bind( this.constructor._stopSlideshowEvents.join(" "), this.stopSlideshow.bind(this)); + }); + + Carousel._stopSlideshowEvents = [ + "akamai-magnifier-in", + "akamai-magnifier-out", + "akamai-spin360-goto", + "akamai-video-play", + "akamai-video-seek" + ]; + + // clearly there should be an autoplayable interface + Carousel.prototype._handleAutoplayables = function(){ + var isParentActive = function(comp){ + return !!$(comp.getElement()) + .closest("[" + this.constructor.activeItemAttr + "]") + .length; + }.bind(this); + + this._videos.concat(this._spins).forEach(function(comp){ + if( isParentActive(comp) && comp._carouselWasPlaying ) { + comp.play(); + } else { + if( comp.getState() == comp.constructor.States.Playing ){ + comp._carouselWasPlaying = true; + comp.pause(); + } else { + comp._carouselWasPlaying = false; + } + } + }.bind(this)); + }; + + // Unique counter for IDs + Carousel.counter = 0; + + Carousel.prototype._updateOptions = function(options){ + if( !options ) { + this._options = this._originalOptions; + } + + // update the current options + this._options = Akamai.Util.extend(true, this._options, options); + + // update the options for each of the subcomponents + var update = function(comp, name){ + comp._updateOptions(this._options[name]); + }.bind(this); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Carousel.defaultOptions, this._options, "Carousel"); + this._$el.snapper( "updateOptions", options ); + + this._spins.forEach(function(c){ update(c, "spin360"); }); + this._magnifiers.forEach(function(c){ update(c, "magnifier"); }); + this._videos.forEach(function(c){ update(c, "video"); }); + + this._setAspectRatio(); + }; + + // Used in preflight to "rename" events based on the child component events + Carousel._componentEventMapping = { + "snapper.after-snap": "goto", + "snapper.snap": "snap", + "snapper.after-next": "next", + "snapper.after-prev": "previous" + }; + + /** + * Defines the global default options for all Carousels on the page + * @static + * @property {Boolean} arrows - Show carousel controls - (default: true) + * @property {Number} aspectratio - Specify a percentage-based height for the carousel, relative to the width. Values: `false`, `100`, `45.6`, `78` - (default `false`, breakpoints supported) + * @property {Integer} slideshow.interval - Time in milliseconds between slide advances - (default: 4000, breakpoints supported) + * @property {Integer} slideshow.autostart - Start the slideshow on instantiation - (default: false) + * @property {String} thumbnail.placement - Placement relative to the slide container: `left`, `right`, `bottom` (default: `bottom`, breakpoints supported) + * @property {String} thumbnail.type - Type of thumbnail: `dots`, `none`, `images` - (default: `images`, breakpoints supported) + * @property {String} thumbnail.policy - String to use for query parameter, ex: "&policy=" (default: undefined) + * @property {String} thumbnail.sizes - Sizes attribute value to use if thumbnail policy is set. Values: `300px`, `200px`, `(min-width:1000px) 300px, 100px` - (default: `300px`) + * @property {Object} images - Defaults to the Magnifier image option's settings + */ + Carousel.defaultOptions = { + arrows: true, + + aspectratio: false, + + slideshow: { + interval: 4000, + autostart: false + }, + + thumbnail: { + placement: "bottom", + type: "images", + policy: undefined, + sizes: "300px" + }, + + images: Akamai.Image.defaultConfig + }; + + Carousel.renderMapping = { + image: Akamai.Magnifier, + spin360: Akamai.Spin360, + video: Akamai.Video + }; + + Carousel._typeMapping = { + image: "magnifier" + }; + + Carousel._uniqueItemId = function(item, i) { + return "akamai-carousel-" + Carousel.counter + "-" + item.type + "-" + i; + }; + + // srcset stuff + Akamai.Sourceable.extendStatic(Carousel); + + Carousel._renderItem = function(item, i, options) { + var mappedType = Carousel._typeMapping[item.type] || item.type; + + if( !Carousel.renderMapping[item.type] ){ + throw new Error(` + item type ${item.type} at index ${i} must be 'image', 'spin360', or 'video' + `); + } + + return ` +
+ ${Carousel.renderMapping[item.type].render(item, options[mappedType])} +
+ `; + }; + + Carousel._renderThumbnailAnchor = function(item, data, i, options) { + var thumbUrl = ""; + var altText = item.alt || ""; + + // use the video poster, the canonical url, or the first in a sequence + if( item.type === 'video' ){ + thumbUrl = item.poster || data.reduce(function(acc, i){ + return acc || Carousel._thumbUrl(i); + }, ""); + } else { + thumbUrl = Carousel._thumbUrl(item); + } + + // TODO this sucks + options.images.policy = options.thumbnail.policy; + + var src = Carousel._fallbackSrc( thumbUrl, options.images ); + var srcset = Carousel._srcset( thumbUrl, options.images ); + var sizes = options.images.sizes; + + // if there's a thumbnail policy, the thumbnails will be fresh image requests, + // so they should have better sizes attribute values + if( options.thumbnail.policy ){ + sizes = options.thumbnail.sizes; + } + + var attrs = [ + `${Carousel._dataAttr}-thumb-type="${item.type}"`, + item.type == "video" && !item.poster ? "${Carousel._dataAttr}-thumb-noposter" : "" + ]; + + return ` + + ${altText} + + `; + }; + + Carousel._thumbUrl = function(item){ + return item.type === 'video' ? item.poster : item.url || (item.urls && item.urls[0]); + }; + + Carousel._renderThumbnails = function(data, options) { + if(data.length <= 1 ){ + return ""; + }; + + return ` +
+
+ ${Akamai.Util.map(data, function(item, i){ + return Carousel._renderThumbnailAnchor(item, data, i, options); + }).join("\n")} +
+
+ `; + }; + + Carousel.prototype._setAspectAttributes = function( $el ){ + var orientationAttr = `${Carousel._dataAttr}-aspectratio-orientation`; + if( $el.height() > $el.width() ) { + $el.attr( orientationAttr, "portrait" ); + } else if( $el.height() < $el.width() ) { + $el.attr( orientationAttr, "landscape" ); + } else { + if ($el.parent().height() >= $el.parent().width()) { + $el.attr( orientationAttr, "landscape" ); + } else { + $el.attr( orientationAttr, "portrait" ); + } + } + }; + + Carousel.prototype._setAspectRatio = function(){ + var value = this._options.aspectratio || 0; + var $items = this._$el.find( ".snapper_item" ); + $items.css( "padding-top", (value / $items.length) + "%" ); + }; + + Carousel.prototype._bindAspectAttributes = function(){ + if( this._options.aspectratio === false ){ + return; + } + + var value = this._options.aspectratio || 0; + var self = this; + var $items = this._$el.find( ".snapper_item" ); + + $items.each(function(){ + var loadBound; + var $item = $(this); + + var $loadable = $item.find( "img, video" ).eq(0); + + $loadable.bind("load loadedmetadata", loadBinding = function(){ + clearTimeout(loadBound); + + if( $item.is(`[${Akamai.Spin360._dataAttr}]`) ){ + // TODO it's not always a canvas, sometimes it's a collection of + // images based on settings + self._setAspectAttributes( $item.find( "canvas" ) ); + } else{ + self._setAspectAttributes( $loadable ); + } + }); + + loadBound = setTimeout(loadBinding, 5000); + }); + + $items.eq(0).find( "img,video" ).eq(0).bind( "load loadedmetadata", function(){ + self._trigger( "first-media-load" ); + }); + }; + + Carousel.render = function(data, options){ + options = options || {}; + + // establish the extended default options for the carousel + var carouselOptions = Akamai.Util.options(Carousel.defaultOptions, options); + + var templateAttrs = [ + `${Carousel._dataAttr}`, + `${Carousel._dataAttr}-item-count="${(data || []).length}"`, + `data-snapper-deeplinking="false"`, + carouselOptions.arrows ? "data-snapper-nextprev" : "" + ]; + + Carousel.counter++; + + // TODO can we do something about the `enlarge_pane` class here, seems it + // could be conditioned on at least one regular image type in the json + // TODO options should dictate `data-snapper-nextprev` + return Akamai.Util.trim(` +
+
+
+
+
+ ${Akamai.Util.map(data, function(item, i){ + return Carousel._renderItem(item, i, carouselOptions); + }).join("\n")} +
+
+
+
+ + ${Carousel._renderThumbnails(data, carouselOptions)} +
+ `); + }; + + /** + * Instantiate Carousels by looking for children matching + * [data-akamai-carousel] in `element` param + * @method + * @static + * @param {HTMLElement} element - the element to search in for + * @returns {Akamai.Carousel[]} + */ + + + /** + * @method + * @returns {Akamai.Spin360[]} + */ + Carousel.prototype.getSpin360s = function(){ + return this._spins; + }; + + /** + * @method + * @returns {Akamai.Magnifier[]} + */ + Carousel.prototype.getMagnifiers = function(){ + return this._magnifiers; + }; + + /** + * @method + * @returns {Akamai.Video[]} + */ + Carousel.prototype.getVideos = function(){ + return this._videos; + }; + + /** + * Go to a particular slide. + * + * Note that the state of the DOM on the page and thus the index of the + * carousel will not be up-to-date until the {@link + * Akamai.Carousel#akamai-carousel-goto} event has been fired. That is, + * calling this method and the calling `getIndex` will not necessarily result + * in an updated index value. Either bind to the event or supply a callback. + * + * @method + * @fires Akamai.Carousel#akamai-carousel-goto + * @param {Integer} index - The zero-based slide index to go to + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Carousel.prototype.goto = function(index, callback){ + this._$el.snapper( "goto", index, callback ); + }; + + Carousel._itemAttr = `${Carousel._dataAttr}-item`; + Carousel._activeItemAttr = `${Carousel._itemAttr}-active`; + + /** + * Add an attribute to all carousel items, + * and additionally maintain an active attribute on the active carousel item + * + * @method + * @returns {undefined} + */ + Carousel.prototype._activeItemAttributes = function(){ + var attrAllItems = this.constructor._itemAttr; + var attrActiveItem = this.constructor._activeItemAttr; + + this._$el + .find( ".snapper_item" ) + .attr( attrAllItems, true ) + .removeAttr( attrActiveItem ) + .eq( this.getIndex() ).attr( attrActiveItem, true ); + }; + + /** + * Return the current slide index + * @method + * @returns {undefined} + */ + Carousel.prototype.getIndex = function(){ + return this._$el.snapper( "getIndex" ); + }; + + // Extend Carousel with Advanceabl interface + Akamai.Advanceable.extend(Carousel); + + /** + * Advance to the next item + * @method + * @fires Akamai.Carousel#akamai-carousel-next + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Carousel.prototype.next; + // required for JSDocs, implementation in Advanceable + + /** + * Retreat to the previous item + * @method + * @fires Akamai.Carousel#akamai-carousel-previous + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Carousel.prototype.previous; + // required for JSDocs, implementation in Advanceable + + /** + * Start automatic advancement of the carousel items + * @method + * @fires Akamai.Carousel#akamai-carousel-start-slideshow + * @returns {undefined} + */ + Carousel.prototype.startSlideshow = function(){ + this._$el.one("mousedown touchstart", this.stopSlideshow.bind(this)); + + this.next(function(){ + this._trigger("start-slideshow"); + this._slideshowTimer = setTimeout(function(){ + this.startSlideshow(); + + // TODO remove || when default options are added + }.bind(this), this._options.slideshow.interval ); + }.bind(this)); + }; + + /** + * Start automatic advancement of the carousel items + * @method + * @fires Akamai.Carousel#akamai-carousel-stop-slideshow + * @returns {undefined} + */ + Carousel.prototype.stopSlideshow = function(){ + clearTimeout(this._slideshowTimer); + this._slideshowTimer = undefined; + this._trigger("stop-slideshow"); + }; + + /** + * Triggered when the carousel has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carousel#goto}. + * + * @event Akamai.Carousel#akamai-carousel-goto + */ + + /** + * Triggered when the carousel has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carousel#next}. + * + * @event Akamai.Carousel#akamai-carousel-next + */ + + /** + * Triggered when the carousel has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carousel#previous}. + * + * @event Akamai.Carousel#akamai-carousel-previous + */ + + /** + * Triggered when the carousel has started the slide show due to a call to + * {@link Akamai.Carousel#startSlideshow}. + * + * @event Akamai.Carousel#akamai-carousel-start-slideshow + */ + + /** + * Triggered when the carousel has stoped the slide show due to a call to + * {@link Akamai.Carousel#stopSlideshow}. + * + * @event Akamai.Carousel#akamai-carousel-stop-slideshow + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Carousel}. + * + * @event Akamai.Carousel#akamai-carousel-init + */ + + Carousel.prototype.refresh = function(){ + this._$el.snapper("updateWidths"); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Carousel = Carousel; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + /** + * Image Fullscreen + * @class + * @alias Akamai.Fullscreen + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + + // TODO JSDocs + // TODO Tests + var Fullscreen = Akamai.Util.component("Fullscreen", function(element, options){ + if( this._options.enabled ) { + this._init(); + } + }); + + Fullscreen.prototype._init = function(){ + this._$fullscreen = this._$el; + + // parent container does not allow widths to be set (Firefox, fullscreen), so we’ll adjust the children + this.addButton(); + + this._fullscreenApiKeys = Fullscreen._keys(); + + if( this._options.native ) { + this._useNativeApi = !!this._fullscreenApiKeys; + } else { + this._useNativeApi = false; + } + + // Fullscreen API is disabled and the fallback behavior is active + this._fullscreenFallbackEnabled = false; + this._$placeholder = $( "
" ); + + this._isFullscreen = false; + this.addEvents(); + }; + + Fullscreen.classes = { + btn: "akamai-fullscreen-btn", + btnContainer: "akamai-fullscreen-btncontainer", + enterBtn: "akamai-fullscreen-btn-enter", + exitBtn: "akamai-fullscreen-btn-exit", + active: "akamai-fullscreen-active", + fallback: "akamai-fullscreen-fallback" + }; + + Fullscreen.attr = { + width: "data-akamai-fullscreen-width", + enterBtn: "data-akamai-fullscreen-btn", + exitBtn: "data-akamai-fullscreen-exit-btn" + }; + + Fullscreen.defaultOptions = { + enabled: false, + native: false + }; + + Fullscreen.prototype.addButton = function(){ + this._$widthAdjust = this._$fullscreen.children().filter(".focused"); + + if( !this._$widthAdjust.length ) { + this._$widthAdjust = this._$fullscreen + .children() + .eq(0); + } + + if( this._$widthAdjust.attr(Fullscreen.attr.width) === null ){ + this._$widthAdjust.attr( Fullscreen.attr.width, "" ); + } + + if( this._$widthAdjust.find("[" + Fullscreen.attr.enterBtn + "]").length ){ + return; + } + + // buttons parent + this._$buttonContainer = this._$widthAdjust; + this._$buttonContainer.addClass( Fullscreen.classes.btnContainer ); + + this._$buttonContainer.append(this.render()); + }; + + Fullscreen.prototype.addEvents = function() { + this._$fullscreen.on( "click", function( e ) { + var $target = $( e.target ); + if( !$target.is("[" + Fullscreen.attr.enterBtn + "]") ){ + return; + } + + this.enter(); + e.preventDefault(); + }.bind( this )); + + this._$fullscreen.on( "click", function( e ) { + var $target = $( e.target ); + + if( !$target.is( "[" + Fullscreen.attr.exitBtn + "]" ) ){ + return; + } + + this.exit(); + e.preventDefault(); + }.bind( this )); + + if( this._useNativeApi ) { + document.addEventListener( this._fullscreenApiKeys.onchange, function() { + // exiting fullscreen using native method (ESC or menu option) + if( !document[ this._fullscreenApiKeys.element ] ) { + this._exit(); + } + }.bind( this ), false ); + } + + // ESC to close + $( document ).on( "keydown", function( e ) { + var code = e.keyCode || e.which; + if( code === 27 ) { + this._exit(); + } + }.bind( this )); + }; + + Fullscreen.prototype.render = function() { + return Akamai.Util.trim(` + + + `); + }; + + Fullscreen._keyLookup = [ + { + enter: "requestFullscreen", + exit: "exitFullscreen", + element: "fullscreenElement", + onchange: "fullscreenchange" + }, + { + enter: "webkitRequestFullscreen", + exit: "webkitExitFullscreen", + element: "webkitFullscreenElement", + onchange: "webkitfullscreenchange" + }, + { + enter: "webkitRequestFullScreen", + exit: "webkitCancelFullScreen", + element: "webkitCurrentFullScreenElement", + onchange: "webkitfullscreenchange" + }, + { + enter: "mozRequestFullScreen", + exit: "mozCancelFullScreen", + element: "mozFullScreenElement", + onchange: "mozfullscreenchange" + }, + { + enter: "msRequestFullscreen", + exit: "msExitFullscreen", + element: "msFullscreenElement", + onchange: "MSFullscreenChange" + } + ]; + + Fullscreen._keys = function( el ) { + var keys = Fullscreen._keyLookup; + + var el = document.body; + for( var j = 0, k = keys.length; j < k; j++ ) { + if( keys[ j ].enter in el ) { + return keys[ j ]; + } + } + }; + + Fullscreen.prototype._maximizePlacement = function(){ + this._fullscreenFallbackEnabled = true; + this._$fullscreen.addClass( Fullscreen.classes.fallback ); + this._$placeholder.insertAfter( this._$fullscreen ); + this._$fullscreen.appendTo( document.body ); + }; + + Fullscreen.prototype._restorePlacement = function(){ + if( !this._fullscreenFallbackEnabled ) { + return; + } + + this._fullscreenFallbackEnabled = false; + this._$fullscreen.removeClass( Fullscreen.classes.fallback ); + this._$fullscreen.insertAfter( this._$placeholder ); + this._$placeholder.remove(); + }; + + Fullscreen.prototype._adjustWidth = function() { + this._$fullscreen.css( "width", "auto !important" ); + // wish this could go into the change event above, but alas the dimensions don’t measure right + var widthSmall = this._$fullscreen.width(); + var heightSmall = this._$fullscreen.height(); + + var viewportWidth = this._useNativeApi ? window.screen.width : Math.max( document.documentElement.clientWidth, window.innerWidth || 0 ); + var viewportHeight = this._useNativeApi ? window.screen.height : Math.max( document.documentElement.clientHeight, window.innerHeight || 0 ); + + // calculate the maximum width we can use to fill the viewportHeight + var widthBig = widthSmall * viewportHeight / heightSmall; + + // if the width is bigger than the maximum, just set to 100% (won’t fill the entire height, but that’s ok) + if( widthBig > viewportWidth ) { + widthBig = "100%"; + } + + this._$widthAdjust.width( widthBig ); + }; + + Fullscreen.prototype._revertWidth = function() { + this._$widthAdjust.css( "width", "" ); + }; + + /** + * Enter full screen mode + * @method + * @returns {undefined} + */ + Fullscreen.prototype.enter = function(){ + var activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex(); + this._$fullscreen.addClass( Fullscreen.classes.active ); + + + if( this._useNativeApi ) { + this._$fullscreen[ 0 ][ this._fullscreenApiKeys.enter ](); + this._adjustWidth(); + } else { + this._adjustWidth(); + this._maximizePlacement(); + } + this._isFullscreen = true; + this._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex); + this._trigger("enter"); + }; + + Fullscreen.prototype._exit = function(){ + var activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex(); + this._$fullscreen.removeClass( Fullscreen.classes.active ); + + if( !this._useNativeApi ) { + this._restorePlacement(); + } + + this._revertWidth(); + this._isFullscreen = false; + this._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex); + this._trigger("exit"); + }; + + /** + * Exits full screen mode + * @method + * @returns {undefined} + */ + Fullscreen.prototype.exit = function(){ + if( this._useNativeApi ) { + // note some close behavior happens in the onchange event handler above + document[ this._fullscreenApiKeys.exit ](); + } + + this._exit(); + }; + + Fullscreen.prototype.isFullscreen = function(){ + return this._isFullscreen; + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Fullscreen = Fullscreen; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + /** + * Akamai Viewer omni-component + * @class + * @alias Akamai.Viewer + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + * + * @example Instatiation + * var element = document.querySelector( "[data-akamai-viewer]" ); + * var viewer = Akamai.Viewer( element ); + */ + var Viewer = Akamai.Util.component("Viewer", true, function(element, options){ + // try to update the carousel and magnifier sizes options to the width of + // the viewer for more accurate srcset selection + this._updateSizesOptions(true); + this._onResizeComplete(this._updateSizesOptions.bind(this)); + + // move all the flattened component options (spin, zoom, video) onto the carousel + this._options.carousel = + Viewer._extendCarouselOptions(this._options); + + this._withData(function(data){ + if( data ) { + // check all of the urls agains the hostname and whitelist + this._checkJSONUrls(data); + + // prevent XSS / injection attacks by escaping string values + data = Akamai.Util.escapeJSONVals(data); + + // store escaped data for later reference + this._options.items.data = data; + } + + this._tagMapping = {}; + + // TODO if the element is empty and/or options has `items` render into element + if( this._$el.children().length == 0 && data ) { + this._tagMapping = this.constructor._tagSplit(data, this._options.items); + this._$el.append(this.constructor.render(data, this._options, true)); + } + + // intantiate all child carousels + this._carousels = Akamai.Carousel.createMany(element, this._options.carousel); + this._fullscreen = new Akamai.Fullscreen(element, this._options.fullscreen); + + // on enter and exit of fullscreen resolve the breakpoints and update options + this._$el.bind( "akamai-fullscreen-enter akamai-fullscreen-exit", function(){ + this._updateOptions(this._resolveBreakpointOptions()); + }.bind(this)); + + // use the breakpoints to set match media listeners + this._setBreakpoints(); + + // have to trigger init explicitly for async constructors + this._trigger("init"); + }.bind(this), function(msg){ + throw new Error(msg); + }); + }); + + Viewer.prototype._updateSizesOptions = function(localUpdateOnly){ + // update sizes option to something more specific if possible + var elWidth = this._$el.width(); + if( elWidth && elWidth > 0 ){ + elWidth += "px"; + this._options = Akamai.Util.extend(true, this._options, { + carousel: { + images: { + sizes: elWidth + } + } + }); + } + + if( !localUpdateOnly ){ + this._updateOptions(this._options); + } + }; + + Viewer.prototype._withData = function(after, fail){ + if(!this._options.items.uri){ + if( this._exceedsLimit(this._options.items.data, fail)) { + return; + } + + after(this._options.items.data); + } + + else if (typeof(this._options.items.uri) !== 'undefined' && this._options.items.uri.length > 0) + { + $.get(this._options.items.uri, function(data){ + // make sure the data gets sorted out regardless of `get` impl + data = (typeof data === "string" ? JSON.parse(data) : data); + + if( this._exceedsLimit(data.items, fail) ){ + return; + } + + after(data.items); + }.bind(this)); + } + }; + + // value in kibibytes + Viewer.prototype._exceedsLimit = function(data, fail){ + // TODO some tests don't provide data + if( !data ) { return false; } + + // 1 unicode character = 4 bytes + // length = # of chars + // kibibyte = 1024 bytes + // # of chars * 4 / 1024 + var kb = JSON.stringify(data).length * 4 / 1024; + + if( kb > this._options.items.limit ) { + fail = fail || function(){}; + fail(`JSON data size exceeds ${this._options.items.limit} KiB. Limit can be configured with items.limit Akamai.Viewer options.`); + return true; + } + + return false; + }; + + Viewer.prototype._setBreakpoints = function(){ + this._breakpoints = this._reduceBreakpoints(this._options.breakpoints || {}); + this._fullscreenBreakpoints = + this._reduceBreakpoints(this._options.fullscreenBreakpoints || {}); + + this._bindBreakpoints(); + }; + + Viewer.prototype._reduceBreakpoints = function(breakpoints){ + var bps = []; + + for( var bp in breakpoints ){ + if(breakpoints.hasOwnProperty(bp)){ + try { + bps.push(parseInt(bp, 10)); + } catch(e){ + Akamai.Util.log(e, 'error'); + } + } + } + + bps.sort(function(a, b){ return b < a;}); + + return bps; + }; + + Viewer.prototype._bindBreakpoints = function(){ + [this._breakpoints, this._fullscreenBreakpoints].forEach(function(bps){ + // bind using the minwidth and the next breakpoint as the maxwidth + // adds a range from 0 to the first breakpoint and from the last + // breakpoint to a very large number + if(!bps.length) { return; } + + for( var i = -1; i < bps.length; i++ ){ + this._bindMatchMedia(bps[i], bps[i+1]); + } + }.bind(this)); + }; + + // bind a callback to run after resize completes + Viewer.prototype._onResizeComplete = function( callback ){ + var cbtimer; + var self = this; + window.addEventListener( "resize", function(){ + clearTimeout( cbtimer ); + cbtimer = setTimeout( callback, 500 ); + }); + }; + + Viewer.prototype._bindMatchMedia = function(minWidth, maxWidth){ + minWidth = minWidth || 0; + + // large number so we can use one media query template + maxWidth = maxWidth || 1000000000; + + if( window.matchMedia ){ + // bind using the minwidth and maxwidth so we get the events at the boundaries + // so we can asses which set of options applies. + var query = `(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`; + + // create a media list to bind to + var initList = window.matchMedia(query); + + if(initList && initList.addListener) { + // handle a match on instantiation + this._mediaMatch(initList, minWidth, true); + + // bind for later changes in whether the media query matches + initList.addListener(function(list){ + this._mediaMatch(list, minWidth); + }.bind(this)); + } + } + }; + + Viewer.prototype._mediaMatch = function(list, minWidth, ignoreDefault){ + // If there is a match for the breakpoint (we're in the bp range) + // Then set the options based on that breakpoint + // Else if there is not a match and the client width is below the + // breakpoint that's being disabled, then use the original options + if(list.matches){ + var resolvedOptions = this._resolveBreakpointOptions(minWidth); + this._updateOptions(resolvedOptions); + } + }; + + // TODO a ton of duplication with bindMatchmedia + Viewer.prototype._getCurrentMinWidth = function(bps){ + // bind using the minwidth and the next breakpoint as the maxwidth + // adds a range from 0 to the first breakpoint and from the last + // breakpoint to a very large number + for( var i = -1; i < bps.length; i++ ){ + minWidth = bps[i] || 0; + maxWidth = bps[i+1] || 100000000; + + if( window.matchMedia ){ + // bind using the minwidth and maxwidth so we get the events at the boundaries + // so we can asses which set of options applies. + var query = `(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`; + + // create a media list to bind to + var initList = window.matchMedia(query); + + if(initList && initList.matches) { + return minWidth; + } + } + } + + return false; + }; + + Viewer.prototype._resolveBreakpointOptions = function(minWidth){ + // TODO bind on the reduce callback was not working + var self = this; + var bps, bpConfig; + + if( this._fullscreen.isFullscreen() ) { + bps = this._fullscreenBreakpoints; + bpConfig = self._options.fullscreenBreakpoints; + } else { + bps = this._breakpoints; + bpConfig = self._options.breakpoints; + } + + if(!minWidth){ + minWidth = self._getCurrentMinWidth(bps); + } + + // There may be no matching breakpoints when this method is called to + // resolve the current options. If that's the case then we need to default + // to the top level options + if(!bps.length){ return self._originalOptions; } + + return bps.reduce(function(acc, bp){ + var cloned = Akamai.Util.extend(true, {}, acc); + var ptions; + + + if(bp <= minWidth){ + cloned = Akamai.Util.extend(true, cloned, bpConfig[bp]); + } + + return cloned; + }, Akamai.Util.extend(true, {}, this._originalOptions)); + }; + + Viewer._extendCarouselOptions = function(options){ + // clone the carousel options for the given breakpoint + var clonedCarousel = Akamai.Util.extend(true, {}, options.carousel || {}); + + // TODO shared code with constructor + // extend the cloned options with the breakpoint options so that all the + // child component config is attached to the carousel config (also happens + // in the constructor ) + return Akamai.Util.extend(true, clonedCarousel, { + magnifier: options.magnifier, + spin360: options.spin360, + video: options.video, + fullscreen: options.fullscreen + }); + }; + + Viewer.prototype._updateOptions = function(options){ + this._options = Akamai.Util.extend(true, this._options, options); + + // extend the carousel options for the given breakpoint + var carouselOptions = Viewer._extendCarouselOptions(options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Viewer.defaultOptions, options, "Viewer"); + + // pass the new options down to all child carousels + this._carousels.forEach(function(comp){ + comp._updateOptions(carouselOptions); + }.bind(this)); + }; + + Viewer.prototype._checkJSONUrls = function(obj){ + var hostnames = this._options.items.hostnames; + + return Akamai.Util.mapJSONVals(obj,function(val, key){ + if(key === "url" && !this._urlHostnameMatch(val)){ + throw new Error(`The URL \`${val}\` does not match this page's hostname or the whitelist defined in Akamai.Viewer option \`hostnames\` which is: + +${hostnames.length ? hostnames.join("\n") : "No hostnames"} +`); + } + + return val; + }.bind(this)); + }; + + Viewer.prototype._urlHostnameMatch = function(url){ + var parser = document.createElement('a'); + parser.href = url; + + return parser.hostname === "" || parser.hostname === window.location.hostname || + this._options.items.hostnames.indexOf(parser.hostname) >= 0; + }; + + /** + * Defines the global default options for all Viewers on the page + * @static + * @property {Object} breakpoints - configuration changes for child components at breakpoints (no default) + * @property {Object} fullscreenBreakpoints - configuration changes for child components at breakpoints when in fullscreen mode (no default) + * @property {Object[]} items.data - array of items from the Akamai JSON (default: undefined) + * @property {String} items.defaultTag - (default: "akamai-untagged") + * @property {String[]} items.hostnames - whitelist of URL hostnames to check for in JSON, (default: empty array) + * @property {Integer} items.limit - size limit of JSON data in kibibytes (default: 100) + * @property {String} items.renderAll - (default: false) + * @property {String[]} items.tags - Set of tags to match against the Akamai JSON data (default: undefined) + * @property {String} items.uri - URI at which to retrieve the Akamai JSON (default: undefined) + * @property {Object} carousel - child {@link Akamai.Carousel} options + * @property {Object} magnifier - child {@link Akamai.Magnifier} options + * @property {Object} spin360 - child {@link Akamai.Spin360} options + * @property {Object} video - child {@link Akamai.Video} options + * @property {Object} fullscreen - child {@link Akamai.Fullscreen} options + */ + Viewer.defaultOptions = { + breakpoints: {}, + items: { + data: undefined, + defaultTag: "akamai-untagged", + hostnames: [], + limit: 100, + renderAll: false, + tags: undefined, + uri: undefined + }, + carousel: Akamai.Carousel.defaultOptions, + magnifier: Akamai.Magnifier.defaultOptions, + spin360: Akamai.Spin360.defaultOptions, + video: Akamai.Video.defaultOptions, + fullscreen: Akamai.Fullscreen.defaultOptions + }; + + Viewer._tagSplit = function(data, options){ + var items = options; + + // set all items without a tag to the default + data = data.map(function(item){ + item.tags = (item.tags && item.tags.length) ? item.tags : [items.defaultTag]; + return item; + }); + + // if the tags option was set, filter items out that don't match + if( items.tags ) { + data = items.data.filter(function(item){ + return item.tags.reduce(function(acc, tag){ + return acc || items.tags.indexOf(tag) >= 0; + }, false); + }); + } + + // from the filtered get the first (may be default tag) + var def = data[0].tags[0]; + + // create a mapping from tags to items in the set + var tagMapping = data.reduce(function(acc, item){ + (item.tags || []).forEach(function(tag){ + acc[tag] = acc[tag] || []; + acc[tag].push(item); + }); + + return acc; + }, {}); + + // set the first (default) object to the first tag + tagMapping[Viewer._firstRenderTag] = tagMapping[def]; + + return tagMapping; + }; + + Viewer._firstRenderTag = "akamai-first-render"; + Viewer._tagAttr = `${Viewer._dataAttr}-tag`; + + Viewer._renderTag = function(data, options){ + // get the first item and it's tag + // NOTE this assumes that the data has been normalized to have a default tag + var tag = data[0].tags[0]; + + // TODO shoestring doesn't treat html correctly unless the leading `<` has + // no whitepsace in front of it: + // shoestring/issues/94 + return Akamai.Util.trim(` +
+ ${Akamai.Carousel.render(data, options)} +
+ `); + }; + + /** + * Takes standard options including an `items` attribute and renders HTML + * that conforms to the component expectations + * @method + * @static + * @params {Object[]} json - Akamai JSON data + * @params {Object} options - options object with an `items` attribute, see default options + * @params {Boolean} options.items - configuration for how the items should be + * rendered, see {@link Akamai.Viewer.defaultOptions} default options items sub-configuration + * @returns { String } + */ + Viewer.render = function(json, options, unwrapped){ + // NOTE we do not extend the default options here because it has no bearing + // on the rendering of the Viewer markup or child markup. This is contrast + // to the carousel which does extend the passed options with defaults so the + // markup can make use of them + var data = Viewer._tagSplit(json, options.items); + var carouselOptions = Viewer._extendCarouselOptions(options); + + var childMarkup; + + if( options.items.renderAll ) { + childMarkup = data.map(function(acc, datum){ + return Viewer._renderTag(datum, carouselOptions); + }).join(" "); + } else { + childMarkup = Viewer._renderTag(data[Viewer._firstRenderTag], carouselOptions); + } + + var wrapped = ` +
+ ${childMarkup} +
+ `; + + return unwrapped ? childMarkup : wrapped ; + }; + + /** + * Accessor for child carousel components. + * @method + * @returns { Akamai.Carousel[] } + */ + Viewer.prototype.getCarousels = function(){ + return this._carousels; + }; + + /** + * Set the visible carousel based on the tag. Note the method activity does + * not complete until all the images from the relevant viewer have loaded, use + * the `akamai-viewer-switch-tag` event + * @method + * @fires Akamai.Viewer#akamai-viewer-switch-tag + * @params {String} tag - The tag corresponding to the desired carousel + * @returns {undefined} + */ + Viewer.prototype.switchTag = function(tag){ + var tags = this.getTags(); + + // if the passed tag isn't in the data set return early and log an error in + // the console + if( tags.indexOf(tag) == -1){ + Akamai.Util.log(`tag: ${tag} is not present in the data for this viewer`, 'error'); + return; + } + + var selector = `[${this.constructor._tagAttr}="${tag}"]`; + + // try to find an existing element with the tag + var $taggedViewer = this._$el.find(selector); + + // if there's no element append the newly rendered tag markup + if( !$taggedViewer.length ){ + // make sure the config takes into account the current breakpoint + var options = this._resolveBreakpointOptions(); + var carouselOptions = Viewer._extendCarouselOptions(options); + + // create the markup that will be inserted + var $markup = $(this.constructor._renderTag(this._tagMapping[tag], carouselOptions, true)); + var $imgs = $markup.find("img"); + var imgCount = $imgs.length; + var loaded = 0; + + // hide the new carousel initially + $markup.css("display", "none"); + + $imgs.bind("load", function(){ + if( ++loaded !== imgCount ) { + return; + } + + // TODO namespace? + $imgs.unbind("load"); + + var carousels = Akamai.Carousel.createMany($markup[0], carouselOptions); + + carousels.forEach(function(c){ + // TODO figure out why the carousel constructor doesn't apply the + // active index class we want on instantiation, likely due to the fact + // that the carousel is hidden so the "getIndex" calc is broken + // mark the carousel item as active + c.goto(c.getIndex()); + }); + + // create and store the new carousels (should be one) + this._carousels = this._carousels.concat(carousels); + this._showViewer($markup, carousels); + }.bind(this)); + + // append the new markup to the existing viewer + this._$el.append($markup); + } else { + this._showViewer($taggedViewer); + } + }; + + // TODO should be handled in CSS + Viewer.prototype._showViewer = function($viewer, carousels){ + this._$el + .find(`[${this.constructor._tagAttr}]`) + .css("display", "none") + .removeClass("focused"); + + $viewer + .css("display", "block") + .addClass("focused"); + + if( carousels ){ + carousels.map(function(c) { c.refresh(); }); + } + + // TODO the placement here seems arbitrary, probably belongs in `_showViewer` + // TODO also sucks to be so tightly coupled + this._fullscreen.addButton(); + + this._trigger( "switch-tag" ); + }; + + /** + * Accessor for tags derived from Akamai JSON data + * @method + * @returns { String[] } + */ + Viewer.prototype.getTags = function(){ + if( this._tags ) { + return this._tags; + }; + + var tags = []; + + // map and store all the carousel tags + for(var tag in this._tagMapping){ + if(this._tagMapping.hasOwnProperty(tag) && tag !== Viewer._firstRenderTag ) { + // otherwise grab the list of tags + tags.push(tag); + } + } + + return this._tags = tags; + }; + + /** + * Triggered when the viewer switches tag views. This includes waiting for + * images to load for carousels dedicated to previously unviewed tags. + * {@link Akamai.Viewer#switchTag}. + * + * @event Akamai.Viewer#akamai-viewer-switch-tag + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Viewer}. + * + * @event Akamai.Viewer#akamai-viewer-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Viewer = Viewer; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + $.fn.akamaiViewer = function(options){ + this.each(function(i, element){ + new Akamai.Viewer(element, options); + }); + }; +})(typeof exports === 'undefined'? window : exports, this.jQuery); diff --git a/app/html/js/akamai-viewer.js b/app/html/js/akamai-viewer.js new file mode 100644 index 0000000..1d12cfd --- /dev/null +++ b/app/html/js/akamai-viewer.js @@ -0,0 +1,4 @@ +/*! Copyright 2019, Akamai Technologies, Inc. All Rights Reserved. akamai-viewer-0.7.2 */ +!function(a){var b=Date.now||function(){return+new Date},c={},d=1;a.core?core.effect||(core.effect={}):a.core={effect:{}},core.effect.Animate={requestAnimationFrame:function(){var b=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame,c=!!b;if(b&&!/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(b.toString())&&(c=!1),c)return function(a,c){b(a,c)};var d={},e=0,f=1,g=null,h=+new Date;return function(a,b){var c=f++;return d[c]=a,e++,null===g&&(g=setInterval(function(){var a=+new Date,b=d;d={},e=0;for(var c in b)b.hasOwnProperty(c)&&(b[c](a),h=a);a-h>2500&&(clearInterval(g),g=null)},1e3/60)),c}}(),stop:function(a){var b=null!=c[a];return b&&(c[a]=null),b},isRunning:function(a){return null!=c[a]},start:function(a,e,f,g,h,i){var j=b(),k=j,l=0,m=0,n=d++;if(i||(i=document.body),n%20==0){var o={};for(var p in c)o[p]=!0;c=o}var q=function(d){var o=!0!==d,p=b();if(!c[n]||e&&!e(n))return c[n]=null,void(f&&f(60-m/((p-j)/1e3),n,!1));if(o)for(var r=Math.round((p-k)/(1e3/60))-1,s=0;s1&&(l=1);var t=h?h(l):l;!1!==a(t,p,o)&&1!==l||!o?o&&(k=p,core.effect.Animate.requestAnimationFrame(q,i)):(c[n]=null,f&&f(60-m/((p-j)/1e3),n,1===l||null==g))};return c[n]=!0,core.effect.Animate.requestAnimationFrame(q,i),n}}}(this);var Scroller;!function(){Scroller=function(a,b){this.__callback=a,this.options={scrollingX:!0,scrollingY:!0,animating:!0,animationDuration:250,bouncing:!0,locking:!0,paging:!1,snapping:!1,zooming:!1,minZoom:.5,maxZoom:3};for(var c in b)this.options[c]=b[c]};var a=function(a){return Math.pow(a-1,3)+1},b=function(a){return(a/=.5)<1?.5*Math.pow(a,3):.5*(Math.pow(a-2,3)+2)},c={__isSingleTouch:!1,__isTracking:!1,__isGesturing:!1,__isDragging:!1,__isDecelerating:!1,__isAnimating:!1,__clientLeft:0,__clientTop:0,__clientWidth:0,__clientHeight:0,__contentWidth:0,__contentHeight:0,__snapWidth:100,__snapHeight:100,__refreshHeight:null,__refreshActive:!1,__refreshActivate:null,__refreshDeactivate:null,__refreshStart:null,__zoomLevel:1,__scrollLeft:0,__scrollTop:0,__maxScrollLeft:0,__maxScrollTop:0,__scheduledLeft:0,__scheduledTop:0,__scheduledZoom:0,__lastTouchLeft:null,__lastTouchTop:null,__lastTouchMove:null,__positions:null,__minDecelerationScrollLeft:null,__minDecelerationScrollTop:null,__maxDecelerationScrollLeft:null,__maxDecelerationScrollTop:null,__decelerationVelocityX:null,__decelerationVelocityY:null,setDimensions:function(a,b,c,d){var e=this;a&&(e.__clientWidth=a),b&&(e.__clientHeight=b),c&&(e.__contentWidth=c),d&&(e.__contentHeight=d),e.__computeScrollMax(),e.scrollTo(e.__scrollLeft,e.__scrollTop,!0)},setPosition:function(a,b){var c=this;c.__clientLeft=a||0,c.__clientTop=b||0},setSnapSize:function(a,b){var c=this;c.__snapWidth=a,c.__snapHeight=b},activatePullToRefresh:function(a,b,c,d){var e=this;e.__refreshHeight=a,e.__refreshActivate=b,e.__refreshDeactivate=c,e.__refreshStart=d},finishPullToRefresh:function(){var a=this;a.__refreshActive=!1,a.__refreshDeactivate&&a.__refreshDeactivate(),a.scrollTo(a.__scrollLeft,a.__scrollTop,!0)},getValues:function(){var a=this;return{left:a.__scrollLeft,top:a.__scrollTop,zoom:a.__zoomLevel}},getScrollMax:function(){var a=this;return{left:a.__maxScrollLeft,top:a.__maxScrollTop}},zoomTo:function(a,b,c,d){var e=this;if(!e.options.zooming)throw new Error("Zooming is not enabled!");e.__isDecelerating&&(core.effect.Animate.stop(e.__isDecelerating),e.__isDecelerating=!1);var f=e.__zoomLevel;null==c&&(c=e.__clientWidth/2),null==d&&(d=e.__clientHeight/2),a=Math.max(Math.min(a,e.options.maxZoom),e.options.minZoom),e.__computeScrollMax(a);var g=(c+e.__scrollLeft)*a/f-c,h=(d+e.__scrollTop)*a/f-d;g>e.__maxScrollLeft?g=e.__maxScrollLeft:g<0&&(g=0),h>e.__maxScrollTop?h=e.__maxScrollTop:h<0&&(h=0),e.__publish(g,h,a,b)},zoomBy:function(a,b,c,d){var e=this;e.zoomTo(e.__zoomLevel*a,b,c,d)},scrollTo:function(a,b,c,d){var e=this;if(e.__isDecelerating&&(core.effect.Animate.stop(e.__isDecelerating),e.__isDecelerating=!1),null!=d&&d!==e.__zoomLevel){if(!e.options.zooming)throw new Error("Zooming is not enabled!");a*=d,b*=d,e.__computeScrollMax(d)}else d=e.__zoomLevel;e.options.scrollingX?e.options.paging?a=Math.round(a/e.__clientWidth)*e.__clientWidth:e.options.snapping&&(a=Math.round(a/e.__snapWidth)*e.__snapWidth):a=e.__scrollLeft,e.options.scrollingY?e.options.paging?b=Math.round(b/e.__clientHeight)*e.__clientHeight:e.options.snapping&&(b=Math.round(b/e.__snapHeight)*e.__snapHeight):b=e.__scrollTop,a=Math.max(Math.min(e.__maxScrollLeft,a),0),b=Math.max(Math.min(e.__maxScrollTop,b),0),a===e.__scrollLeft&&b===e.__scrollTop&&(c=!1),e.__publish(a,b,d,c)},scrollBy:function(a,b,c){var d=this,e=d.__isAnimating?d.__scheduledLeft:d.__scrollLeft,f=d.__isAnimating?d.__scheduledTop:d.__scrollTop;d.scrollTo(e+(a||0),f+(b||0),c)},doMouseZoom:function(a,b,c,d){var e=this,f=a>0?.97:1.03;return e.zoomTo(e.__zoomLevel*f,!1,c-e.__clientLeft,d-e.__clientTop)},doTouchStart:function(a,b){if(null==a.length)throw new Error("Invalid touch list: "+a);if(b instanceof Date&&(b=b.valueOf()),"number"!=typeof b)throw new Error("Invalid timestamp value: "+b);var c=this;c.__isDecelerating&&(core.effect.Animate.stop(c.__isDecelerating),c.__isDecelerating=!1),c.__isAnimating&&(core.effect.Animate.stop(c.__isAnimating),c.__isAnimating=!1);var d,e,f=1===a.length;f?(d=a[0].pageX,e=a[0].pageY):(d=Math.abs(a[0].pageX+a[1].pageX)/2,e=Math.abs(a[0].pageY+a[1].pageY)/2),c.__initialTouchLeft=d,c.__initialTouchTop=e,c.__zoomLevelStart=c.__zoomLevel,c.__lastTouchLeft=d,c.__lastTouchTop=e,c.__lastTouchMove=b,c.__lastScale=1,c.__enableScrollX=!f&&c.options.scrollingX,c.__enableScrollY=!f&&c.options.scrollingY,c.__isTracking=!0,c.__isDragging=!f,c.__isSingleTouch=f,c.__positions=[]},doTouchMove:function(a,b,c){if(null==a.length)throw new Error("Invalid touch list: "+a);if(b instanceof Date&&(b=b.valueOf()),"number"!=typeof b)throw new Error("Invalid timestamp value: "+b);var d=this;if(d.__isTracking){var e,f;2===a.length?(e=Math.abs(a[0].pageX+a[1].pageX)/2,f=Math.abs(a[0].pageY+a[1].pageY)/2):(e=a[0].pageX,f=a[0].pageY);var g=d.__positions;if(d.__isDragging){var h=e-d.__lastTouchLeft,i=f-d.__lastTouchTop,j=d.__scrollLeft,k=d.__scrollTop,l=d.__zoomLevel;if(null!=c&&d.options.zooming){var m=l;if(l=l/d.__lastScale*c,l=Math.max(Math.min(l,d.options.maxZoom),d.options.minZoom),m!==l){var n=e-d.__clientLeft,o=f-d.__clientTop;j=(n+j)*l/m-n,k=(o+k)*l/m-o,d.__computeScrollMax(l)}}if(d.__enableScrollX){j-=h;var p=d.__maxScrollLeft;(j>p||j<0)&&(d.options.bouncing?j+=h/2:j=j>p?p:0)}if(d.__enableScrollY){k-=i;var q=d.__maxScrollTop;(k>q||k<0)&&(d.options.bouncing?(k+=i/2,d.__enableScrollX||null==d.__refreshHeight||(!d.__refreshActive&&k<=-d.__refreshHeight?(d.__refreshActive=!0,d.__refreshActivate&&d.__refreshActivate()):d.__refreshActive&&k>-d.__refreshHeight&&(d.__refreshActive=!1,d.__refreshDeactivate&&d.__refreshDeactivate()))):k=k>q?q:0)}g.length>60&&g.splice(0,30),g.push(j,k,b),d.__publish(j,k,l)}else{var r=d.options.locking?3:0,s=Math.abs(e-d.__initialTouchLeft),t=Math.abs(f-d.__initialTouchTop);d.__enableScrollX=d.options.scrollingX&&s>=r,d.__enableScrollY=d.options.scrollingY&&t>=r,g.push(d.__scrollLeft,d.__scrollTop,b),d.__isDragging=(d.__enableScrollX||d.__enableScrollY)&&(s>=5||t>=5)}d.__lastTouchLeft=e,d.__lastTouchTop=f,d.__lastTouchMove=b,d.__lastScale=c}},doTouchEnd:function(a){if(a instanceof Date&&(a=a.valueOf()),"number"!=typeof a)throw new Error("Invalid timestamp value: "+a);var b=this;if(b.__isTracking){if(b.__isTracking=!1,b.__isDragging&&(b.__isDragging=!1,b.__isSingleTouch&&b.options.animating&&a-b.__lastTouchMove<=100)){for(var c=b.__positions,d=c.length-1,e=d,f=d;f>0&&c[f]>b.__lastTouchMove-100;f-=3)e=f;if(e!==d){var g=c[d]-c[e],h=b.__scrollLeft-c[e-2],i=b.__scrollTop-c[e-1];b.__decelerationVelocityX=h/g*(1e3/60),b.__decelerationVelocityY=i/g*(1e3/60);var j=b.options.paging||b.options.snapping?4:1;(Math.abs(b.__decelerationVelocityX)>j||Math.abs(b.__decelerationVelocityY)>j)&&(b.__refreshActive||b.__startDeceleration(a))}}b.__isDecelerating||(b.__refreshActive&&b.__refreshStart?(b.__publish(b.__scrollLeft,-b.__refreshHeight,b.__zoomLevel,!0),b.__refreshStart&&b.__refreshStart()):(b.scrollTo(b.__scrollLeft,b.__scrollTop,!0,b.__zoomLevel),b.__refreshActive&&(b.__refreshActive=!1,b.__refreshDeactivate&&b.__refreshDeactivate()))),b.__positions.length=0}},__publish:function(c,d,e,f){var g=this,h=g.__isAnimating;if(h&&(core.effect.Animate.stop(h),g.__isAnimating=!1),f&&g.options.animating){g.__scheduledLeft=c,g.__scheduledTop=d,g.__scheduledZoom=e;var i=g.__scrollLeft,j=g.__scrollTop,k=g.__zoomLevel,l=c-i,m=d-j,n=e-k,o=function(a,b,c){c&&(g.__scrollLeft=i+l*a,g.__scrollTop=j+m*a,g.__zoomLevel=k+n*a,g.__callback&&g.__callback(g.__scrollLeft,g.__scrollTop,g.__zoomLevel))},p=function(a){return g.__isAnimating===a},q=function(a,b,c){b===g.__isAnimating&&(g.__isAnimating=!1),g.options.zooming&&g.__computeScrollMax()};g.__isAnimating=core.effect.Animate.start(o,p,q,g.options.animationDuration,h?a:b)}else g.__scheduledLeft=g.__scrollLeft=c,g.__scheduledTop=g.__scrollTop=d,g.__scheduledZoom=g.__zoomLevel=e,g.__callback&&g.__callback(c,d,e),g.options.zooming&&g.__computeScrollMax()},__computeScrollMax:function(a){var b=this;null==a&&(a=b.__zoomLevel),b.__maxScrollLeft=Math.max(b.__contentWidth*a-b.__clientWidth,0),b.__maxScrollTop=Math.max(b.__contentHeight*a-b.__clientHeight,0)},__startDeceleration:function(a){var b=this;if(b.options.paging){var c=Math.max(Math.min(b.__scrollLeft,b.__maxScrollLeft),0),d=Math.max(Math.min(b.__scrollTop,b.__maxScrollTop),0),e=b.__clientWidth,f=b.__clientHeight;b.__minDecelerationScrollLeft=Math.floor(c/e)*e,b.__minDecelerationScrollTop=Math.floor(d/f)*f,b.__maxDecelerationScrollLeft=Math.ceil(c/e)*e,b.__maxDecelerationScrollTop=Math.ceil(d/f)*f}else b.__minDecelerationScrollLeft=0,b.__minDecelerationScrollTop=0,b.__maxDecelerationScrollLeft=b.__maxScrollLeft,b.__maxDecelerationScrollTop=b.__maxScrollTop;var g=function(a,c,d){b.__stepThroughDeceleration(d)},h=b.options.snapping?4:.1,i=function(){return Math.abs(b.__decelerationVelocityX)>=h||Math.abs(b.__decelerationVelocityY)>=h},j=function(a,c,d){b.__isDecelerating=!1,b.scrollTo(b.__scrollLeft,b.__scrollTop,b.options.snapping)};b.__isDecelerating=core.effect.Animate.start(g,i,j)},__stepThroughDeceleration:function(a){var b=this,c=b.__scrollLeft+b.__decelerationVelocityX,d=b.__scrollTop+b.__decelerationVelocityY;if(!b.options.bouncing){var e=Math.max(Math.min(b.__maxDecelerationScrollLeft,c),b.__minDecelerationScrollLeft);e!==c&&(c=e,b.__decelerationVelocityX=0);var f=Math.max(Math.min(b.__maxDecelerationScrollTop,d),b.__minDecelerationScrollTop);f!==d&&(d=f,b.__decelerationVelocityY=0)}if(a?b.__publish(c,d,b.__zoomLevel):(b.__scrollLeft=c,b.__scrollTop=d),!b.options.paging){b.__decelerationVelocityX*=.95,b.__decelerationVelocityY*=.95}if(b.options.bouncing){var g=0,h=0;cb.__maxDecelerationScrollLeft&&(g=b.__maxDecelerationScrollLeft-c),db.__maxDecelerationScrollTop&&(h=b.__maxDecelerationScrollTop-d),0!==g&&(g*b.__decelerationVelocityX<=0?b.__decelerationVelocityX+=.03*g:b.__decelerationVelocityX=.08*g),0!==h&&(h*b.__decelerationVelocityY<=0?b.__decelerationVelocityY+=.03*h:b.__decelerationVelocityY=.08*h)}}};for(var d in c)Scroller.prototype[d]=c[d]}(),function(a){"function"==typeof define&&define.amd?define(["shoestring"],a):"object"==typeof module&&module.exports?module.exports=a():a()}(function(){function a(b,c){var d,e=typeof b,f=[];if(!b)return new i(f);if(b.call)return a.ready(b);if(b.constructor===i&&!c)return b;if("string"===e&&0===b.indexOf("<")){var j=h.createElement("div");return j.innerHTML=b,a(j).children().each(function(){j.removeChild(this)})}return"string"===e?c?a(c).find(b):(d=h.querySelectorAll(b),new i(d,b)):"[object Array]"===Object.prototype.toString.call(e)||g.NodeList&&b instanceof g.NodeList?new i(b,b):b.constructor===Array?new i(b,b):new i([b],b)}function b(a,b){var c=!1;return a.each(function(){for(var a=0;a1&&(b=b.reverse()),this.each(function(a){for(var c=0,d=b.length;c0?b[c].cloneNode(!0):b[c];this.parentNode.insertBefore(e,this.nextSibling)}})},a.fn.append=function(b){return"string"!=typeof b&&void 0===b.nodeType||(b=a(b)),this.each(function(a){for(var c=0,d=b.length;c0?b[c].cloneNode(!0):b[c])})},a.fn.appendTo=function(b){return this.each(function(){a(b).append(this)})},a.fn.attr=function(a,b){var c="string"==typeof a;return void 0===b&&c?this[0]?this[0].getAttribute(a):void 0:this.each(function(){if(c)this.setAttribute(a,b);else for(var d in a)a.hasOwnProperty(d)&&this.setAttribute(d,a[d])})},a.fn.before=function(b){return"string"!=typeof b&&void 0===b.nodeType||(b=a(b)),this.each(function(a){for(var c=0,d=b.length;c0?b[c].cloneNode(!0):b[c],this)})},a.fn.children=function(){var b,c,d=[];return this.each(function(){for(b=this.children,c=-1;c++-1&&c.push(this)}}),a(c)},a.fn.find=function(b){var c,d=[];return this.each(function(){c=this.querySelectorAll(b);for(var a=0,e=c.length;a0?b[c].cloneNode(!0):b[c];this.firstChild?this.insertBefore(e,this.firstChild):this.appendChild(e)}})},a.fn.prependTo=function(b){return this.each(function(){a(b).prepend(this)})},a.fn.prev=function(){var b=[];return this.each(function(){var c,d,e;c=a(this.parentNode)[0].childNodes;for(var f=c.length-1;f>=0;f--){if(d=c.item(f),e&&1===d.nodeType){b.push(d);break}d===this&&(e=!0)}}),a(b)},a.fn.prevAll=function(){var b=[];return this.each(function(){for(var c=a(this).prev();c.length;)b.push(c[0]),c=c.prev()}),a(b)},a.propFix={class:"className",contenteditable:"contentEditable",for:"htmlFor",readonly:"readOnly",tabindex:"tabIndex"},a.fn.prop=function(b,c){if(this[0])return b=a.propFix[b]||b,void 0!==c?this.each(function(){this[b]=c}):this[0][b]},a.fn.removeAttr=function(a){return this.each(function(){this.removeAttribute(a)})},a.fn.removeClass=function(a){var b=a.replace(/^\s+|\s+$/g,"").split(" ");return this.each(function(){for(var a,c,d=0,e=b.length;d1&&(b=b.reverse()),this.each(function(a){var d,e=this.cloneNode(!0);if(c.push(e),this.parentNode)if(1===b.length)d=a>0?b[0].cloneNode(!0):b[0],this.parentNode.replaceChild(d,this);else{for(var f=0,g=b.length;f0?b[f].cloneNode(!0):b[f],this.parentNode.insertBefore(d,this.nextSibling);this.parentNode.removeChild(this)}}),a(c)},a.inputTypes=["text","hidden","password","color","date","datetime","email","month","number","range","search","tel","time","url","week"],a.inputTypeTest=new RegExp(a.inputTypes.join("|")),a.fn.serialize=function(){var b={};return a("input, select",this).each(function(){var c=this.type,d=this.name,e=this.value;a.inputTypeTest.test(c)||("checkbox"===c||"radio"===c)&&this.checked?b[d]=e:"SELECT"===this.nodeName&&(b[d]=this.options[this.selectedIndex].nodeValue)}),b},a.fn.siblings=function(){if(!this.length)return a([]);var b=[],c=this[0].parentNode.firstChild;do{1===c.nodeType&&c!==this[0]&&b.push(c),c=c.nextSibling}while(c);return a(b)};var n=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(1===e||9===e||11===e){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=n(a)}else if(3===e||4===e)return a.nodeValue}else for(;b=a[d++];)c+=n(b);return c};return a.fn.text=function(){return n(this)},a.fn.val=function(b){var c;return void 0!==b?this.each(function(){if("SELECT"===this.tagName){var c,d,e,f=this.options,g=[],h=f.length;for(g[0]=b;h--;)d=f[h],(d.selected=a.inArray(d.value,g)>=0)&&(c=!0,e=h);this.selectedIndex=c?e:-1}else this.value=b}):(c=this[0],"SELECT"===c.tagName?c.selectedIndex<0?"":c.options[c.selectedIndex].value:c.value)},a.fn.width=function(b){return a._dimension(this,"width",b)},a.fn.wrapInner=function(b){return this.each(function(){var c=this.innerHTML;this.innerHTML="",a(this).append(a(b).html(c))})},a.fn.bind=function(a,b,e){function f(a,c,d){var f;if(!a._namespace||a._namespace===c){a.data=b,a.namespace=a._namespace;var g=function(){return!0};a.isDefaultPrevented=function(){return!1};var h=a.preventDefault;return a.target=d||a.target||a.srcElement,a.preventDefault=function(){return h?function(){a.isDefaultPrevented=g,h.call(a)}:function(){a.isDefaultPrevented=g,a.returnValue=!1}}(),a.stopPropagation=a.stopPropagation||function(){a.cancelBubble=!0},f=e.apply(this,[a].concat(a._args)),!1===f&&(a.preventDefault(),a.stopPropagation()),f}}"function"==typeof b&&(e=b,b=null);var g=a.split(" ");return this.each(function(){for(var a,b,h,i=this,j=0,k=g.length;j0?l[1]:null;a=function(a){return i.ssEventTrigger&&(a._namespace=i.ssEventTrigger._namespace,a._args=i.ssEventTrigger._args,i.ssEventTrigger=null),f.call(i,a,n)},b=null,h=null,c(this,m),this.addEventListener(m,a,!1),d(this,m,{callfunc:b||a,isCustomEvent:!!b,customEventLoop:h,originalCallback:e,namespace:n})}})},a.fn.on=a.fn.bind,a.fn.unbind=function(a,b){var c=a?a.split(" "):[];return this.each(function(){if(this.shoestringData&&this.shoestringData.events)if(c.length)for(var a,d,g,h=0,i=c.length;h0?a[1]:null,d?e.call(this,d,g,b):f.call(this,g,b);else f.call(this)})},a.fn.off=a.fn.unbind,a.fn.one=function(b,c){var d=b.split(" ");return this.each(function(){for(var b,e={},f=a(this),g=0,h=d.length;g0?a[1]:null,"click"===d&&"INPUT"===this.tagName&&"checkbox"===this.type&&this.click)return this.click(),!1;if(h.createEvent){var i=h.createEvent("Event");i.initEvent(d,!0,!0),i._args=b,i._namespace=e,this.dispatchEvent(i)}}})},a}),window.jQuery||(window.jQuery=window.jQuery||window.shoestring),function(a){a.Akamai=a.Akamai||{},window.componentNamespace="Akamai"}("undefined"==typeof exports?window:exports),function(a,b){var c;b(a),c=b(document.documentElement);var d=a.componentNamespace=a.componentNamespace||"FG";a[d]=a[d]||{},Function.prototype.bind=Function.prototype.bind||function(a){var b=this;return function(){b.apply(a,arguments)}};var e=a[d].Tau=function(c,d){var f;this.element=c,this.options=d||{},this.$element=b(c),this.$initial=this.$element.find("img"),this.$loading=this.$element.find(".loading"),this.index=0,this.frames=this.$initial.length>1?this.$initial.length:parseInt(this.$initial.attr("data-frames"),10)||this.options.frames,f=parseInt(this.$initial.attr("data-reduced-step-size"),10)||4,this.stepSize=a.requestAnimationFrame?1:f,this.autoRotateStartDelay=(this.options.autoplay||{}).delay||parseInt(this.$initial.attr("data-auto-rotate-delay"),10)||e.autoRotateStartDelay,this.mouseMoveBinding=this.rotateEvent.bind(this),this.touchMoveBinding=this.rotateEvent.bind(this),this.path=new e.Path,this.$initial.first().addClass("focused"),this.$element.addClass("tau-enhanced"),this.$render=b('
').css("position","absolute").css("left","0").css("top","0").prependTo(this.element),!1!==this.options.canvas&&(this.canvas=b("").prependTo(this.element)[0],this.canvas.getContext&&(this.canvasCtx=this.canvas.getContext("2d"),this.$element.addClass("tau-canvas"),b(a).bind("resize",function(){clearTimeout(this.canvasResizeTimeout),this.canvasResizeTimeout=setTimeout(this.renderCanvas.bind(this),100)}.bind(this)))),this.options.controls&&(this.options.controls.text=this.options.controls.text||{play:"Spin Object",left:"Rotate Left",right:"Rotate Right"},this.createControls()),this.createImages(),this.options.autoplay&&this.options.autoplay.enabled&&(this.autostartTimeout=setTimeout(this.autoRotate.bind(this),this.autoRotateStartDelay)),this.bind()};e.autoRotateTraversalTime=4500,e.autoRotateStartDelay=100,e.verticalScrollRatio=4,e.maxVelocity=60,e.prototype.createControls=function(){this.$controls=b("
"),this.options.controls.play&&this.$controls.append(this.controlAnchorMarkup("play")),this.options.controls.arrows&&this.$controls.prepend(this.controlAnchorMarkup("left")).append(this.controlAnchorMarkup("right")),this.$controls.bind("mousedown touchstart",this.onControlDown.bind(this)),this.$controls.bind("mouseup",this.onControlUp.bind(this)),this.$controls.bind("click",function(a){b(a.target).is("a")&&a.preventDefault()}),this.$element.append(this.$controls)},e.prototype.controlAnchorMarkup=function(a){var b=this.options.controls.text[a];return""+b+""},e.prototype.onControlDown=function(a){switch(b(a.target).closest("a").attr("data-tau-controls")){case"left":this.$element.addClass("control-left-down"),this.stopAutoRotate(),this.autoRotate();break;case"right":this.$element.addClass("control-right-down"),this.stopAutoRotate(),this.autoRotate(!0)}},e.prototype.onControlUp=function(a){switch(b(a.target).closest("a").attr("data-tau-controls")){case"left":case"right":this.$element.removeClass("control-left-down"),this.$element.removeClass("control-right-down"),this.stopAutoRotate();break;case"play":this.autoInterval?this.stopAutoRotate():this.autoRotate()}},e.prototype.change=function(a){this.goto(this.options.reverse?this.index-a:this.index+a)},e.prototype.goto=function(a){var b,c,d=this.$images.length;return a%=d,c=(d+a)%d,b=this.$images.eq(c),b[0].tauImageLoaded?(this.$current?this.$current.removeClass("focused"):this.$images.removeClass("focused"),this.$current=b,this.index=c,this.canvasCtx?this.renderCanvas():(this.$current.addClass("focused"),!0)):(this.showLoading(),!1)},e.prototype.renderCanvas=function(){var a=this.$current,b=a[0],c=b.naturalWidth,d=b.naturalHeight,e=this.element.clientWidth,f=e/c*d;return!!(c&&d&&b.complete)&&((this.canvas.width!==e||this.canvas.height!==f||e&&f)&&(this.canvas.width=e,this.canvas.height=f),this.canvasCtx.drawImage(b,0,0,e,f),!0)},e.prototype.createImages=function(){var a,c,d;if(this.$initial.length<1)throw new Error("At least one image required");if(this.loadedCount=0,1==this.$initial.length){this.markImageLoaded(this.$initial[0]),a=this.options.template||this.$initial.attr("data-src-template");for(var e=[],f=this.stepSize+1;f<=this.frames;f+=this.stepSize)c="",d=b(c),e.push(d);b.each(e,function(a,c){var d=b(c);d.bind("load error",function(b){this.imageLoaded(a,b.target,b)}.bind(this)),this.$element.append(d),this.$render.append(d.html())}.bind(this)),this.$images=this.$element.children().filter("img"),this.$current=this.$images,this.goto(0)}else this.$images=this.$element.children().filter("img"), +this.$images.each(function(a,c){b(c).height()>0?this.imageLoaded(a,c):b(c).bind("load error",function(b){this.imageLoaded(a,b.target,b)}.bind(this))}.bind(this))},e.prototype.imageLoaded=function(a,b,c){var d=!1;this.markImageLoaded(b),this.$element.find("img")[0]!=b||c&&"error"===c.type||this.options.autoplay&&this.options.autoplay.enabled||(this.goto(0),this.$element.trigger("tau.init"),d=!0),++this.loadedCount>=this.frames-1&&(this.hideLoading(),d||(this.$element.trigger("tau.init"),this.initialized=!0))},e.prototype.markImageLoaded=function(a){a.tauImageLoaded=!0},e.prototype.bind=function(){this.$element.bind("mousedown touchstart",this.track.bind(this))},e.prototype.autoRotate=function(a){this.autoInterval||(this.$element.addClass("spinning"),this.change(a?-1:1),this.autoInterval=setInterval(function(){this.change(a?-1:1)}.bind(this),this.autoRotateDelay()*this.stepSize),this.$element.trigger("tau.auto-rotate-start"))},e.prototype.autoRotateDelay=function(){return(this.options.interval||e.autoRotateTraversalTime)/this.frames},e.prototype.stopAutoRotate=function(){clearInterval(this.autoInterval),clearInterval(this.autostartTimeout),this.$element.removeClass("spinning"),this.autoInterval=void 0,this.$element.trigger("tau.auto-rotate-stop")},e.prototype.track=function(a){var d;if(!b(a.target).closest(".tau-controls").length&&("mousedown"===a.type&&a.preventDefault(),"touchstart"===a.type?this.$element.trigger("tau.touch-tracking-start"):this.$element.trigger("tau.mouse-tracking-start"),!this.tracking)){c.one("mouseup",this.release.bind(this)),c.one("touchend",this.release.bind(this)),this.tracking=!0,this.path.reset(),this.cursorGrab();var e=this.$element[0].clientWidth/this.frames;this.rotateThreshold=e/(this.options.sensitivity||1),d=this.getPoint(a),this.downX=d.x,this.downY=d.y,this.downIndex=this.index,c.bind("mousemove",this.mouseMoveBinding),c.bind("touchmove",this.touchMoveBinding)}},e.prototype.slow=function(){if(!this.path.isSufficient())return void this.clearSlowInterval();this.rotate({x:this.path.last().x+this.velocity,y:this.path.last().y}),this.velocity>0?(this.velocity=this.velocity-this.decelVal(),this.velocity<=0&&this.clearSlowInterval()):(this.velocity=this.velocity+this.decelVal(),this.velocity>=0&&this.clearSlowInterval())},e.prototype.decelVal=function(){return this.decelTimeStep()/8},e.prototype.clearSlowInterval=function(){clearInterval(this.slowInterval),this.velocity=0,this.slowInterval=void 0},e.prototype.decel=function(){var a,b;this.path.isSufficient()&&(a=this.path.velocity(this.decelTimeStep()),b=a>0?1:a<0?-1:0,Math.abs(a)>e.maxVelocity&&(a=b*e.maxVelocity),this.velocity=a,this.slowInterval=setInterval(this.slow.bind(this),this.decelTimeStep()))},e.prototype.decelTimeStep=function(){return this.autoRotateDelay()/2},e.prototype.release=function(a){b(a.target).closest(".tau-controls").length||this.tracking&&("touchend"===a.type?this.$element.trigger("tau.touch-tracking-stop"):this.$element.trigger("tau.mouse-tracking-stop"),this.decel(),this.cursorRelease(),c.unbind("mousemove",this.mouseMoveBinding),c.unbind("touchmove",this.touchMoveBinding),this.tracking=!1)},e.prototype.cursorGrab=function(){c.addClass("grabbing")},e.prototype.cursorRelease=function(){c.removeClass("grabbing")},e.prototype.showLoading=function(){this.$loading.attr("style","display: block")},e.prototype.hideLoading=function(){this.$loading.attr("style","display: none")},e.prototype.getPoint=function(a){var b=a.touches||a.originalEvent&&a.originalEvent.touches;return b?{x:b[0].pageX,y:b[0].pageY}:{x:a.pageX||a.clientX,y:a.pageY||a.clientY}},e.prototype.rotateEvent=function(a){this.rotate(this.getPoint(a))&&a.preventDefault()},e.prototype.rotate=function(a){var b,c;if(b=a.x-this.downX,c=a.y-this.downY,Math.abs(c)/Math.abs(b)>=e.verticalScrollRatio)return!1;if(this.path.record(a),Math.abs(b)>=this.rotateThreshold){this.stopAutoRotate();var d;return d=this.options.reverse?this.downIndex+Math.round(b/this.rotateThreshold):this.downIndex-Math.round(b/this.rotateThreshold),this.goto(d),!0}}}(this,jQuery),function(a,b){var c=a.componentNamespace=a.componentNamespace||"FG";a[c]=a[c]||{},Date.now=Date.now||function(){return(new Date).getTime()};var d=a[c].Tau.Path=function(){this.reset()};d.prototype.isSufficient=function(){return!!this.prevPoint&&this.prevPrevPoint},d.prototype.distance=function(){return this.prevPoint.x-this.prevPrevPoint.x},d.prototype.duration=function(){return this.prevTime-this.prevPrevTime},d.prototype.record=function(a){this.prevPrevTime=this.prevTime,this.prevPrevPoint=this.prevPoint,this.prevTime=Date.now(),this.prevPoint=a},d.prototype.velocity=function(a){var b,c;return b=this.distance(),c=this.duration(),b/(c/a)},d.prototype.reset=function(){this.prevPoint=void 0,this.prevTime=void 0,this.prevPrevTime=void 0,this.prevPrevPoint=void 0},d.prototype.last=function(){return this.prevPoint}}(this,jQuery),function(a,b){function c(a){return String(a).replace(/[&<>"'`=\/]/g,function(a){return f[a]})}a.Akamai=a.Akamai||{};var d=a.Akamai.Util={};d.preflight=function(a,c,e,f){if(f=f||a.constructor.name,a._$el=b(c),!a._$el.length)throw new Error(f+" component requires element");return a._$el.data(f)?a._$el.data(f):(a._$el.data(f,a),a._$el.attr(a.constructor._dataAttr,!0),a._options=d.options(a.constructor.defaultOptions||{},e||{},a._$el,f),a._originalOptions=d.extend(!0,{},a._options),d.mapEvents(a),!1)},d.component=function(a,b,c){c||(c=b,b=!1);var e=function(e,f){var g=d.preflight(this,e,f,a);if(g)return this._trigger("init"),g;c.apply(this,arguments),b||this._trigger("init")};return e.name=a,e._dataAttr="data-akamai-"+a.toLowerCase(),e.createMany=d.createMany(e),e.prototype._trigger=d.trigger(a),e},d.mapEvents=function(a){var b=a.constructor._componentEventMapping||{};for(var c in b)if(b.hasOwnProperty(c)){var e=b[c].to||b[c],f=b[c].selector?a._$el.find(b[c].selector):void 0;d.mapEvent(a,c,e,f)}},d.mapEvent=function(a,b,c,d){(d||a._$el).bind(b,function(){a._trigger(c)}.bind(a))},d.createMany=function(a,c){return c=c||"["+a._dataAttr+"]",function(d,e){var f=b(d).find(c),g=[];return f.each(function(b,c){g.push(new a(c,e))}),g}},d.propRequired=function(a,b,c){return function(){var d=arguments;if(!this[a])throw new Error(b);return c.apply(this,d)}},d.trigger=function(a){return a=a.toLowerCase(),function(b){var c="akamai-"+a+"-"+b;this._$el.trigger(c)}},d.camelToKabob=function(a){return a.replace(/([A-Z]+)/g,function(a){return"-"+a.toLowerCase()})},d.options=function(a,b,c,e){var f={},g=d.extend(!0,{},a);c&&e&&(f=d.getDataAttrOptions(c,a,"data-akamai-"+e.toLowerCase()));var h=d.extend(!0,d.extend(!0,g,f),b);return c&&e&&d.setDataAttrOptions(c,a,h,e),h},d.getDataAttrOptions=function(a,b,c){var e={};for(var f in b)if(b.hasOwnProperty(f)){var g=d.camelToKabob(f),h=c+"-"+g;if(!/object/.test(typeof b[f])||b[f]instanceof Array){var i=a.attr(h);i=d.coerceAttrVal(i),void 0!==i&&null!==i&&(e[f]=i)}else{var j=b[f],k=h,l=d.getDataAttrOptions(a,j,k);e[f]=l}}return e},d.setDataAttrOptions=function(a,b,c,e){d.setDataAttrOptionsPrefix(a,b,c,"data-akamai-"+e.toLowerCase())},d.setDataAttrOptionsPrefix=function(a,b,c,e){b=b||{};for(var f in c)if(c.hasOwnProperty(f)&&"breakpoints"!==f){var g=d.camelToKabob(f),h=e+"-"+g;if(/object/.test(typeof c[f])){var i=c[f],j=h;d.setDataAttrOptionsPrefix(a,b[f],i,j)}else(a.attr(h)||void 0!==b[f])&&a.attr(h,c[f])}},d.coerceAttrVal=function(a){return"true"===a||"false"!==a&&(/^[0-9]+$/.test(a)?parseInt(a,10):/^[0-9]+.[0-9]+$/.test(a)?parseFloat(a,10):a)},d.extend=function(a,b,c){var e,f,g,h;if(null!=c)for(e in c)f=b[e],g=c[e],b!==g&&(a&&g&&d.isPlainObject(g)?(h=f&&d.isPlainObject(f)?f:{},b[e]=d.extend(a,h,g)):void 0!==g&&(b[e]=g));return b},d.isPlainObject=function(a){return!(!a||"[object Object]"!==Object.prototype.toString.call(a))};var e=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;d.trim=function(a){return null==a?"":(a+"").replace(e,"")},d.map=function(a,b){return d.reduce(a,function(a,c,d){return a.push(b(c,d)),a},[])},d.reduce=function(a,b,c){for(var d=0;d=0)return b(a,c);for(var f in a)a.hasOwnProperty(f)&&(e[f]=d.mapJSONVals(a[f],b,f));return e},d.escapeJSONVals=function(a){return d.mapJSONVals(a,function(a){return"string"==typeof a?c(a):a})};var f={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}}("undefined"==typeof exports?window:exports,this.jQuery),function(a,b){var c={extension:{next:function(a){this.goto(this.getIndex()+1,function(){a&&a(),this._trigger("next")}.bind(this))},previous:function(a){this.goto(this.getIndex()-1,function(){a&&a(),this._trigger("previous")}.bind(this))}},extend:function(a){if(!a.prototype.goto||!a.prototype.getIndex)throw new Error("Advanceable mixin requires `goto` and `getIndex` methods");b.extend(a.prototype,this.extension)}};a.Akamai=a.Akamai||{},a.Akamai.Advanceable=c}("undefined"==typeof exports?window:exports,this.jQuery),function(a,b){var c={extension:{_srcArray:function(a,b,c){var d=b.policy||b.thumbnail&&b.thumbnail.policy,e=b.widthParam;return b.widths.map(function(b){var f=a+(-1===a.indexOf("?")?"?":"&")+e+"="+b;return d&&(f+="&impolicy="+d),c&&(f+=" "+b+"w"),f})},_srcset:function(a,b){return this._srcArray(a,b,!0).join(", ")},_fallbackSrc:function(a,b){return this._srcArray(a,b)[0]},_largestSrc:function(a,b){return this._srcArray(a,b).pop()}},extendStatic:function(a){Akamai.Util.extend(!1,a,this.extension)}};a.Akamai=a.Akamai||{},a.Akamai.Sourceable=c}("undefined"==typeof exports?window:exports,this.jQuery),function(a,b){var c=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(b){a.setTimeout(b,1e3/60)}}();a.toss=function(a,b){toss.tossing(a,!1);var d,e,f=0,g=a.scrollLeft,h=a.scrollTop,i={top:"+0",left:"+0",duration:200,easing:toss.easing,finished:function(){}};if(b)for(var j in i)void 0!==b[j]&&(i[j]=b[j]);"string"==typeof i.left?(i.left=parseFloat(i.left),d=i.left+g):(d=i.left,i.left=i.left-g),"string"==typeof i.top?(i.top=parseFloat(i.top),e=i.top+h):(e=i.top,i.top=i.top-h),toss.tossing(a,!0);var k=(new Date).getTime(),l=k+i.duration,m=function(){var b=(new Date).getTime();if(toss.tossing(a)){if(b=d-5&&this.offsetLefte&&(d=e),a!==d&&o?(h(B[0],d),h(B[0],d,"after")):i(B[0],d),O=!1}function n(){var a=B[0].scrollLeft,b=E.length,c=D.width();void 0===M&&(M=Math.round(a/c*b)),N&&clearTimeout(N),N=setTimeout(function(){j(),i(B[0],E[M].offsetLeft,!0),M=N=void 0},50)}function p(a){var b=B[0].scrollLeft,d=D.width(),e=f(B),g=d-e-5;return a?b>=g?(z.addClass(c+"-looping"),s()):q():0===b?(z.addClass(c+"-looping"),t()):r()}function q(){i(B[0],B[0].scrollLeft+D.width()/F,!1,function(){B.trigger(c+".after-next")})}function r(){i(B[0],B[0].scrollLeft-D.width()/F,!1,function(){B.trigger(c+".after-prev")})}function s(){i(B[0],0)}function t(){i(B[0],D.width()-B.width())}function u(b,c,d){void 0!==a.toss?a.toss(b,{left:c,top:d}):(b.scrollLeft=c,b.scrollTop=d)}function v(a){var b=(new Date).getTime();if(!(!a&&P&&b-P<200)){P=b;var c=B[0].scrollLeft,d=f(D),e=f(G),h=g(G),i=Math.round(c/d*F)||0,j=G.find("a").removeClass(H),k=j.eq(i).addClass(H),l=k[0].offsetLeft-e/2,m=k[0].offsetTop-h/2;u(J[0],l,m)}}function w(a){a&&(x=setTimeout(function(){var a=e();a&&(p(!0),w(a))},a))}if(void 0===k){var x,y=this,z=b(y),A=z.is("[data-"+c+"-nextprev]"),B=b("."+c+"_pane",y),C=c+"-enhanced",D=B.find("."+c+"_items"),E=D.children();E.addClass(c+"_item");var F=E.length,G=b("."+c+"_nav",y),H=c+"_nav_item-selected",I="false"!==z.attr("data-snapper-deeplinking");if("string"!=typeof d){if(!z.attr("data-"+c+"-enhanced")){var J=G.find("."+c+"_nav_inner");if(J.length||(J=b('
').append(G.children()).appendTo(G)),B.attr("tabindex","0"),j(),b(y).addClass(C),A){var K=b(''),L=b(".snapper_nextprev_contain",y);L.length||(L=b(y)),K.appendTo(L)}b("a",this).bind("click",function(c){clearTimeout(x);var d=b(this).attr("href");if(b(this).is(".snapper_nextprev_next"))return c.preventDefault(),p(!0);if(b(this).is(".snapper_nextprev_prev"))return c.preventDefault(),p(!1);if(0===d.indexOf("#")&&d.length>1){c.preventDefault();var e=b(d,y);e.length&&(i(B[0],e[0].offsetLeft),I&&"replaceState"in a.history&&a.history.replaceState({},document.title,d))}}),b(this).bind("keydown",function(a){37!==a.keyCode&&38!==a.keyCode||(clearTimeout(x),a.preventDefault(),a.stopImmediatePropagation(),p(!1)),39!==a.keyCode&&40!==a.keyCode||(clearTimeout(x),a.preventDefault(),a.stopImmediatePropagation(),p(!0))});var M,N,O=!1;if(b(a).bind("resize",n),G.length){var P;v(),B.bind("scroll",v)}var Q,R=0;B.bind("scroll",function(a){R=(new Date).getTime(),Q=!0}),setInterval(function(){Q&&R<=(new Date).getTime()-150&&(m(),v&&v(!0),Q=!1)},150);var S=!1;B.bind("touchstart",function(){clearTimeout(x),S=!0}),B.bind("touchend",function(){S=!1,O&&!Q&&(m(),Q=!1)}),w(e()),z.attr("data-"+c+"-enhanced",!0)}}else{var T,U=Array.prototype.slice.call(l,1),V=D.width()/F;switch(d){case"goto":T=U[0]%F,offset=V*T,i(B[0],offset,!1,function(){m(),"function"==typeof U[1]&&U[1]()});break;case"getIndex":k=Math.floor((B[0].scrollLeft+1)/V);break;case"updateWidths":j()}}}}),void 0!==k?k:j}}(this,jQuery),function(a){var b=function(){var b=a.jQuery,c="enlarge";b.fn[c]=function(d){var e=arguments,f=b(this).data("options")||{button:!0,hoverZoomWithoutClick:!0,delay:300,flyout:{width:200,height:200},placement:"inline",magnification:3};"string"!=typeof d&&(f=b.extend(f,d),b(this).data("options",f));var g,h=this.each(function(){function h(){"inline"===f.placement?(C=B,M=L,v.data("zoomParent",M),K=J):(C=V.find("img")[0],M=K=V,v.data("zoomParent",M))}function i(){V.css({width:f.flyout.width+"px",height:f.flyout.height+"px",top:"",left:"","margin-left":"","margin-top":""});var a=f.placement.match(/left|right/);a&&V.css(a[0],-f.flyout.width+"px"),f.placement.match(/loupe/)&&V.css({"margin-left":-f.flyout.width/2+"px","margin-top":-f.flyout.height/2+"px"}),V[0].className=V[0].className.replace(/enlarge_flyout\-[^$\s]+/," "),V.addClass("enlarge_flyout-"+f.placement)}function j(){f.disabled?v.addClass("enlarge_disabled"):v.removeClass("enlarge_disabled")}function k(){N=!v.data("zoomed"),v.data("zoomed",N)}function l(a){if(a=a||function(){},N){if(f.disabled)return a(),!1;var c=new Image;c.className="enlarge_img-loading",c.onload=function(){C.sizes=c.sizes,z&&E||(C.src=G),b(c).remove(),a()},c.sizes=o()+"px",z&&E?E&&(c.srcset=E):c.src=G,b(c).insertBefore(C)}else C.sizes=F,z||(C.src=D),a()}function m(){var a=K.width(),b=K.height(),c=C.offsetWidth,d=C.offsetHeight;K[0].scrollLeft=c/2-a/2,K[0].scrollTop=d/2-b/2}function n(){if(v.data("lockedZoom"))L.add(M).removeClass(O),v.data("lockedZoom",lockedZoom=!1),K.removeAttr("tabindex");else{if(f.disabled)return!1;L.add(M).addClass(O),v.data("lockedZoom",lockedZoom=!0),K.attr("tabindex","0"),K[0].focus()}}function o(){return L[0].offsetWidth*f.magnification}function p(){if(v.data("zoomed")){if(f.disabled)return!1;"inline"===f.placement&&J.add(L).css({width:L[0].offsetWidth+"px",height:parseFloat(getComputedStyle(L[0]).height)+"px"}),M.addClass(H),b(C).css("width",o()+"px"),b(w).trigger(c+".after-zoom-in")}else M.removeClass(H),"inline"===f.placement&&J.add(L).css({width:"",height:""}),b(C).css("width",""),b(w).trigger(c+".after-zoom-out")}function q(){function a(){f.placement=d,h(),b(w).unbind(c+".after-zoom-out",a)}var d=f.placement;"inline"!==d&&(b(w).bind(c+".after-zoom-out",a),f.placement="inline",h())}function r(){if(f.disabled&&!v.data("zoomed"))return!1;k(),l(function(){n(),p(),m()})}function s(a){"touchstart"===a.type&&(S=!0),S&&"mouseenter"===a.type||"mouseenter"===a.type&&!S&&!U||v.data("lockedZoom")||R||(R=!0,J.addClass(I),Q=setTimeout(function(){J.removeClass(I),k(),l(function(){p(),P=!0,u(T?T:a)})},f.delay))}function t(a){J.removeClass(I),clearTimeout(Q),P=!1,!1!==f.hoverZoomWithoutClick||S||(U=!1),S&&"mouseleave"===a.type&&(S=!1)}function u(a){if(P){if(S&&"mousemove"===a.type)return;var b=a.touches?a.touches[0]:a;a.preventDefault();var c=b.clientX-J[0].getBoundingClientRect().left,d=b.clientY-J[0].getBoundingClientRect().top;if(f.placement.match(/loupe/)){var e=(a.touches?-f.flyout.width/1.3:-f.flyout.width/2)+"px",g=(a.touches?-f.flyout.height/1.3:-f.flyout.height/2)+"px";requestAnimationFrame(function(){V.css({top:d+"px",left:c+"px","margin-left":e,"margin-top":g})})}var h=J[0].offsetWidth,i=J[0].offsetHeight,j=C.offsetWidth,k=C.offsetHeight,l=K[0].offsetWidth,m=K[0].offsetHeight;K[0].scrollLeft=c/h*(j-l),K[0].scrollLeft+=(c/h-.5)*l,K[0].scrollTop=d/i*(k-m),K[0].scrollTop+=(d/i-.5)*m}else T=a}var v=b(this),w=this,x=a.document.createElement("img"),y="srcset"in x,z=y&&"sizes"in x,A=b(this).find("a");if(!A.length)throw new Error(c+": requires an anchor element with `href` for the enlarged image source");var B=v.find("img")[0],C=B,D=C.src,E=b(C).attr("srcset"),F=b(C).attr("sizes"),G=A[0].href,H=(A[0].innerText,c+"-zoomed"),I=c+"-delay",J=b(C).closest(".enlarge_contain"),K=J,L=b(C).closest(".enlarge_pane")||v,M=b(this).data("zoomParent")||L;b(this).data("zoomParent",M);var N=v.data("zoomed")||!1;v.data("zoomed",N),v.data("lockedZoom",v.data("lockedZoom")||!1);var O=c+"-locked";if(!J.length)throw new Error(c+": requires an element above the image marked with the class `enlarge_contain`");if("string"!=typeof d){j(),v.data("updateOptions",function(a){f=b.extend(f,a),b(this).data("options",f),h(),i(),U=f.hoverZoomWithoutClick,f.image&&f.image.sizes&&(F=f.image.sizes,l()),j(),f.disabled&&v.data("zoomed")&&r()});var P,Q,R,S,T,U=f.hoverZoomWithoutClick,V=b('
').append(J.clone());V.insertAfter(L),function(){J.append('')}(),h(),i(),A.bind("keydown",function(a){13!==a.keyCode&&32!==a.keyCode||q(),32===a.keyCode&&(a.preventDefault(),b(this).trigger("click"))}).bind("click",function(a){a.preventDefault(),r()}),b(a).bind("resize",function(a){v.data("lockedZoom")&&r()}),b(a.document).bind("mouseup",function(a){v.data("lockedZoom")&&!b(a.target).closest(L).length&&r()}),b(B).bind("mouseenter touchstart",s).bind("mousemove touchmove",u).bind("mouseleave touchend",function(a){R=!1,N&&!v.data("lockedZoom")&&(k(),l(function(){p()})),t(a)}).bind("click",function(a){a.preventDefault(),S&&"inline"===f.placement&&r(),!1!==f.hoverZoomWithoutClick||S||(U=!U,U?b(this).trigger("mouseenter"):b(this).trigger("mouseleave"))}),b(this).bind("keydown keyup",function(a){37===a.keyCode||38===a.keyCode||39===a.keyCode||40===a.keyCode?(a.stopImmediatePropagation(),v.data("lockedZoom")||a.preventDefault()):"keyup"===a.type&&b(this).data("lockedZoom")&&27===a.keyCode&&(r(),A[0].focus(),a.stopImmediatePropagation())}),L.bind("scroll",function(){v.data("zoomed")&&(k(),l(function(){v.data("lockedZoom")&&n(),p()}))})}else{var W=Array.prototype.slice.call(e,1);switch(d){case"in":v.data("zoomed")||r();break;case"out":v.data("zoomed")&&r();break;case"isZoomed":g=v.data("zoomed");break;case"updateOptions":v.data("updateOptions")(W[0])}}});return void 0!==g?g:h}};"undefined"!=typeof module?module.exports=b:b()}("undefined"!=typeof global?global:this),function(a){var b={defaultConfig:{widths:["320","640","800","1024","2048","5000"],sizes:"100vw",policy:void 0,widthParam:"imwidth"}};a.Akamai=a.Akamai||{},a.Akamai.Image=b}("undefined"==typeof exports?window:exports),function(a,b){var c=Akamai.Util.component("Spin360",function(a,b){this._comp=new Akamai.Tau(a,this._options)});Akamai.Sourceable.extendStatic(c),c._componentEventMapping={"tau.auto-rotate-start":"play","tau.auto-rotate-stop":"pause"},c.prototype._updateOptions=function(a){a||(this._options=this._originalOptions),this._options=Akamai.Util.extend(!0,this._options,a),Akamai.Util.setDataAttrOptions(this._$el,c.defaultOptions,this._options,"Spin360")},c._renderImg=function(a,b){return'\n\t\t\t\n\t\t'},c.render=function(a,b){var d=Akamai.Util.options(c.defaultOptions,b);return Akamai.Util.trim('\n\t\t\t
\n\t\t\t\t'+Akamai.Util.map(a.urls,function(a){return c._renderImg(a,d)}).join("\n")+"\n\t\t\t
\n\t\t")},c.defaultOptions={autoplay:{enabled:!1,delay:1e3},controls:{arrows:!1,play:!1,text:{left:"Rotate Left",right:"Rotate Right",play:"Spin Object"}},frames:72,images:Akamai.Image.defaultConfig,interval:3e3,reverse:!1,sensitivity:1,template:void 0},c.prototype.goto=function(a,b){this._comp.goto(a),this._trigger("goto"),b&&b()},c.prototype.getIndex=function(){return this._comp.index},Akamai.Advanceable.extend(c),c.prototype.next,c.prototype.previous,c.prototype.play=function(){this._comp.autoRotate()},c.prototype.pause=function(){this._comp.stopAutoRotate()},c.prototype.getElement=function(){return this._$el[0]},c.States={Playing:0,Paused:1},c.prototype.getState=function(){return this._comp.autoInterval?c.States.Playing:c.States.Paused},a.Akamai=a.Akamai||{},a.Akamai.Spin360=c}("undefined"==typeof exports?window:exports,this.jQuery),function(){function a(a,b){throw new Error("Not allowed to instantiate MagnifierImpl")}a.prototype.updateOptions=function(a){throw new Error("magnifier updateOptions not implemented")},a.prototype.zoomIn=function(){throw new Error("magnifier zoomIn not implemented")},a.prototype.zoomOut=function(){throw new Error("magnifier zoomOut not implemented")},a.prototype.isMagnified=function(){throw new Error("magnifier isMagnified not implemented")},a.prototype.toggleZoom=function(){throw new Error("magnifier toggleZoom not implemented")},a.prototype.render=function(a,b,c,d,e){throw new Error("magnifier render not implemented")};var b="undefined"==typeof exports?window:exports;b.Akamai=b.Akamai||{},b.Akamai.MagnifierImpl=a}(),function(){function a(a,b){this._$el=$(a),this._$el.enlarge(b)}a.prototype=Object.create(Akamai.MagnifierImpl.prototype),a.prototype.constructor=a,a.prototype.updateOptions=function(a){this._$el.enlarge("updateOptions",a)},a.prototype.zoomIn=function(){this._$el.enlarge("in")},a.prototype.zoomOut=function(){this._$el.enlarge("out")},a.prototype.isMagnified=function(){return this._$el.enlarge("isZoomed")},a.prototype.toggleZoom=function(){this.isMagnified()?this.zoomOut():this.zoomIn()},a.render=function(a,b,c,d,e){return"\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t'+a.buttonText+"\n\t\t\t\t\n\t\t\t
\n\t\t"};var b="undefined"==typeof exports?window:exports;b.Akamai=b.Akamai||{},b.Akamai.MagnifierImplEnlarge=a}(),function(){function a(a,b){var c=this;c.element=a,c.handler=b,this.reset()}function b(a,b){this._$el=$(a),this._options=b,this._container=this._$el.get(0),this._content=this._$el.children().get(0),this._imgCast=$(this._content).find("div").get(0),this._zoomInBtn=this._$el.find("a.scroller-zoom-in").get(0),this._zoomOutBtn=this._$el.find("a.scroller-zoom-out").get(0),this._magnification=1;var c=this;this.scroller=new Scroller(this._renderer(),{zooming:b.enabled,minZoom:1,maxZoom:b.magnification,animationDuration:b.animationDuration});var d=this._container.getBoundingClientRect();this.scroller.setPosition(d.left+this._container.clientLeft,d.top+this._container.clientTop),this._installEventHandlers(),this._updateButtonStates(),setTimeout(function(){c._onResize()})}a.prototype.onDown=function(a){a.target==this.element&&(this.isDown?this.reset():(this.down.x=a.clientX,this.down.y=a.clientY,this.down.timestamp=new Date,this.isDown=!0))},a.prototype.onMove=function(){this.isMoved=!0},a.prototype.onUp=function(a){this.isDown&&(this.up.x=a.clientX,this.up.y=a.clientY,this.up.timestamp=new Date,!this.isMoved&&this.down.x===this.up.x&&this.down.y===this.up.y&&this.up.timestamp.getTime()-this.down.timestamp.getTime()<=500&&this.handler()),this.reset()},a.prototype.reset=function(a,b){this.down={x:-1/0,y:-1/0,timestamp:new Date(0)},this.isDown=!1,this.isMoved=!1,this.up={x:1/0,y:1/0,timestamp:new Date}},b.prototype=Object.create(Akamai.MagnifierImpl.prototype),b.prototype.constructor=b,b.prototype.updateOptions=function(a){var b=this._container.getBoundingClientRect();this.scroller.setPosition(b.left+this._container.clientLeft,b.top+this._container.clientTop),this._onResize(),this._zoomBy(1/this._options.magnification,!1)},b.prototype.zoomIn=function(){this._zoomBy(this._options.incrementalZoomFactor)},b.prototype.cyclicZoom=function(){this._magnification>=this._options.magnification?this._zoomBy(1/this._options.magnification):this.zoomIn()},b.prototype.zoomOut=function(){this._zoomBy(1/this._options.incrementalZoomFactor)},b.prototype.isMagnified=function(){return this._magnification>1},b.prototype.toggleZoom=function(){throw new Error("zoom toggle behavior is undefined in scroller mode")},b.render=function(a,b,c,d,e){return"\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t'+a.zoomInBtnText+'\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t'+a.zoomOutBtnText+"\n\t\t\t\t\n\t\t\t
\n\t\t"},b.prototype._zoomBy=function(a,b){var c=this,a=Number(a);void 0===b&&(b=this._options.animateZoom),1===a||1===this._magnification&&a<1||this._magnification===this._options.magnification&&a>1||(this.scroller.zoomTo(this._magnification*a,this._options.animateZoom),setTimeout(function(){c._onZoom()},this._options.animationDuration))},b.prototype._onZoom=function(){var a=this._magnification;this._magnification=Number(this.scroller.getValues().zoom.toFixed(2)),this._updateButtonStates(),a>this._magnification?this._$el.trigger("scroller.after-zoom-out"):a\n\t\t\t\t\n\t\t\t
\n\t\t")},a.Akamai=a.Akamai||{},a.Akamai.VideoImplPristine=c}("undefined"==typeof exports?window:exports,this.jQuery),function(a,b){var c=Akamai.Util.component("VideoImplIm",function(a,b){var d=Akamai.Util.options(Akamai.Video.defaultOptions,b);c._validateOptions(d)});c.prototype=Object.create(Akamai.VideoImpl.prototype),c.prototype.constructor=c,c._validateOptions=function(a){if(!Array.isArray(a.sizes)||3!==a.sizes.length)throw new Error("Akamai.Video sizes must have three entries");if(a.sizes.some(function(a){return isNaN(a)}))throw new Error("Akamai.VideoImplIm all sizes must be numbers")},c.render=function(a,b){var d=Akamai.Util.options(Akamai.Video.defaultOptions,b);c._validateOptions(d);var e,f=a.url,g=a.poster||"",h=window.innerWidth;e=h<992?d.sizes[0]:h<1200?d.sizes[1]:d.sizes[2];var i=[d.loop?"loop":"",d.autoplay?"autoplay":"",d.controls?"controls":"",d.muted?"muted":"","playsinline"],j=-1===f.indexOf("?")?"?":"&";return Akamai.Util.trim("\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t')},a.Akamai=a.Akamai||{},a.Akamai.VideoImplIm=c}("undefined"==typeof exports?window:exports,this.jQuery),function(a,b){var c=Akamai.Util.component("Video",function(a,b){if(this._comp=this._$el.length&&void 0,this._$videoElement=this._$el.find("video"),this._videoElement=this._$videoElement[0],!this._videoElement)throw new Error("Akamai.Video requires a child HTML Video element");if(!this._videoElement.play||!this._videoElement.pause)return this._unsupportedAPI=!0,void this._unsupported();this._setState(this._videoElement.paused?c.States.Paused:c.States.Playing),this._$el.bind("akamai-video-play",function(){this._setState(c.States.Playing)}.bind(this)).bind("akamai-video-pause",function(){this._setState(c.States.Paused)}.bind(this)),this._createPlayButton()});c.MODE_IM="im",c.MODE_PRISTINE="pristine",c._componentEventMapping={play:{to:"play",selector:"video"},pause:{to:"pause",selector:"video"},seeked:{to:"seek",selector:"video"}},c.prototype._updateOptions=function(a){a||(this._options=this._originalOptions),this._options=Akamai.Util.extend(!0,this._options,a),Akamai.Util.setDataAttrOptions(this._$el,c.defaultOptions,this._options,"Video")},c.defaultOptions={autoplay:!1,controls:!0,loop:!1,muted:!0,mode:c.MODE_PRISTINE,sizes:[854,1280,1920]},c.render=function(a,b){return(b&&b.mode===c.MODE_IM?Akamai.VideoImplIm:Akamai.VideoImplPristine).render(a,b)},c.States={Playing:0,Paused:1},c.prototype._unsupported=function(){return this._unsupportedAPI&&Akamai.Util.log("Video: video API not supported","error"),this._unsupportedAPI},c.prototype.play=function(){this._unsupported()||this._videoElement.play()},c.prototype.pause=function(){this._unsupported()||this._videoElement.pause()},c.prototype.getState=function(){return this._state},c.prototype._setState=function(a){this._state=a},c.prototype.seek=function(a){if(a<0||100\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t'),c.removeAttribute("controls"),a=this._$el.find("svg")[0],a.addEventListener("click",function(){c.play(),a.classList.add("is-hidden"),this._options.controls&&c.setAttribute("controls","controls")}.bind(this)))},a.Akamai=a.Akamai||{},a.Akamai.Video=c}("undefined"==typeof exports?window:exports,this.jQuery),function(a,b){var c=Akamai.Util.component("Carousel",function(a,b){this._spins=Akamai.Spin360.createMany(this._$el[0],this._options.spin360),this._magnifiers=Akamai.Magnifier.createMany(this._$el[0],this._options.magnifier),this._videos=Akamai.Video.createMany(this._$el[0],this._options.video),this._$el.snapper(this._options),this._setAspectRatio(),this._bindAspectAttributes(),this._options.slideshow.autostart&&setTimeout(function(){this.startSlideshow()}.bind(this),this._options.slideshow.interval),this._activeItemAttributes(),this._$el.bind("akamai-carousel-goto",this._activeItemAttributes.bind(this)),this._handleAutoplayables(),this._$el.bind("akamai-carousel-goto",this._handleAutoplayables.bind(this)),this._$el.bind("tau.touch-tracking-start",function(){this._$el.find(".snapper_pane").addClass("no-scroll")}.bind(this)).bind("tau.touch-tracking-stop",function(){this._$el.find(".snapper_pane").removeClass("no-scroll")}.bind(this)),this._$el.bind(this.constructor._stopSlideshowEvents.join(" "),this.stopSlideshow.bind(this))});c._stopSlideshowEvents=["akamai-magnifier-in","akamai-magnifier-out","akamai-spin360-goto","akamai-video-play","akamai-video-seek"],c.prototype._handleAutoplayables=function(){var a=function(a){return!!b(a.getElement()).closest("["+this.constructor.activeItemAttr+"]").length}.bind(this);this._videos.concat(this._spins).forEach(function(b){a(b)&&b._carouselWasPlaying?b.play():b.getState()==b.constructor.States.Playing?(b._carouselWasPlaying=!0,b.pause()):b._carouselWasPlaying=!1}.bind(this))},c.counter=0,c.prototype._updateOptions=function(a){a||(this._options=this._originalOptions),this._options=Akamai.Util.extend(!0,this._options,a);var b=function(a,b){a._updateOptions(this._options[b])}.bind(this);Akamai.Util.setDataAttrOptions(this._$el,c.defaultOptions,this._options,"Carousel"),this._$el.snapper("updateOptions",a),this._spins.forEach(function(a){b(a,"spin360")}),this._magnifiers.forEach(function(a){b(a,"magnifier")}),this._videos.forEach(function(a){b(a,"video")}),this._setAspectRatio()},c._componentEventMapping={"snapper.after-snap":"goto","snapper.snap":"snap","snapper.after-next":"next","snapper.after-prev":"previous"},c.defaultOptions={arrows:!0,aspectratio:!1,slideshow:{interval:4e3,autostart:!1},thumbnail:{placement:"bottom",type:"images",policy:void 0,sizes:"300px"},images:Akamai.Image.defaultConfig},c.renderMapping={image:Akamai.Magnifier,spin360:Akamai.Spin360,video:Akamai.Video},c._typeMapping={image:"magnifier"},c._uniqueItemId=function(a,b){return"akamai-carousel-"+c.counter+"-"+a.type+"-"+b},Akamai.Sourceable.extendStatic(c),c._renderItem=function(a,b,d){var e=c._typeMapping[a.type]||a.type;if(!c.renderMapping[a.type])throw new Error("\n\t\t\t\titem type "+a.type+" at index "+b+" must be 'image', 'spin360', or 'video'\n\t\t\t");return'\n\t\t\t
\n\t\t\t\t'+c.renderMapping[a.type].render(a,d[e])+"\n\t\t\t
\n\t\t"},c._renderThumbnailAnchor=function(a,b,d,e){var f="",g=a.alt||"";f="video"===a.type?a.poster||b.reduce(function(a,b){return a||c._thumbUrl(b)},""):c._thumbUrl(a),e.images.policy=e.thumbnail.policy;var h=c._fallbackSrc(f,e.images),i=c._srcset(f,e.images),j=e.images.sizes;e.thumbnail.policy&&(j=e.thumbnail.sizes);var k=[c._dataAttr+'-thumb-type="'+a.type+'"',"video"!=a.type||a.poster?"":"${Carousel._dataAttr}-thumb-noposter"];return'\n\t\t\t\n\t\t\t\t'+g+'\n\t\t\t\n\t\t'},c._thumbUrl=function(a){return"video"===a.type?a.poster:a.url||a.urls&&a.urls[0]},c._renderThumbnails=function(a,b){return a.length<=1?"":'\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t'+Akamai.Util.map(a,function(d,e){return c._renderThumbnailAnchor(d,a,e,b)}).join("\n")+"\n\t\t\t\t
\n\t\t\t
\n\t\t"},c.prototype._setAspectAttributes=function(a){var b=c._dataAttr+"-aspectratio-orientation";a.height()>a.width()?a.attr(b,"portrait"):a.height()=a.parent().width()?a.attr(b,"landscape"):a.attr(b,"portrait")},c.prototype._setAspectRatio=function(){var a=this._options.aspectratio||0,b=this._$el.find(".snapper_item");b.css("padding-top",a/b.length+"%")},c.prototype._bindAspectAttributes=function(){if(!1!==this._options.aspectratio){var a=(this._options.aspectratio,this),c=this._$el.find(".snapper_item");c.each(function(){var c,d=b(this),e=d.find("img, video").eq(0);e.bind("load loadedmetadata",loadBinding=function(){clearTimeout(c),d.is("["+Akamai.Spin360._dataAttr+"]")?a._setAspectAttributes(d.find("canvas")):a._setAspectAttributes(e)}),c=setTimeout(loadBinding,5e3)}),c.eq(0).find("img,video").eq(0).bind("load loadedmetadata",function(){a._trigger("first-media-load")})}},c.render=function(a,b){b=b||{};var d=Akamai.Util.options(c.defaultOptions,b),e=[""+c._dataAttr,c._dataAttr+'-item-count="'+(a||[]).length+'"','data-snapper-deeplinking="false"',d.arrows?"data-snapper-nextprev":""];return c.counter++,Akamai.Util.trim("\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t'+Akamai.Util.map(a,function(a,b){return c._renderItem(a,b,d)}).join("\n")+"\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t"+c._renderThumbnails(a,d)+"\n\t\t\t
\n\t\t")},c.prototype.getSpin360s=function(){return this._spins},c.prototype.getMagnifiers=function(){return this._magnifiers},c.prototype.getVideos=function(){return this._videos},c.prototype.goto=function(a,b){this._$el.snapper("goto",a,b)},c._itemAttr=c._dataAttr+"-item",c._activeItemAttr=c._itemAttr+"-active",c.prototype._activeItemAttributes=function(){var a=this.constructor._itemAttr,b=this.constructor._activeItemAttr;this._$el.find(".snapper_item").attr(a,!0).removeAttr(b).eq(this.getIndex()).attr(b,!0)},c.prototype.getIndex=function(){return this._$el.snapper("getIndex")},Akamai.Advanceable.extend(c),c.prototype.next,c.prototype.previous,c.prototype.startSlideshow=function(){this._$el.one("mousedown touchstart",this.stopSlideshow.bind(this)),this.next(function(){this._trigger("start-slideshow"),this._slideshowTimer=setTimeout(function(){this.startSlideshow()}.bind(this),this._options.slideshow.interval)}.bind(this))},c.prototype.stopSlideshow=function(){clearTimeout(this._slideshowTimer),this._slideshowTimer=void 0,this._trigger("stop-slideshow")},c.prototype.refresh=function(){this._$el.snapper("updateWidths")},a.Akamai=a.Akamai||{},a.Akamai.Carousel=c}("undefined"==typeof exports?window:exports,this.jQuery),function(a,b){var c=Akamai.Util.component("Fullscreen",function(a,b){this._options.enabled&&this._init()});c.prototype._init=function(){this._$fullscreen=this._$el,this.addButton(),this._fullscreenApiKeys=c._keys(),this._options.native?this._useNativeApi=!!this._fullscreenApiKeys:this._useNativeApi=!1,this._fullscreenFallbackEnabled=!1,this._$placeholder=b("
"),this._isFullscreen=!1,this.addEvents()},c.classes={btn:"akamai-fullscreen-btn",btnContainer:"akamai-fullscreen-btncontainer",enterBtn:"akamai-fullscreen-btn-enter",exitBtn:"akamai-fullscreen-btn-exit",active:"akamai-fullscreen-active",fallback:"akamai-fullscreen-fallback"},c.attr={width:"data-akamai-fullscreen-width",enterBtn:"data-akamai-fullscreen-btn",exitBtn:"data-akamai-fullscreen-exit-btn"},c.defaultOptions={enabled:!1,native:!1},c.prototype.addButton=function(){this._$widthAdjust=this._$fullscreen.children().filter(".focused"),this._$widthAdjust.length||(this._$widthAdjust=this._$fullscreen.children().eq(0)),null===this._$widthAdjust.attr(c.attr.width)&&this._$widthAdjust.attr(c.attr.width,""),this._$widthAdjust.find("["+c.attr.enterBtn+"]").length||(this._$buttonContainer=this._$widthAdjust,this._$buttonContainer.addClass(c.classes.btnContainer),this._$buttonContainer.append(this.render()))},c.prototype.addEvents=function(){this._$fullscreen.on("click",function(a){b(a.target).is("["+c.attr.enterBtn+"]")&&(this.enter(),a.preventDefault())}.bind(this)),this._$fullscreen.on("click",function(a){b(a.target).is("["+c.attr.exitBtn+"]")&&(this.exit(),a.preventDefault())}.bind(this)),this._useNativeApi&&document.addEventListener(this._fullscreenApiKeys.onchange,function(){document[this._fullscreenApiKeys.element]||this._exit()}.bind(this),!1),b(document).on("keydown",function(a){27===(a.keyCode||a.which)&&this._exit()}.bind(this))},c.prototype.render=function(){return Akamai.Util.trim("\n\t\t\t\n\t\t\t\n\t\t')},c._keyLookup=[{enter:"requestFullscreen",exit:"exitFullscreen",element:"fullscreenElement",onchange:"fullscreenchange"},{enter:"webkitRequestFullscreen",exit:"webkitExitFullscreen",element:"webkitFullscreenElement",onchange:"webkitfullscreenchange"},{enter:"webkitRequestFullScreen",exit:"webkitCancelFullScreen",element:"webkitCurrentFullScreenElement",onchange:"webkitfullscreenchange"},{enter:"mozRequestFullScreen",exit:"mozCancelFullScreen",element:"mozFullScreenElement",onchange:"mozfullscreenchange"},{enter:"msRequestFullscreen",exit:"msExitFullscreen",element:"msFullscreenElement",onchange:"MSFullscreenChange"}],c._keys=function(a){for(var b=c._keyLookup,a=document.body,d=0,e=b.length;dc&&(e="100%"),this._$widthAdjust.width(e)},c.prototype._revertWidth=function(){this._$widthAdjust.css("width","")},c.prototype.enter=function(){var a=this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex();this._$fullscreen.addClass(c.classes.active),this._useNativeApi?(this._$fullscreen[0][this._fullscreenApiKeys.enter](),this._adjustWidth()):(this._adjustWidth(),this._maximizePlacement()),this._isFullscreen=!0,this._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(a),this._trigger("enter")},c.prototype._exit=function(){var a=this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex();this._$fullscreen.removeClass(c.classes.active),this._useNativeApi||this._restorePlacement(),this._revertWidth(),this._isFullscreen=!1,this._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(a),this._trigger("exit")},c.prototype.exit=function(){this._useNativeApi&&document[this._fullscreenApiKeys.exit](),this._exit()},c.prototype.isFullscreen=function(){return this._isFullscreen},a.Akamai=a.Akamai||{},a.Akamai.Fullscreen=c}("undefined"==typeof exports?window:exports,this.jQuery),function(a,b){var c=Akamai.Util.component("Viewer",!0,function(a,b){this._updateSizesOptions(!0),this._onResizeComplete(this._updateSizesOptions.bind(this)),this._options.carousel=c._extendCarouselOptions(this._options),this._withData(function(b){b&&(this._checkJSONUrls(b),b=Akamai.Util.escapeJSONVals(b),this._options.items.data=b),this._tagMapping={},0==this._$el.children().length&&b&&(this._tagMapping=this.constructor._tagSplit(b,this._options.items),this._$el.append(this.constructor.render(b,this._options,!0))),this._carousels=Akamai.Carousel.createMany(a,this._options.carousel),this._fullscreen=new Akamai.Fullscreen(a,this._options.fullscreen),this._$el.bind("akamai-fullscreen-enter akamai-fullscreen-exit",function(){this._updateOptions(this._resolveBreakpointOptions())}.bind(this)),this._setBreakpoints(),this._trigger("init")}.bind(this),function(a){throw new Error(a)})});c.prototype._updateSizesOptions=function(a){var b=this._$el.width();b&&b>0&&(b+="px",this._options=Akamai.Util.extend(!0,this._options,{carousel:{images:{sizes:b}}})),a||this._updateOptions(this._options)},c.prototype._withData=function(a,c){if(this._options.items.uri)void 0!==this._options.items.uri&&this._options.items.uri.length>0&&b.get(this._options.items.uri,function(b){b="string"==typeof b?JSON.parse(b):b,this._exceedsLimit(b.items,c)||a(b.items)}.bind(this));else{if(this._exceedsLimit(this._options.items.data,c))return;a(this._options.items.data)}},c.prototype._exceedsLimit=function(a,b){return!!a&&(4*JSON.stringify(a).length/1024>this._options.items.limit&&(b=b||function(){},b("JSON data size exceeds "+this._options.items.limit+" KiB. Limit can be configured with items.limit Akamai.Viewer options."),!0))},c.prototype._setBreakpoints=function(){this._breakpoints=this._reduceBreakpoints(this._options.breakpoints||{}),this._fullscreenBreakpoints=this._reduceBreakpoints(this._options.fullscreenBreakpoints||{}),this._bindBreakpoints()},c.prototype._reduceBreakpoints=function(a){var b=[];for(var c in a)if(a.hasOwnProperty(c))try{b.push(parseInt(c,10))}catch(a){Akamai.Util.log(a,"error")}return b.sort(function(a,b){return b=0},c.defaultOptions={breakpoints:{},items:{data:void 0,defaultTag:"akamai-untagged",hostnames:[],limit:100,renderAll:!1,tags:void 0,uri:void 0},carousel:Akamai.Carousel.defaultOptions,magnifier:Akamai.Magnifier.defaultOptions,spin360:Akamai.Spin360.defaultOptions,video:Akamai.Video.defaultOptions,fullscreen:Akamai.Fullscreen.defaultOptions},c._tagSplit=function(a,b){var d=b;a=a.map(function(a){return a.tags=a.tags&&a.tags.length?a.tags:[d.defaultTag],a}),d.tags&&(a=d.data.filter(function(a){return a.tags.reduce(function(a,b){return a||d.tags.indexOf(b)>=0},!1)}));var e=a[0].tags[0],f=a.reduce(function(a,b){return(b.tags||[]).forEach(function(c){a[c]=a[c]||[],a[c].push(b)}),a},{});return f[c._firstRenderTag]=f[e],f},c._firstRenderTag="akamai-first-render",c._tagAttr=c._dataAttr+"-tag",c._renderTag=function(a,b){var d=a[0].tags[0];return Akamai.Util.trim("\n\t\t\t
\n\t\t\t\t'+Akamai.Carousel.render(a,b)+"\n\t\t\t
\n\t\t")},c.render=function(a,b,d){var e,f=c._tagSplit(a,b.items),g=c._extendCarouselOptions(b);e=b.items.renderAll?f.map(function(a,b){return c._renderTag(b,g)}).join(" "):c._renderTag(f[c._firstRenderTag],g);var h="\n\t\t\t
\n\t\t\t\t"+e+"\n\t\t\t
\n\t\t";return d?e:h},c.prototype.getCarousels=function(){return this._carousels},c.prototype.switchTag=function(a){if(-1==this.getTags().indexOf(a))return void Akamai.Util.log("tag: "+a+" is not present in the data for this viewer","error");var d="["+this.constructor._tagAttr+'="'+a+'"]',e=this._$el.find(d);if(e.length)this._showViewer(e);else{var f=this._resolveBreakpointOptions(),g=c._extendCarouselOptions(f),h=b(this.constructor._renderTag(this._tagMapping[a],g,!0)),i=h.find("img"),j=i.length,k=0;h.css("display","none"),i.bind("load",function(){if(++k===j){i.unbind("load");var a=Akamai.Carousel.createMany(h[0],g);a.forEach(function(a){a.goto(a.getIndex())}),this._carousels=this._carousels.concat(a),this._showViewer(h,a)}}.bind(this)),this._$el.append(h)}},c.prototype._showViewer=function(a,b){this._$el.find("["+this.constructor._tagAttr+"]").css("display","none").removeClass("focused"),a.css("display","block").addClass("focused"),b&&b.map(function(a){a.refresh()}),this._fullscreen.addButton(),this._trigger("switch-tag")},c.prototype.getTags=function(){if(this._tags)return this._tags;var a=[];for(var b in this._tagMapping)this._tagMapping.hasOwnProperty(b)&&b!==c._firstRenderTag&&a.push(b);return this._tags=a},a.Akamai=a.Akamai||{},a.Akamai.Viewer=c}("undefined"==typeof exports?window:exports,this.jQuery),function(a,b){b.fn.akamaiViewer=function(a){this.each(function(b,c){new Akamai.Viewer(c,a)})}}("undefined"==typeof exports?window:exports,this.jQuery); \ No newline at end of file diff --git a/app/html/js/akamai-viewer.js.map b/app/html/js/akamai-viewer.js.map new file mode 100644 index 0000000..89a4cfe --- /dev/null +++ b/app/html/js/akamai-viewer.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["akamai-viewer.es2015"],"names":["global","time","Date","now","desiredFrames","millisecondsPerSecond","running","counter","core","effect","Animate","requestAnimationFrame","requestFrame","webkitRequestAnimationFrame","mozRequestAnimationFrame","oRequestAnimationFrame","isNative","test","toString","callback","root","TARGET_FPS","requests","requestCount","rafHandle","intervalHandle","lastActive","callbackHandle","setInterval","currentRequests","key","hasOwnProperty","clearInterval","stop","id","cleared","isRunning","start","stepCallback","verifyCallback","completedCallback","duration","easingMethod","lastFrame","percent","dropCounter","document","body","newRunning","usedId","step","virtual","render","droppedFrames","Math","round","j","min","value","Scroller","options","__callback","scrollingX","scrollingY","animating","animationDuration","bouncing","locking","paging","snapping","zooming","minZoom","maxZoom","easeOutCubic","pos","pow","easeInOutCubic","members","__isSingleTouch","__isTracking","__isGesturing","__isDragging","__isDecelerating","__isAnimating","__clientLeft","__clientTop","__clientWidth","__clientHeight","__contentWidth","__contentHeight","__snapWidth","__snapHeight","__refreshHeight","__refreshActive","__refreshActivate","__refreshDeactivate","__refreshStart","__zoomLevel","__scrollLeft","__scrollTop","__maxScrollLeft","__maxScrollTop","__scheduledLeft","__scheduledTop","__scheduledZoom","__lastTouchLeft","__lastTouchTop","__lastTouchMove","__positions","__minDecelerationScrollLeft","__minDecelerationScrollTop","__maxDecelerationScrollLeft","__maxDecelerationScrollTop","__decelerationVelocityX","__decelerationVelocityY","setDimensions","clientWidth","clientHeight","contentWidth","contentHeight","self","__computeScrollMax","scrollTo","setPosition","left","top","setSnapSize","width","height","activatePullToRefresh","activateCallback","deactivateCallback","startCallback","finishPullToRefresh","getValues","zoom","getScrollMax","zoomTo","level","animate","originLeft","originTop","Error","oldLevel","max","__publish","zoomBy","factor","scrollBy","startLeft","startTop","doMouseZoom","wheelDelta","timeStamp","pageX","pageY","change","doTouchStart","touches","length","valueOf","currentTouchLeft","currentTouchTop","isSingleTouch","abs","__initialTouchLeft","__initialTouchTop","__zoomLevelStart","__lastScale","__enableScrollX","__enableScrollY","doTouchMove","scale","positions","moveX","moveY","scrollLeft","scrollTop","currentTouchLeftRel","currentTouchTopRel","maxScrollLeft","maxScrollTop","splice","push","minimumTrackingForScroll","minimumTrackingForDrag","distanceX","distanceY","doTouchEnd","endPos","startPos","i","timeOffset","movedLeft","movedTop","minVelocityToStartDeceleration","__startDeceleration","wasAnimating","oldLeft","oldTop","oldZoom","diffLeft","diffTop","diffZoom","verify","completed","renderedFramesPerSecond","animationId","wasFinished","zoomLevel","floor","ceil","__stepThroughDeceleration","minVelocityToKeepDecelerating","scrollLeftFixed","scrollTopFixed","frictionFactor","scrollOutsideX","scrollOutsideY","penetrationDeceleration","penetrationAcceleration","prototype","factory","define","amd","module","exports","win","window","doc","shoestring","prim","sec","pType","ret","sel","Shoestring","call","ready","constructor","indexOf","dfrag","createElement","innerHTML","children","each","removeChild","find","querySelectorAll","Object","NodeList","Array","selector","merge","reverse","fn","extend","first","second","len","ajax","url","params","req","XMLHttpRequest","settings","data","encodeURIComponent","method","open","async","setRequestHeader","headers","onreadystatechange","readyState","res","responseText","replace","status","cancel","match","RegExp","$1","error","success","send","get","load","args","arguments","intCB","html","apply","post","collection","val","il","inArray","needle","haystack","isin","readyQueue","runReady","shift","attachEvent","addEventListener","is","parents","check","_checkElements","parent","e","needles","name","undefined","shoestringData","removeData","$","addClass","className","classes","split","add","after","fragment","nodeType","jl","insertEl","cloneNode","parentNode","insertBefore","nextSibling","append","appendChild","appendTo","attr","nameStr","setAttribute","getAttribute","before","childs","clone","closest","element","$self","parentElement","cssExceptions","convertPropertyName","str","character","toUpperCase","_getStyle","property","getComputedStyle","getPropertyValue","vendorPrefixes","convert","k","_setStyle","convertedProperty","style","css","eq","index","filter","wsel","context","createDocumentFragment","finds","concat","elements","_dimension","set","offsetName","letter","h","l","outerHTML","pile","_getIndex","result","item","documentElement","childNodes","insertAfter","last","next","found","not","offset","offsetTop","offsetLeft","curr","prepend","firstChild","prependTo","prev","prevAll","$previous","propFix","contenteditable","readonly","tabindex","prop","removeAttr","removeAttribute","removeClass","cname","newClassName","regex","remove","removeProp","replaceWith","replaceChild","inputTypes","inputTypeTest","join","serialize","type","checked","nodeName","selectedIndex","nodeValue","siblings","sibs","el","getText","elem","node","textContent","text","tagName","optionSet","option","values","newIndex","selected","wrapInner","inH","initEventCache","evt","events","loop","addToEventCache","eventInfo","obj","isCustomEvent","callfunc","originalCallback","namespace","customEventLoop","bind","evts","encasedCallback","triggeredElement","_namespace","returnTrue","isDefaultPrevented","originalPreventDefault","preventDefault","preventDefaultConstructor","returnValue","target","srcElement","stopPropagation","cancelBubble","_args","domEventCallback","customEventCallback","oEl","originalEvent","ssEventTrigger","on","unbind","event","unbindAll","bound","matched","removeEventListener","evtKey","off","one","thisevt","cbs","$t","triggerHandler","createEvent","bindings","initEvent","unshift","trigger","click","dispatchEvent","jQuery","Akamai","componentNamespace","$window","$doc","ns","Function","Tau","startIndex","reducedStepSize","$element","$initial","$loading","frames","parseInt","stepSize","autoRotateStartDelay","autoplay","delay","mouseMoveBinding","rotateEvent","touchMoveBinding","path","Path","$render","canvas","getContext","canvasCtx","clearTimeout","canvasResizeTimeout","setTimeout","renderCanvas","controls","play","right","createControls","createImages","enabled","autostartTimeout","autoRotate","autoRotateTraversalTime","verticalScrollRatio","maxVelocity","$controls","controlAnchorMarkup","arrows","onControlDown","onControlUp","$link","stopAutoRotate","autoInterval","delta","goto","$next","normalizedIndex","imageCount","$images","tauImageLoaded","showLoading","$current","$img","img","naturalWidth","naturalHeight","parentWidth","calcHeight","complete","drawImage","src","$new","boundImageLoaded","loadedCount","markImageLoaded","template","imgs","imageLoaded","initTriggered","hideLoading","initialized","track","autoRotateDelay","interval","point","tracking","release","reset","cursorGrab","defaultThreshold","rotateThreshold","sensitivity","getPoint","downX","x","downY","y","downIndex","slow","isSufficient","clearSlowInterval","rotate","velocity","decelVal","decelTimeStep","slowInterval","decel","sign","cursorRelease","touch","clientX","clientY","deltaX","deltaY","record","getTime","prevPoint","prevPrevPoint","distance","prevTime","prevPrevTime","timeStep","Util","preflight","_$el","_dataAttr","_options","defaultOptions","_originalOptions","mapEvents","component","init","constr","existing","_trigger","toLowerCase","createMany","mapping","_componentEventMapping","mappedTo","to","$el","mapEvent","from","$comps","objects","propRequired","msg","componentName","fullEvent","camelToKabob","string","m","defaults","dataAttrOptions","cloneDefaults","getDataAttrOptions","finalOptions","setDataAttrOptions","prefix","dashedProp","currentDataProp","subDefaults","newPrefix","subConfig","coerceAttrVal","setDataAttrOptionsPrefix","subOptions","parseFloat","deep","copy","copyIsArray","isPlainObject","proto","Ctor","rtrim","trim","map","array","reduce","acc","log","console","mapJSONVals","o","escapeJSONVals","escapeHTML","entityMap","String","fromEntityMap","s","Advanceable","extension","getIndex","previous","Sourceable","_srcArray","includeW","policy","thumbnail","widthParam","widths","w","_srcset","_fallbackSrc","_largestSrc","pop","extendStatic","raf","toss","tossing","sLeft","sTop","op","easing","finished","endLeft","endTop","startTime","endTime","run","curTime","tossingElems","state","t","b","c","d","pluginName","optionsOrMethod","pluginArgs","scrollListening","testProp","snapSupported","CSS","supports","itemsAtOffset","$childNodes","containWidth","activeItems","outerWidth","$elem","outerHeight","snapEvent","activeSlides","nothrow","overthrow","innerResult","addNextPrev","autoTimeout","$slider","enhancedClass","$itemsContain","$items","numItems","$nav","navSelectedClass","useDeepLinking","slice","itemWidth","snapScroll","updateWidths","$navInner","getAutoplayInterval","autoTiming","parseError","itemsContainStyle","itemStyle","sliderWidth","computed","itemLeftMargin","itemRightMargin","outerItemWidth","iPercentWidth","iPercentRightMargin","iPercentLeftMargin","outerPercentWidth","percentAsWidth","percentAsRightMargin","percentAsLeftMargin","$nextprev","$nextprevContain","slideID","arrowNavigate","$slide","history","replaceState","title","keyCode","stopImmediatePropagation","snapScrollCancelled","isTouched","currScroll","roundedScroll","maxScroll","startSlide","afterResize","snapStay","forward","scrollNav","lastActiveItem","activeItem","force","currTime","navWidth","navHeight","activeIndex","activeChild","thumbX","thumbY","scrollStop","scrolling","lastScroll","timeout","enlarge","button","hoverZoomWithoutClick","flyout","placement","magnification","internalResult","testimg","srcsetSupported","srcsetSizesSupported","$anchor","initialImg","targetImg","imgOriginalSrc","srcset","imgOriginalSizes","imgZoomSrc","href","initialText","innerText","zoomClass","delayClass","$contain","$zoomContain","$parentPane","$zoomParent","zoomed","lockZoomClass","standardToggleZoom","updatePlacement","$flyout","positionFlyout","flyoutSide","loupe","disable","disabled","opts","hoverEnabled","image","sizes","toggleImgSrc","addLoader","toggleZoomState","zoomimg","Image","onload","imgZoomWidth","scrollToCenter","pw","ph","offsetWidth","offsetHeight","toggleLockZoom","lockedZoom","focus","toggleImgZoom","forceInlineMode","oldO","resetPlacement","trackingOn","trackingTimer","mouseEntered","touchStarted","lastMouseMove","startTrackingDelay","scrollWithMouse","stopTrackingDelay","ev","getBoundingClientRect","mLeft","mTop","containHeight","containScrollWidth","containScrollHeight","zoomContainWidth","zoomContainHeight","widthFactor","heightFactor","defaultConfig","Spin360","_comp","_updateOptions","_renderImg","images","json","alt","urls","pause","getElement","States","Playing","Paused","getState","MagnifierImpl","updateOptions","zoomIn","zoomOut","isMagnified","toggleZoom","dataAttr","fallbackSrc","largestSrc","srcSet","ex","MagnifierImplEnlarge","create","buttonText","ClickTracker","handler","onDown","isDown","down","timestamp","onMove","isMoved","onUp","up","Infinity","MagnifierImplScroller","_container","_content","_imgCast","_zoomInBtn","_zoomOutBtn","_magnification","scroller","_renderer","rect","clientLeft","clientTop","_installEventHandlers","_updateButtonStates","_onResize","_zoomBy","incrementalZoomFactor","cyclicZoom","zoomInBtnText","zoomOutBtnText","Number","animateZoom","_onZoom","oldMagnification","toFixed","docStyle","engine","opera","navigator","cpuClass","vendorPrefix","trident","gecko","webkit","presto","helperElem","undef","perspectiveProperty","transformProperty","marginLeft","marginTop","clickTracker","changedTouches","Magnifier","mode","MODE_ANIMATED_ZOOM","_impl","MODE_HOVER_ZOOM","impl","in","out","toggle","VideoImpl","VideoImplPristine","Video","poster","mime","attrs","muted","VideoImplIm","_validateOptions","isArray","some","size","isNaN","viewPortWidth","innerWidth","joiner","_$videoElement","_videoElement","_unsupportedAPI","_unsupported","_setState","paused","_createPlayButton","MODE_IM","MODE_PRISTINE","_state","seek","newTime","currentTime","setLoop","videoPlayButton","videoWrapper","video","insertAdjacentHTML","classList","Carousel","_spins","spin360","_magnifiers","magnifier","_videos","snapper","_setAspectRatio","_bindAspectAttributes","slideshow","autostart","startSlideshow","_activeItemAttributes","_handleAutoplayables","_stopSlideshowEvents","stopSlideshow","isParentActive","comp","activeItemAttr","forEach","_carouselWasPlaying","update","aspectratio","renderMapping","_typeMapping","_uniqueItemId","_renderItem","mappedType","_renderThumbnailAnchor","thumbUrl","altText","_thumbUrl","_renderThumbnails","_setAspectAttributes","orientationAttr","loadBound","$item","$loadable","loadBinding","carouselOptions","templateAttrs","getSpin360s","getMagnifiers","getVideos","_itemAttr","_activeItemAttr","attrAllItems","attrActiveItem","_slideshowTimer","refresh","Fullscreen","_init","_$fullscreen","addButton","_fullscreenApiKeys","_keys","native","_useNativeApi","_fullscreenFallbackEnabled","_$placeholder","_isFullscreen","addEvents","btn","btnContainer","enterBtn","exitBtn","active","fallback","_$widthAdjust","_$buttonContainer","$target","enter","exit","onchange","_exit","code","which","_keyLookup","keys","_maximizePlacement","_restorePlacement","_adjustWidth","widthSmall","heightSmall","viewportWidth","screen","viewportHeight","innerHeight","widthBig","_revertWidth","Viewer","_carousels","isFullscreen","_updateSizesOptions","_onResizeComplete","carousel","_extendCarouselOptions","_withData","_checkJSONUrls","items","_tagMapping","_tagSplit","_fullscreen","fullscreen","_resolveBreakpointOptions","_setBreakpoints","localUpdateOnly","elWidth","fail","uri","_exceedsLimit","JSON","parse","kb","stringify","limit","_breakpoints","_reduceBreakpoints","breakpoints","_fullscreenBreakpoints","fullscreenBreakpoints","_bindBreakpoints","bps","bp","sort","a","_bindMatchMedia","cbtimer","minWidth","maxWidth","matchMedia","query","initList","addListener","_mediaMatch","list","ignoreDefault","matches","resolvedOptions","_getCurrentMinWidth","bpConfig","cloned","ptions","clonedCarousel","hostnames","_urlHostnameMatch","parser","hostname","location","defaultTag","renderAll","tags","tag","def","tagMapping","_firstRenderTag","_tagAttr","_renderTag","unwrapped","childMarkup","datum","wrapped","getCarousels","switchTag","getTags","$taggedViewer","$markup","$imgs","imgCount","loaded","carousels","_showViewer","$viewer","_tags","akamaiViewer"],"mappings":"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","file":"akamai-viewer.js","sourcesContent":["/*! Copyright 2019, Akamai Technologies, Inc. All Rights Reserved. akamai-viewer-0.7.2 *//**\n * Generic animation class with support for dropped frames both optional easing and duration.\n *\n * Optional duration is useful when the lifetime is defined by another condition than time\n * e.g. speed of an animating object, etc.\n *\n * Dropped frame logic allows to keep using the same updater logic independent from the actual\n * rendering. This eases a lot of cases where it might be pretty complex to break down a state\n * based on the pure time difference.\n */\n(function(global) {\n\tvar time = Date.now || function() {\n\t\treturn +new Date();\n\t};\n\tvar desiredFrames = 60;\n\tvar millisecondsPerSecond = 1000;\n\tvar running = {};\n\tvar counter = 1;\n\n\t// Create namespaces\n\tif (!global.core) {\n\t\tglobal.core = { effect : {} };\n\n\t} else if (!core.effect) {\n\t\tcore.effect = {};\n\t}\n\n\tcore.effect.Animate = {\n\n\t\t/**\n\t\t * A requestAnimationFrame wrapper / polyfill.\n\t\t *\n\t\t * @param callback {Function} The callback to be invoked before the next repaint.\n\t\t * @param root {HTMLElement} The root element for the repaint\n\t\t */\n\t\trequestAnimationFrame: (function() {\n\n\t\t\t// Check for request animation Frame support\n\t\t\tvar requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;\n\t\t\tvar isNative = !!requestFrame;\n\n\t\t\tif (requestFrame && !/requestAnimationFrame\\(\\)\\s*\\{\\s*\\[native code\\]\\s*\\}/i.test(requestFrame.toString())) {\n\t\t\t\tisNative = false;\n\t\t\t}\n\n\t\t\tif (isNative) {\n\t\t\t\treturn function(callback, root) {\n\t\t\t\t\trequestFrame(callback, root)\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tvar TARGET_FPS = 60;\n\t\t\tvar requests = {};\n\t\t\tvar requestCount = 0;\n\t\t\tvar rafHandle = 1;\n\t\t\tvar intervalHandle = null;\n\t\t\tvar lastActive = +new Date();\n\n\t\t\treturn function(callback, root) {\n\t\t\t\tvar callbackHandle = rafHandle++;\n\n\t\t\t\t// Store callback\n\t\t\t\trequests[callbackHandle] = callback;\n\t\t\t\trequestCount++;\n\n\t\t\t\t// Create timeout at first request\n\t\t\t\tif (intervalHandle === null) {\n\n\t\t\t\t\tintervalHandle = setInterval(function() {\n\n\t\t\t\t\t\tvar time = +new Date();\n\t\t\t\t\t\tvar currentRequests = requests;\n\n\t\t\t\t\t\t// Reset data structure before executing callbacks\n\t\t\t\t\t\trequests = {};\n\t\t\t\t\t\trequestCount = 0;\n\n\t\t\t\t\t\tfor(var key in currentRequests) {\n\t\t\t\t\t\t\tif (currentRequests.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tcurrentRequests[key](time);\n\t\t\t\t\t\t\t\tlastActive = time;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Disable the timeout when nothing happens for a certain\n\t\t\t\t\t\t// period of time\n\t\t\t\t\t\tif (time - lastActive > 2500) {\n\t\t\t\t\t\t\tclearInterval(intervalHandle);\n\t\t\t\t\t\t\tintervalHandle = null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}, 1000 / TARGET_FPS);\n\t\t\t\t}\n\n\t\t\t\treturn callbackHandle;\n\t\t\t};\n\n\t\t})(),\n\n\n\t\t/**\n\t\t * Stops the given animation.\n\t\t *\n\t\t * @param id {Integer} Unique animation ID\n\t\t * @return {Boolean} Whether the animation was stopped (aka, was running before)\n\t\t */\n\t\tstop: function(id) {\n\t\t\tvar cleared = running[id] != null;\n\t\t\tif (cleared) {\n\t\t\t\trunning[id] = null;\n\t\t\t}\n\n\t\t\treturn cleared;\n\t\t},\n\n\n\t\t/**\n\t\t * Whether the given animation is still running.\n\t\t *\n\t\t * @param id {Integer} Unique animation ID\n\t\t * @return {Boolean} Whether the animation is still running\n\t\t */\n\t\tisRunning: function(id) {\n\t\t\treturn running[id] != null;\n\t\t},\n\n\n\t\t/**\n\t\t * Start the animation.\n\t\t *\n\t\t * @param stepCallback {Function} Pointer to function which is executed on every step.\n\t\t * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`\n\t\t * @param verifyCallback {Function} Executed before every animation step.\n\t\t * Signature of the method should be `function() { return continueWithAnimation; }`\n\t\t * @param completedCallback {Function}\n\t\t * Signature of the method should be `function(droppedFrames, finishedAnimation) {}`\n\t\t * @param duration {Integer} Milliseconds to run the animation\n\t\t * @param easingMethod {Function} Pointer to easing function\n\t\t * Signature of the method should be `function(percent) { return modifiedValue; }`\n\t\t * @param root {Element ? document.body} Render root, when available. Used for internal\n\t\t * usage of requestAnimationFrame.\n\t\t * @return {Integer} Identifier of animation. Can be used to stop it any time.\n\t\t */\n\t\tstart: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {\n\n\t\t\tvar start = time();\n\t\t\tvar lastFrame = start;\n\t\t\tvar percent = 0;\n\t\t\tvar dropCounter = 0;\n\t\t\tvar id = counter++;\n\n\t\t\tif (!root) {\n\t\t\t\troot = document.body;\n\t\t\t}\n\n\t\t\t// Compacting running db automatically every few new animations\n\t\t\tif (id % 20 === 0) {\n\t\t\t\tvar newRunning = {};\n\t\t\t\tfor (var usedId in running) {\n\t\t\t\t\tnewRunning[usedId] = true;\n\t\t\t\t}\n\t\t\t\trunning = newRunning;\n\t\t\t}\n\n\t\t\t// This is the internal step method which is called every few milliseconds\n\t\t\tvar step = function(virtual) {\n\n\t\t\t\t// Normalize virtual value\n\t\t\t\tvar render = virtual !== true;\n\n\t\t\t\t// Get current time\n\t\t\t\tvar now = time();\n\n\t\t\t\t// Verification is executed before next animation step\n\t\t\t\tif (!running[id] || (verifyCallback && !verifyCallback(id))) {\n\n\t\t\t\t\trunning[id] = null;\n\t\t\t\t\tcompletedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);\n\t\t\t\t\treturn;\n\n\t\t\t\t}\n\n\t\t\t\t// For the current rendering to apply let's update omitted steps in memory.\n\t\t\t\t// This is important to bring internal state variables up-to-date with progress in time.\n\t\t\t\tif (render) {\n\n\t\t\t\t\tvar droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;\n\t\t\t\t\tfor (var j = 0; j < Math.min(droppedFrames, 4); j++) {\n\t\t\t\t\t\tstep(true);\n\t\t\t\t\t\tdropCounter++;\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t\t// Compute percent value\n\t\t\t\tif (duration) {\n\t\t\t\t\tpercent = (now - start) / duration;\n\t\t\t\t\tif (percent > 1) {\n\t\t\t\t\t\tpercent = 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Execute step callback, then...\n\t\t\t\tvar value = easingMethod ? easingMethod(percent) : percent;\n\t\t\t\tif ((stepCallback(value, now, render) === false || percent === 1) && render) {\n\t\t\t\t\trunning[id] = null;\n\t\t\t\t\tcompletedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null);\n\t\t\t\t} else if (render) {\n\t\t\t\t\tlastFrame = now;\n\t\t\t\t\tcore.effect.Animate.requestAnimationFrame(step, root);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Mark as running\n\t\t\trunning[id] = true;\n\n\t\t\t// Init first step\n\t\t\tcore.effect.Animate.requestAnimationFrame(step, root);\n\n\t\t\t// Return unique animation ID\n\t\t\treturn id;\n\t\t}\n\t};\n})(this);\n\n\nvar Scroller;\n\n(function() {\n\t\n\t/**\n\t * A pure logic 'component' for 'virtual' scrolling/zooming.\n\t */\n\tScroller = function(callback, options) {\n\n\t\tthis.__callback = callback;\n\n\t\tthis.options = {\n\t\t\t\n\t\t\t/** Enable scrolling on x-axis */\n\t\t\tscrollingX: true,\n\n\t\t\t/** Enable scrolling on y-axis */\n\t\t\tscrollingY: true,\n\n\t\t\t/** Enable animations for deceleration, snap back, zooming and scrolling */\n\t\t\tanimating: true,\n\n\t\t\t/** duration for animations triggered by scrollTo/zoomTo */\n\t\t\tanimationDuration: 250,\n\n\t\t\t/** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */\n\t\t\tbouncing: true,\n\n\t\t\t/** Enable locking to the main axis if user moves only slightly on one of them at start */\n\t\t\tlocking: true,\n\n\t\t\t/** Enable pagination mode (switching between full page content panes) */\n\t\t\tpaging: false,\n\n\t\t\t/** Enable snapping of content to a configured pixel grid */\n\t\t\tsnapping: false,\n\n\t\t\t/** Enable zooming of content via API, fingers and mouse wheel */\n\t\t\tzooming: false,\n\n\t\t\t/** Minimum zoom level */\n\t\t\tminZoom: 0.5,\n\n\t\t\t/** Maximum zoom level */\n\t\t\tmaxZoom: 3\n\t\t\t\n\t\t};\n\n\t\tfor (var key in options) {\n\t\t\tthis.options[key] = options[key];\n\t\t}\n\n\t};\n\t\n\t\n\t// Easing Equations (c) 2003 Robert Penner, all rights reserved.\n\t// Open source under the BSD License.\n\t\n\t/**\n\t * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)\n\t**/\n\tvar easeOutCubic = function(pos) {\n\t\treturn (Math.pow((pos - 1), 3) + 1);\n\t};\n\n\t/**\n\t * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)\n\t**/\n\tvar easeInOutCubic = function(pos) {\n\t\tif ((pos /= 0.5) < 1) {\n\t\t\treturn 0.5 * Math.pow(pos, 3);\n\t\t}\n\n\t\treturn 0.5 * (Math.pow((pos - 2), 3) + 2);\n\t};\n\t\n\t\n\tvar members = {\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tINTERNAL FIELDS :: STATUS\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/** {Boolean} Whether only a single finger is used in touch handling */\n\t\t__isSingleTouch: false,\n\n\t\t/** {Boolean} Whether a touch event sequence is in progress */\n\t\t__isTracking: false,\n\n\t\t/**\n\t\t * {Boolean} Whether a gesture zoom/rotate event is in progress. Activates when\n\t\t * a gesturestart event happens. This has higher priority than dragging.\n\t\t */\n\t\t__isGesturing: false,\n\n\t\t/**\n\t\t * {Boolean} Whether the user has moved by such a distance that we have enabled\n\t\t * dragging mode. Hint: It's only enabled after some pixels of movement to\n\t\t * not interrupt with clicks etc.\n\t\t */\n\t\t__isDragging: false,\n\n\t\t/**\n\t\t * {Boolean} Not touching and dragging anymore, and smoothly animating the\n\t\t * touch sequence using deceleration.\n\t\t */\n\t\t__isDecelerating: false,\n\n\t\t/**\n\t\t * {Boolean} Smoothly animating the currently configured change\n\t\t */\n\t\t__isAnimating: false,\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tINTERNAL FIELDS :: DIMENSIONS\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/** {Integer} Available outer left position (from document perspective) */\n\t\t__clientLeft: 0,\n\n\t\t/** {Integer} Available outer top position (from document perspective) */\n\t\t__clientTop: 0,\n\n\t\t/** {Integer} Available outer width */\n\t\t__clientWidth: 0,\n\n\t\t/** {Integer} Available outer height */\n\t\t__clientHeight: 0,\n\n\t\t/** {Integer} Outer width of content */\n\t\t__contentWidth: 0,\n\n\t\t/** {Integer} Outer height of content */\n\t\t__contentHeight: 0,\n\n\t\t/** {Integer} Snapping width for content */\n\t\t__snapWidth: 100,\n\n\t\t/** {Integer} Snapping height for content */\n\t\t__snapHeight: 100,\n\n\t\t/** {Integer} Height to assign to refresh area */\n\t\t__refreshHeight: null,\n\t\t\n\t\t/** {Boolean} Whether the refresh process is enabled when the event is released now */\n\t\t__refreshActive: false,\n\t\t\n\t\t/** {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */\n\t\t__refreshActivate: null,\n\n\t\t/** {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */\n\t\t__refreshDeactivate: null,\n\t\t\n\t\t/** {Function} Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */\n\t\t__refreshStart: null,\n\n\t\t/** {Number} Zoom level */\n\t\t__zoomLevel: 1,\n\n\t\t/** {Number} Scroll position on x-axis */\n\t\t__scrollLeft: 0,\n\n\t\t/** {Number} Scroll position on y-axis */\n\t\t__scrollTop: 0,\n\n\t\t/** {Integer} Maximum allowed scroll position on x-axis */\n\t\t__maxScrollLeft: 0,\n\n\t\t/** {Integer} Maximum allowed scroll position on y-axis */\n\t\t__maxScrollTop: 0,\n\n\t\t/* {Number} Scheduled left position (final position when animating) */\n\t\t__scheduledLeft: 0,\n\n\t\t/* {Number} Scheduled top position (final position when animating) */\n\t\t__scheduledTop: 0,\n\n\t\t/* {Number} Scheduled zoom level (final scale when animating) */\n\t\t__scheduledZoom: 0,\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tINTERNAL FIELDS :: LAST POSITIONS\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/** {Number} Left position of finger at start */\n\t\t__lastTouchLeft: null,\n\n\t\t/** {Number} Top position of finger at start */\n\t\t__lastTouchTop: null,\n\n\t\t/** {Date} Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */\n\t\t__lastTouchMove: null,\n\t\t\n\t\t/** {Array} List of positions, uses three indexes for each state: left, top, timestamp */\n\t\t__positions: null,\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tINTERNAL FIELDS :: DECELERATION SUPPORT\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/** {Integer} Minimum left scroll position during deceleration */\n\t\t__minDecelerationScrollLeft: null,\n\n\t\t/** {Integer} Minimum top scroll position during deceleration */\n\t\t__minDecelerationScrollTop: null,\n\n\t\t/** {Integer} Maximum left scroll position during deceleration */\n\t\t__maxDecelerationScrollLeft: null,\n\n\t\t/** {Integer} Maximum top scroll position during deceleration */\n\t\t__maxDecelerationScrollTop: null,\n\n\t\t/** {Number} Current factor to modify horizontal scroll position with on every step */\n\t\t__decelerationVelocityX: null,\n\n\t\t/** {Number} Current factor to modify vertical scroll position with on every step */\n\t\t__decelerationVelocityY: null,\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tPUBLIC API\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/**\n\t\t * Configures the dimensions of the client (outer) and content (inner) elements.\n\t\t * Requires the available space for the outer element and the outer size of the inner element.\n\t\t * All values which are falsy (null or zero etc.) are ignored and the old value is kept.\n\t\t *\n\t\t * @param clientWidth {Integer ? null} Inner width of outer element\n\t\t * @param clientHeight {Integer ? null} Inner height of outer element\n\t\t * @param contentWidth {Integer ? null} Outer width of inner element\n\t\t * @param contentHeight {Integer ? null} Outer height of inner element\n\t\t */\n\t\tsetDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) {\n\n\t\t\tvar self = this;\n\n\t\t\t// Only update values which are defined\n\t\t\tif (clientWidth) {\n\t\t\t\tself.__clientWidth = clientWidth;\n\t\t\t}\n\n\t\t\tif (clientHeight) {\n\t\t\t\tself.__clientHeight = clientHeight;\n\t\t\t}\n\n\t\t\tif (contentWidth) {\n\t\t\t\tself.__contentWidth = contentWidth;\n\t\t\t}\n\n\t\t\tif (contentHeight) {\n\t\t\t\tself.__contentHeight = contentHeight;\n\t\t\t}\n\n\t\t\t// Refresh maximums\n\t\t\tself.__computeScrollMax();\n\n\t\t\t// Refresh scroll position\n\t\t\tself.scrollTo(self.__scrollLeft, self.__scrollTop, true);\n\t\t\t\n\t\t},\n\n\n\t\t/**\n\t\t * Sets the client coordinates in relation to the document.\n\t\t *\n\t\t * @param left {Integer ? 0} Left position of outer element\n\t\t * @param top {Integer ? 0} Top position of outer element\n\t\t */\n\t\tsetPosition: function(left, top) {\n\n\t\t\tvar self = this;\n\n\t\t\tself.__clientLeft = left || 0;\n\t\t\tself.__clientTop = top || 0;\n\n\t\t},\n\n\n\t\t/**\n\t\t * Configures the snapping (when snapping is active)\n\t\t *\n\t\t * @param width {Integer} Snapping width\n\t\t * @param height {Integer} Snapping height\n\t\t */\n\t\tsetSnapSize: function(width, height) {\n\n\t\t\tvar self = this;\n\n\t\t\tself.__snapWidth = width;\n\t\t\tself.__snapHeight = height;\n\n\t\t},\n\n\n\t\t/**\n\t\t * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever\n\t\t * the user event is released during visibility of this zone. This was introduced by some apps on iOS like\n\t\t * the official Twitter client.\n\t\t *\n\t\t * @param height {Integer} Height of pull-to-refresh zone on top of rendered list\n\t\t * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release.\n\t\t * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled.\n\t\t * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.\n\t\t */\n\t\tactivatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback) {\n\n\t\t\tvar self = this;\n\n\t\t\tself.__refreshHeight = height;\n\t\t\tself.__refreshActivate = activateCallback;\n\t\t\tself.__refreshDeactivate = deactivateCallback;\n\t\t\tself.__refreshStart = startCallback;\n\n\t\t},\n\t\t\n\t\t\n\t\t/**\n\t\t * Signalizes that pull-to-refresh is finished. \n\t\t */\n\t\tfinishPullToRefresh: function() {\n\t\t\t\n\t\t\tvar self = this;\n\t\t\t\n\t\t\tself.__refreshActive = false;\n\t\t\tif (self.__refreshDeactivate) {\n\t\t\t\tself.__refreshDeactivate();\n\t\t\t}\n\t\t\t\n\t\t\tself.scrollTo(self.__scrollLeft, self.__scrollTop, true);\n\t\t\t\n\t\t},\n\n\n\t\t/**\n\t\t * Returns the scroll position and zooming values\n\t\t *\n\t\t * @return {Map} `left` and `top` scroll position and `zoom` level\n\t\t */\n\t\tgetValues: function() {\n\n\t\t\tvar self = this;\n\n\t\t\treturn {\n\t\t\t\tleft: self.__scrollLeft,\n\t\t\t\ttop: self.__scrollTop,\n\t\t\t\tzoom: self.__zoomLevel\n\t\t\t};\n\n\t\t},\n\t\t\n\t\t\n\t\t/**\n\t\t * Returns the maximum scroll values\n\t\t *\n\t\t * @return {Map} `left` and `top` maximum scroll values\n\t\t */\n\t\tgetScrollMax: function() {\n\t\t\t\n\t\t\tvar self = this;\n\t\t\t\n\t\t\treturn {\n\t\t\t\tleft: self.__maxScrollLeft,\n\t\t\t\ttop: self.__maxScrollTop\n\t\t\t};\n\t\t\t\n\t\t},\n\n\n\t\t/**\n\t\t * Zooms to the given level. Supports optional animation. Zooms\n\t\t * the center when no coordinates are given.\n\t\t *\n\t\t * @param level {Number} Level to zoom to\n\t\t * @param animate {Boolean ? false} Whether to use animation\n\t\t * @param originLeft {Number ? null} Zoom in at given left coordinate\n\t\t * @param originTop {Number ? null} Zoom in at given top coordinate\n\t\t */\n\t\tzoomTo: function(level, animate, originLeft, originTop) {\n\n\t\t\tvar self = this;\n\n\t\t\tif (!self.options.zooming) {\n\t\t\t\tthrow new Error(\"Zooming is not enabled!\");\n\t\t\t}\n\n\t\t\t// Stop deceleration\n\t\t\tif (self.__isDecelerating) {\n\t\t\t\tcore.effect.Animate.stop(self.__isDecelerating);\n\t\t\t\tself.__isDecelerating = false;\n\t\t\t}\n\n\t\t\tvar oldLevel = self.__zoomLevel;\n\n\t\t\t// Normalize input origin to center of viewport if not defined\n\t\t\tif (originLeft == null) {\n\t\t\t\toriginLeft = self.__clientWidth / 2;\n\t\t\t}\n\n\t\t\tif (originTop == null) {\n\t\t\t\toriginTop = self.__clientHeight / 2;\n\t\t\t}\n\n\t\t\t// Limit level according to configuration\n\t\t\tlevel = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);\n\n\t\t\t// Recompute maximum values while temporary tweaking maximum scroll ranges\n\t\t\tself.__computeScrollMax(level);\n\n\t\t\t// Recompute left and top coordinates based on new zoom level\n\t\t\tvar left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft;\n\t\t\tvar top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop;\n\n\t\t\t// Limit x-axis\n\t\t\tif (left > self.__maxScrollLeft) {\n\t\t\t\tleft = self.__maxScrollLeft;\n\t\t\t} else if (left < 0) {\n\t\t\t\tleft = 0;\n\t\t\t}\n\n\t\t\t// Limit y-axis\n\t\t\tif (top > self.__maxScrollTop) {\n\t\t\t\ttop = self.__maxScrollTop;\n\t\t\t} else if (top < 0) {\n\t\t\t\ttop = 0;\n\t\t\t}\n\n\t\t\t// Push values out\n\t\t\tself.__publish(left, top, level, animate);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Zooms the content by the given factor.\n\t\t *\n\t\t * @param factor {Number} Zoom by given factor\n\t\t * @param animate {Boolean ? false} Whether to use animation\n\t\t * @param originLeft {Number ? 0} Zoom in at given left coordinate\n\t\t * @param originTop {Number ? 0} Zoom in at given top coordinate\n\t\t */\n\t\tzoomBy: function(factor, animate, originLeft, originTop) {\n\n\t\t\tvar self = this;\n\n\t\t\tself.zoomTo(self.__zoomLevel * factor, animate, originLeft, originTop);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Scrolls to the given position. Respect limitations and snapping automatically.\n\t\t *\n\t\t * @param left {Number?null} Horizontal scroll position, keeps current if value is null\n\t\t * @param top {Number?null} Vertical scroll position, keeps current if value is null\n\t\t * @param animate {Boolean?false} Whether the scrolling should happen using an animation\n\t\t * @param zoom {Number?null} Zoom level to go to\n\t\t */\n\t\tscrollTo: function(left, top, animate, zoom) {\n\n\t\t\tvar self = this;\n\t\t\t\n\t\t\t// Stop deceleration\n\t\t\tif (self.__isDecelerating) {\n\t\t\t\tcore.effect.Animate.stop(self.__isDecelerating);\n\t\t\t\tself.__isDecelerating = false;\n\t\t\t}\n\t\t\t\n\t\t\t// Correct coordinates based on new zoom level\n\t\t\tif (zoom != null && zoom !== self.__zoomLevel) {\n\t\t\t\t\n\t\t\t\tif (!self.options.zooming) {\n\t\t\t\t\tthrow new Error(\"Zooming is not enabled!\");\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tleft *= zoom;\n\t\t\t\ttop *= zoom;\n\t\t\t\t\n\t\t\t\t// Recompute maximum values while temporary tweaking maximum scroll ranges\n\t\t\t\tself.__computeScrollMax(zoom);\n\t\t\t\t\n\t\t\t} else {\n\t\t\t\t\n\t\t\t\t// Keep zoom when not defined\n\t\t\t\tzoom = self.__zoomLevel;\n\t\t\t\t\n\t\t\t}\n\n\t\t\tif (!self.options.scrollingX) {\n\n\t\t\t\tleft = self.__scrollLeft;\n\n\t\t\t} else {\n\n\t\t\t\tif (self.options.paging) {\n\t\t\t\t\tleft = Math.round(left / self.__clientWidth) * self.__clientWidth;\n\t\t\t\t} else if (self.options.snapping) {\n\t\t\t\t\tleft = Math.round(left / self.__snapWidth) * self.__snapWidth;\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif (!self.options.scrollingY) {\n\n\t\t\t\ttop = self.__scrollTop;\n\n\t\t\t} else {\n\n\t\t\t\tif (self.options.paging) {\n\t\t\t\t\ttop = Math.round(top / self.__clientHeight) * self.__clientHeight;\n\t\t\t\t} else if (self.options.snapping) {\n\t\t\t\t\ttop = Math.round(top / self.__snapHeight) * self.__snapHeight;\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t// Limit for allowed ranges\n\t\t\tleft = Math.max(Math.min(self.__maxScrollLeft, left), 0);\n\t\t\ttop = Math.max(Math.min(self.__maxScrollTop, top), 0);\n\n\t\t\t// Don't animate when no change detected, still call publish to make sure\n\t\t\t// that rendered position is really in-sync with internal data\n\t\t\tif (left === self.__scrollLeft && top === self.__scrollTop) {\n\t\t\t\tanimate = false;\n\t\t\t}\n\t\t\t\n\t\t\t// Publish new values\n\t\t\tself.__publish(left, top, zoom, animate);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Scroll by the given offset\n\t\t *\n\t\t * @param left {Number ? 0} Scroll x-axis by given offset\n\t\t * @param top {Number ? 0} Scroll x-axis by given offset\n\t\t * @param animate {Boolean ? false} Whether to animate the given change\n\t\t */\n\t\tscrollBy: function(left, top, animate) {\n\n\t\t\tvar self = this;\n\n\t\t\tvar startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;\n\t\t\tvar startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;\n\n\t\t\tself.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);\n\n\t\t},\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tEVENT CALLBACKS\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/**\n\t\t * Mouse wheel handler for zooming support\n\t\t */\n\t\tdoMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) {\n\n\t\t\tvar self = this;\n\t\t\tvar change = wheelDelta > 0 ? 0.97 : 1.03;\n\n\t\t\treturn self.zoomTo(self.__zoomLevel * change, false, pageX - self.__clientLeft, pageY - self.__clientTop);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Touch start handler for scrolling support\n\t\t */\n\t\tdoTouchStart: function(touches, timeStamp) {\n\n\t\t\t// Array-like check is enough here\n\t\t\tif (touches.length == null) {\n\t\t\t\tthrow new Error(\"Invalid touch list: \" + touches);\n\t\t\t}\n\n\t\t\tif (timeStamp instanceof Date) {\n\t\t\t\ttimeStamp = timeStamp.valueOf();\n\t\t\t}\n\t\t\tif (typeof timeStamp !== \"number\") {\n\t\t\t\tthrow new Error(\"Invalid timestamp value: \" + timeStamp);\n\t\t\t}\n\t\t\t\n\t\t\tvar self = this;\n\n\t\t\t// Stop deceleration\n\t\t\tif (self.__isDecelerating) {\n\t\t\t\tcore.effect.Animate.stop(self.__isDecelerating);\n\t\t\t\tself.__isDecelerating = false;\n\t\t\t}\n\n\t\t\t// Stop animation\n\t\t\tif (self.__isAnimating) {\n\t\t\t\tcore.effect.Animate.stop(self.__isAnimating);\n\t\t\t\tself.__isAnimating = false;\n\t\t\t}\n\n\t\t\t// Use center point when dealing with two fingers\n\t\t\tvar currentTouchLeft, currentTouchTop;\n\t\t\tvar isSingleTouch = touches.length === 1;\n\t\t\tif (isSingleTouch) {\n\t\t\t\tcurrentTouchLeft = touches[0].pageX;\n\t\t\t\tcurrentTouchTop = touches[0].pageY;\n\t\t\t} else {\n\t\t\t\tcurrentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;\n\t\t\t\tcurrentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;\n\t\t\t}\n\n\t\t\t// Store initial positions\n\t\t\tself.__initialTouchLeft = currentTouchLeft;\n\t\t\tself.__initialTouchTop = currentTouchTop;\n\n\t\t\t// Store current zoom level\n\t\t\tself.__zoomLevelStart = self.__zoomLevel;\n\n\t\t\t// Store initial touch positions\n\t\t\tself.__lastTouchLeft = currentTouchLeft;\n\t\t\tself.__lastTouchTop = currentTouchTop;\n\n\t\t\t// Store initial move time stamp\n\t\t\tself.__lastTouchMove = timeStamp;\n\n\t\t\t// Reset initial scale\n\t\t\tself.__lastScale = 1;\n\n\t\t\t// Reset locking flags\n\t\t\tself.__enableScrollX = !isSingleTouch && self.options.scrollingX;\n\t\t\tself.__enableScrollY = !isSingleTouch && self.options.scrollingY;\n\n\t\t\t// Reset tracking flag\n\t\t\tself.__isTracking = true;\n\n\t\t\t// Dragging starts directly with two fingers, otherwise lazy with an offset\n\t\t\tself.__isDragging = !isSingleTouch;\n\n\t\t\t// Some features are disabled in multi touch scenarios\n\t\t\tself.__isSingleTouch = isSingleTouch;\n\n\t\t\t// Clearing data structure\n\t\t\tself.__positions = [];\n\n\t\t},\n\n\n\t\t/**\n\t\t * Touch move handler for scrolling support\n\t\t */\n\t\tdoTouchMove: function(touches, timeStamp, scale) {\n\n\t\t\t// Array-like check is enough here\n\t\t\tif (touches.length == null) {\n\t\t\t\tthrow new Error(\"Invalid touch list: \" + touches);\n\t\t\t}\n\n\t\t\tif (timeStamp instanceof Date) {\n\t\t\t\ttimeStamp = timeStamp.valueOf();\n\t\t\t}\n\t\t\tif (typeof timeStamp !== \"number\") {\n\t\t\t\tthrow new Error(\"Invalid timestamp value: \" + timeStamp);\n\t\t\t}\n\t\t\t\n\t\t\tvar self = this;\n\n\t\t\t// Ignore event when tracking is not enabled (event might be outside of element)\n\t\t\tif (!self.__isTracking) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t\n\t\t\t\n\t\t\tvar currentTouchLeft, currentTouchTop;\n\n\t\t\t// Compute move based around of center of fingers\n\t\t\tif (touches.length === 2) {\n\t\t\t\tcurrentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;\n\t\t\t\tcurrentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;\n\t\t\t} else {\n\t\t\t\tcurrentTouchLeft = touches[0].pageX;\n\t\t\t\tcurrentTouchTop = touches[0].pageY;\n\t\t\t}\n\n\t\t\tvar positions = self.__positions;\n\n\t\t\t// Are we already is dragging mode?\n\t\t\tif (self.__isDragging) {\n\n\t\t\t\t// Compute move distance\n\t\t\t\tvar moveX = currentTouchLeft - self.__lastTouchLeft;\n\t\t\t\tvar moveY = currentTouchTop - self.__lastTouchTop;\n\n\t\t\t\t// Read previous scroll position and zooming\n\t\t\t\tvar scrollLeft = self.__scrollLeft;\n\t\t\t\tvar scrollTop = self.__scrollTop;\n\t\t\t\tvar level = self.__zoomLevel;\n\n\t\t\t\t// Work with scaling\n\t\t\t\tif (scale != null && self.options.zooming) {\n\n\t\t\t\t\tvar oldLevel = level;\n\n\t\t\t\t\t// Recompute level based on previous scale and new scale\n\t\t\t\t\tlevel = level / self.__lastScale * scale;\n\n\t\t\t\t\t// Limit level according to configuration\n\t\t\t\t\tlevel = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);\n\n\t\t\t\t\t// Only do further compution when change happened\n\t\t\t\t\tif (oldLevel !== level) {\n\n\t\t\t\t\t\t// Compute relative event position to container\n\t\t\t\t\t\tvar currentTouchLeftRel = currentTouchLeft - self.__clientLeft;\n\t\t\t\t\t\tvar currentTouchTopRel = currentTouchTop - self.__clientTop;\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Recompute left and top coordinates based on new zoom level\n\t\t\t\t\t\tscrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel;\n\t\t\t\t\t\tscrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel;\n\n\t\t\t\t\t\t// Recompute max scroll values\n\t\t\t\t\t\tself.__computeScrollMax(level);\n\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (self.__enableScrollX) {\n\n\t\t\t\t\tscrollLeft -= moveX;\n\t\t\t\t\tvar maxScrollLeft = self.__maxScrollLeft;\n\n\t\t\t\t\tif (scrollLeft > maxScrollLeft || scrollLeft < 0) {\n\n\t\t\t\t\t\t// Slow down on the edges\n\t\t\t\t\t\tif (self.options.bouncing) {\n\n\t\t\t\t\t\t\tscrollLeft += (moveX / 2);\n\n\t\t\t\t\t\t} else if (scrollLeft > maxScrollLeft) {\n\n\t\t\t\t\t\t\tscrollLeft = maxScrollLeft;\n\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\tscrollLeft = 0;\n\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Compute new vertical scroll position\n\t\t\t\tif (self.__enableScrollY) {\n\n\t\t\t\t\tscrollTop -= moveY;\n\t\t\t\t\tvar maxScrollTop = self.__maxScrollTop;\n\n\t\t\t\t\tif (scrollTop > maxScrollTop || scrollTop < 0) {\n\n\t\t\t\t\t\t// Slow down on the edges\n\t\t\t\t\t\tif (self.options.bouncing) {\n\n\t\t\t\t\t\t\tscrollTop += (moveY / 2);\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// Support pull-to-refresh (only when only y is scrollable)\n\t\t\t\t\t\t\tif (!self.__enableScrollX && self.__refreshHeight != null) {\n\n\t\t\t\t\t\t\t\tif (!self.__refreshActive && scrollTop <= -self.__refreshHeight) {\n\n\t\t\t\t\t\t\t\t\tself.__refreshActive = true;\n\t\t\t\t\t\t\t\t\tif (self.__refreshActivate) {\n\t\t\t\t\t\t\t\t\t\tself.__refreshActivate();\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t} else if (self.__refreshActive && scrollTop > -self.__refreshHeight) {\n\n\t\t\t\t\t\t\t\t\tself.__refreshActive = false;\n\t\t\t\t\t\t\t\t\tif (self.__refreshDeactivate) {\n\t\t\t\t\t\t\t\t\t\tself.__refreshDeactivate();\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t} else if (scrollTop > maxScrollTop) {\n\n\t\t\t\t\t\t\tscrollTop = maxScrollTop;\n\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\tscrollTop = 0;\n\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Keep list from growing infinitely (holding min 10, max 20 measure points)\n\t\t\t\tif (positions.length > 60) {\n\t\t\t\t\tpositions.splice(0, 30);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Track scroll movement for decleration\n\t\t\t\tpositions.push(scrollLeft, scrollTop, timeStamp);\n\n\t\t\t\t// Sync scroll position\n\t\t\t\tself.__publish(scrollLeft, scrollTop, level);\n\n\t\t\t// Otherwise figure out whether we are switching into dragging mode now.\n\t\t\t} else {\n\n\t\t\t\tvar minimumTrackingForScroll = self.options.locking ? 3 : 0;\n\t\t\t\tvar minimumTrackingForDrag = 5;\n\n\t\t\t\tvar distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft);\n\t\t\t\tvar distanceY = Math.abs(currentTouchTop - self.__initialTouchTop);\n\n\t\t\t\tself.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll;\n\t\t\t\tself.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll;\n\t\t\t\t\n\t\t\t\tpositions.push(self.__scrollLeft, self.__scrollTop, timeStamp);\n\n\t\t\t\tself.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag);\n\n\t\t\t}\n\n\t\t\t// Update last touch positions and time stamp for next event\n\t\t\tself.__lastTouchLeft = currentTouchLeft;\n\t\t\tself.__lastTouchTop = currentTouchTop;\n\t\t\tself.__lastTouchMove = timeStamp;\n\t\t\tself.__lastScale = scale;\n\n\t\t},\n\n\n\t\t/**\n\t\t * Touch end handler for scrolling support\n\t\t */\n\t\tdoTouchEnd: function(timeStamp) {\n\t\t\t\n\t\t\tif (timeStamp instanceof Date) {\n\t\t\t\ttimeStamp = timeStamp.valueOf();\n\t\t\t}\n\t\t\tif (typeof timeStamp !== \"number\") {\n\t\t\t\tthrow new Error(\"Invalid timestamp value: \" + timeStamp);\n\t\t\t}\n\t\t\t\n\t\t\tvar self = this;\n\n\t\t\t// Ignore event when tracking is not enabled (no touchstart event on element)\n\t\t\t// This is required as this listener ('touchmove') sits on the document and not on the element itself.\n\t\t\tif (!self.__isTracking) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t\n\t\t\t// Not touching anymore (when two finger hit the screen there are two touch end events)\n\t\t\tself.__isTracking = false;\n\n\t\t\t// Be sure to reset the dragging flag now. Here we also detect whether\n\t\t\t// the finger has moved fast enough to switch into a deceleration animation.\n\t\t\tif (self.__isDragging) {\n\n\t\t\t\t// Reset dragging flag\n\t\t\t\tself.__isDragging = false;\n\n\t\t\t\t// Start deceleration\n\t\t\t\t// Verify that the last move detected was in some relevant time frame\n\t\t\t\tif (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) {\n\n\t\t\t\t\t// Then figure out what the scroll position was about 100ms ago\n\t\t\t\t\tvar positions = self.__positions;\n\t\t\t\t\tvar endPos = positions.length - 1;\n\t\t\t\t\tvar startPos = endPos;\n\t\t\t\t\t\n\t\t\t\t\t// Move pointer to position measured 100ms ago\n\t\t\t\t\tfor (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) {\n\t\t\t\t\t\tstartPos = i;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// If start and stop position is identical in a 100ms timeframe, \n\t\t\t\t\t// we cannot compute any useful deceleration.\n\t\t\t\t\tif (startPos !== endPos) {\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Compute relative movement between these two points\n\t\t\t\t\t\tvar timeOffset = positions[endPos] - positions[startPos];\n\t\t\t\t\t\tvar movedLeft = self.__scrollLeft - positions[startPos - 2];\n\t\t\t\t\t\tvar movedTop = self.__scrollTop - positions[startPos - 1];\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Based on 50ms compute the movement to apply for each render step\n\t\t\t\t\t\tself.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60);\n\t\t\t\t\t\tself.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60);\n\n\t\t\t\t\t\t// How much velocity is required to start the deceleration\n\t\t\t\t\t\tvar minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? 4 : 1;\n\n\t\t\t\t\t\t// Verify that we have enough velocity to start deceleration\n\t\t\t\t\t\tif (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) {\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// Deactivate pull-to-refresh when decelerating\n\t\t\t\t\t\t\tif (!self.__refreshActive) {\n\n\t\t\t\t\t\t\t\tself.__startDeceleration(timeStamp);\n\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If this was a slower move it is per default non decelerated, but this\n\t\t\t// still means that we want snap back to the bounds which is done here.\n\t\t\t// This is placed outside the condition above to improve edge case stability\n\t\t\t// e.g. touchend fired without enabled dragging. This should normally do not\n\t\t\t// have modified the scroll positions or even showed the scrollbars though.\n\t\t\tif (!self.__isDecelerating) {\n\n\t\t\t\tif (self.__refreshActive && self.__refreshStart) {\n\t\t\t\t\t\n\t\t\t\t\t// Use publish instead of scrollTo to allow scrolling to out of boundary position\n\t\t\t\t\t// We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled\n\t\t\t\t\tself.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true);\n\t\t\t\t\t\n\t\t\t\t\tif (self.__refreshStart) {\n\t\t\t\t\t\tself.__refreshStart();\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t} else {\n\t\t\t\t\t\n\t\t\t\t\tself.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel);\n\t\t\t\t\t\n\t\t\t\t\t// Directly signalize deactivation (nothing todo on refresh?)\n\t\t\t\t\tif (self.__refreshActive) {\n\t\t\t\t\t\t\n\t\t\t\t\t\tself.__refreshActive = false;\n\t\t\t\t\t\tif (self.__refreshDeactivate) {\n\t\t\t\t\t\t\tself.__refreshDeactivate();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// Fully cleanup list\n\t\t\tself.__positions.length = 0;\n\n\t\t},\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tPRIVATE API\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/**\n\t\t * Applies the scroll position to the content element\n\t\t *\n\t\t * @param left {Number} Left scroll position\n\t\t * @param top {Number} Top scroll position\n\t\t * @param animate {Boolean?false} Whether animation should be used to move to the new coordinates\n\t\t */\n\t\t__publish: function(left, top, zoom, animate) {\n\n\t\t\tvar self = this;\n\t\t\t\n\t\t\t// Remember whether we had an animation, then we try to continue based on the current \"drive\" of the animation\n\t\t\tvar wasAnimating = self.__isAnimating;\n\t\t\tif (wasAnimating) {\n\t\t\t\tcore.effect.Animate.stop(wasAnimating);\n\t\t\t\tself.__isAnimating = false;\n\t\t\t}\n\n\t\t\tif (animate && self.options.animating) {\n\n\t\t\t\t// Keep scheduled positions for scrollBy/zoomBy functionality\n\t\t\t\tself.__scheduledLeft = left;\n\t\t\t\tself.__scheduledTop = top;\n\t\t\t\tself.__scheduledZoom = zoom;\n\n\t\t\t\tvar oldLeft = self.__scrollLeft;\n\t\t\t\tvar oldTop = self.__scrollTop;\n\t\t\t\tvar oldZoom = self.__zoomLevel;\n\n\t\t\t\tvar diffLeft = left - oldLeft;\n\t\t\t\tvar diffTop = top - oldTop;\n\t\t\t\tvar diffZoom = zoom - oldZoom;\n\n\t\t\t\tvar step = function(percent, now, render) {\n\n\t\t\t\t\tif (render) {\n\n\t\t\t\t\t\tself.__scrollLeft = oldLeft + (diffLeft * percent);\n\t\t\t\t\t\tself.__scrollTop = oldTop + (diffTop * percent);\n\t\t\t\t\t\tself.__zoomLevel = oldZoom + (diffZoom * percent);\n\n\t\t\t\t\t\t// Push values out\n\t\t\t\t\t\tif (self.__callback) {\n\t\t\t\t\t\t\tself.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tvar verify = function(id) {\n\t\t\t\t\treturn self.__isAnimating === id;\n\t\t\t\t};\n\n\t\t\t\tvar completed = function(renderedFramesPerSecond, animationId, wasFinished) {\n\t\t\t\t\tif (animationId === self.__isAnimating) {\n\t\t\t\t\t\tself.__isAnimating = false;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif (self.options.zooming) {\n\t\t\t\t\t\tself.__computeScrollMax();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\t\n\t\t\t\t// When continuing based on previous animation we choose an ease-out animation instead of ease-in-out\n\t\t\t\tself.__isAnimating = core.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic);\n\n\t\t\t} else {\n\n\t\t\t\tself.__scheduledLeft = self.__scrollLeft = left;\n\t\t\t\tself.__scheduledTop = self.__scrollTop = top;\n\t\t\t\tself.__scheduledZoom = self.__zoomLevel = zoom;\n\n\t\t\t\t// Push values out\n\t\t\t\tif (self.__callback) {\n\t\t\t\t\tself.__callback(left, top, zoom);\n\t\t\t\t}\n\n\t\t\t\t// Fix max scroll ranges\n\t\t\t\tif (self.options.zooming) {\n\t\t\t\t\tself.__computeScrollMax();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\n\t\t/**\n\t\t * Recomputes scroll minimum values based on client dimensions and content dimensions.\n\t\t */\n\t\t__computeScrollMax: function(zoomLevel) {\n\n\t\t\tvar self = this;\n\t\t\t\n\t\t\tif (zoomLevel == null) {\n\t\t\t\tzoomLevel = self.__zoomLevel;\n\t\t\t}\n\n\t\t\tself.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0);\n\t\t\tself.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0);\n\t\t\t\n\t\t},\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tANIMATION (DECELERATION) SUPPORT\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/**\n\t\t * Called when a touch sequence end and the speed of the finger was high enough\n\t\t * to switch into deceleration mode.\n\t\t */\n\t\t__startDeceleration: function(timeStamp) {\n\n\t\t\tvar self = this;\n\n\t\t\tif (self.options.paging) {\n\n\t\t\t\tvar scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0);\n\t\t\t\tvar scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0);\n\t\t\t\tvar clientWidth = self.__clientWidth;\n\t\t\t\tvar clientHeight = self.__clientHeight;\n\n\t\t\t\t// We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area.\n\t\t\t\t// Each page should have exactly the size of the client area.\n\t\t\t\tself.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth;\n\t\t\t\tself.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight;\n\t\t\t\tself.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth;\n\t\t\t\tself.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight;\n\n\t\t\t} else {\n\n\t\t\t\tself.__minDecelerationScrollLeft = 0;\n\t\t\t\tself.__minDecelerationScrollTop = 0;\n\t\t\t\tself.__maxDecelerationScrollLeft = self.__maxScrollLeft;\n\t\t\t\tself.__maxDecelerationScrollTop = self.__maxScrollTop;\n\n\t\t\t}\n\n\t\t\t// Wrap class method\n\t\t\tvar step = function(percent, now, render) {\n\t\t\t\tself.__stepThroughDeceleration(render);\n\t\t\t};\n\n\t\t\t// How much velocity is required to keep the deceleration running\n\t\t\tvar minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1;\n\n\t\t\t// Detect whether it's still worth to continue animating steps\n\t\t\t// If we are already slow enough to not being user perceivable anymore, we stop the whole process here.\n\t\t\tvar verify = function() {\n\t\t\t\treturn Math.abs(self.__decelerationVelocityX) >= minVelocityToKeepDecelerating || Math.abs(self.__decelerationVelocityY) >= minVelocityToKeepDecelerating;\n\t\t\t};\n\n\t\t\tvar completed = function(renderedFramesPerSecond, animationId, wasFinished) {\n\t\t\t\tself.__isDecelerating = false;\n\n\t\t\t\t// Animate to grid when snapping is active, otherwise just fix out-of-boundary positions\n\t\t\t\tself.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping);\n\t\t\t};\n\n\t\t\t// Start animation and switch on flag\n\t\t\tself.__isDecelerating = core.effect.Animate.start(step, verify, completed);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Called on every step of the animation\n\t\t *\n\t\t * @param inMemory {Boolean?false} Whether to not render the current step, but keep it in memory only. Used internally only!\n\t\t */\n\t\t__stepThroughDeceleration: function(render) {\n\n\t\t\tvar self = this;\n\n\n\t\t\t//\n\t\t\t// COMPUTE NEXT SCROLL POSITION\n\t\t\t//\n\n\t\t\t// Add deceleration to scroll position\n\t\t\tvar scrollLeft = self.__scrollLeft + self.__decelerationVelocityX;\n\t\t\tvar scrollTop = self.__scrollTop + self.__decelerationVelocityY;\n\n\n\t\t\t//\n\t\t\t// HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE\n\t\t\t//\n\n\t\t\tif (!self.options.bouncing) {\n\n\t\t\t\tvar scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft);\n\t\t\t\tif (scrollLeftFixed !== scrollLeft) {\n\t\t\t\t\tscrollLeft = scrollLeftFixed;\n\t\t\t\t\tself.__decelerationVelocityX = 0;\n\t\t\t\t}\n\n\t\t\t\tvar scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop);\n\t\t\t\tif (scrollTopFixed !== scrollTop) {\n\t\t\t\t\tscrollTop = scrollTopFixed;\n\t\t\t\t\tself.__decelerationVelocityY = 0;\n\t\t\t\t}\n\n\t\t\t}\n\n\n\t\t\t//\n\t\t\t// UPDATE SCROLL POSITION\n\t\t\t//\n\n\t\t\tif (render) {\n\n\t\t\t\tself.__publish(scrollLeft, scrollTop, self.__zoomLevel);\n\n\t\t\t} else {\n\n\t\t\t\tself.__scrollLeft = scrollLeft;\n\t\t\t\tself.__scrollTop = scrollTop;\n\n\t\t\t}\n\n\n\t\t\t//\n\t\t\t// SLOW DOWN\n\t\t\t//\n\n\t\t\t// Slow down velocity on every iteration\n\t\t\tif (!self.options.paging) {\n\n\t\t\t\t// This is the factor applied to every iteration of the animation\n\t\t\t\t// to slow down the process. This should emulate natural behavior where\n\t\t\t\t// objects slow down when the initiator of the movement is removed\n\t\t\t\tvar frictionFactor = 0.95;\n\n\t\t\t\tself.__decelerationVelocityX *= frictionFactor;\n\t\t\t\tself.__decelerationVelocityY *= frictionFactor;\n\n\t\t\t}\n\n\n\t\t\t//\n\t\t\t// BOUNCING SUPPORT\n\t\t\t//\n\n\t\t\tif (self.options.bouncing) {\n\n\t\t\t\tvar scrollOutsideX = 0;\n\t\t\t\tvar scrollOutsideY = 0;\n\n\t\t\t\t// This configures the amount of change applied to deceleration/acceleration when reaching boundaries\n\t\t\t\tvar penetrationDeceleration = 0.03;\n\t\t\t\tvar penetrationAcceleration = 0.08;\n\n\t\t\t\t// Check limits\n\t\t\t\tif (scrollLeft < self.__minDecelerationScrollLeft) {\n\t\t\t\t\tscrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft;\n\t\t\t\t} else if (scrollLeft > self.__maxDecelerationScrollLeft) {\n\t\t\t\t\tscrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft;\n\t\t\t\t}\n\n\t\t\t\tif (scrollTop < self.__minDecelerationScrollTop) {\n\t\t\t\t\tscrollOutsideY = self.__minDecelerationScrollTop - scrollTop;\n\t\t\t\t} else if (scrollTop > self.__maxDecelerationScrollTop) {\n\t\t\t\t\tscrollOutsideY = self.__maxDecelerationScrollTop - scrollTop;\n\t\t\t\t}\n\n\t\t\t\t// Slow down until slow enough, then flip back to snap position\n\t\t\t\tif (scrollOutsideX !== 0) {\n\t\t\t\t\tif (scrollOutsideX * self.__decelerationVelocityX <= 0) {\n\t\t\t\t\t\tself.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (scrollOutsideY !== 0) {\n\t\t\t\t\tif (scrollOutsideY * self.__decelerationVelocityY <= 0) {\n\t\t\t\t\t\tself.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\t\n\t// Copy over members to prototype\n\tfor (var key in members) {\n\t\tScroller.prototype[key] = members[key];\n\t}\n\t\t\n})();\n\n(function( factory ) {\n\tif( typeof define === 'function' && define.amd ) {\n\t\t\t// AMD. Register as an anonymous module.\n\t\t\tdefine( [ 'shoestring' ], factory );\n\t} else if (typeof module === 'object' && module.exports) {\n\t\t// Node/CommonJS\n\t\tmodule.exports = factory();\n\t} else {\n\t\t// Browser globals\n\t\tfactory();\n\t}\n}(function () {\n\tvar win = typeof window !== \"undefined\" ? window : this;\n\tvar doc = win.document;\n\n\n\t/**\n\t * The shoestring object constructor.\n\t *\n\t * @param {string,object} prim The selector to find or element to wrap.\n\t * @param {object} sec The context in which to match the `prim` selector.\n\t * @returns shoestring\n\t * @this window\n\t */\n\tfunction shoestring( prim, sec ){\n\t\tvar pType = typeof( prim ),\n\t\t\t\tret = [],\n\t\t\t\tsel;\n\n\t\t// return an empty shoestring object\n\t\tif( !prim ){\n\t\t\treturn new Shoestring( ret );\n\t\t}\n\n\t\t// ready calls\n\t\tif( prim.call ){\n\t\t\treturn shoestring.ready( prim );\n\t\t}\n\n\t\t// handle re-wrapping shoestring objects\n\t\tif( prim.constructor === Shoestring && !sec ){\n\t\t\treturn prim;\n\t\t}\n\n\t\t// if string starting with <, make html\n\t\tif( pType === \"string\" && prim.indexOf( \"<\" ) === 0 ){\n\t\t\tvar dfrag = doc.createElement( \"div\" );\n\n\t\t\tdfrag.innerHTML = prim;\n\n\t\t\t// TODO depends on children (circular)\n\t\t\treturn shoestring( dfrag ).children().each(function(){\n\t\t\t\tdfrag.removeChild( this );\n\t\t\t});\n\t\t}\n\n\t\t// if string, it's a selector, use qsa\n\t\tif( pType === \"string\" ){\n\t\t\tif( sec ){\n\t\t\t\treturn shoestring( sec ).find( prim );\n\t\t\t}\n\n\t\t\t\tsel = doc.querySelectorAll( prim );\n\n\t\t\treturn new Shoestring( sel, prim );\n\t\t}\n\n\t\t// array like objects or node lists\n\t\tif( Object.prototype.toString.call( pType ) === '[object Array]' ||\n\t\t\t\t(win.NodeList && prim instanceof win.NodeList) ){\n\n\t\t\treturn new Shoestring( prim, prim );\n\t\t}\n\n\t\t// if it's an array, use all the elements\n\t\tif( prim.constructor === Array ){\n\t\t\treturn new Shoestring( prim, prim );\n\t\t}\n\n\t\t// otherwise assume it's an object the we want at an index\n\t\treturn new Shoestring( [prim], prim );\n\t}\n\n\tvar Shoestring = function( ret, prim ) {\n\t\tthis.length = 0;\n\t\tthis.selector = prim;\n\t\tshoestring.merge(this, ret);\n\t};\n\n\t// TODO only required for tests\n\tShoestring.prototype.reverse = [].reverse;\n\n\t// For adding element set methods\n\tshoestring.fn = Shoestring.prototype;\n\n\tshoestring.Shoestring = Shoestring;\n\n\t// For extending objects\n\t// TODO move to separate module when we use prototypes\n\tshoestring.extend = function( first, second ){\n\t\tfor( var i in second ){\n\t\t\tif( second.hasOwnProperty( i ) ){\n\t\t\t\tfirst[ i ] = second[ i ];\n\t\t\t}\n\t\t}\n\n\t\treturn first;\n\t};\n\n\t// taken directly from jQuery\n\tshoestring.merge = function( first, second ) {\n\t\tvar len, j, i;\n\n\t\tlen = +second.length,\n\t\tj = 0,\n\t\ti = first.length;\n\n\t\tfor ( ; j < len; j++ ) {\n\t\t\tfirst[ i++ ] = second[ j ];\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t};\n\n\t// expose\n\twin.shoestring = shoestring;\n\n\n\n\t/**\n\t * Make an HTTP request to a url.\n\t *\n\t * **NOTE** the following options are supported:\n\t *\n\t * - *method* - The HTTP method used with the request. Default: `GET`.\n\t * - *data* - Raw object with keys and values to pass with request as query params. Default `null`.\n\t * - *headers* - Set of request headers to add. Default `{}`.\n\t * - *async* - Whether the opened request is asynchronouse. Default `true`.\n\t * - *success* - Callback for successful request and response. Passed the response data.\n\t * - *error* - Callback for failed request and response.\n\t * - *cancel* - Callback for cancelled request and response.\n\t *\n\t * @param {string} url The url to request.\n\t * @param {object} options The options object, see Notes.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\n\tshoestring.ajax = function( url, options ) {\n\t\tvar params = \"\", req = new XMLHttpRequest(), settings, key;\n\n\t\tsettings = shoestring.extend( {}, shoestring.ajax.settings );\n\n\t\tif( options ){\n\t\t\tshoestring.extend( settings, options );\n\t\t}\n\n\t\tif( !url ){\n\t\t\turl = settings.url;\n\t\t}\n\n\t\tif( !req || !url ){\n\t\t\treturn;\n\t\t}\n\n\t\t// create parameter string from data object\n\t\tif( settings.data ){\n\t\t\tfor( key in settings.data ){\n\t\t\t\tif( settings.data.hasOwnProperty( key ) ){\n\t\t\t\t\tif( params !== \"\" ){\n\t\t\t\t\t\tparams += \"&\";\n\t\t\t\t\t}\n\t\t\t\t\tparams += encodeURIComponent( key ) + \"=\" +\n\t\t\t\t\t\tencodeURIComponent( settings.data[key] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// append params to url for GET requests\n\t\tif( settings.method === \"GET\" && params ){\n\t\t\t\n\t\t\turl += \"?\" + params;\n\t\t}\n\n\t\treq.open( settings.method, url, settings.async );\n\n\t\tif( req.setRequestHeader ){\n\t\t\treq.setRequestHeader( \"X-Requested-With\", \"XMLHttpRequest\" );\n\n\t\t\t// Set 'Content-type' header for POST requests\n\t\t\tif( settings.method === \"POST\" && params ){\n\t\t\t\treq.setRequestHeader( \"Content-type\", \"application/x-www-form-urlencoded\" );\n\t\t\t}\n\n\t\t\tfor( key in settings.headers ){\n\t\t\t\tif( settings.headers.hasOwnProperty( key ) ){\n\t\t\t\t\treq.setRequestHeader(key, settings.headers[ key ]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treq.onreadystatechange = function () {\n\t\t\tif( req.readyState === 4 ){\n\t\t\t\t// Trim the whitespace so shoestring('
') works\n\t\t\t\tvar res = (req.responseText || '').replace(/^\\s+|\\s+$/g, '');\n\t\t\t\tif( req.status.toString().indexOf( \"0\" ) === 0 ){\n\t\t\t\t\treturn settings.cancel( res, req.status, req );\n\t\t\t\t}\n\t\t\t\telse if ( req.status.toString().match( /^(4|5)/ ) && RegExp.$1 ){\n\t\t\t\t\treturn settings.error( res, req.status, req );\n\t\t\t\t}\n\t\t\t\telse if (settings.success) {\n\t\t\t\t\treturn settings.success( res, req.status, req );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tif( req.readyState === 4 ){\n\t\t\treturn req;\n\t\t}\n\n\t\t// Send request\n\t\tif( settings.method === \"POST\" && params ){\n\t\t\treq.send( params );\n\t\t} else {\n\t\t\treq.send();\n\t\t}\n\n\t\treturn req;\n\t};\n\n\tshoestring.ajax.settings = {\n\t\tsuccess: function(){},\n\t\terror: function(){},\n\t\tcancel: function(){},\n\t\tmethod: \"GET\",\n\t\tasync: true,\n\t\tdata: null,\n\t\theaders: {}\n\t};\n\n\n\n\t/**\n\t * Helper function wrapping a call to [ajax](ajax.js.html) using the `GET` method.\n\t *\n\t * @param {string} url The url to GET from.\n\t * @param {function} callback Callback to invoke on success.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.get = function( url, callback ){\n\t\treturn shoestring.ajax( url, { success: callback } );\n\t};\n\n\n\n /**\n\t * Load the HTML response from `url` into the current set of elements.\n\t *\n\t * @param {string} url The url to GET from.\n\t * @param {function} callback Callback to invoke after HTML is inserted.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.load = function( url, callback ){\n\t\tvar self = this,\n\t\t\targs = arguments,\n\t\t\tintCB = function( data ){\n\t\t\t\tself.each(function(){\n\t\t\t\t\tshoestring( this ).html( data );\n\t\t\t\t});\n\n\t\t\t\tif( callback ){\n\t\t\t\t\tcallback.apply( self, args );\n\t\t\t\t}\n\t\t };\n\n\t\tshoestring.ajax( url, { success: intCB } );\n\t\treturn this;\n\t};\n\n\n\n\t/**\n\t * Helper function wrapping a call to [ajax](ajax.js.html) using the `POST` method.\n\t *\n\t * @param {string} url The url to POST to.\n\t * @param {object} data The data to send.\n\t * @param {function} callback Callback to invoke on success.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.post = function( url, data, callback ){\n\t\treturn shoestring.ajax( url, { data: data, method: \"POST\", success: callback } );\n\t};\n\n\n\n\t/**\n\t * Iterates over `shoestring` collections.\n\t *\n\t * @param {function} callback The callback to be invoked on each element and index\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.each = function( callback ){\n\t\treturn shoestring.each( this, callback );\n\t};\n\n\tshoestring.each = function( collection, callback ) {\n\t\tvar val;\n\t\tfor( var i = 0, il = collection.length; i < il; i++ ){\n\t\t\tval = callback.call( collection[i], i, collection[i] );\n\t\t\tif( val === false ){\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn collection;\n\t};\n\n\n\n /**\n\t * Check for array membership.\n\t *\n\t * @param {object} needle The thing to find.\n\t * @param {object} haystack The thing to find the needle in.\n\t * @return {boolean}\n\t * @this window\n\t */\n\tshoestring.inArray = function( needle, haystack ){\n\t\tvar isin = -1;\n\t\tfor( var i = 0, il = haystack.length; i < il; i++ ){\n\t\t\tif( haystack.hasOwnProperty( i ) && haystack[ i ] === needle ){\n\t\t\t\tisin = i;\n\t\t\t}\n\t\t}\n\t\treturn isin;\n\t};\n\n\n\n /**\n\t * Bind callbacks to be run when the DOM is \"ready\".\n\t *\n\t * @param {function} fn The callback to be run\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.ready = function( fn ){\n\t\tif( ready && fn ){\n\t\t\tfn.call( doc );\n\t\t}\n\t\telse if( fn ){\n\t\t\treadyQueue.push( fn );\n\t\t}\n\t\telse {\n\t\t\trunReady();\n\t\t}\n\n\t\treturn [doc];\n\t};\n\n\t// TODO necessary?\n\tshoestring.fn.ready = function( fn ){\n\t\tshoestring.ready( fn );\n\t\treturn this;\n\t};\n\n\t// Empty and exec the ready queue\n\tvar ready = false,\n\t\treadyQueue = [],\n\t\trunReady = function(){\n\t\t\tif( !ready ){\n\t\t\t\twhile( readyQueue.length ){\n\t\t\t\t\treadyQueue.shift().call( doc );\n\t\t\t\t}\n\t\t\t\tready = true;\n\t\t\t}\n\t\t};\n\n\t// If DOM is already ready at exec time, depends on the browser.\n\t// From: https://github.com/mobify/mobifyjs/blob/526841be5509e28fc949038021799e4223479f8d/src/capture.js#L128\n\tif (doc.attachEvent ? doc.readyState === \"complete\" : doc.readyState !== \"loading\") {\n\t\trunReady();\n\t} else {\n\t\tdoc.addEventListener( \"DOMContentLoaded\", runReady, false );\n\t\tdoc.addEventListener( \"readystatechange\", runReady, false );\n\t\twin.addEventListener( \"load\", runReady, false );\n\t}\n\n\n\n /**\n\t * Checks the current set of elements against the selector, if one matches return `true`.\n\t *\n\t * @param {string} selector The selector to check.\n\t * @return {boolean}\n\t * @this {shoestring}\n\t */\n\tshoestring.fn.is = function( selector ){\n\t\tvar ret = false, self = this, parents, check;\n\n\t\t// assume a dom element\n\t\tif( typeof selector !== \"string\" ){\n\t\t\t// array-like, ie shoestring objects or element arrays\n\t\t\tif( selector.length && selector[0] ){\n\t\t\t\tcheck = selector;\n\t\t\t} else {\n\t\t\t\tcheck = [selector];\n\t\t\t}\n\n\t\t\treturn _checkElements(this, check);\n\t\t}\n\n\t\tparents = this.parent();\n\n\t\tif( !parents.length ){\n\t\t\tparents = shoestring( doc );\n\t\t}\n\n\t\tparents.each(function( i, e ) {\n\t\t\tvar children;\n\n\t\t\t\t\tchildren = e.querySelectorAll( selector );\n\n\t\t\tret = _checkElements( self, children );\n\t\t});\n\n\t\treturn ret;\n\t};\n\n\tfunction _checkElements(needles, haystack){\n\t\tvar ret = false;\n\n\t\tneedles.each(function() {\n\t\t\tvar j = 0;\n\n\t\t\twhile( j < haystack.length ){\n\t\t\t\tif( this === haystack[j] ){\n\t\t\t\t\tret = true;\n\t\t\t\t}\n\n\t\t\t\tj++;\n\t\t\t}\n\t\t});\n\n\t\treturn ret;\n\t}\n\n\n\n\t/**\n\t * Get data attached to the first element or set data values on all elements in the current set.\n\t *\n\t * @param {string} name The data attribute name.\n\t * @param {any} value The value assigned to the data attribute.\n\t * @return {any|shoestring}\n\t * @this shoestring\n\t */\n\tshoestring.fn.data = function( name, value ){\n\t\tif( name !== undefined ){\n\t\t\tif( value !== undefined ){\n\t\t\t\treturn this.each(function(){\n\t\t\t\t\tif( !this.shoestringData ){\n\t\t\t\t\t\tthis.shoestringData = {};\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.shoestringData[ name ] = value;\n\t\t\t\t});\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif( this[ 0 ] ) {\n\t\t\t\t\tif( this[ 0 ].shoestringData ) {\n\t\t\t\t\t\treturn this[ 0 ].shoestringData[ name ];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\treturn this[ 0 ] ? this[ 0 ].shoestringData || {} : undefined;\n\t\t}\n\t};\n\n\n\t/**\n\t * Remove data associated with `name` or all the data, for each element in the current set.\n\t *\n\t * @param {string} name The data attribute name.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.removeData = function( name ){\n\t\treturn this.each(function(){\n\t\t\tif( name !== undefined && this.shoestringData ){\n\t\t\t\tthis.shoestringData[ name ] = undefined;\n\t\t\t\tdelete this.shoestringData[ name ];\n\t\t\t}\telse {\n\t\t\t\tthis[ 0 ].shoestringData = {};\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * An alias for the `shoestring` constructor.\n\t */\n\tif (typeof(win.$) === \"undefined\") { win.$ = shoestring; }\n\n\n\n\t/**\n\t * Add a class to each DOM element in the set of elements.\n\t *\n\t * @param {string} className The name of the class to be added.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.addClass = function( className ){\n\t\tvar classes = className.replace(/^\\s+|\\s+$/g, '').split( \" \" );\n\n\t\treturn this.each(function(){\n\t\t\tfor( var i = 0, il = classes.length; i < il; i++ ){\n\t\t\t\tif( this.className !== undefined &&\n\t\t\t\t\t\t(this.className === \"\" ||\n\t\t\t\t\t\t!this.className.match( new RegExp( \"(^|\\\\s)\" + classes[ i ] + \"($|\\\\s)\"))) ){\n\t\t\t\t\tthis.className += \" \" + classes[ i ];\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\n\n /**\n\t * Add elements matching the selector to the current set.\n\t *\n\t * @param {string} selector The selector for the elements to add from the DOM\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.add = function( selector ){\n\t\tvar ret = [];\n\t\tthis.each(function(){\n\t\t\tret.push( this );\n\t\t});\n\n\t\tshoestring( selector ).each(function(){\n\t\t\tret.push( this );\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Insert an element or HTML string after each element in the current set.\n\t *\n\t * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.after = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" || fragment.nodeType !== undefined ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\tif( fragment.length > 1 ){\n\t\t\tfragment = fragment.reverse();\n\t\t}\n\t\treturn this.each(function( i ){\n\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\tvar insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ];\n\t\t\t\tthis.parentNode.insertBefore( insertEl, this.nextSibling );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Insert an element or HTML string as the last child of each element in the set.\n\t *\n\t * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.append = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" || fragment.nodeType !== undefined ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\treturn this.each(function( i ){\n\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\tthis.appendChild( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ] );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Insert the current set as the last child of the elements matching the selector.\n\t *\n\t * @param {string} selector The selector after which to append the current set.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.appendTo = function( selector ){\n\t\treturn this.each(function(){\n\t\t\tshoestring( selector ).append( this );\n\t\t});\n\t};\n\n\n\n /**\n\t * Get the value of the first element of the set or set the value of all the elements in the set.\n\t *\n\t * @param {string} name The attribute name.\n\t * @param {string} value The new value for the attribute.\n\t * @return {shoestring|string|undefined}\n\t * @this {shoestring}\n\t */\n\tshoestring.fn.attr = function( name, value ){\n\t\tvar nameStr = typeof( name ) === \"string\";\n\n\t\tif( value !== undefined || !nameStr ){\n\t\t\treturn this.each(function(){\n\t\t\t\tif( nameStr ){\n\t\t\t\t\tthis.setAttribute( name, value );\n\t\t\t\t}\telse {\n\t\t\t\t\tfor( var i in name ){\n\t\t\t\t\t\tif( name.hasOwnProperty( i ) ){\n\t\t\t\t\t\t\tthis.setAttribute( i, name[ i ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\treturn this[ 0 ] ? this[ 0 ].getAttribute( name ) : undefined;\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Insert an element or HTML string before each element in the current set.\n\t *\n\t * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.before = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" || fragment.nodeType !== undefined ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\treturn this.each(function( i ){\n\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\tthis.parentNode.insertBefore( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ], this );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Get the children of the current collection.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.children = function(){\n\t\t\t\tvar ret = [],\n\t\t\tchilds,\n\t\t\tj;\n\t\tthis.each(function(){\n\t\t\tchilds = this.children;\n\t\t\tj = -1;\n\n\t\t\twhile( j++ < childs.length-1 ){\n\t\t\t\tif( shoestring.inArray( childs[ j ], ret ) === -1 ){\n\t\t\t\t\tret.push( childs[ j ] );\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\treturn shoestring(ret);\n\t};\n\n\n\n\t/**\n\t * Clone and return the current set of nodes into a new `shoestring` object.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.clone = function() {\n\t\tvar ret = [];\n\n\t\tthis.each(function() {\n\t\t\tret.push( this.cloneNode( true ) );\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Find an element matching the selector in the set of the current element and its parents.\n\t *\n\t * @param {string} selector The selector used to identify the target element.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.closest = function( selector ){\n\t\tvar ret = [];\n\n\t\tif( !selector ){\n\t\t\treturn shoestring( ret );\n\t\t}\n\n\t\tthis.each(function(){\n\t\t\tvar element, $self = shoestring( element = this );\n\n\t\t\tif( $self.is(selector) ){\n\t\t\t\tret.push( this );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\twhile( element.parentElement ) {\n\t\t\t\tif( shoestring(element.parentElement).is(selector) ){\n\t\t\t\t\tret.push( element.parentElement );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\telement = element.parentElement;\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n shoestring.cssExceptions = {\n\t\t'float': [ 'cssFloat' ]\n\t};\n\n\n\n\t(function() {\n\t\tvar cssExceptions = shoestring.cssExceptions;\n\n\t\t// marginRight instead of margin-right\n\t\tfunction convertPropertyName( str ) {\n\t\t\treturn str.replace( /\\-([A-Za-z])/g, function ( match, character ) {\n\t\t\t\treturn character.toUpperCase();\n\t\t\t});\n\t\t}\n\n\t\tfunction _getStyle( element, property ) {\n\t\t\treturn win.getComputedStyle( element, null ).getPropertyValue( property );\n\t\t}\n\n\t\tvar vendorPrefixes = [ '', '-webkit-', '-ms-', '-moz-', '-o-', '-khtml-' ];\n\n\t\t/**\n\t\t * Private function for getting the computed style of an element.\n\t\t *\n\t\t * **NOTE** Please use the [css](../css.js.html) method instead.\n\t\t *\n\t\t * @method _getStyle\n\t\t * @param {HTMLElement} element The element we want the style property for.\n\t\t * @param {string} property The css property we want the style for.\n\t\t */\n\t\tshoestring._getStyle = function( element, property ) {\n\t\t\tvar convert, value, j, k;\n\n\t\t\tif( cssExceptions[ property ] ) {\n\t\t\t\tfor( j = 0, k = cssExceptions[ property ].length; j < k; j++ ) {\n\t\t\t\t\tvalue = _getStyle( element, cssExceptions[ property ][ j ] );\n\n\t\t\t\t\tif( value ) {\n\t\t\t\t\t\treturn value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor( j = 0, k = vendorPrefixes.length; j < k; j++ ) {\n\t\t\t\tconvert = convertPropertyName( vendorPrefixes[ j ] + property );\n\n\t\t\t\t// VendorprefixKeyName || key-name\n\t\t\t\tvalue = _getStyle( element, convert );\n\n\t\t\t\tif( convert !== property ) {\n\t\t\t\t\tvalue = value || _getStyle( element, property );\n\t\t\t\t}\n\n\t\t\t\tif( vendorPrefixes[ j ] ) {\n\t\t\t\t\t// -vendorprefix-key-name\n\t\t\t\t\tvalue = value || _getStyle( element, vendorPrefixes[ j ] + property );\n\t\t\t\t}\n\n\t\t\t\tif( value ) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t};\n\t})();\n\n\n\n\t(function() {\n\t\tvar cssExceptions = shoestring.cssExceptions;\n\n\t\t// marginRight instead of margin-right\n\t\tfunction convertPropertyName( str ) {\n\t\t\treturn str.replace( /\\-([A-Za-z])/g, function ( match, character ) {\n\t\t\t\treturn character.toUpperCase();\n\t\t\t});\n\t\t}\n\n\t\t/**\n\t\t * Private function for setting the style of an element.\n\t\t *\n\t\t * **NOTE** Please use the [css](../css.js.html) method instead.\n\t\t *\n\t\t * @method _setStyle\n\t\t * @param {HTMLElement} element The element we want to style.\n\t\t * @param {string} property The property being used to style the element.\n\t\t * @param {string} value The css value for the style property.\n\t\t */\n\t\tshoestring._setStyle = function( element, property, value ) {\n\t\t\tvar convertedProperty = convertPropertyName(property);\n\n\t\t\telement.style[ property ] = value;\n\n\t\t\tif( convertedProperty !== property ) {\n\t\t\t\telement.style[ convertedProperty ] = value;\n\t\t\t}\n\n\t\t\tif( cssExceptions[ property ] ) {\n\t\t\t\tfor( var j = 0, k = cssExceptions[ property ].length; j -1 ){\n\t\t\t\t\tret.push( this );\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Find descendant elements of the current collection.\n\t *\n\t * @param {string} selector The selector used to find the children\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.find = function( selector ){\n\t\tvar ret = [],\n\t\t\tfinds;\n\t\tthis.each(function(){\n\t\t\t\tfinds = this.querySelectorAll( selector );\n\n\t\t\tfor( var i = 0, il = finds.length; i < il; i++ ){\n\t\t\t\tret = ret.concat( finds[i] );\n\t\t\t}\n\t\t});\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Returns the first element of the set wrapped in a new `shoestring` object.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.first = function(){\n\t\treturn this.eq( 0 );\n\t};\n\n\n\n\t/**\n\t * Returns the raw DOM node at the passed index.\n\t *\n\t * @param {integer} index The index of the element to wrap and return.\n\t * @return {HTMLElement|undefined|array}\n\t * @this shoestring\n\t */\n\tshoestring.fn.get = function( index ){\n\n\t\t// return an array of elements if index is undefined\n\t\tif( index === undefined ){\n\t\t\tvar elements = [];\n\n\t\t\tfor( var i = 0; i < this.length; i++ ){\n\t\t\t\telements.push( this[ i ] );\n\t\t\t}\n\n\t\t\treturn elements;\n\t\t} else {\n\t\t\treturn this[ index ];\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Private function for setting/getting the offset property for height/width.\n\t *\n\t * **NOTE** Please use the [width](width.js.html) or [height](height.js.html) methods instead.\n\t *\n\t * @param {shoestring} set The set of elements.\n\t * @param {string} name The string \"height\" or \"width\".\n\t * @param {float|undefined} value The value to assign.\n\t * @return shoestring\n\t * @this window\n\t */\n\tshoestring._dimension = function( set, name, value ){\n\t\tvar offsetName;\n\n\t\tif( value === undefined ){\n\t\t\toffsetName = name.replace(/^[a-z]/, function( letter ) {\n\t\t\t\treturn letter.toUpperCase();\n\t\t\t});\n\n\t\t\treturn set[ 0 ][ \"offset\" + offsetName ];\n\t\t} else {\n\t\t\t// support integer values as pixels\n\t\t\tvalue = typeof value === \"string\" ? value : value + \"px\";\n\n\t\t\treturn set.each(function(){\n\t\t\t\tthis.style[ name ] = value;\n\t\t\t});\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Gets the height value of the first element or sets the height for the whole set.\n\t *\n\t * @param {float|undefined} value The value to assign.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.height = function( value ){\n\t\treturn shoestring._dimension( this, \"height\", value );\n\t};\n\n\n\n\tvar set = function( html ){\n\t\tif( typeof html === \"string\" || typeof html === \"number\" ){\n\t\t\treturn this.each(function(){\n\t\t\t\tthis.innerHTML = \"\" + html;\n\t\t\t});\n\t\t} else {\n\t\t\tvar h = \"\";\n\t\t\tif( typeof html.length !== \"undefined\" ){\n\t\t\t\tfor( var i = 0, l = html.length; i < l; i++ ){\n\t\t\t\t\th += html[i].outerHTML;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\th = html.outerHTML;\n\t\t\t}\n\t\t\treturn this.each(function(){\n\t\t\t\tthis.innerHTML = h;\n\t\t\t});\n\t\t}\n\t};\n\t/**\n\t * Gets or sets the `innerHTML` from all the elements in the set.\n\t *\n\t * @param {string|undefined} html The html to assign\n\t * @return {string|shoestring}\n\t * @this shoestring\n\t */\n\tshoestring.fn.html = function( html ){\n\t\t\t\tif( typeof html !== \"undefined\" ){\n\t\t\treturn set.call( this, html );\n\t\t} else { // get\n\t\t\tvar pile = \"\";\n\n\t\t\tthis.each(function(){\n\t\t\t\tpile += this.innerHTML;\n\t\t\t});\n\n\t\t\treturn pile;\n\t\t}\n\t};\n\n\n\n\t(function() {\n\t\tfunction _getIndex( set, test ) {\n\t\t\tvar i, result, element;\n\n\t\t\tfor( i = result = 0; i < set.length; i++ ) {\n\t\t\t\telement = set.item ? set.item(i) : set[i];\n\n\t\t\t\tif( test(element) ){\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\t// ignore text nodes, etc\n\t\t\t\t// NOTE may need to be more permissive\n\t\t\t\tif( element.nodeType === 1 ){\n\t\t\t\t\tresult++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn -1;\n\t\t}\n\n\t\t/**\n\t\t * Find the index in the current set for the passed selector.\n\t\t * Without a selector it returns the index of the first node within the array of its siblings.\n\t\t *\n\t\t * @param {string|undefined} selector The selector used to search for the index.\n\t\t * @return {integer}\n\t\t * @this {shoestring}\n\t\t */\n\t\tshoestring.fn.index = function( selector ){\n\t\t\tvar self, children;\n\n\t\t\tself = this;\n\n\t\t\t// no arg? check the children, otherwise check each element that matches\n\t\t\tif( selector === undefined ){\n\t\t\t\tchildren = ( ( this[ 0 ] && this[0].parentNode ) || doc.documentElement).childNodes;\n\n\t\t\t\t// check if the element matches the first of the set\n\t\t\t\treturn _getIndex(children, function( element ) {\n\t\t\t\t\treturn self[0] === element;\n\t\t\t\t});\n\t\t\t} else {\n\n\t\t\t\t// check if the element matches the first selected node from the parent\n\t\t\t\treturn _getIndex(self, function( element ) {\n\t\t\t\t\treturn element === (shoestring( selector, element.parentNode )[ 0 ]);\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t})();\n\n\n\n\t/**\n\t * Insert the current set after the elements matching the selector.\n\t *\n\t * @param {string} selector The selector after which to insert the current set.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.insertAfter = function( selector ){\n\t\treturn this.each(function(){\n\t\t\tshoestring( selector ).after( this );\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Insert the current set before the elements matching the selector.\n\t *\n\t * @param {string} selector The selector before which to insert the current set.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.insertBefore = function( selector ){\n\t\treturn this.each(function(){\n\t\t\tshoestring( selector ).before( this );\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Returns the last element of the set wrapped in a new `shoestring` object.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.last = function(){\n\t\treturn this.eq( this.length - 1 );\n\t};\n\n\n\n\t/**\n\t * Returns a `shoestring` object with the set of siblings of each element in the original set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.next = function(){\n\t\t\n\t\tvar result = [];\n\n\t\t// TODO need to implement map\n\t\tthis.each(function() {\n\t\t\tvar children, item, found;\n\n\t\t\t// get the child nodes for this member of the set\n\t\t\tchildren = shoestring( this.parentNode )[0].childNodes;\n\n\t\t\tfor( var i = 0; i < children.length; i++ ){\n\t\t\t\titem = children.item( i );\n\n\t\t\t\t// found the item we needed (found) which means current item value is\n\t\t\t\t// the next node in the list, as long as it's viable grab it\n\t\t\t\t// NOTE may need to be more permissive\n\t\t\t\tif( found && item.nodeType === 1 ){\n\t\t\t\t\tresult.push( item );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// find the current item and mark it as found\n\t\t\t\tif( item === this ){\n\t\t\t\t\tfound = true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( result );\n\t};\n\n\n\n\t/**\n\t * Removes elements from the current set.\n\t *\n\t * @param {string} selector The selector to use when removing the elements.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.not = function( selector ){\n\t\tvar ret = [];\n\n\t\tthis.each(function(){\n\t\t\tvar found = shoestring( selector, this.parentNode );\n\n\t\t\tif( shoestring.inArray(this, found) === -1 ){\n\t\t\t\tret.push( this );\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Returns an object with the `top` and `left` properties corresponging to the first elements offsets.\n\t *\n\t * @return object\n\t * @this shoestring\n\t */\n\tshoestring.fn.offset = function(){\n\t\treturn {\n\t\t\ttop: this[ 0 ].offsetTop,\n\t\t\tleft: this[ 0 ].offsetLeft\n\t\t};\n\t};\n\n\n\n\t/**\n\t * Returns the set of first parents for each element in the current set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.parent = function(){\n\t\tvar ret = [],\n\t\t\tparent;\n\n\t\tthis.each(function(){\n\t\t\t// no parent node, assume top level\n\t\t\t// jQuery parent: return the document object for or the parent node if it exists\n\t\t\tparent = (this === doc.documentElement ? doc : this.parentNode);\n\n\t\t\t// if there is a parent and it's not a document fragment\n\t\t\tif( parent && parent.nodeType !== 11 ){\n\t\t\t\tret.push( parent );\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring(ret);\n\t};\n\n\n\n\t/**\n\t * Returns the set of all parents matching the selector if provided for each element in the current set.\n\t *\n\t * @param {string} selector The selector to check the parents with.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.parents = function( selector ){\n\t\tvar ret = [];\n\n\t\tthis.each(function(){\n\t\t\tvar curr = this, match;\n\n\t\t\twhile( curr.parentElement && !match ){\n\t\t\t\tcurr = curr.parentElement;\n\n\t\t\t\tif( selector ){\n\t\t\t\t\tif( curr === shoestring( selector )[0] ){\n\t\t\t\t\t\tmatch = true;\n\n\t\t\t\t\t\tif( shoestring.inArray( curr, ret ) === -1 ){\n\t\t\t\t\t\t\tret.push( curr );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif( shoestring.inArray( curr, ret ) === -1 ){\n\t\t\t\t\t\tret.push( curr );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring(ret);\n\t};\n\n\n\n\t/**\n\t * Add an HTML string or element before the children of each element in the current set.\n\t *\n\t * @param {string|HTMLElement} fragment The HTML string or element to add.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.prepend = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" || fragment.nodeType !== undefined ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\treturn this.each(function( i ){\n\n\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\tvar insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ];\n\t\t\t\tif ( this.firstChild ){\n\t\t\t\t\tthis.insertBefore( insertEl, this.firstChild );\n\t\t\t\t} else {\n\t\t\t\t\tthis.appendChild( insertEl );\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Add each element of the current set before the children of the selected elements.\n\t *\n\t * @param {string} selector The selector for the elements to add the current set to..\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.prependTo = function( selector ){\n\t\treturn this.each(function(){\n\t\t\tshoestring( selector ).prepend( this );\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Returns a `shoestring` object with the set of *one* siblingx before each element in the original set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.prev = function(){\n\t\t\n\t\tvar result = [];\n\n\t\t// TODO need to implement map\n\t\tthis.each(function() {\n\t\t\tvar children, item, found;\n\n\t\t\t// get the child nodes for this member of the set\n\t\t\tchildren = shoestring( this.parentNode )[0].childNodes;\n\n\t\t\tfor( var i = children.length -1; i >= 0; i-- ){\n\t\t\t\titem = children.item( i );\n\n\t\t\t\t// found the item we needed (found) which means current item value is\n\t\t\t\t// the next node in the list, as long as it's viable grab it\n\t\t\t\t// NOTE may need to be more permissive\n\t\t\t\tif( found && item.nodeType === 1 ){\n\t\t\t\t\tresult.push( item );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// find the current item and mark it as found\n\t\t\t\tif( item === this ){\n\t\t\t\t\tfound = true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( result );\n\t};\n\n\n\n\t/**\n\t * Returns a `shoestring` object with the set of *all* siblings before each element in the original set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.prevAll = function(){\n\t\t\n\t\tvar result = [];\n\n\t\tthis.each(function() {\n\t\t\tvar $previous = shoestring( this ).prev();\n\n\t\t\twhile( $previous.length ){\n\t\t\t\tresult.push( $previous[0] );\n\t\t\t\t$previous = $previous.prev();\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( result );\n\t};\n\n\n\n\t// Property normalization, a subset taken from jQuery src\n\tshoestring.propFix = {\n\t\t\"class\": \"className\",\n\t\tcontenteditable: \"contentEditable\",\n\t\t\"for\": \"htmlFor\",\n\t\treadonly: \"readOnly\",\n\t\ttabindex: \"tabIndex\"\n\t};\n\n\n\n\t/**\n\t * Gets the property value from the first element or sets the property value on all elements of the currrent set.\n *\n\t * @param {string} name The property name.\n * @param {any} value The property value.\n\t * @return {any|shoestring}\n\t * @this shoestring\n\t */\n\tshoestring.fn.prop = function( name, value ){\n\t\tif( !this[0] ){\n\t\t\treturn;\n\t\t}\n\n\t\tname = shoestring.propFix[ name ] || name;\n\n\t\tif( value !== undefined ){\n\t\t\treturn this.each(function(){\n\t\t\t\tthis[ name ] = value;\n\t\t\t});\n\t\t}\telse {\n\t\t\treturn this[ 0 ][ name ];\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Remove an attribute from each element in the current set.\n\t *\n\t * @param {string} name The name of the attribute.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.removeAttr = function( name ){\n\t\treturn this.each(function(){\n\t\t\tthis.removeAttribute( name );\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Remove a class from each DOM element in the set of elements.\n\t *\n\t * @param {string} className The name of the class to be removed.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.removeClass = function( cname ){\n\t\tvar classes = cname.replace(/^\\s+|\\s+$/g, '').split( \" \" );\n\n\t\treturn this.each(function(){\n\t\t\tvar newClassName, regex;\n\n\t\t\tfor( var i = 0, il = classes.length; i < il; i++ ){\n\t\t\t\tif( this.className !== undefined ){\n\t\t\t\t\tregex = new RegExp( \"(^|\\\\s)\" + classes[ i ] + \"($|\\\\s)\", \"gmi\" );\n\t\t\t\t\tnewClassName = this.className.replace( regex, \" \" );\n\n\t\t\t\t\tthis.className = newClassName.replace(/^\\s+|\\s+$/g, '');\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Remove the current set of elements from the DOM.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.remove = function(){\n\t\treturn this.each(function(){\n\t\t\tif( this.parentNode ) {\n\t\t\t\tthis.parentNode.removeChild( this );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Remove a proprety from each element in the current set.\n\t *\n\t * @param {string} name The name of the property.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.removeProp = function( property ){\n\t\tvar name = shoestring.propFix[ property ] || property;\n\n\t\treturn this.each(function(){\n\t\t\tthis[ name ] = undefined;\n\t\t\tdelete this[ name ];\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Replace each element in the current set with that argument HTML string or HTMLElement.\n\t *\n\t * @param {string|HTMLElement} fragment The value to assign.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.replaceWith = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\tvar ret = [];\n\n\t\tif( fragment.length > 1 ){\n\t\t\tfragment = fragment.reverse();\n\t\t}\n\t\tthis.each(function( i ){\n\t\t\tvar clone = this.cloneNode( true ),\n\t\t\t\tinsertEl;\n\t\t\tret.push( clone );\n\n\t\t\t// If there is no parentNode, this is pointless, drop it.\n\t\t\tif( !this.parentNode ){ return; }\n\n\t\t\tif( fragment.length === 1 ){\n\t\t\t\tinsertEl = i > 0 ? fragment[ 0 ].cloneNode( true ) : fragment[ 0 ];\n\t\t\t\tthis.parentNode.replaceChild( insertEl, this );\n\t\t\t} else {\n\t\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\t\tinsertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ];\n\t\t\t\t\tthis.parentNode.insertBefore( insertEl, this.nextSibling );\n\t\t\t\t}\n\t\t\t\tthis.parentNode.removeChild( this );\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\tshoestring.inputTypes = [\n\t\t\"text\",\n\t\t\"hidden\",\n\t\t\"password\",\n\t\t\"color\",\n\t\t\"date\",\n\t\t\"datetime\",\n\t\t// \"datetime\\-local\" matched by datetime\n\t\t\"email\",\n\t\t\"month\",\n\t\t\"number\",\n\t\t\"range\",\n\t\t\"search\",\n\t\t\"tel\",\n\t\t\"time\",\n\t\t\"url\",\n\t\t\"week\"\n\t];\n\n\tshoestring.inputTypeTest = new RegExp( shoestring.inputTypes.join( \"|\" ) );\n\n\n\t/**\n\t * Serialize child input element values into an object.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.serialize = function(){\n\t\tvar data = {};\n\n\t\tshoestring( \"input, select\", this ).each(function(){\n\t\t\tvar type = this.type, name = this.name,\tvalue = this.value;\n\n\t\t\tif( shoestring.inputTypeTest.test( type ) ||\n\t\t\t\t\t( type === \"checkbox\" || type === \"radio\" ) &&\n\t\t\t\t\tthis.checked ){\n\n\t\t\t\tdata[ name ] = value;\n\t\t\t}\telse if( this.nodeName === \"SELECT\" ){\n\t\t\t\tdata[ name ] = this.options[ this.selectedIndex ].nodeValue;\n\t\t\t}\n\t\t});\n\n\t\treturn data;\n\t};\n\n\n\n /**\n\t * Get all of the sibling elements for each element in the current set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.siblings = function(){\n\t\t\n\t\tif( !this.length ) {\n\t\t\treturn shoestring( [] );\n\t\t}\n\n\t\tvar sibs = [], el = this[ 0 ].parentNode.firstChild;\n\n\t\tdo {\n\t\t\tif( el.nodeType === 1 && el !== this[ 0 ] ) {\n\t\t\t\tsibs.push( el );\n\t\t\t}\n\n el = el.nextSibling;\n\t\t} while( el );\n\n\t\treturn shoestring( sibs );\n\t};\n\n\n\n\tvar getText = function( elem ){\n\t\tvar node,\n\t\t\tret = \"\",\n\t\t\ti = 0,\n\t\t\tnodeType = elem.nodeType;\n\n\t\tif ( !nodeType ) {\n\t\t\t// If no nodeType, this is expected to be an array\n\t\t\twhile ( (node = elem[i++]) ) {\n\t\t\t\t// Do not traverse comment nodes\n\t\t\t\tret += getText( node );\n\t\t\t}\n\t\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\t\t\t// Use textContent for elements\n\t\t\t// innerText usage removed for consistency of new lines (jQuery #11153)\n\t\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\t\treturn elem.textContent;\n\t\t\t} else {\n\t\t\t\t// Traverse its children\n\t\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\t\tret += getText( elem );\n\t\t\t\t}\n\t\t\t}\n\t\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\t\treturn elem.nodeValue;\n\t\t}\n\t\t// Do not include comment or processing instruction nodes\n\n\t\treturn ret;\n\t};\n\n /**\n\t * Recursively retrieve the text content of the each element in the current set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.text = function() {\n\t\t\n\t\treturn getText( this );\n\t};\n\n\n\n\n\t/**\n\t * Get the value of the first element or set the value of all elements in the current set.\n\t *\n\t * @param {string} value The value to set.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.val = function( value ){\n\t\tvar el;\n\t\tif( value !== undefined ){\n\t\t\treturn this.each(function(){\n\t\t\t\tif( this.tagName === \"SELECT\" ){\n\t\t\t\t\tvar optionSet, option,\n\t\t\t\t\t\toptions = this.options,\n\t\t\t\t\t\tvalues = [],\n\t\t\t\t\t\ti = options.length,\n\t\t\t\t\t\tnewIndex;\n\n\t\t\t\t\tvalues[0] = value;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\toption = options[ i ];\n\t\t\t\t\t\tif ( (option.selected = shoestring.inArray( option.value, values ) >= 0) ) {\n\t\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t\t\tnewIndex = i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// force browsers to behave consistently when non-matching value is set\n\t\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\t\tthis.selectedIndex = -1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.selectedIndex = newIndex;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.value = value;\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tel = this[0];\n\n\t\t\tif( el.tagName === \"SELECT\" ){\n\t\t\t\tif( el.selectedIndex < 0 ){ return \"\"; }\n\t\t\t\treturn el.options[ el.selectedIndex ].value;\n\t\t\t} else {\n\t\t\t\treturn el.value;\n\t\t\t}\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Gets the width value of the first element or sets the width for the whole set.\n\t *\n\t * @param {float|undefined} value The value to assign.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.width = function( value ){\n\t\treturn shoestring._dimension( this, \"width\", value );\n\t};\n\n\n\n\t/**\n\t * Wraps the child elements in the provided HTML.\n\t *\n\t * @param {string} html The wrapping HTML.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.wrapInner = function( html ){\n\t\treturn this.each(function(){\n\t\t\tvar inH = this.innerHTML;\n\n\t\t\tthis.innerHTML = \"\";\n\t\t\tshoestring( this ).append( shoestring( html ).html( inH ) );\n\t\t});\n\t};\n\n\n\n\tfunction initEventCache( el, evt ) {\n\t\tif ( !el.shoestringData ) {\n\t\t\tel.shoestringData = {};\n\t\t}\n\t\tif ( !el.shoestringData.events ) {\n\t\t\tel.shoestringData.events = {};\n\t\t}\n\t\tif ( !el.shoestringData.loop ) {\n\t\t\tel.shoestringData.loop = {};\n\t\t}\n\t\tif ( !el.shoestringData.events[ evt ] ) {\n\t\t\tel.shoestringData.events[ evt ] = [];\n\t\t}\n\t}\n\n\tfunction addToEventCache( el, evt, eventInfo ) {\n\t\tvar obj = {};\n\t\tobj.isCustomEvent = eventInfo.isCustomEvent;\n\t\tobj.callback = eventInfo.callfunc;\n\t\tobj.originalCallback = eventInfo.originalCallback;\n\t\tobj.namespace = eventInfo.namespace;\n\n\t\tel.shoestringData.events[ evt ].push( obj );\n\n\t\tif( eventInfo.customEventLoop ) {\n\t\t\tel.shoestringData.loop[ evt ] = eventInfo.customEventLoop;\n\t\t}\n\t}\n\n\t/**\n\t * Bind a callback to an event for the currrent set of elements.\n\t *\n\t * @param {string} evt The event(s) to watch for.\n\t * @param {object,function} data Data to be included with each event or the callback.\n\t * @param {function} originalCallback Callback to be invoked when data is define.d.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.bind = function( evt, data, originalCallback ){\n\n\t\t\t\tif( typeof data === \"function\" ){\n\t\t\toriginalCallback = data;\n\t\t\tdata = null;\n\t\t}\n\n\t\tvar evts = evt.split( \" \" );\n\n\t\t// NOTE the `triggeredElement` is purely for custom events from IE\n\t\tfunction encasedCallback( e, namespace, triggeredElement ){\n\t\t\tvar result;\n\n\t\t\tif( e._namespace && e._namespace !== namespace ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\te.data = data;\n\t\t\te.namespace = e._namespace;\n\n\t\t\tvar returnTrue = function(){\n\t\t\t\treturn true;\n\t\t\t};\n\n\t\t\te.isDefaultPrevented = function(){\n\t\t\t\treturn false;\n\t\t\t};\n\n\t\t\tvar originalPreventDefault = e.preventDefault;\n\t\t\tvar preventDefaultConstructor = function(){\n\t\t\t\tif( originalPreventDefault ) {\n\t\t\t\t\treturn function(){\n\t\t\t\t\t\te.isDefaultPrevented = returnTrue;\n\t\t\t\t\t\toriginalPreventDefault.call(e);\n\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\treturn function(){\n\t\t\t\t\t\te.isDefaultPrevented = returnTrue;\n\t\t\t\t\t\te.returnValue = false;\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// thanks https://github.com/jonathantneal/EventListener\n\t\t\te.target = triggeredElement || e.target || e.srcElement;\n\t\t\te.preventDefault = preventDefaultConstructor();\n\t\t\te.stopPropagation = e.stopPropagation || function () {\n\t\t\t\te.cancelBubble = true;\n\t\t\t};\n\n\t\t\tresult = originalCallback.apply(this, [ e ].concat( e._args ) );\n\n\t\t\tif( result === false ){\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\treturn this.each(function(){\n\t\t\tvar domEventCallback,\n\t\t\t\tcustomEventCallback,\n\t\t\t\tcustomEventLoop,\n\t\t\t\toEl = this;\n\n\t\t\tfor( var i = 0, il = evts.length; i < il; i++ ){\n\t\t\t\tvar split = evts[ i ].split( \".\" ),\n\t\t\t\t\tevt = split[ 0 ],\n\t\t\t\t\tnamespace = split.length > 0 ? split[ 1 ] : null;\n\n\t\t\t\tdomEventCallback = function( originalEvent ) {\n\t\t\t\t\tif( oEl.ssEventTrigger ) {\n\t\t\t\t\t\toriginalEvent._namespace = oEl.ssEventTrigger._namespace;\n\t\t\t\t\t\toriginalEvent._args = oEl.ssEventTrigger._args;\n\n\t\t\t\t\t\toEl.ssEventTrigger = null;\n\t\t\t\t\t}\n\t\t\t\t\treturn encasedCallback.call( oEl, originalEvent, namespace );\n\t\t\t\t};\n\t\t\t\tcustomEventCallback = null;\n\t\t\t\tcustomEventLoop = null;\n\n\t\t\t\tinitEventCache( this, evt );\n\n\t\t\t\tthis.addEventListener( evt, domEventCallback, false );\n\n\t\t\t\taddToEventCache( this, evt, {\n\t\t\t\t\tcallfunc: customEventCallback || domEventCallback,\n\t\t\t\t\tisCustomEvent: !!customEventCallback,\n\t\t\t\t\tcustomEventLoop: customEventLoop,\n\t\t\t\t\toriginalCallback: originalCallback,\n\t\t\t\t\tnamespace: namespace\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t};\n\n\tshoestring.fn.on = shoestring.fn.bind;\n\n\t\n\n\n\t/**\n\t * Unbind a previous bound callback for an event.\n\t *\n\t * @param {string} event The event(s) the callback was bound to..\n\t * @param {function} callback Callback to unbind.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.unbind = function( event, callback ){\n\n\t\t\n\t\tvar evts = event ? event.split( \" \" ) : [];\n\n\t\treturn this.each(function(){\n\t\t\tif( !this.shoestringData || !this.shoestringData.events ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif( !evts.length ) {\n\t\t\t\tunbindAll.call( this );\n\t\t\t} else {\n\t\t\t\tvar split, evt, namespace;\n\t\t\t\tfor( var i = 0, il = evts.length; i < il; i++ ){\n\t\t\t\t\tsplit = evts[ i ].split( \".\" ),\n\t\t\t\t\tevt = split[ 0 ],\n\t\t\t\t\tnamespace = split.length > 0 ? split[ 1 ] : null;\n\n\t\t\t\t\tif( evt ) {\n\t\t\t\t\t\tunbind.call( this, evt, namespace, callback );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tunbindAll.call( this, namespace, callback );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\tfunction unbind( evt, namespace, callback ) {\n\t\tvar bound = this.shoestringData.events[ evt ];\n\t\tif( !(bound && bound.length) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar matched = [], j, jl;\n\t\tfor( j = 0, jl = bound.length; j < jl; j++ ) {\n\t\t\tif( !namespace || namespace === bound[ j ].namespace ) {\n\t\t\t\tif( callback === undefined || callback === bound[ j ].originalCallback ) {\n\t\t\t\t\tthis.removeEventListener( evt, bound[ j ].callback, false );\n\t\t\t\t\tmatched.push( j );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor( j = 0, jl = matched.length; j < jl; j++ ) {\n\t\t\tthis.shoestringData.events[ evt ].splice( j, 1 );\n\t\t}\n\t}\n\n\tfunction unbindAll( namespace, callback ) {\n\t\tfor( var evtKey in this.shoestringData.events ) {\n\t\t\tunbind.call( this, evtKey, namespace, callback );\n\t\t}\n\t}\n\n\tshoestring.fn.off = shoestring.fn.unbind;\n\n\n\t/**\n\t * Bind a callback to an event for the currrent set of elements, unbind after one occurence.\n\t *\n\t * @param {string} event The event(s) to watch for.\n\t * @param {function} callback Callback to invoke on the event.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.one = function( event, callback ){\n\t\tvar evts = event.split( \" \" );\n\n\t\treturn this.each(function(){\n\t\t\tvar thisevt, cbs = {},\t$t = shoestring( this );\n\n\t\t\tfor( var i = 0, il = evts.length; i < il; i++ ){\n\t\t\t\tthisevt = evts[ i ];\n\n\t\t\t\tcbs[ thisevt ] = function( e ){\n\t\t\t\t\tvar $t = shoestring( this );\n\n\t\t\t\t\tfor( var j in cbs ) {\n\t\t\t\t\t\t$t.unbind( j, cbs[ j ] );\n\t\t\t\t\t}\n\n\t\t\t\t\treturn callback.apply( this, [ e ].concat( e._args ) );\n\t\t\t\t};\n\n\t\t\t\t$t.bind( thisevt, cbs[ thisevt ] );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Trigger an event on the first element in the set, no bubbling, no defaults.\n\t *\n\t * @param {string} event The event(s) to trigger.\n\t * @param {object} args Arguments to append to callback invocations.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.triggerHandler = function( event, args ){\n\t\tvar e = event.split( \" \" )[ 0 ],\n\t\t\tel = this[ 0 ],\n\t\t\tret;\n\n\t\t// See this.fireEvent( 'on' + evts[ i ], document.createEventObject() ); instead of click() etc in trigger.\n\t\tif( doc.createEvent && el.shoestringData && el.shoestringData.events && el.shoestringData.events[ e ] ){\n\t\t\tvar bindings = el.shoestringData.events[ e ];\n\t\t\tfor (var i in bindings ){\n\t\t\t\tif( bindings.hasOwnProperty( i ) ){\n\t\t\t\t\tevent = doc.createEvent( \"Event\" );\n\t\t\t\t\tevent.initEvent( e, true, true );\n\t\t\t\t\tevent._args = args;\n\t\t\t\t\targs.unshift( event );\n\n\t\t\t\t\tret = bindings[ i ].originalCallback.apply( event.target, args );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t};\n\n\n\n\t/**\n\t * Trigger an event on each of the DOM elements in the current set.\n\t *\n\t * @param {string} event The event(s) to trigger.\n\t * @param {object} args Arguments to append to callback invocations.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.trigger = function( event, args ){\n\t\tvar evts = event.split( \" \" );\n\n\t\treturn this.each(function(){\n\t\t\tvar split, evt, namespace;\n\t\t\tfor( var i = 0, il = evts.length; i < il; i++ ){\n\t\t\t\tsplit = evts[ i ].split( \".\" ),\n\t\t\t\tevt = split[ 0 ],\n\t\t\t\tnamespace = split.length > 0 ? split[ 1 ] : null;\n\n\t\t\t\tif( evt === \"click\" ){\n\t\t\t\t\tif( this.tagName === \"INPUT\" && this.type === \"checkbox\" && this.click ){\n\t\t\t\t\t\tthis.click();\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif( doc.createEvent ){\n\t\t\t\t\tvar event = doc.createEvent( \"Event\" );\n\t\t\t\t\tevent.initEvent( evt, true, true );\n\t\t\t\t\tevent._args = args;\n\t\t\t\t\tevent._namespace = namespace;\n\n\t\t\t\t\tthis.dispatchEvent( event );\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\treturn shoestring;\n}));\n\nif( !window.jQuery ){\n window.jQuery = window.jQuery || window.shoestring;\n}\n\n(function(exports){\n\t/**\n\t * The Akamai component namespace contains the {@link Akamai.Viewer}, {@link\n\t * Akamai.Spin360}, {@link Akamai.Carousel}, and {@link Akamai.Zoom} components.\n\t * The {@link Akamai.Viewer} component manages the other components depending on\n\t * the state of the markup it is provided, start there for more.\n\t *\n\t * @example On-ready automatic initialization with jQuery\n\t * $(function(){\n\t *\t $( \"[data-akamai-viewer]\" ).each(function(i, element){\n\t *\t\t var viewer = Akamai.Viewer( element );\n\t *\t\t ...\n\t *\t\t // example API use, stoping autoration of first {@link Akamai.Spin360}\n\t *\t\t viewer.getCarousels()[0].getSpin360s()[0].stopSpin();\n\t *\t })\n\t * })\n\t *\n\t * @namespace Akamai\n\t */\n\texports.Akamai = exports.Akamai || {};\n\twindow.componentNamespace = \"Akamai\";\n})(typeof exports === 'undefined'? window : exports);\n\n(function( window, $ ) {\n var $window, $doc;\n\n $window = $(window);\n $doc = $( document.documentElement );\n\n var ns = window.componentNamespace = window.componentNamespace || \"FG\";\n window[ns] = window[ns] || {};\n\n Function.prototype.bind = Function.prototype.bind || function( context ) {\n var self = this;\n\n return function() {\n self.apply( context, arguments );\n };\n };\n\n var Tau = window[ns].Tau = function( element, options ) {\n var startIndex, reducedStepSize;\n\n this.element = element;\n this.options = options || {};\n this.$element = $( element );\n this.$initial = this.$element.find( \"img\" );\n this.$loading = this.$element.find( \".loading\" );\n this.index = 0;\n\n // frame count by order of precendence\n // 1. initial frames when they are specified explicitly\n // 2. the data attribute on the initial image\n // 3. the configured number of frames\n this.frames =\n this.$initial.length > 1 ? this.$initial.length :\n parseInt( this.$initial.attr(\"data-frames\"), 10 ) ||\n this.options.frames;\n\n // grab the user specified step size for when the browser is less-abled\n reducedStepSize = parseInt( this.$initial.attr(\"data-reduced-step-size\"), 10 ) || 4;\n\n // TODO sort out a better qualification for the full set of images?\n this.stepSize = window.requestAnimationFrame ? 1 : reducedStepSize;\n\n // grab the user specified auto start delay\n this.autoRotateStartDelay =\n (this.options.autoplay || {}).delay ||\n parseInt( this.$initial.attr(\"data-auto-rotate-delay\"), 10 ) ||\n Tau.autoRotateStartDelay;\n\n this.mouseMoveBinding = this.rotateEvent.bind(this);\n this.touchMoveBinding = this.rotateEvent.bind(this);\n\n this.path = new Tau.Path();\n\n // make sure the initial image stays visible after enhance\n this.$initial.first().addClass( \"focused\" );\n\n // hide all other images\n this.$element.addClass( \"tau-enhanced\" );\n\n // create a rendering spot to force decoding in IE and prevent blinking\n this.$render = $( \"
\" )\n .css( \"position\", \"absolute\" )\n .css( \"left\", \"0\" )\n .css( \"top\", \"0\" )\n .prependTo( this.element );\n\n if( this.options.canvas !== false ){\n this.canvas = $( \"\").prependTo( this.element )[0];\n\n if(this.canvas.getContext ){\n this.canvasCtx = this.canvas.getContext(\"2d\");\n this.$element.addClass( \"tau-canvas\" );\n\n $(window).bind(\"resize\", function(){\n clearTimeout(this.canvasResizeTimeout);\n this.canvasResizeTimeout = setTimeout(this.renderCanvas.bind(this), 100);\n }.bind(this));\n }\n }\n\n if( this.options.controls ){\n this.options.controls.text = this.options.controls.text || {\n play: \"Spin Object\",\n left: \"Rotate Left\",\n right: \"Rotate Right\"\n };\n\n this.createControls();\n }\n\n // create the rest of the images\n this.createImages();\n\n // set the initial index and image\n if( this.options.autoplay && this.options.autoplay.enabled ){\n // start the automatic rotation\n this.autostartTimeout = setTimeout(this.autoRotate.bind(this), this.autoRotateStartDelay);\n }\n\n // setup the event bindings for touch drag and mouse drag rotation\n this.bind();\n };\n\n Tau.autoRotateTraversalTime = 4500;\n Tau.autoRotateStartDelay = 100;\n Tau.verticalScrollRatio = 4;\n // Tau.decelTimeStep = Tau.autoRotateDelay / 2;\n // Tau.decel = Tau.decelTimeStep / 8;\n Tau.maxVelocity = 60;\n\n Tau.prototype.createControls = function(){\n this.$controls = $(\"
\");\n\n if(this.options.controls.play){\n this.$controls.append(this.controlAnchorMarkup(\"play\"));\n }\n\n if(this.options.controls.arrows){\n this.$controls\n .prepend(this.controlAnchorMarkup(\"left\"))\n .append(this.controlAnchorMarkup(\"right\"));\n }\n\n this.$controls.bind(\"mousedown touchstart\", this.onControlDown.bind(this));\n this.$controls.bind(\"mouseup\", this.onControlUp.bind(this));\n\n // prevent link clicks from bubbling\n this.$controls.bind(\"click\", function(event){\n if( $(event.target).is(\"a\") ){\n event.preventDefault();\n }\n });\n\n this.$element.append(this.$controls);\n };\n\n Tau.prototype.controlAnchorMarkup = function(name){\n var text = this.options.controls.text[name];\n\n return \"\" + text +\n \"\";\n };\n\n Tau.prototype.onControlDown = function(event){\n var $link = $(event.target).closest(\"a\");\n\n switch($link.attr(\"data-tau-controls\")){\n case \"left\":\n this.$element.addClass(\"control-left-down\");\n this.stopAutoRotate();\n this.autoRotate();\n break;\n case \"right\":\n this.$element.addClass(\"control-right-down\");\n this.stopAutoRotate();\n this.autoRotate(true);\n break;\n }\n };\n\n Tau.prototype.onControlUp = function(event){\n var $link = $(event.target).closest(\"a\");\n\n switch($link.attr(\"data-tau-controls\")){\n case \"left\":\n case \"right\":\n this.$element.removeClass(\"control-left-down\");\n this.$element.removeClass(\"control-right-down\");\n this.stopAutoRotate();\n break;\n case \"play\":\n if( this.autoInterval ){\n this.stopAutoRotate();\n } else {\n this.autoRotate();\n }\n break;\n }\n };\n\n Tau.prototype.change = function( delta ) {\n this.goto( this.options.reverse ? this.index - delta : this.index + delta );\n };\n\n Tau.prototype.goto = function( index ) {\n var $next, normalizedIndex, imageCount = this.$images.length;\n\n index = index % imageCount;\n\n // stay within the bounds of the array\n normalizedIndex = (imageCount + index) % imageCount;\n\n // set the next image that's going to be shown/focused\n $next = this.$images.eq( normalizedIndex );\n\n // skip this action if the desired image isn't loaded yet\n // TODO do something fancier here instead of just throwing up hands\n if( !$next[0].tauImageLoaded ) {\n this.showLoading();\n return false;\n }\n\n // hide any image that happens to be visible (initial image when canvas)\n if( this.$current ) {\n this.$current.removeClass( \"focused\" );\n } else {\n this.$images.removeClass( \"focused\" );\n }\n\n // record the current focused image and make it visible\n this.$current = $next;\n\n // record the updated index only after advancing is possible\n this.index = normalizedIndex;\n\n if( this.canvasCtx ) {\n return this.renderCanvas();\n } else {\n // show the new focused image\n this.$current.addClass( \"focused\" );\n return true;\n }\n };\n\n Tau.prototype.renderCanvas = function() {\n var $img = this.$current;\n var img = $img[0];\n\n var width = img.naturalWidth;\n var height = img.naturalHeight;\n\n var parentWidth = this.element.clientWidth;\n var calcHeight = (parentWidth/width) * height;\n\n if(!width || !height || !img.complete){\n return false;\n }\n\n if( this.canvas.width !== parentWidth ||\n this.canvas.height !== calcHeight || (parentWidth && calcHeight) ) {\n this.canvas.width = parentWidth;\n this.canvas.height = calcHeight;\n }\n\n this.canvasCtx.drawImage(img, 0, 0, parentWidth, calcHeight);\n\n return true;\n };\n\n // TODO transplant the attributes from the initial image\n Tau.prototype.createImages = function() {\n var src, frames, html, $new, boundImageLoaded;\n\n // if there are no image elements, raise an exception\n if( this.$initial.length < 1 ){\n throw new Error( \"At least one image required\" );\n }\n\n this.loadedCount = 0;\n\n // if there is only one image element, assume it's a template\n if( this.$initial.length == 1 ) {\n this.markImageLoaded( this.$initial[0] );\n\n src =\n this.options.template ||\n this.$initial.attr( \"data-src-template\" );\n\n var imgs = [];\n for( var i = this.stepSize + 1; i <= this.frames; i+= this.stepSize ) {\n html = \"\";\n $new = $( html );\n imgs.push($new);\n }\n\n $.each(imgs, function(i, e){\n var $img = $(e);\n\n $img.bind(\"load error\", function(e){ this.imageLoaded(i, e.target, e); }.bind(this));\n\n this.$element.append( $img );\n this.$render.append( $img.html() );\n }.bind(this));\n\n // take all the child images and use them as frames of the rotation\n this.$images = this.$element.children().filter( \"img\" );\n this.$current = this.$images;\n this.goto(0);\n } else {\n // take all the child images and use them as frames of the rotation\n this.$images = this.$element.children().filter( \"img\" );\n\n this.$images.each(function(i, e){\n // if the image height is greater than zero we assume the image is loaded\n // otherwise we bind to onload and pray that we win the race\n if( $(e).height() > 0 ){\n this.imageLoaded( i, e );\n } else {\n $(e).bind(\"load error\", function(event){\n this.imageLoaded( i, event.target, event );\n }.bind(this));\n }\n }.bind(this));\n }\n };\n\n\n Tau.prototype.imageLoaded = function( index, element, event ) {\n var initTriggered = false;\n this.markImageLoaded( element );\n\n // if the isn't going to play automatically and the first image is\n // loaded make sure to render it\n if( this.$element.find(\"img\")[0] == element &&\n (!event || event.type !== \"error\") &&\n (!this.options.autoplay || !this.options.autoplay.enabled) ){\n this.goto(0);\n this.$element.trigger(\"tau.init\");\n initTriggered = true;\n }\n\n this.loadedCount++;\n\n if( this.loadedCount >= this.frames - 1) {\n this.hideLoading();\n\n if(!initTriggered) {\n this.$element.trigger(\"tau.init\");\n this.initialized = true;\n }\n }\n };\n\n Tau.prototype.markImageLoaded = function( element ) {\n element.tauImageLoaded = true;\n };\n\n Tau.prototype.bind = function() {\n this.$element.bind( \"mousedown touchstart\", this.track.bind(this) );\n };\n\n Tau.prototype.autoRotate = function( right ) {\n // already rotating\n if( this.autoInterval ) {\n return;\n }\n\n this.$element.addClass(\"spinning\");\n\n // move once initially\n this.change( right ? -1 : 1 );\n\n // move after the interval\n this.autoInterval = setInterval(function() {\n this.change( right ? -1 : 1 );\n }.bind(this), this.autoRotateDelay() * this.stepSize);\n\n this.$element.trigger( \"tau.auto-rotate-start\" );\n };\n\n Tau.prototype.autoRotateDelay = function(){\n return (this.options.interval || Tau.autoRotateTraversalTime) / this.frames;\n };\n\n Tau.prototype.stopAutoRotate = function() {\n clearInterval( this.autoInterval );\n clearInterval( this.autostartTimeout );\n this.$element.removeClass(\"spinning\");\n this.autoInterval = undefined;\n this.$element.trigger( \"tau.auto-rotate-stop\" );\n };\n\n Tau.prototype.track = function( event ) {\n var point;\n\n // ignore tracking on control clicks\n if( $(event.target).closest(\".tau-controls\").length ){\n return;\n }\n\n // prevent dragging behavior for mousedown\n if( event.type === \"mousedown\" ){\n event.preventDefault();\n }\n\n if( event.type === \"touchstart\" ) {\n this.$element.trigger(\"tau.touch-tracking-start\");\n } else {\n this.$element.trigger(\"tau.mouse-tracking-start\");\n }\n\n if( this.tracking ) {\n return;\n }\n\n $doc.one( \"mouseup\", this.release.bind(this) );\n $doc.one( \"touchend\", this.release.bind(this) );\n\n this.tracking = true;\n\n // clean out the path since we'll need a new one for decel\n this.path.reset();\n\n // show the cursor as grabbing\n this.cursorGrab();\n\n // By default the number of pixels required to move the carousel by one\n // frame is the ratio of the tau element width to the number of frames. That\n // is, by default the user should be able to see the full rotation by moving\n // their input device from one side of the tau element to the other.\n var defaultThreshold = this.$element[0].clientWidth / this.frames ;\n\n // divide the default by the sensitivity. If the senstivity is greater than\n // 1 it will require less effort (smaller distance) to advance the rotation\n // by a single slide. If the sensitivity is less than 1 it will require more\n // effort\n this.rotateThreshold = defaultThreshold / (this.options.sensitivity || 1);\n\n // record the x for threshold calculations\n point = this.getPoint( event );\n this.downX = point.x;\n this.downY = point.y;\n this.downIndex = this.index;\n\n $doc.bind( \"mousemove\", this.mouseMoveBinding );\n $doc.bind( \"touchmove\", this.touchMoveBinding );\n };\n\n Tau.prototype.slow = function() {\n // if the path gets broken during the decel just stop\n if( !this.path.isSufficient() ) {\n this.clearSlowInterval();\n return;\n }\n\n this.rotate({\n x: this.path.last().x + this.velocity,\n y: this.path.last().y\n });\n\n if( this.velocity > 0 ){\n this.velocity = this.velocity - this.decelVal();\n\n if( this.velocity <= 0 ){\n this.clearSlowInterval();\n }\n } else {\n this.velocity = this.velocity + this.decelVal();\n\n if( this.velocity >= 0 ){\n this.clearSlowInterval();\n }\n }\n };\n\n Tau.prototype.decelVal = function(){\n return this.decelTimeStep() / 8;\n };\n\n Tau.prototype.clearSlowInterval = function() {\n clearInterval(this.slowInterval);\n this.velocity = 0;\n this.slowInterval = undefined;\n };\n\n Tau.prototype.decel = function() {\n var velocity, sign;\n\n // if we don't have two points of mouse or touch tracking this won't work\n if( !this.path.isSufficient() ) {\n return;\n }\n\n // determine the starting velocity based on the traced path\n velocity = this.path.velocity( this.decelTimeStep() );\n\n // borrowed from http://stackoverflow.com/questions/7624920/number-sign-in-javascript\n sign = velocity > 0 ? 1 : velocity < 0 ? -1 : 0;\n\n // keep a lid on how fast the rotation spins out\n if( Math.abs(velocity) > Tau.maxVelocity ){\n velocity = sign * Tau.maxVelocity;\n }\n\n this.velocity = velocity;\n this.slowInterval = setInterval(this.slow.bind(this), this.decelTimeStep());\n };\n\n Tau.prototype.decelTimeStep = function(){\n return this.autoRotateDelay() / 2;\n };\n\n Tau.prototype.release = function( event ) {\n if( $(event.target).closest(\".tau-controls\").length ){\n return;\n }\n\n if( !this.tracking ){\n return;\n }\n\n if( event.type === \"touchend\" ) {\n this.$element.trigger(\"tau.touch-tracking-stop\");\n } else {\n this.$element.trigger(\"tau.mouse-tracking-stop\");\n }\n\n this.decel();\n\n this.cursorRelease();\n\n // TODO sort out why shoestring borks when unbinding with a string split list\n $doc.unbind( \"mousemove\", this.mouseMoveBinding );\n $doc.unbind( \"touchmove\", this.touchMoveBinding );\n\n this.tracking = false;\n };\n\n Tau.prototype.cursorGrab = function() {\n $doc.addClass( \"grabbing\" );\n };\n\n Tau.prototype.cursorRelease = function() {\n $doc.removeClass( \"grabbing\" );\n };\n\n Tau.prototype.showLoading = function() {\n this.$loading.attr( \"style\" , \"display: block\" );\n };\n\n Tau.prototype.hideLoading = function() {\n this.$loading.attr( \"style\" , \"display: none\" );\n };\n\n Tau.prototype.getPoint = function( event ) {\n var touch = event.touches || (event.originalEvent && event.originalEvent.touches);\n\n if( touch ){\n return {\n x: touch[0].pageX,\n y: touch[0].pageY\n };\n }\n\n return {\n x: event.pageX || event.clientX,\n y: event.pageY || event.clientY\n };\n };\n\n Tau.prototype.rotateEvent = function( event ) {\n // NOTE it might be better to prevent when the rotation returns anything BUT false\n // so that slow drags still get the scroll prevented\n if( this.rotate(this.getPoint(event)) ){\n event.preventDefault();\n }\n };\n\n Tau.prototype.rotate = function( point ) {\n var deltaX, deltaY;\n\n deltaX = point.x - this.downX;\n deltaY = point.y - this.downY;\n\n // if the movement on the Y dominates X then skip and allow scroll\n if( Math.abs(deltaY) / Math.abs(deltaX) >= Tau.verticalScrollRatio ) {\n return false;\n }\n\n // since we're rotating record the point for decel\n this.path.record( point );\n\n // NOTE to reverse the spin direction add the delta/thresh to the downIndex\n if( Math.abs(deltaX) >= this.rotateThreshold ) {\n // NOTE works better on mousedown, here allows autorotate to continue\n this.stopAutoRotate();\n\n var index;\n\n if( this.options.reverse ) {\n index = this.downIndex + Math.round(deltaX / this.rotateThreshold);\n } else {\n index = this.downIndex - Math.round(deltaX / this.rotateThreshold);\n }\n\n this.goto( index );\n\n return true;\n }\n };\n})(this, jQuery);\n\n(function( window, $ ) {\n var ns = window.componentNamespace = window.componentNamespace || \"FG\";\n window[ns] = window[ns] || {};\n\n // IE 8\n Date.now = Date.now || function now() {\n return new Date().getTime();\n };\n\n var Path = window[ns].Tau.Path = function() {\n this.reset();\n };\n\n Path.prototype.isSufficient = function() {\n return !!this.prevPoint && this.prevPrevPoint;\n };\n\n Path.prototype.distance = function() {\n return this.prevPoint.x - this.prevPrevPoint.x;\n };\n\n Path.prototype.duration = function() {\n return this.prevTime - this.prevPrevTime;\n };\n\n // TODO sort out variable names\n Path.prototype.record = function( point ) {\n this.prevPrevTime = this.prevTime;\n this.prevPrevPoint = this.prevPoint;\n\n // record the most recent drag point for decel on release\n this.prevTime = Date.now();\n this.prevPoint = point;\n };\n\n Path.prototype.velocity = function( timeStep ) {\n var distance, time;\n\n distance = this.distance();\n time = this.duration();\n\n return distance / ( time / timeStep );\n };\n\n Path.prototype.reset = function() {\n this.prevPoint = undefined;\n this.prevTime = undefined;\n\n this.prevPrevTime = undefined;\n this.prevPrevPoint = undefined;\n };\n\n Path.prototype.last = function() {\n return this.prevPoint;\n };\n})(this, jQuery);\n\n(function(exports, $){\n\texports.Akamai = exports.Akamai || {};\n\tvar Util = exports.Akamai.Util = {};\n\n\tUtil.preflight = function(context, element, options, name){\n\t\tname = name || context.constructor.name;\n\n\t\tcontext._$el = $(element);\n\n\t\tif( !context._$el.length ){\n\t\t\tthrow new Error( name + \" component requires element\" );\n\t\t}\n\n\t\t// If the element has already been instantiated with this component, skip\n\t\tif( context._$el.data(name) ) {\n\t\t\treturn context._$el.data(name);\n\t\t}\n\n\t\t// Store the instance for access and to prevent double init above\n\t\tcontext._$el.data( name, context );\n\n\t\t// make sure the element has the data attribute for CSS that keys off it\n\t\tcontext._$el.attr(context.constructor._dataAttr, true);\n\n\t\tcontext._options =\n\t\t\tUtil.options(context.constructor.defaultOptions || {}, options || {}, context._$el, name);\n\n\t\t// retain the options decided at instantiation for reseting the options\n\t\t// when the breakpoint options don't apply (are \"backed out\")\n\t\tcontext._originalOptions = Util.extend(true, {}, context._options);\n\n\t\t// map all the child component events to the wrapper component events\n\t\tUtil.mapEvents(context);\n\n\t\treturn false;\n\t};\n\n\tUtil.component = function(name, async, init){\n\t\tif( !init ){\n\t\t\tinit = async;\n\t\t\tasync = false;\n\t\t}\n\n\t\tvar constr = function(element, options){\n\t\t\t// do the preflight\n\t\t\tvar existing = Util.preflight(this, element, options, name);\n\n\t\t\t// return on double init\n\t\t\tif( existing ){\n\t\t\t\tthis._trigger(\"init\");\n\t\t\t\treturn existing;\n\t\t\t}\n\n\t\t\t// call the init code\n\t\t\tinit.apply(this, arguments);\n\n\t\t\t// trigger the init event\n\t\t\tif( !async ) { this._trigger(\"init\"); }\n\t\t};\n\n\t\tconstr.name = name;\n\n\t\tconstr._dataAttr = `data-akamai-${name.toLowerCase()}`;\n\n\t\t// static constructor for many possible objects\n\t\tconstr.createMany = Util.createMany(constr);\n\n\t\t// component prefixed event trigger\n\t\tconstr.prototype._trigger = Util.trigger(name);\n\n\t\treturn constr;\n\t};\n\n\tUtil.mapEvents = function(context){\n\t\tvar mapping = context.constructor._componentEventMapping || {};\n\n\t\tfor( var event in mapping) {\n\t\t\tif( mapping.hasOwnProperty(event) ){\n\t\t\t\tvar mappedTo = mapping[event].to || mapping[event];\n\t\t\t\tvar $el =\n\t\t\t\t\tmapping[event].selector ? context._$el.find(mapping[event].selector) : undefined;\n\n\t\t\t\tUtil.mapEvent(context, event, mappedTo, $el);\n\t\t\t}\n\t\t}\n\t};\n\n\tUtil.mapEvent = function(context, from, to, $el){\n\t\t($el || context._$el).bind(from, function(){\n\t\t\tcontext._trigger(to);\n\t\t}.bind(context));\n\t};\n\n\tUtil.createMany = function(constructor, selector){\n\t\tselector = selector || `[${constructor._dataAttr}]`;\n\n\t\treturn function(element, options){\n\t\t\tvar $comps = $(element).find( selector );\n\n\t\t\t// sigh, shoestring needs map\n\t\t\tvar objects = [];\n\n\t\t\t$comps.each(function(i, el){\n\t\t\t\tobjects.push(new constructor(el, options));\n\t\t\t});\n\n\t\t\treturn objects;\n\t\t};\n\t};\n\n\t/**\n\t * Helper for building assertions into methods based on property requirements\n\t * @private\n\t * @static\n\t * @param {prop} prop - the property required for the function to operate correctly\n\t * @param {fn} function - the function definition\n\t */\n\tUtil.propRequired = function(prop, msg, fn){\n\t\treturn function(){\n\t\t\tvar args = arguments;\n\n\t\t\tif( !this[prop] ){\n\t\t\t\tthrow new Error( msg );\n\t\t\t}\n\n\t\t\treturn fn.apply(this, args);\n\t\t};\n\t};\n\n\tUtil.trigger = function(componentName){\n\t\tcomponentName = componentName.toLowerCase();\n\t\treturn function(event) {\n\t\t\tvar fullEvent = \"akamai-\" + componentName + \"-\" + event;\n\n\t\t\t// NOTE we assume here that if jQuery is present we will only want to trigger\n\t\t\t// with jQuery and that we are using the same DOM lib across the library\n\t\t\tthis._$el.trigger(fullEvent);\n\t\t};\n\t};\n\n\n\tUtil.camelToKabob = function(string){\n\t\treturn string.replace(/([A-Z]+)/g, function(m){\n\t\t\treturn \"-\" + m.toLowerCase();\n\t\t});\n\t};\n\n\tUtil.options = function(defaults, options, $element, name){\n\t\tvar dataAttrOptions = {};\n\t\t// make sure to make a new copy of the defaults so we don't mess with the original\n\t\tvar cloneDefaults = Util.extend(true, {}, defaults);\n\n\t\tif( $element && name ) {\n\t\t\tdataAttrOptions =\n\t\t\t\tUtil.getDataAttrOptions($element, defaults, \"data-akamai-\" + name.toLowerCase());\n\t\t}\n\n\t\t// use the following precedence, prefering settings further to the right\n\t\t// defaults -> options -> datattributes\n\t\tvar finalOptions =\n\t\t\t\tUtil.extend(true, Util.extend(true, cloneDefaults, dataAttrOptions), options);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tif( $element && name ){\n\t\t\tUtil.setDataAttrOptions($element, defaults, finalOptions, name);\n\t\t}\n\n\t\treturn finalOptions;\n\t};\n\n\tUtil.getDataAttrOptions = function($element, defaults, prefix){\n\t\tvar dataAttrOptions = {};\n\n\t\tfor( var prop in defaults ) {\n\t\t\tif( defaults.hasOwnProperty(prop) ){\n\t\t\t\tvar dashedProp = Util.camelToKabob(prop);\n\t\t\t\tvar currentDataProp = prefix + \"-\" + dashedProp;\n\n\t\t\t\tif( /object/.test(typeof defaults[prop]) && !(defaults[prop] instanceof Array)){\n\t\t\t\t\tvar subDefaults = defaults[prop];\n\t\t\t\t\tvar newPrefix = currentDataProp;\n\n\t\t\t\t\tvar subConfig = Util.getDataAttrOptions($element, subDefaults, newPrefix);\n\t\t\t\t\tdataAttrOptions[prop] = subConfig;\n\t\t\t\t} else {\n\t\t\t\t\tvar value = $element.attr(currentDataProp);\n\t\t\t\t\tvalue = Util.coerceAttrVal(value);\n\n\t\t\t\t\tif( value !== undefined && value !== null ){\n\t\t\t\t\t\t// set the config\n\t\t\t\t\t\tdataAttrOptions[prop] = value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn dataAttrOptions;\n\t};\n\n\tUtil.setDataAttrOptions = function($element, defaults, options, name){\n\t\tUtil.setDataAttrOptionsPrefix($element, defaults, options, \"data-akamai-\" + name.toLowerCase());\n\t};\n\n\n\tUtil.setDataAttrOptionsPrefix = function($element, defaults, options, prefix){\n\t\tdefaults = defaults || {};\n\n\t\tfor( var prop in options ) {\n\t\t\tif( options.hasOwnProperty(prop) && prop !== \"breakpoints\" ){\n\t\t\t\tvar dashedProp = Util.camelToKabob(prop);\n\t\t\t\tvar currentDataProp = prefix + \"-\" + dashedProp;\n\n\t\t\t\tif( /object/.test(typeof options[prop]) ){\n\t\t\t\t\tvar subOptions = options[prop];\n\t\t\t\t\tvar newPrefix = currentDataProp;\n\n\t\t\t\t\tUtil.setDataAttrOptionsPrefix($element, defaults[prop], subOptions, newPrefix);\n\t\t\t\t} else {\n\n\t\t\t\t\t// if the $element had the attribute already or the property exists\n\t\t\t\t\t// as one of the defaults then we can write it to the element.\n\t\t\t\t\tif( $element.attr(currentDataProp) || defaults[prop] !== undefined ) {\n\t\t\t\t\t\t$element.attr(currentDataProp, options[prop]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tUtil.coerceAttrVal = function(value){\n\t\tif( value === \"true\" ) { return true; }\n\t\tif( value === \"false\" ) { return false; }\n\t\tif( /^[0-9]+$/.test(value) ) { return parseInt(value, 10); }\n\t\tif( /^[0-9]+.[0-9]+$/.test(value) ) { return parseFloat(value, 10); }\n\n\t\treturn value;\n\t};\n\n\t// Due to jQuery, but with less argument handling\n\tUtil.extend = function( deep, target, options ) {\n\t\tvar name, src, copy, copyIsArray, clone;\n\n\t\t// Only deal with non-null/undefined values\n\t\tif ( options\t!= null ) {\n\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && Util.isPlainObject( copy ) ) {\n\n\t\t\t\t\tclone = src && Util.isPlainObject( src ) ? src : {};\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = Util.extend( deep, clone, copy );\n\n\t\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Return the modified object\n\t\treturn target;\n\t};\n\n\t// Due to jQuery\n\tUtil.isPlainObject = function( obj ) {\n\t\tvar proto, Ctor;\n\n\t\t// taken from https://github.com/jupiter/simple-mock/pull/3/files\n\t\tif ( !obj || Object.prototype.toString.call(obj) !== \"[object Object]\" ) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t};\n\n\t// Due to jQuery\n\tvar rtrim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n\tUtil.trim = function( text ) {\n\t\treturn text == null ?\n\t\t\t\"\" :\n\t\t\t( text + \"\" ).replace( rtrim, \"\" );\n\t};\n\n\tUtil.map = function(array, fn){\n\t\treturn Util.reduce(array, function(acc, val, i){\n\t\t\tacc.push(fn(val, i));\n\t\t\treturn acc;\n\t\t}, []);\n\t};\n\n\tUtil.reduce = function(array, fn, acc){\n\t\tfor(var i = 0; i < array.length; i++){\n\t\t\tacc = fn(acc, array[i], i);\n\t\t}\n\n\t\treturn acc;\n\t};\n\n\tUtil.log = function(msg, type){\n\t\ttype = type || \"log\";\n\n\t\tif( console ){\n\t\t\tconsole[type](msg);\n\t\t}\n\t};\n\n\tUtil.mapJSONVals = function(obj, callback, key){\n\t\tvar result = {};\n\n\t\tif(obj.map){\n\t\t\treturn obj.map(function(o){\n\t\t\t\treturn Util.mapJSONVals(o, callback);\n\t\t\t});\n\t\t}\n\n\t\tif([\"string\", \"number\", \"boolean\"].indexOf( typeof obj ) >= 0 ){\n\t\t\treturn callback(obj, key);\n\t\t}\n\n\t\tfor( var prop in obj ){\n\t\t\tif(obj.hasOwnProperty(prop)){\n\t\t\t\tresult[prop] = Util.mapJSONVals(obj[prop], callback, prop);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tUtil.escapeJSONVals = function(obj){\n\t\treturn Util.mapJSONVals(obj, function(val){\n\t\t\tif(typeof val === \"string\" ) {\n\t\t\t\treturn escapeHTML(val);\n\t\t\t}\n\n\t\t\treturn val;\n\t\t});\n\t};\n\n\t// https://github.com/janl/mustache.js/blob/23beb3a8805c9a857e3ea777431481599fab503e/mustache.js#L60\n\tvar entityMap = {\n\t\t'&': '&',\n\t\t'<': '<',\n\t\t'>': '>',\n\t\t'\"': '"',\n\t\t\"'\": ''',\n\t\t'/': '/',\n\t\t'`': '`',\n\t\t'=': '='\n\t};\n\n\tfunction escapeHTML(string) {\n\t\treturn String(string).replace(/[&<>\"'`=\\/]/g, function fromEntityMap (s) {\n\t\t\treturn entityMap[s];\n\t\t});\n\t}\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\tvar Advanceable = {\n\t\textension: {\n\t\t\tnext: function(callback){\n\t\t\t\tthis.goto(this.getIndex() + 1, function(){\n\t\t\t\t\tif( callback ) { callback(); };\n\t\t\t\t\tthis._trigger(\"next\");\n\t\t\t\t}.bind(this));\n\t\t\t},\n\n\t\t\tprevious: function(callback){\n\t\t\t\tthis.goto(this.getIndex() - 1, function(){\n\t\t\t\t\tif( callback ) { callback(); };\n\t\t\t\t\tthis._trigger(\"previous\");\n\t\t\t\t}.bind(this));\n\t\t\t}\n\t\t},\n\n\t\textend: function(constr) {\n\t\t\tif(!constr.prototype.goto || !constr.prototype.getIndex) {\n\t\t\t\tthrow new Error(\"Advanceable mixin requires `goto` and `getIndex` methods\");\n\t\t\t}\n\n\t\t\t$.extend(constr.prototype, this.extension);\n\t\t}\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Advanceable = Advanceable;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n\n// TODO this should be handled in an image component\n(function(exports, $){\n\tvar Sourceable = {\n\t\textension: {\n\t\t\t_srcArray: function(url, options, includeW ){\n\t\t\t\tvar policy = options.policy || options.thumbnail && options.thumbnail.policy;\n\t\t\t\tvar widthParam = options.widthParam;\n\n\t\t\t\treturn options.widths.map(function(w){\n\t\t\t\t\tvar src = url + (url.indexOf(\"?\") === -1 ? \"?\" : \"&\") + widthParam + \"=\" + w;\n\n\t\t\t\t\tif( policy ){\n\t\t\t\t\t\tsrc += \"&impolicy=\" + policy;\n\t\t\t\t\t}\n\n\t\t\t\t\t//note src width for srcset (eg ' 500w')\n\t\t\t\t\tif( includeW ){\n\t\t\t\t\t\tsrc += \" \" + w + \"w\";\n\t\t\t\t\t}\n\n\t\t\t\t\treturn src;\n\t\t\t\t});\n\t\t\t},\n\n\t\t\t_srcset: function(url, options){\n\t\t\t\treturn this._srcArray( url, options, true ).join(\", \");\n\t\t\t},\n\n\t\t\t_fallbackSrc: function(url, options){\n\t\t\t\treturn this._srcArray( url, options )[ 0 ];\n\t\t\t},\n\n\t\t\t_largestSrc: function(url, options){\n\t\t\t\treturn this._srcArray( url, options ).pop();\n\t\t\t}\n\t\t},\n\n\t\textendStatic: function(constr) {\n\t\t\tAkamai.Util.extend(false, constr, this.extension);\n\t\t}\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Sourceable = Sourceable;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n\n(function( w, undefined ){\n\n\t// requestAnimationFrame pfill\n\tvar raf = (function(){\n\t\treturn window.requestAnimationFrame ||\n\t\t\twindow.webkitRequestAnimationFrame ||\n\t\t\twindow.mozRequestAnimationFrame ||\n\t\t\tfunction( callback ){\n\t\t\t\tw.setTimeout(callback, 1000 / 60);\n\t\t\t};\n\t})();\n\n\n\n\n\t/* toss scrolls and element with easing\n\n\t// elem is the element to scroll\n\t// options hash:\n\t\t* left is the desired horizontal scroll. Default is \"+0\". For relative distances, pass a string with \"+\" or \"-\" in front.\n\t\t* top is the desired vertical scroll. Default is \"+0\". For relative distances, pass a string with \"+\" or \"-\" in front.\n\t\t* duration is the number of milliseconds the throw will take. Default is 100.\n\t\t* easing is an optional custom easing function. Default is w.toss.easing. Must follow the easing function signature\n\n\t*/\n\tw.toss = function( elem, options ){\n\t\ttoss.tossing( elem, false );\n\t\tvar i = 0,\n\t\t\tsLeft = elem.scrollLeft,\n\t\t\tsTop = elem.scrollTop,\n\t\t\t// Toss defaults\n\t\t\top = {\n\t\t\t\ttop: \"+0\",\n\t\t\t\tleft: \"+0\",\n\t\t\t\tduration: 200,\n\t\t\t\teasing: toss.easing,\n\t\t\t\tfinished: function() {}\n\t\t\t},\n\t\t\tendLeft, endTop;\n\n\t\t// Mixin based on predefined defaults\n\t\tif( options ){\n\t\t\tfor( var j in op ){\n\t\t\t\tif( options[ j ] !== undefined ){\n\t\t\t\t\top[ j ] = options[ j ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Convert relative values to ints\n\t\t// First the left val\n\t\tif( typeof op.left === \"string\" ){\n\t\t\top.left = parseFloat( op.left );\n\t\t\tendLeft = op.left + sLeft;\n\t\t}\n\t\telse {\n\t\t\tendLeft = op.left;\n\t\t\top.left = op.left - sLeft;\n\t\t}\n\t\t// Then the top val\n\t\tif( typeof op.top === \"string\" ){\n\n\t\t\top.top = parseFloat( op.top );\n\t\t\tendTop = op.top + sTop;\n\t\t}\n\t\telse {\n\t\t\tendTop = op.top;\n\t\t\top.top = op.top - sTop;\n\t\t}\n\n\t\ttoss.tossing( elem, true );\n\t\tvar startTime = new Date().getTime();\n\t\tvar endTime = startTime + op.duration;\n\t\tvar run = function(){\n\t\t\tvar curTime = new Date().getTime();\n\t\t\t// if tossing is suddenly not true, return the callback\n\t\t\tif( !toss.tossing( elem ) ){\n\t\t\t\tif( op.finished ){\n\t\t\t\t\top.finished();\n\t\t\t\t}\n\t\t\t}\n\t\t\t// if the time is still less than the end of duration, keep scrolling\n\t\t\telse if( curTime < endTime ){\n\t\t\t\ti = ( ( curTime - startTime ) / op.duration ) * op.duration;\n\t\t\t\telem.scrollLeft = op.easing( i, sLeft, op.left, op.duration );\n\t\t\t\telem.scrollTop = op.easing( i, sTop, op.top, op.duration );\n\t\t\t\treturn raf( run );\n\t\t\t}\n\t\t\t// if time is up,\n\t\t\telse{\n\t\t\t\telem.scrollLeft = endLeft;\n\t\t\t\telem.scrollTop = endTop;\n\t\t\t\tif( op.finished ){\n\t\t\t\t\top.finished();\n\t\t\t\t}\n\t\t\t\ttoss.tossing( elem, false );\n\t\t\t}\n\t\t};\n\n\t\traf( run );\n\n\t\t// Return the values, post-mixin, with end values specified\n\t\treturn { top: endTop, left: endLeft, duration: op.duration, easing: op.easing };\n\t};\n\n // tossing object keeps track of currently tossing elements. true during a programatic scroll\n var tossingElems = {};\n toss.tossing = function( elem, state ){\n if( state !== undefined ){\n tossingElems[ elem ] = state;\n }\n return tossingElems[ elem ];\n };\n\n // Easing can use any of Robert Penner's equations (http://www.robertpenner.com/easing_terms_of_use.html). By default, toss includes ease-out-cubic\n // arguments: t = current iteration, b = initial value, c = end value, d = total iterations\n // use w.toss.easing to provide a custom function externally, or pass an easing function as a callback to the toss method\n toss.easing = function (t, b, c, d) {\n return c*((t=t/d-1)*t*t + 1) + b;\n };\n\n //retain old api\n toss.toss = toss;\n\n})( this );\n\n;(function( w, $ ){\n\tvar pluginName = \"snapper\";\n\t$.fn[ pluginName ] = function(optionsOrMethod){\n\t\tvar pluginArgs = arguments;\n\t\tvar scrollListening = true;\n\n\t\t// css snap points feature test.\n\t\t// even if this test passes, several behaviors will still be polyfilled, such as snapping after resize, and animated advancing of slides with anchor links or next/prev links\n\t\tvar testProp = \"scroll-snap-type\";\n\t\t// test latest spec first. then fallback older\n\t\tvar snapSupported = w.CSS && w.CSS.supports && (w.CSS.supports(testProp, \"x mandatory\") || w.CSS.supports(testProp, \"mandatory\") || w.CSS.supports(\"-webkit-\" + testProp, \"mandatory\") || w.CSS.supports(\"-ms-\" + testProp, \"mandatory\") );\n\n\t\t// get the snapper_item elements whose left offsets fall within the scroll pane. Returns a wrapped array.\n\t\tfunction itemsAtOffset( elem, offset ){\n\t\t\tvar $childNodes = $( elem ).find( \".\" + pluginName + \"_item\" );\n\t\t\tvar containWidth = $( elem ).width();\n\t\t\tvar activeItems = [];\n\n\t\t\t$childNodes.each(function( i ){\n\t\t\t\tif( this.offsetLeft >= offset - 5 && this.offsetLeft < offset + containWidth - 5 ){\n\t\t\t\t\tactiveItems.push( this );\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn $( activeItems );\n\t\t}\n\n\t\tfunction outerWidth( $elem ){\n\t\t\treturn $elem.width() + parseFloat( $elem.css( \"margin-left\" ) ) + parseFloat( $elem.css( \"margin-right\" ) );\n\t\t}\n\n\t\tfunction outerHeight( $elem ){\n\t\t\treturn $elem.height() + parseFloat( $elem.css( \"margin-bottom\" ) ) + parseFloat( $elem.css( \"margin-top\" ) );\n\t\t}\n\n\n\t\t// snapEvent dispatches the \"snapper.snap\" event.\n\t\t// The snapper_item elements with left offsets that are inside the scroll viewport are listed in an array in the second callback argument's activeSlides property.\n\t\t// use like this: $( \".snapper\" ).bind( \"snapper.snap\", function( event, data ){ console.log( data.activeSlides );\t} );\n\t\tfunction snapEvent( elem, x, prefix ){\n\t\t\tprefix = prefix ? prefix + \"-\" : \"\";\n\t\t\tvar activeSlides = itemsAtOffset( elem, x );\n\t\t\t$( elem ).trigger( pluginName + \".\" + prefix + \"snap\", { activeSlides: activeSlides } );\n\t\t}\n\n\t\t// optional: include toss() in your page to get a smooth scroll, otherwise it'll just jump to the slide\n\t\tfunction goto( elem, x, nothrow, callback ){\n\t\t\tscrollListening = false;\n\t\t\tsnapEvent( elem, x );\n\n\t\t\tvar after = function(){\n\t\t\t\telem.scrollLeft = x;\n\t\t\t\t$(elem).closest( \".\" + pluginName ).removeClass( pluginName + \"-looping\" );\n\t\t\t\t$( elem ).trigger( pluginName + \".after-goto\", {\n\t\t\t\t\tactiveSlides: itemsAtOffset( elem, x )\n\t\t\t\t});\n\n\t\t\t\tif( callback ){ callback(); };\n\t\t\t\tsnapEvent( elem, x, \"after\" );\n\t\t\t\tscrollListening = true;\n\t\t\t};\n\n\t\t\t// backport to old toss for compat\n\t\t\tif( !w.toss && w.overthrow ){\n\t\t\t\tw.toss = w.overthrow.toss;\n\t\t\t}\n\t\t\tif( typeof w.toss !== \"undefined\" && !nothrow ){\n\t\t\t\tw.toss( elem, { left: x, finished: after });\n\t\t\t}\n\t\t\telse {\n\t\t\t\telem.scrollLeft = x;\n\t\t\t\tafter();\n\t\t\t}\n\t\t}\n\n\n\t\tvar result, innerResult;\n\n\t\t// Loop through snapper elements and enhance/bind events\n\t\tresult = this.each(function(){\n\t\t\tif( innerResult !== undefined ){\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar self = this;\n\t\t\tvar $self = $( self );\n\t\t\tvar addNextPrev = $self.is( \"[data-\" + pluginName + \"-nextprev]\" );\n\t\t\tvar autoTimeout;\n\t\t\tvar $slider = $( \".\" + pluginName + \"_pane\", self );\n\t\t\tvar enhancedClass = pluginName + \"-enhanced\";\n\t\t\tvar $itemsContain = $slider.find( \".\" + pluginName + \"_items\" );\n\t\t\tvar $items = $itemsContain.children();\n\t\t\t$items.addClass( pluginName + \"_item\" );\n\t\t\tvar numItems = $items.length;\n\t\t\tvar $nav = $( \".\" + pluginName + \"_nav\", self );\n\t\t\tvar navSelectedClass = pluginName + \"_nav_item-selected\";\n\t\t\tvar useDeepLinking = $self.attr( \"data-snapper-deeplinking\" ) !== \"false\";\n\n\t\t\tif( typeof optionsOrMethod === \"string\" ){\n\t\t\t\tvar args = Array.prototype.slice.call(pluginArgs, 1);\n\t\t\t\tvar index;\n\t\t\t\tvar itemWidth = ( $itemsContain.width() / numItems );\n\n\t\t\t\tswitch(optionsOrMethod) {\n\t\t\t\tcase \"goto\":\n\t\t\t\t\tindex = args[0] % numItems;\n\n\t\t\t\t\t// width / items * index to make sure it goes\n\t\t\t\t\toffset = itemWidth * index;\n\t\t\t\t\tgoto( $slider[ 0 ], offset, false, function(){\n\t\t\t\t\t\t// snap the scroll to the right position\n\t\t\t\t\t\tsnapScroll();\n\n\t\t\t\t\t\t// invoke the callback if it was supplied\n\t\t\t\t\t\tif( typeof args[1] === \"function\" ){\n\t\t\t\t\t\t\targs[1]();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"getIndex\":\n\t\t\t\t\t// NOTE make the scroll left value large enough to overcome\n\t\t\t\t\t// subpixel widths\n\t\t\t\t\tinnerResult = Math.floor(($slider[ 0 ].scrollLeft + 1)/ itemWidth);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"updateWidths\":\n\t\t\t\t\tupdateWidths();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// avoid double enhance activities\n\t\t\tif( $self.attr(\"data-\" + pluginName + \"-enhanced\") ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// NOTE all state manipulation has to come after method invocation to\n\t\t\t// avoid monkeying with the DOM when it's unwarranted\n\t\t\tvar $navInner = $nav.find( \".\" + pluginName + \"_nav_inner\" );\n\t\t\tif( !$navInner.length ){\n\t\t\t\t$navInner = $( '
' ).append( $nav.children() ).appendTo( $nav );\n\t\t\t}\n\n\t\t\t// give the pane a tabindex for arrow key handling\n\t\t\t$slider.attr(\"tabindex\", \"0\");\n\n\t\t\tfunction getAutoplayInterval() {\n\t\t\t\tvar autoTiming = $self.attr( \"data-autoplay\" ) || $self.attr( \"data-snapper-autoplay\" );\n\t\t\t\tvar parseError = false;\n\n\t\t\t\tif( autoTiming ) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tautoTiming = parseInt(autoTiming, 10);\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\tparseError = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// if NaN or there was an error throw an exception\n\t\t\t\t\tif( !autoTiming || parseError ) {\n\t\t\t\t\t\tvar msg = \"Snapper: `data-autoplay` must have an natural number value.\";\n\t\t\t\t\t\tthrow new Error(msg);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn autoTiming;\n\t\t\t}\n\n\t\t\t// this function updates the widths of the items within the slider, and their container.\n\t\t\t// It factors in margins and converts those to values that make sense when all items are placed in a long row\n\t\t\tfunction updateWidths(){\n\t\t\t\tvar itemsContainStyle = $itemsContain.attr( \"style\" );\n\t\t\t\t$itemsContain.attr( \"style\", \"\" );\n\t\t\t\tvar itemStyle = $items.eq(0).attr( \"style\" );\n\t\t\t\t$items.eq(0).attr( \"style\", \"\" );\n\t\t\t\tvar sliderWidth = $slider.width();\n\t\t\t\tvar itemWidth = $items.eq(0).width();\n\t\t\t\tvar computed = w.getComputedStyle( $items[ 0 ], null );\n\t\t\t\tvar itemLeftMargin = parseFloat( computed.getPropertyValue( \"margin-left\" ) );\n\t\t\t\tvar itemRightMargin = parseFloat( computed.getPropertyValue( \"margin-right\" ) );\n\t\t\t\tvar outerItemWidth = itemWidth + itemLeftMargin + itemRightMargin;\n\t\t\t\t$items.eq(0).attr( \"style\", itemStyle );\n\t\t\t\t$itemsContain.attr( \"style\", itemsContainStyle );\n\t\t\t\tvar parentWidth = numItems / Math.round(sliderWidth / outerItemWidth) * 100;\n\t\t\t\tvar iPercentWidth = itemWidth / sliderWidth * 100;\n\t\t\t\tvar iPercentRightMargin = itemRightMargin / sliderWidth * 100;\n\t\t\t\tvar iPercentLeftMargin = itemLeftMargin / sliderWidth * 100;\n\t\t\t\tvar outerPercentWidth = iPercentWidth + iPercentLeftMargin + iPercentRightMargin;\n\t\t\t\tvar percentAsWidth = iPercentWidth / outerPercentWidth;\n\t\t\t\tvar percentAsRightMargin = iPercentRightMargin / outerPercentWidth;\n\t\t\t\tvar percentAsLeftMargin = iPercentLeftMargin / outerPercentWidth;\n\t\t\t\t$itemsContain.css( \"width\", parentWidth + \"%\");\n\t\t\t\t$items.css( \"width\", 100 / numItems * percentAsWidth + \"%\" );\n\t\t\t\t$items.css( \"margin-left\", 100 / numItems * percentAsLeftMargin + \"%\" );\n\t\t\t\t$items.css( \"margin-right\", 100 / numItems * percentAsRightMargin + \"%\" );\n\t\t\t}\n\n\t\t\tupdateWidths();\n\t\t\t$( self ).addClass( enhancedClass );\n\n\t\t\t// if the nextprev option is set, add the nextprev nav\n\t\t\tif( addNextPrev ){\n\t\t\t\tvar\t$nextprev = $( '' );\n\t\t\t\tvar $nextprevContain = $( \".snapper_nextprev_contain\", self );\n\t\t\t\tif( !$nextprevContain.length ){\n\t\t\t\t\t$nextprevContain = $( self );\n\t\t\t\t}\n\t\t\t\t$nextprev.appendTo( $nextprevContain );\n\t\t\t}\n\n\t\t\t// This click binding will allow deep-linking to slides without causing the page to scroll to the carousel container\n\t\t\t// this also supports click handling for generated next/prev links\n\t\t\t$( \"a\", this ).bind( \"click\", function( e ){\n\t\t\t\tclearTimeout(autoTimeout);\n\t\t\t\tvar slideID = $( this ).attr( \"href\" );\n\n\t\t\t\tif( $( this ).is( \".snapper_nextprev_next\" ) ){\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\treturn arrowNavigate( true );\n\t\t\t\t}\n\t\t\t\telse if( $( this ).is( \".snapper_nextprev_prev\" ) ){\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\treturn arrowNavigate( false );\n\t\t\t\t}\n\t\t\t\t// internal links to slides\n\t\t\t\telse if( slideID.indexOf( \"#\" ) === 0 && slideID.length > 1 ){\n\t\t\t\t\te.preventDefault();\n\n\t\t\t\t\tvar $slide = $( slideID, self );\n\t\t\t\t\tif( $slide.length ){\n\t\t\t\t\t\tgoto( $slider[ 0 ], $slide[ 0 ].offsetLeft );\n\t\t\t\t\t\tif( useDeepLinking && \"replaceState\" in w.history ){\n\t\t\t\t\t\t\tw.history.replaceState( {}, document.title, slideID );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// arrow key bindings for next/prev\n\t\t\t$( this )\n\t\t\t\t.bind( \"keydown\", function( e ){\n\t\t\t\t\tif( e.keyCode === 37 || e.keyCode === 38 ){\n\t\t\t\t\t\tclearTimeout(autoTimeout);\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\te.stopImmediatePropagation();\n\t\t\t\t\t\tarrowNavigate( false );\n\t\t\t\t\t}\n\t\t\t\t\tif( e.keyCode === 39 || e.keyCode === 40 ){\n\t\t\t\t\t\tclearTimeout(autoTimeout);\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\te.stopImmediatePropagation();\n\t\t\t\t\t\tarrowNavigate( true );\n\t\t\t\t\t}\n\t\t\t\t} );\n\n\t\t\tvar snapScrollCancelled = false;\n\n\t\t\t// snap to nearest slide. Useful after a scroll stops, for polyfilling snap points\n\t\t\tfunction snapScroll(){\n\t\t\t\tif(isTouched){\n\t\t\t\t\tsnapScrollCancelled = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar currScroll = $slider[ 0 ].scrollLeft;\n\t\t\t\tvar width = $itemsContain.width();\n\t\t\t\tvar itemWidth = $items[ 1 ] ? $items[ 1 ].offsetLeft : outerWidth( $items.eq( 0 ) );\n\t\t\t\tvar roundedScroll = Math.round(currScroll/itemWidth)*itemWidth;\n\t\t\t\tvar maxScroll = width - $slider.width();\n\t\t\t\tif( roundedScroll > maxScroll ){\n\t\t\t\t\troundedScroll = maxScroll;\n\t\t\t\t}\n\t\t\t\tif( currScroll !== roundedScroll ){\n\t\t\t\t\tif( snapSupported ){\n\t\t\t\t\t\tsnapEvent( $slider[ 0 ], roundedScroll );\n\t\t\t\t\t\tsnapEvent( $slider[ 0 ], roundedScroll, \"after\" );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tgoto( $slider[ 0 ], roundedScroll );\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tgoto( $slider[ 0 ], roundedScroll );\n\t\t\t\t}\n\n\t\t\t\tsnapScrollCancelled = false;\n\t\t\t}\n\n\t\t\t// retain snapping on resize (necessary even in scroll-snap supporting browsers currently, unfortunately)\n\t\t\tvar startSlide;\n\t\t\tvar afterResize;\n\t\t\tfunction snapStay(){\n\t\t\t\tvar currScroll = $slider[ 0 ].scrollLeft;\n\t\t\t\tvar numItems = $items.length;\n\t\t\t\tvar width = $itemsContain.width();\n\t\t\t\tif( startSlide === undefined ){\n\t\t\t\t\tstartSlide = Math.round( currScroll / width * numItems );\n\t\t\t\t}\n\t\t\t\tif( afterResize ){\n\t\t\t\t\tclearTimeout( afterResize );\n\t\t\t\t}\n\t\t\t\tafterResize = setTimeout( function(){\n\t\t\t\t\tupdateWidths();\n\t\t\t\t\tgoto( $slider[ 0 ], $items[ startSlide ].offsetLeft, true );\n\t\t\t\t\tstartSlide = afterResize = undefined;\n\t\t\t\t}, 50 );\n\t\t\t}\n\t\t\t$( w ).bind( \"resize\", snapStay );\n\n\t\t\t// next/prev links or arrows should loop back to the other end when an extreme is reached\n\t\t\tfunction arrowNavigate( forward ){\n\t\t\t\tvar currScroll = $slider[ 0 ].scrollLeft;\n\t\t\t\tvar width = $itemsContain.width();\n\t\t\t\tvar itemWidth = outerWidth( $slider );\n\t\t\t\tvar maxScroll = width - itemWidth - 5;\n\t\t\t\tif( forward ){\n\t\t\t\t\tif( currScroll >= maxScroll ){\n\t\t\t\t\t\t$self.addClass( pluginName + \"-looping\" );\n\t\t\t\t\t\treturn first();\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\treturn next();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif( currScroll === 0 ){\n\t\t\t\t\t\t$self.addClass( pluginName + \"-looping\" );\n\t\t\t\t\t\treturn last();\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\treturn prev();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// advance slide one full scrollpane's width forward\n\t\t\tfunction next(){\n\t\t\t\tgoto( $slider[ 0 ], $slider[ 0 ].scrollLeft + ( $itemsContain.width() / numItems ), false, function(){\n\t\t\t\t\t$slider.trigger( pluginName + \".after-next\" );\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// advance slide one full scrollpane's width backwards\n\t\t\tfunction prev(){\n\t\t\t\tgoto( $slider[ 0 ], $slider[ 0 ].scrollLeft - ( $itemsContain.width() / numItems ), false, function(){\n\t\t\t\t\t$slider.trigger( pluginName + \".after-prev\" );\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// go to first slide\n\t\t\tfunction first(){\n\t\t\t\tgoto( $slider[ 0 ], 0 );\n\t\t\t}\n\n\t\t\t// go to last slide\n\t\t\tfunction last(){\n\t\t\t\tgoto( $slider[ 0 ], $itemsContain.width() - $slider.width() );\n\t\t\t}\n\n\t\t\t// update thumbnail state on pane scroll\n\t\t\tif( $nav.length ){\n\t\t\t\t// function for scrolling to the xy of the active thumbnail\n\t\t\t\tfunction scrollNav(elem, x, y){\n\t\t\t\t\tif( typeof w.toss !== \"undefined\" ){\n\t\t\t\t\t\tw.toss( elem, { left: x, top:y });\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\telem.scrollLeft = x;\n\t\t\t\t\t\telem.scrollTop = y;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tvar lastActiveItem;\n\t\t\t\tfunction activeItem( force ){\n\t\t\t\t\tvar currTime = new Date().getTime();\n\t\t\t\t\tif( !force && lastActiveItem && currTime - lastActiveItem < 200 ){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlastActiveItem = currTime;\n\t\t\t\t\tvar currScroll = $slider[ 0 ].scrollLeft;\n\t\t\t\t\tvar width = outerWidth( $itemsContain );\n\t\t\t\t\tvar navWidth = outerWidth( $nav );\n\t\t\t\t\tvar navHeight = outerHeight( $nav );\n\t\t\t\t\tvar activeIndex = Math.round( currScroll / width * numItems ) || 0;\n\t\t\t\t\tvar childs = $nav.find( \"a\" ).removeClass( navSelectedClass );\n\t\t\t\t\tvar activeChild = childs.eq( activeIndex ).addClass( navSelectedClass );\n\n\t\t\t\t\tvar thumbX = activeChild[ 0 ].offsetLeft - (navWidth/2);\n\t\t\t\t\tvar thumbY = activeChild[ 0 ].offsetTop - (navHeight/2);\n\n\t\t\t\t\tscrollNav( $navInner[ 0 ], thumbX, thumbY );\n\t\t\t\t}\n\n\t\t\t\t// set active item on init\n\t\t\t\tactiveItem();\n\n\t\t\t\t$slider.bind( \"scroll\", activeItem );\n\t\t\t}\n\n\t\t\t// apply snapping after scroll, in browsers that don't support CSS scroll-snap\n\t\t\tvar scrollStop;\n\t\t\tvar scrolling;\n\t\t\tvar lastScroll = 0;\n\n\n\t\t\t$slider.bind( \"scroll\", function(e){\n\t\t\t\tlastScroll = new Date().getTime();\n\t\t\t\tscrolling = true;\n\t\t\t});\n\n\t\t\tsetInterval(function(){\n\t\t\t\tif( scrolling && lastScroll <= new Date().getTime() - 150) {\n\t\t\t\t\tsnapScroll();\n\t\t\t\t\tif( activeItem ){\n\t\t\t\t\t\tactiveItem( true );\n\t\t\t\t\t}\n\t\t\t\t\tscrolling = false;\n\t\t\t\t}\n\t\t\t}, 150);\n\n\t\t\tvar isTouched = false;\n\n\t\t\t// if a touch event is fired on the snapper we know the user is trying to\n\t\t\t// interact with it and we should disable the auto play\n\t\t\t$slider.bind(\"touchstart\", function(){\n\t\t\t\tclearTimeout(autoTimeout);\n\n\t\t\t\tisTouched = true;\n\t\t\t});\n\n\t\t\t$slider.bind(\"touchend\", function(){\n\t\t\t\tisTouched = false;\n\n\t\t\t\tif(snapScrollCancelled && !scrolling){\n\t\t\t\t\tsnapScroll();\n\t\t\t\t\tscrolling = false;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// if the `data-autoplay` attribute is assigned a natural number value\n\t\t\t// use it to make the slides cycle until there is a user interaction\n\t\t\tfunction autoplay( autoTiming ) {\n\t\t\t\tif( autoTiming ){\n\t\t\t\t\t// autoTimeout is cleared in each user interaction binding\n\t\t\t\t\tautoTimeout = setTimeout(function(){\n\t\t\t\t\t\tvar timeout = getAutoplayInterval();\n\t\t\t\t\t\tif( timeout ) {\n\t\t\t\t\t\t\tarrowNavigate(true);\n\t\t\t\t\t\t\tautoplay( timeout );\n\t\t\t\t\t\t}\n\t\t\t\t\t}, autoTiming);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tautoplay( getAutoplayInterval() );\n\t\t\t$self.attr(\"data-\" + pluginName + \"-enhanced\", true);\n\t\t});\n\n\t\treturn (innerResult !== undefined ? innerResult : result);\n\t};\n}( this, jQuery ));\n\n;(function( w ){\n\tvar enlarge = function(){\n\t\tvar $ = w.jQuery;\n\t\tvar pluginName = \"enlarge\";\n\t\t$.fn[ pluginName ] = function( options ){\n\t\t\tvar pluginArgs = arguments;\n\n\t\t\t// options\n\t\t\tvar o = $(this).data(\"options\") || {\n\t\t\t\tbutton: true,\n\t\t\t\thoverZoomWithoutClick: true,\n\t\t\t\tdelay: 300,\n\t\t\t\tflyout: {\n\t\t\t\t\twidth: 200,\n\t\t\t\t\theight: 200\n\t\t\t\t},\n\t\t\t\tplacement: \"inline\",\n\t\t\t\tmagnification: 3\n\t\t\t};\n\n\t\t\tif( typeof options !== \"string\" ) {\n\t\t\t\t// extend with passed options\n\t\t\t\to = $.extend( o, options );\n\t\t\t\t$(this).data(\"options\", o);\n\t\t\t}\n\n\t\t\tvar internalResult;\n\n\t\t\tvar result = this.each(function(){\n\t\t\t\tvar $element = $(this);\n\n\t\t\t\tvar self = this;\n\t\t\t\tvar testimg = w.document.createElement( \"img\" );\n\t\t\t\tvar srcsetSupported = \"srcset\" in testimg;\n\t\t\t\tvar srcsetSizesSupported = srcsetSupported && \"sizes\" in testimg;\n\t\t\t\tvar $anchor = $( this ).find( \"a\" );\n\n\t\t\t\tif( !$anchor.length ){\n\t\t\t\t\tthrow new Error(pluginName + \": requires an anchor element with `href` for the enlarged image source\");\n\t\t\t\t}\n\n\t\t\t\t// find image within container\n\t\t\t\tvar initialImg = $element.find( \"img\" )[ 0 ];\n\t\t\t\tvar targetImg = initialImg;\n\t\t\t\tvar imgOriginalSrc = targetImg.src;\n\t\t\t\tvar srcset = $( targetImg ).attr( \"srcset\" );\n\t\t\t\tvar imgOriginalSizes = $( targetImg ).attr( \"sizes\" );\n\t\t\t\tvar imgZoomSrc = $anchor[ 0 ].href;\n\t\t\t\tvar initialText = $anchor[ 0 ].innerText;\n\t\t\t\tvar zoomClass = pluginName + \"-zoomed\";\n\t\t\t\tvar delayClass = pluginName + \"-delay\";\n\t\t\t\tvar $contain = $( targetImg ).closest( \".enlarge_contain\" );\n\t\t\t\tvar $zoomContain = $contain;\n\t\t\t\tvar $parentPane = $( targetImg ).closest( \".enlarge_pane\" ) || $element;\n\n\t\t\t\tvar $zoomParent = $(this).data(\"zoomParent\") || $parentPane;\n\t\t\t\t$(this).data(\"zoomParent\", $zoomParent);\n\n\t\t\t\tvar zoomed = $element.data(\"zoomed\") || false;\n\t\t\t\t$element.data(\"zoomed\", zoomed);\n\n\t\t\t\t$element.data(\"lockedZoom\", $element.data(\"lockedZoom\") || false);\n\n\t\t\t\tvar lockZoomClass = pluginName + \"-locked\";\n\n\t\t\t\tif( !$contain.length ){\n\t\t\t\t\tthrow new Error(pluginName + \": requires an element above the image marked with the class `enlarge_contain`\");\n\t\t\t\t}\n\n\t\t\t\t// this allows for methods and changing options in subsequent calls to the plugin\n\t\t\t\tif( typeof options === \"string\" ) {\n\t\t\t\t\tvar args = Array.prototype.slice.call(pluginArgs, 1);\n\n\t\t\t\t\tswitch(options){\n\t\t\t\t\tcase \"in\":\n\t\t\t\t\t\tif( !$element.data(\"zoomed\") ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"out\":\n\t\t\t\t\t\tif( $element.data(\"zoomed\") ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"isZoomed\":\n\t\t\t\t\t\tinternalResult = $element.data(\"zoomed\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"updateOptions\":\n\t\t\t\t\t\t$element.data( \"updateOptions\" )( args[ 0 ] );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// to toggle between inline and flyout modes, we change the elements that\n\t\t\t\t// the targetImg, zoomParent, and zoomContain vars refer to\n\t\t\t\tfunction updatePlacement(){\n\t\t\t\t\tif( o.placement === \"inline\" ){\n\t\t\t\t\t\ttargetImg = initialImg;\n\t\t\t\t\t\t$zoomParent = $parentPane;\n\t\t\t\t\t\t$element.data(\"zoomParent\", $zoomParent);\n\t\t\t\t\t\t$zoomContain = $contain;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttargetImg = $flyout.find( \"img\" )[ 0 ];\n\t\t\t\t\t\t$zoomParent = $zoomContain = $flyout;\n\t\t\t\t\t\t$element.data(\"zoomParent\", $zoomParent);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// this positions the loupe or side flyout element either according to mouse/touch coordinates\n\t\t\t\t// or the sides of the viewer specified\n\t\t\t\tfunction positionFlyout(){\n\n\t\t\t\t\t// set flyout width and height\n\t\t\t\t\t$flyout.css({\n\t\t\t\t\t\t\"width\": o.flyout.width + \"px\",\n\t\t\t\t\t\t\"height\": o.flyout.height + \"px\",\n\t\t\t\t\t\ttop: \"\",\n\t\t\t\t\t\tleft: \"\",\n\t\t\t\t\t\t\"margin-left\": \"\",\n\t\t\t\t\t\t\"margin-top\": \"\"\n\t\t\t\t\t});\n\n\t\t\t\t\t// set negative left or right value to match width\n\t\t\t\t\tvar flyoutSide = o.placement.match( /left|right/ );\n\n\t\t\t\t\tif( flyoutSide ){\n\t\t\t\t\t\t$flyout.css( flyoutSide[ 0 ], -o.flyout.width + \"px\" );\n\t\t\t\t\t}\n\t\t\t\t\t// if loupe mode, center offset\n\t\t\t\t\tvar loupe = o.placement.match( /loupe/ );\n\n\t\t\t\t\tif( loupe ) {\n\t\t\t\t\t\t// loupe\n\t\t\t\t\t\t$flyout.css({\n\t\t\t\t\t\t\t\"margin-left\": ( -o.flyout.width / 2 ) + \"px\",\n\t\t\t\t\t\t\t\"margin-top\": ( -o.flyout.height / 2 ) + \"px\"\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// add class to specify positioning spot for static css to apply\n\t\t\t\t\t$flyout[ 0 ].className = $flyout[ 0 ].className.replace( /enlarge_flyout\\-[^$\\s]+/, ' ' );\n\t\t\t\t\t$flyout.addClass( \"enlarge_flyout-\" +\t o.placement );\n\t\t\t\t}\n\n\t\t\t\tfunction disable(){\n\t\t\t\t\tif(o.disabled){\n\t\t\t\t\t\t$element.addClass(\"enlarge_disabled\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$element.removeClass(\"enlarge_disabled\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tdisable();\n\n\t\t\t\t// this allows for subsequent calls to the plugin to pass an updateOptions method and object,\n\t\t\t\t// which will pass through to the existing viewer on that element\n\t\t\t\t$element.data( \"updateOptions\", function( opts ){\n\t\t\t\t\to = $.extend( o, opts );\n\t\t\t\t\t$(this).data(\"options\", o);\n\n\t\t\t\t\tupdatePlacement();\n\t\t\t\t\tpositionFlyout();\n\t\t\t\t\thoverEnabled = o.hoverZoomWithoutClick;\n\t\t\t\t\tif( o.image && o.image.sizes ){\n\t\t\t\t\t\timgOriginalSizes = o.image.sizes;\n\t\t\t\t\t\ttoggleImgSrc();\n\t\t\t\t\t}\n\n\t\t\t\t\tdisable();\n\n\t\t\t\t\tif( o.disabled && $element.data(\"zoomed\") ) {\n\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// loader div holds a new image while its new source is loading\n\t\t\t\t// we insert this into the dom so that srcset/sizes can calculate a best source\n\t\t\t\tfunction addLoader(){\n\t\t\t\t\t$contain.append( '' );\n\t\t\t\t}\n\n\t\t\t\t// zoom state toggle boolean\n\t\t\t\tfunction toggleZoomState(){\n\t\t\t\t\tzoomed = !$element.data(\"zoomed\");\n\t\t\t\t\t$element.data(\"zoomed\", zoomed);\n\t\t\t\t}\n\n\t\t\t\t// toggle the image source bigger or smaller\n\t\t\t\t// ideally, this toggles the sizes attribute and allows the browser to select a new source from srcset\n\t\t\t\t// if srcset isn't supported or sizes attribute is not provided, the link href is used for the larger source\n\t\t\t\tfunction toggleImgSrc(after){\n\t\t\t\t\tafter = after || function(){};\n\n\t\t\t\t\tif( !zoomed ){\n\t\t\t\t\t\ttargetImg.sizes = imgOriginalSizes;\n\t\t\t\t\t\tif( !srcsetSizesSupported ){\n\t\t\t\t\t\t\ttargetImg.src = imgOriginalSrc;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tafter();\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// if the zooming is disabled do not replace with the larger source\n\t\t\t\t\t\t// NOTE we don't prevent switching to the original source because we\n\t\t\t\t\t\t// always want to allow the plugin to back out of the zoomed state\n\t\t\t\t\t\t// when disabled\n\t\t\t\t\t\tif( o.disabled ) { after(); return false; }\n\n\t\t\t\t\t\tvar zoomimg = new Image();\n\t\t\t\t\t\tzoomimg.className = \"enlarge_img-loading\";\n\t\t\t\t\t\tzoomimg.onload = function(){\n\t\t\t\t\t\t\ttargetImg.sizes = zoomimg.sizes;\n\t\t\t\t\t\t\tif( !srcsetSizesSupported || !srcset ){\n\t\t\t\t\t\t\t\ttargetImg.src = imgZoomSrc;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t$( zoomimg ).remove();\n\n\t\t\t\t\t\t\tafter();\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tzoomimg.sizes = imgZoomWidth() + \"px\";\n\n\t\t\t\t\t\tif( !srcsetSizesSupported || !srcset ){\n\t\t\t\t\t\t\tzoomimg.src = imgZoomSrc;\n\t\t\t\t\t\t} else if (srcset) {\n\t\t\t\t\t\t\tzoomimg.srcset = srcset;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t$( zoomimg ).insertBefore( targetImg );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// scroll to the center of the zoomed image\n\t\t\t\tfunction scrollToCenter(){\n\t\t\t\t\tvar pw = $zoomContain.width();\n\t\t\t\t\tvar ph = $zoomContain.height();\n\t\t\t\t\tvar w = targetImg.offsetWidth;\n\t\t\t\t\tvar h = targetImg.offsetHeight;\n\t\t\t\t\t$zoomContain[ 0 ].scrollLeft = ( w / 2 ) - ( pw / 2 );\n\t\t\t\t\t$zoomContain[ 0 ].scrollTop = ( h / 2 ) - ( ph / 2 );\n\t\t\t\t}\n\n\t\t\t\t// lock zoom mode allows for scrolling around normally without a cursor-follow behavior\n\t\t\t\tfunction toggleLockZoom(){\n\t\t\t\t\tif( !$element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t// NOTE we allow the image to zoom out if functionality gets disabled\n\t\t\t\t\t\t// when it's in a zoomed state\n\t\t\t\t\t\tif(o.disabled) { return false; }\n\n\t\t\t\t\t\t$parentPane.add( $zoomParent ).addClass( lockZoomClass );\n\t\t\t\t\t\t$element.data(\"lockedZoom\", lockedZoom = true);\n\t\t\t\t\t\t$zoomContain.attr( \"tabindex\", \"0\" );\n\t\t\t\t\t\t$zoomContain[ 0 ].focus();\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\t$parentPane.add( $zoomParent ).removeClass( lockZoomClass );\n\t\t\t\t\t\t$element.data(\"lockedZoom\", lockedZoom = false);\n\t\t\t\t\t\t$zoomContain.removeAttr( \"tabindex\" );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfunction imgZoomWidth(){\n\t\t\t\t\treturn $parentPane[0].offsetWidth * o.magnification;\n\t\t\t\t}\n\n\t\t\t\t// toggle magnification of image\n\t\t\t\tfunction toggleImgZoom(){\n\t\t\t\t\tif( $element.data(\"zoomed\") ){\n\t\t\t\t\t\t// NOTE we allow the image to zoom out if functionality gets disabled\n\t\t\t\t\t\t// when it's in a zoomed state\n\t\t\t\t\t\tif(o.disabled) { return false; }\n\n\t\t\t\t\t\tif( o.placement === \"inline\" ){\n\t\t\t\t\t\t\t$contain.add( $parentPane ).css( { \"width\": $parentPane[0].offsetWidth + \"px\", \"height\": parseFloat( getComputedStyle( $parentPane[0] ).height ) + \"px\" } );\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$zoomParent.addClass( zoomClass );\n\t\t\t\t\t\t$( targetImg ).css( \"width\", imgZoomWidth() + \"px\" );\n\n\t\t\t\t\t\t$(self).trigger( pluginName + \".after-zoom-in\");\n\t\t\t\t\t}\n\t\t\t\t\telse{\n\t\t\t\t\t\t$zoomParent.removeClass( zoomClass );\n\t\t\t\t\t\tif( o.placement === \"inline\" ){\n\t\t\t\t\t\t\t$contain.add( $parentPane ).css( { \"width\": \"\", \"height\": \"\" } );\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$( targetImg ).css( \"width\", \"\" );\n\n\t\t\t\t\t\t$(self).trigger( pluginName + \".after-zoom-out\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfunction forceInlineMode(){\n\t\t\t\t\tvar oldO = o.placement;\n\t\t\t\t\tif( oldO !== \"inline\" ){\n\t\t\t\t\t\tfunction resetPlacement(){\n\t\t\t\t\t\t\to.placement = oldO;\n\t\t\t\t\t\t\tupdatePlacement();\n\t\t\t\t\t\t\t$(self).unbind( pluginName + \".after-zoom-out\", resetPlacement );\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$(self).bind( pluginName + \".after-zoom-out\", resetPlacement );\n\t\t\t\t\t\to.placement = \"inline\";\n\t\t\t\t\t\tupdatePlacement();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// lock zoom mode toggle\n\t\t\t\tfunction standardToggleZoom(){\n\t\t\t\t\t// NOTE if the current is zoomed out and it's disabled prevent toggling\n\t\t\t\t\tif(o.disabled && !$element.data(\"zoomed\")) { return false; }\n\t\t\t\t\ttoggleZoomState();\n\t\t\t\t\ttoggleImgSrc(function(){\n\t\t\t\t\t\ttoggleLockZoom();\n\t\t\t\t\t\ttoggleImgZoom();\n\t\t\t\t\t\tscrollToCenter();\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tvar trackingOn;\n\t\t\t\tvar trackingTimer;\n\t\t\t\tvar mouseEntered;\n\t\t\t\tvar touchStarted;\n\t\t\t\tvar hoverEnabled = o.hoverZoomWithoutClick;\n\t\t\t\tvar lastMouseMove;\n\n\t\t\t\t// mouseenter or touchstart handler for dragging image\n\t\t\t\tfunction startTrackingDelay(e){\n\t\t\t\t\tif( e.type === \"touchstart\" ){\n\t\t\t\t\t\ttouchStarted = true;\n\t\t\t\t\t}\n\t\t\t\t\tif( touchStarted && e.type === \"mouseenter\" ||\n\t\t\t\t\t\t\te.type === \"mouseenter\" && !touchStarted && !hoverEnabled ||\n\t\t\t\t\t\t\t$element.data(\"lockedZoom\") ||\n\t\t\t\t\t\t\tmouseEntered ){\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\tmouseEntered = true;\n\t\t\t\t\t$contain.addClass( delayClass );\n\t\t\t\t\ttrackingTimer = setTimeout( function(){\n\t\t\t\t\t\t$contain.removeClass( delayClass );\n\t\t\t\t\t\ttoggleZoomState();\n\t\t\t\t\t\ttoggleImgSrc(function(){\n\t\t\t\t\t\t\ttoggleImgZoom();\n\t\t\t\t\t\t\ttrackingOn = true;\n\t\t\t\t\t\t\tif (lastMouseMove) {\n\t\t\t\t\t\t\t\tscrollWithMouse(lastMouseMove);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tscrollWithMouse(e);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}, o.delay );\n\t\t\t\t}\n\n\t\t\t\t// mouseleave or touchend after a drag\n\t\t\t\tfunction stopTrackingDelay( e ){\n\t\t\t\t\t$contain.removeClass( delayClass );\n\t\t\t\t\tclearTimeout( trackingTimer );\n\t\t\t\t\ttrackingOn = false;\n\t\t\t\t\tif( o.hoverZoomWithoutClick === false && !touchStarted ){\n\t\t\t\t\t\thoverEnabled = false;\n\t\t\t\t\t}\n\t\t\t\t\tif( touchStarted && e.type === \"mouseleave\" ){\n\t\t\t\t\t\ttouchStarted = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// mousemove or touch-drag image placement\n\t\t\t\tfunction scrollWithMouse( e ){\n\t\t\t\t\t// if tracking's not on yet, ignore. This allows the delay to work\n\t\t\t\t\tif( trackingOn ){\n\t\t\t\t\t\t// if the move was touch-started, and the event is mousemove, it can be ignored\n\t\t\t\t\t\t// (mouse events fire along with touch events and we just want the touch)\n\t\t\t\t\t\tif( touchStarted && e.type === \"mousemove\" ){\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// normalize ev to touch or mouse\n\t\t\t\t\t\tvar ev = e.touches ? e.touches[ 0 ] : e;\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tvar x = ev.clientX - $contain[ 0 ].getBoundingClientRect().left;\n\t\t\t\t\t\tvar y = ev.clientY - $contain[ 0 ].getBoundingClientRect().top;\n\n\t\t\t\t\t\tif( o.placement.match( /loupe/ ) ) {\n\t\t\t\t\t\t\t// offset the loupe a little differently for touch so that it's not directly beneath a finger\n\t\t\t\t\t\t\tvar mLeft = ( e.touches ? -o.flyout.width / 1.3 : -o.flyout.width / 2 ) + \"px\";\n\t\t\t\t\t\t\tvar mTop = ( e.touches ? -o.flyout.height / 1.3 : -o.flyout.height / 2 ) + \"px\";\n\t\t\t\t\t\t\trequestAnimationFrame(function(){\n\t\t\t\t\t\t\t\t$flyout.css( {\n\t\t\t\t\t\t\t\t\ttop: y + \"px\",\n\t\t\t\t\t\t\t\t\tleft: x + \"px\",\n\t\t\t\t\t\t\t\t\t\"margin-left\": mLeft,\n\t\t\t\t\t\t\t\t\t\"margin-top\": mTop\n\t\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar containWidth = $contain[ 0 ].offsetWidth;\n\t\t\t\t\t\tvar containHeight = $contain[ 0 ].offsetHeight;\n\t\t\t\t\t\tvar containScrollWidth =\ttargetImg.offsetWidth;\n\t\t\t\t\t\tvar containScrollHeight = targetImg.offsetHeight;\n\t\t\t\t\t\tvar zoomContainWidth = $zoomContain[ 0 ].offsetWidth;\n\t\t\t\t\t\tvar zoomContainHeight = $zoomContain[ 0 ].offsetHeight;\n\t\t\t\t\t\tvar widthFactor = containWidth / zoomContainWidth;\n\t\t\t\t\t\tvar heightFactor = containHeight / zoomContainHeight;\n\n\t\t\t\t\t\t$zoomContain[ 0 ].scrollLeft = (( x / containWidth ) * ( containScrollWidth - zoomContainWidth ));\n\t\t\t\t\t\t$zoomContain[0].scrollLeft += ((x / containWidth) - 0.5) * zoomContainWidth;\n\t\t\t\t\t\t$zoomContain[ 0 ].scrollTop = (( y / containHeight ) * ( containScrollHeight - zoomContainHeight )) ;\n\t\t\t\t\t\t$zoomContain[0].scrollTop += ((y / containHeight) - 0.5) * zoomContainHeight;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlastMouseMove = e;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// add flyout for flyout and loupe modes\n\t\t\t\t// flyout is always present, yet hidden when inline mode is active\n\t\t\t\tvar $flyout = $( '
' ).append( $contain.clone() );\n\t\t\t\t$flyout.insertAfter( $parentPane );\n\n\t\t\t\t// add loader div\n\t\t\t\taddLoader();\n\t\t\t\tupdatePlacement();\n\t\t\t\tpositionFlyout();\n\n\t\t\t\t// clicking the magnify anchor toggles lock-zoom mode\n\t\t\t\t$anchor\n\t\t\t\t\t.bind( \"keydown\", function(e){\n\t\t\t\t\t\tif( e.keyCode === 13 || e.keyCode === 32 ){\n\t\t\t\t\t\t\tforceInlineMode();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// spacebar triggers click too\n\t\t\t\t\t\tif( e.keyCode === 32 ){\n\t\t\t\t\t\t\te.preventDefault(); // don't scroll the new focused area\n\t\t\t\t\t\t\t$( this ).trigger( \"click\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.bind( \"click\", function( e ){\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t});\n\n\t\t\t\t// on resize, if in lock zoom mode, un zoom\n\t\t\t\t$( w )\n\t\t\t\t\t.bind( \"resize\", function( e ){\n\t\t\t\t\t\tif( $element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t// on click-out on the page, if in locked zoom mode, zoom out\n\t\t\t\t$( w.document )\n\t\t\t\t\t.bind( \"mouseup\", function( e ){\n\t\t\t\t\t\tif( $element.data(\"lockedZoom\") && !$( e.target ).closest( $parentPane ).length ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t// mouse hover and touch-drag gestures for a cursor-tracked zoom behavior\n\t\t\t\t$( initialImg )\n\t\t\t\t\t.bind( \"mouseenter touchstart\", startTrackingDelay )\n\t\t\t\t\t.bind( \"mousemove touchmove\", scrollWithMouse )\n\t\t\t\t\t.bind( \"mouseleave touchend\", function( e ){\n\t\t\t\t\t\tmouseEntered = false;\n\t\t\t\t\t\tif( zoomed && !$element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t\ttoggleZoomState();\n\t\t\t\t\t\t\ttoggleImgSrc(function(){\n\t\t\t\t\t\t\t\ttoggleImgZoom();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstopTrackingDelay( e );\n\t\t\t\t\t})\n\t\t\t\t// tapping the image should trigger a lock zoom\n\t\t\t\t// click will not fire after a touch-drag so it works as a tap for our needs here\n\t\t\t\t\t.bind( \"click\", function( e ){\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t// if the click was started with a touchstart event,\n\t\t\t\t\t\t// and placement is inline\n\t\t\t\t\t\t// toggle the locked zoom mode\n\t\t\t\t\t\tif( touchStarted && o.placement === \"inline\" ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( o.hoverZoomWithoutClick === false && !touchStarted ){\n\t\t\t\t\t\t\thoverEnabled = !hoverEnabled;\n\t\t\t\t\t\t\tif( hoverEnabled ){\n\t\t\t\t\t\t\t\t$( this ).trigger( \"mouseenter\" );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t$( this ).trigger( \"mouseleave\" );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} );\n\n\t\t\t\t// keyboard handling for arrows in zoom mode\n\t\t\t\t$(this).bind( \"keydown keyup\", function( e ){\n\t\t\t\t\tif( e.keyCode === 37 ||\n\t\t\t\t\t\t\te.keyCode === 38 ||\n\t\t\t\t\t\t\te.keyCode === 39 ||\n\t\t\t\t\t\t\te.keyCode === 40 ){\n\t\t\t\t\t\t\t\te.stopImmediatePropagation();\n\t\t\t\t\t\t\t\tif( !$element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if( e.type === \"keyup\" && $(this).data(\"lockedZoom\") && e.keyCode === 27 ){ //esc or backspace closes zoom\n\t\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t\t\t$anchor[0].focus();\n\t\t\t\t\t\t\t\te.stopImmediatePropagation();\n\t\t\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// on scroll, zoom out\n\t\t\t\t$parentPane.bind( \"scroll\", function(){\n\t\t\t\t\tif( $element.data(\"zoomed\") ){\n\t\t\t\t\t\ttoggleZoomState();\n\t\t\t\t\t\ttoggleImgSrc(function(){\n\t\t\t\t\t\t\tif( $element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t\t\ttoggleLockZoom();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttoggleImgZoom();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\n\t\t\treturn internalResult !== undefined ? internalResult : result;\n\t\t};\n\t};\n\n\tif( typeof module !== \"undefined\" ){\n\t\tmodule.exports = enlarge;\n\t} else {\n\t\tenlarge();\n\t}\n}( typeof global !== \"undefined\" ? global : this ));\n\n(function(exports){\n\tvar Image = {\n\t\tdefaultConfig: {\n\t\t\twidths: [\n\t\t\t\t\"320\",\n\t\t\t\t\"640\",\n\t\t\t\t\"800\",\n\t\t\t\t\"1024\",\n\t\t\t\t\"2048\",\n\t\t\t\t\"5000\"\n\t\t\t],\n\n\t\t\tsizes: \"100vw\",\n\t\t\tpolicy: undefined,\n\t\t\twidthParam: \"imwidth\"\n\t\t}\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Image = Image;\n})(typeof exports === 'undefined'? window : exports);\n\n(function(exports, $){\n\t/**\n\t * 360 degree viewer\n\t * @class\n\t * @alias Akamai.Spin360\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\tvar Spin360 = Akamai.Util.component(\"Spin360\", function(element, options){\n\t\tthis._comp = new Akamai.Tau(element, this._options);\n\t});\n\n\t// srcset stuff\n\tAkamai.Sourceable.extendStatic(Spin360);\n\n\t// Used in preflight to \"rename\" events based on the child component events\n\tSpin360._componentEventMapping = {\n\t\t\"tau.auto-rotate-start\": \"play\",\n\t\t\"tau.auto-rotate-stop\": \"pause\"\n\t};\n\n\tSpin360.prototype._updateOptions = function(options){\n\t\tif( !options ) {\n\t\t\tthis._options = this._originalOptions;\n\t\t}\n\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Spin360.defaultOptions, this._options, \"Spin360\");\n\n\t\t// TODO actually update the options\n\t};\n\n\tSpin360._renderImg = function(url, options){\n\t\treturn `\n\t\t\t\n\t\t`;\n\t};\n\n\tSpin360.render = function(json, options){\n\t\tvar finalOptions = Akamai.Util.options(Spin360.defaultOptions, options);\n\n\t\t// TODO fix the string problem in shoestring\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t${Akamai.Util\n\t\t\t\t\t\t.map(json.urls, function(url) { return Spin360._renderImg(url, finalOptions); })\n\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t}\n\t\t\t
\n\t\t`);\n\t};\n\n\t/**\n\t * Defines the global default options for all Spin360s on the page\n\t * @static\n\t * @property {Boolean} autoplay.enabled - Enable autoplay (default: false)\n\t * @property {Integer} autoplay.delay - Delay in milliseconds after initialization before spinning begins (default: 1000)\n\t * @property {Boolean} controls.arrows - Render controls to spin left and right (default: false)\n\t * @property {Boolean} controls.play - Render controls to enable and disable automatic spinning (default: false)\n\t * @property {String} controls.text.left - Left rotation control title and text (default: \"Rotate Left\")\n\t * @property {String} controls.text.right - Right rotation control title and text (default: \"Rotate Right\")\n\t * @property {String} controls.text.play - Spin control title and text (default: \"Spin Object\")\n\t * @property {Array} images.widths - list of available widths for an image (to be combined with image.widthParam), Default: [\"320\",\"640\",\"800\",\"1024\",\"2048\",\"5000\"]\n\t * @property {String} images.sizes - value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw`, `200px`, `(min-width:1000px) 500px, 100vw`.\n\t * @property {String} images.policy - query param value for policy, appended to &policy= per image url when specified. Values: `foo`. Default: undefined.\n\t * @property {String} images.widthParam - query string name to use for setting each url width. Default urls will be ?imwidth=320 for example. Values: `imwidth` (default), `w`, `width`, etc.\n\t * @property {Integer} interval - The full rotation interval in milliseconds, determines physics, (default: 3000)\n\t * @property {Boolean} reverse - Reverse the direction of the spin (default: false)\n\t * @property {Float} sensitivity - The speed at which the object rotates relative to user input (default: 1). At the default value of `1` the object will complete a full 360 rotation when you drag across the entire width of the spin360 component. To require less effort to complete a rotation, change this value to a higher number. For example, setting `sensitivity: 2` would complete a full 360 rotation by dragging halfway (ex. from the center to the edge).\n\t */\n\tSpin360.defaultOptions = {\n\t\tautoplay: {\n\t\t\tenabled: false,\n\t\t\tdelay: 1000\n\t\t},\n\n\t\tcontrols: {\n\t\t\tarrows: false,\n\t\t\tplay: false,\n\t\t\ttext: {\n\t\t\t\tleft: \"Rotate Left\",\n\t\t\t\tright: \"Rotate Right\",\n\t\t\t\tplay: \"Spin Object\"\n\t\t\t}\n\t\t},\n\n\t\t// unsupported, the number of frame images to create using the configured\n\t\t// template\n\t\tframes: 72,\n\t\timages: Akamai.Image.defaultConfig,\n\t\tinterval: 3000,\n\t\treverse: false,\n\n\t\tsensitivity: 1,\n\n\t\t// unsupported, template used to generate urls when only one image is\n\t\t// present in the 360 viewer\n\t\ttemplate: undefined\n\t};\n\n\t/**\n\t * Goto to a particular frame of the spining image\n\t * @method\n\t * @param {Integer} index - the frame to advance to\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.goto = function(index, callback){\n\t\tthis._comp.goto(index);\n\t\t// NOTE goto must come before the callback because the callback will be\n\t\t// used to trigger the `next` and `previous` events. The order should be\n\t\t// maintained consistently as `goto` -> `next`/`previous`\n\t\tthis._trigger(\"goto\");\n\t\tif(callback){ callback(); }\n\t};\n\n\t/**\n\t * Return the current frame index\n\t * @method\n\t * @returns {Integer}\n\t */\n\tSpin360.prototype.getIndex = function(){\n\t\treturn this._comp.index;\n\t};\n\n\t// Extend Carousel with Advanceable interface\n\tAkamai.Advanceable.extend(Spin360);\n\n\t/**\n\t * Go to the next frame\n\t * @method\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.next;\n\t// required for JSDocs\n\n\t/**\n\t * Go to the previous frame\n\t * @method\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.previous;\n\t// required for JSDocs\n\n\t/**\n\t * Begin the automatic rotation of the images\n\t * @todo support passing in an interval or speed?\n\t * @method\n\t * @fires Akamai.Spin360#akamai-spin360-play\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.play = function(){\n\t\tthis._comp.autoRotate();\n\t};\n\n\t/**\n\t * Stop the automatic rotation of the images\n\t * @method\n\t * @fires Akamai.Spin360#akamai-spin360-pause\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.pause = function(){\n\t\tthis._comp.stopAutoRotate();\n\t};\n\n\tSpin360.prototype.getElement = function(){\n\t\treturn this._$el[0];\n\t};\n\n\tSpin360.States = {\n\t\tPlaying: 0,\n\t\tPaused: 1\n\t};\n\n\t/**\n\t * Return the current state of the spin360\n\t * @example Spin360 states\n\t * Akamai.Spin360.States = {\n\t *\tPlaying: 0,\n\t *\tPaused: 1\n\t * };\n\t *\n\t * @example Conditioning on states\n\t * if( spin360.getState() === Akamai.Spin360.States.Paused ) {\n\t *\t spin360.play()\n\t * }\n\t * @method\n\t * @returns {Akamai.Spin360.State}\n\t */\n\tSpin360.prototype.getState = function(){\n\t\t// TODO expose using method in Tau\n\t\treturn !!this._comp.autoInterval ? Spin360.States.Playing : Spin360.States.Paused;\n\t};\n\n\t/**\n\t * Triggered when the spin360 has stoped automatically spinning\n\t * {@link Akamai.Spin360#pause}.\n\t *\n\t * @event Akamai.Spin360#akamai-spin360-stop-spin\n\t */\n\n\t/**\n\t * Triggered when the spin360 has started automatically spinning\n\t * {@link Akamai.Spin360#play}.\n\t *\n\t * @event Akamai.Spin360#akamai-spin360-start-spin\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Spin360}.\n\t *\n\t * @event Akamai.Spin360#akamai-spin360-init\n\t */\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Spin360 = Spin360;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(){\n\tfunction MagnifierImpl(element, options){\n\t\tthrow new Error('Not allowed to instantiate MagnifierImpl');\n\t}\n\n\tMagnifierImpl.prototype.updateOptions = function(options){\n\t\tthrow new Error('magnifier updateOptions not implemented');\n\t};\n\n\tMagnifierImpl.prototype.zoomIn = function(){\n\t\tthrow new Error('magnifier zoomIn not implemented');\n\t};\n\n\tMagnifierImpl.prototype.zoomOut = function(){\n\t\tthrow new Error('magnifier zoomOut not implemented');\n\t};\n\n\tMagnifierImpl.prototype.isMagnified = function(){\n\t\tthrow new Error('magnifier isMagnified not implemented');\n\t};\n\n\tMagnifierImpl.prototype.toggleZoom = function(){\n\t\tthrow new Error('magnifier toggleZoom not implemented');\n\t};\n\n\tMagnifierImpl.prototype.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){\n\t\tthrow new Error('magnifier render not implemented');\n\t};\n\n\tvar ex = (typeof exports === 'undefined') ? window : exports;\n\tex.Akamai = ex.Akamai || {};\n\tex.Akamai.MagnifierImpl = MagnifierImpl;\n})();\n(function(){\n\tfunction MagnifierImplEnlarge(element, options){\n\t\tthis._$el = $(element);\n\t\tthis._$el.enlarge(options);\n\t}\n\n\tMagnifierImplEnlarge.prototype = Object.create(Akamai.MagnifierImpl.prototype);\n\tMagnifierImplEnlarge.prototype.constructor = MagnifierImplEnlarge;\n\n\tMagnifierImplEnlarge.prototype.updateOptions = function(options){\n\t\tthis._$el.enlarge( \"updateOptions\", options );\n\t};\n\n\tMagnifierImplEnlarge.prototype.zoomIn = function(){\n\t\tthis._$el.enlarge( \"in\" );\n\t};\n\n\tMagnifierImplEnlarge.prototype.zoomOut = function(){\n\t\tthis._$el.enlarge( \"out\" );\n\t};\n\n\tMagnifierImplEnlarge.prototype.isMagnified = function(){\n\t\treturn this._$el.enlarge( \"isZoomed\" );\n\t};\n\n\tMagnifierImplEnlarge.prototype.toggleZoom = function(){\n\t\tif(this.isMagnified()){\n\t\t\tthis.zoomOut();\n\t\t} else {\n\t\t\tthis.zoomIn();\n\t\t}\n\t};\n\n\tMagnifierImplEnlarge.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){\n\t\treturn `\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t${options.buttonText}\n\t\t\t\t\n\t\t\t
\n\t\t`;\n\t};\n\t\n\tvar ex = (typeof exports === 'undefined') ? window : exports;\n\tex.Akamai = ex.Akamai || {};\n\tex.Akamai.MagnifierImplEnlarge = MagnifierImplEnlarge;\n})();\n(function(){\n\tfunction ClickTracker(element, handler){\n\t\tvar self = this;\n\t\tself.element = element;\n\t\tself.handler = handler;\n\t\tthis.reset();\n\t}\n\n\tClickTracker.prototype.onDown = function(e){\n\t\tif (e.target == this.element) {\n\t\t\tif (this.isDown) {\n\t\t\t\tthis.reset();\n\t\t\t} else {\n\t\t\t\tthis.down.x = e.clientX;\n\t\t\t\tthis.down.y = e.clientY;\n\t\t\t\tthis.down.timestamp = new Date();\n\t\t\t\tthis.isDown = true;\n\t\t\t}\n\t\t}\n\t};\n\n\tClickTracker.prototype.onMove = function(){\n\t\tthis.isMoved = true;\n\t};\n\n\tClickTracker.prototype.onUp = function(e){\n\t\tif (this.isDown) {\n\t\t\tthis.up.x = e.clientX;\n\t\t\tthis.up.y = e.clientY;\n\t\t\tthis.up.timestamp = new Date();\n\n\t\t\tif (!this.isMoved && (this.down.x === this.up.x && this.down.y === this.up.y && (this.up.timestamp.getTime() - this.down.timestamp.getTime() <= 500))) {\n\t\t\t\tthis.handler();\n\t\t\t}\n\t\t}\n\n\t\tthis.reset();\n\t};\n\n\tClickTracker.prototype.reset = function(x, y){\n\t\tthis.down = {\n\t\t\tx: -Infinity,\n\t\t\ty: -Infinity,\n\t\t\ttimestamp: new Date(0)\n\t\t};\n\n\t\tthis.isDown = false;\n\t\tthis.isMoved = false;\n\n\t\tthis.up = {\n\t\t\tx: Infinity,\n\t\t\ty: Infinity,\n\t\t\ttimestamp: new Date()\n\t\t};\n\t};\n\n\tfunction MagnifierImplScroller(element, options){\n\t\tthis._$el = $(element);\n\t\tthis._options = options;\n\t\tthis._container = this._$el.get(0);\n\t\tthis._content = this._$el.children().get(0);\n\t\tthis._imgCast = $(this._content).find('div').get(0);\n\t\tthis._zoomInBtn = this._$el.find(\"a.scroller-zoom-in\").get(0);\n\t\tthis._zoomOutBtn = this._$el.find(\"a.scroller-zoom-out\").get(0);\n\t\tthis._magnification = 1;\n\n\t\tvar self = this;\n\n\t\t// Initialize Scroller\n\t\tthis.scroller = new Scroller(this._renderer(), {\n\t\t\tzooming: options.enabled,\n\t\t\tminZoom: 1,\n\t\t\tmaxZoom: options.magnification,\n\t\t\tanimationDuration: options.animationDuration\n\t\t});\n\n\t\tvar rect = this._container.getBoundingClientRect();\n\t\tthis.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop);\n\n\t\tthis._installEventHandlers();\n\t\tthis._updateButtonStates();\n\t\tsetTimeout(function(){\n\t\t\tself._onResize();\n\t\t});\n\t}\n\n\tMagnifierImplScroller.prototype = Object.create(Akamai.MagnifierImpl.prototype);\n\tMagnifierImplScroller.prototype.constructor = MagnifierImplScroller;\n\n\tMagnifierImplScroller.prototype.updateOptions = function(options){\n\t\tvar rect = this._container.getBoundingClientRect();\n\t\tthis.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop);\n\t\tthis._onResize();\n\t\tthis._zoomBy(1 / this._options.magnification, false);\n\t};\n\n\tMagnifierImplScroller.prototype.zoomIn = function(){\n\t\tthis._zoomBy(this._options.incrementalZoomFactor);\n\t};\n\n\tMagnifierImplScroller.prototype.cyclicZoom = function(){\n\t\tvar self = this;\n\t\tif (this._magnification >= this._options.magnification) {\n\t\t\tthis._zoomBy(1 / this._options.magnification);\n\t\t} else {\n\t\t\tthis.zoomIn();\n\t\t}\n\t};\n\n\tMagnifierImplScroller.prototype.zoomOut = function(){\n\t\tthis._zoomBy(1 / this._options.incrementalZoomFactor);\n\t};\n\n\tMagnifierImplScroller.prototype.isMagnified = function(){\n\t\treturn this._magnification > 1;\n\t};\n\n\tMagnifierImplScroller.prototype.toggleZoom = function(){\n\t\tthrow new Error('zoom toggle behavior is undefined in scroller mode');\n\t};\n\n\tMagnifierImplScroller.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){\n\t\treturn `\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t${options.zoomInBtnText}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t${options.zoomOutBtnText}\n\t\t\t\t\n\t\t\t
\n\t\t`;\n\t};\n\n\tMagnifierImplScroller.prototype._zoomBy = function(magnification, animate){\n\t\tvar self = this, magnification = Number(magnification);\n\n\t\tif (typeof(animate) === \"undefined\") {\n\t\t\tanimate = this._options.animateZoom;\n\t\t}\n\n\t\tif (magnification === 1 || \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// if we are magnifying by 1 OR\n\t\t\t(this._magnification === 1 && magnification < 1) || \t\t\t\t\t\t\t// fully zoomed out and still attempting a zoom out OR\n\t\t\t(this._magnification === this._options.magnification && magnification > 1)) {\t// fully zoomed in and still attempting a zoom in\n\t\t\treturn;\n\t\t}\n\n\t\tthis.scroller.zoomTo(this._magnification * magnification, this._options.animateZoom);\n\n\t\tsetTimeout(function(){\n\t\t\tself._onZoom();\n\t\t}, this._options.animationDuration);\n\t}\n\n\tMagnifierImplScroller.prototype._onZoom = function(){\n\t\tvar oldMagnification = this._magnification;\n\n\t\tthis._magnification = Number(this.scroller.getValues().zoom.toFixed(2));\n\t\tthis._updateButtonStates();\n\n\t\tif (oldMagnification > this._magnification) {\n\t\t\tthis._$el.trigger(\"scroller.after-zoom-out\");\n\t\t} else if (oldMagnification < this._magnification) {\n\t\t\tthis._$el.trigger(\"scroller.after-zoom-in\");\n\t\t}\n\t};\n\n\tMagnifierImplScroller.prototype._renderer = function(){\n\t\tvar docStyle = document.documentElement.style, self = this;\n\n\t\tvar engine;\n\t\tif (window.opera && Object.prototype.toString.call(opera) === '[object Opera]') {\n\t\t\tengine = 'presto';\n\t\t} else if ('MozAppearance' in docStyle) {\n\t\t\tengine = 'gecko';\n\t\t} else if ('WebkitAppearance' in docStyle) {\n\t\t\tengine = 'webkit';\n\t\t} else if (typeof navigator.cpuClass === 'string') {\n\t\t\tengine = 'trident';\n\t\t}\n\n\t\tvar vendorPrefix = {\n\t\t\ttrident: 'ms',\n\t\t\tgecko: 'Moz',\n\t\t\twebkit: 'Webkit',\n\t\t\tpresto: 'O'\n\t\t}[engine];\n\n\t\tvar helperElem = document.createElement(\"div\");\n\t\tvar undef;\n\n\t\tvar perspectiveProperty = vendorPrefix + \"Perspective\";\n\t\tvar transformProperty = vendorPrefix + \"Transform\";\n\n\t\tif (helperElem.style[perspectiveProperty] !== undef) {\n\t\t\treturn function(left, top, zoom) {\n\t\t\t\tself._content.style[transformProperty] = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')';\n\t\t\t};\n\n\t\t} else if (helperElem.style[transformProperty] !== undef) {\n\t\t\treturn function(left, top, zoom) {\n\t\t\t\tself._content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')';\n\t\t\t};\n\n\t\t} else {\n\t\t\treturn function(left, top, zoom) {\n\t\t\t\tself._content.style.marginLeft = left ? (-left/zoom) + 'px' : '';\n\t\t\t\tself._content.style.marginTop = top ? (-top/zoom) + 'px' : '';\n\t\t\t\tself._content.style.zoom = zoom || '';\n\t\t\t};\n\t\t}\n\t};\n\n\tMagnifierImplScroller.prototype._onResize = function(){\n\t\tthis.scroller.setDimensions(this._container.clientWidth, this._container.clientHeight, this._container.clientWidth, this._container.clientHeight);\n\t};\n\n\tMagnifierImplScroller.prototype._installEventHandlers = function(){\n\t\tvar self = this, clickTracker = new ClickTracker(this._imgCast, function(){\n\t\t\tself.cyclicZoom();\n\t\t});\n\n\t\twindow.addEventListener(\"resize\", function(e){\n\t\t\tself._onResize(e);\n\t\t}, false);\n\n\t\tthis._zoomInBtn.addEventListener(\"click\", function(e) {\n\t\t\te.preventDefault();\n\t\t\tself.zoomIn();\n\t\t}, false);\n\n\t\tthis._zoomOutBtn.addEventListener(\"click\", function(e) {\n\t\t\te.preventDefault();\n\t\t\tself.zoomOut();\n\t\t}, false);\n\n\t\tif ('ontouchstart' in window) {\n\t\t\tself._container.addEventListener(\"touchstart\", function(e) {\n\t\t\t\t// Don't react if initial down happens on one of the zoom buttons\n\t\t\t\tif (e.touches[0].target == self._$el.find('a.scroller-zoom-in').get(0) || e.touches[0].target == self._$el.find('a.scroller-zoom-out').get(0)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Don't react if initial down happens on a form element\n\t\t\t\tif (e.touches[0] && e.touches[0].target && e.touches[0].target.tagName.match(/input|textarea|select/i)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.scroller.doTouchStart(e.touches, e.timeStamp);\n\n\t\t\t\tfor (var i = 0; i < e.touches.length; i++) {\n\t\t\t\t\tclickTracker.onDown(e.touches[i]);\n\t\t\t\t}\n\n\t\t\t\te.preventDefault();\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"touchmove\", function(e) {\n\t\t\t\tself.scroller.doTouchMove(e.touches, e.timeStamp, e.scale);\n\t\t\t\tclickTracker.onMove();\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"touchend\", function(e) {\n\t\t\t\tself.scroller.doTouchEnd(e.timeStamp);\n\n\t\t\t\t// handle any zoom that may have occured\n\t\t\t\tself._onZoom();\n\n\t\t\t\tfor (var i = 0; i < e.changedTouches.length; i++) {\n\t\t\t\t\tclickTracker.onUp(e.changedTouches[i]);\n\t\t\t\t}\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"touchcancel\", function(e) {\n\t\t\t\tself.scroller.doTouchEnd(e.timeStamp);\n\t\t\t\t// handle any zoom that may have occured\n\t\t\t\tself._onZoom();\n\n\t\t\t\tfor (var i = 0; i < e.changedTouches.length; i++) {\n\t\t\t\t\tclickTracker.onUp(e.changedTouches[i]);\n\t\t\t\t}\n\t\t\t}, false);\n\t\t} else {\n\t\t\tself._container.addEventListener(\"mousedown\", function(e) {\n\t\t\t\tif (e.target.tagName.match(/input|textarea|select/i)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (self.scroller.__clientWidth === 0) {\n\t\t\t\t\tself._onResize();\n\t\t\t\t}\n\t\t\t\tself.scroller.doTouchStart([{\n\t\t\t\t\tpageX: e.pageX,\n\t\t\t\t\tpageY: e.pageY\n\t\t\t\t}], e.timeStamp);\n\n\t\t\t\tclickTracker.onDown(e);\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"mousemove\", function(e) {\n\t\t\t\tif (!clickTracker.isDown) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.scroller.doTouchMove([{\n\t\t\t\t\tpageX: e.pageX,\n\t\t\t\t\tpageY: e.pageY\n\t\t\t\t}], e.timeStamp);\n\n\t\t\t\tclickTracker.onMove();\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"mouseup\", function(e) {\n\t\t\t\tif (!clickTracker.isDown) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.scroller.doTouchEnd(e.timeStamp);\n\t\t\t\tclickTracker.onUp(e);\n\t\t\t}, false);\n\n\t\t\t// self._container.addEventListener(navigator.userAgent.indexOf(\"Firefox\") > -1 ? \"DOMMouseScroll\" : \"mousewheel\", function(e) {\n\t\t\t// \te.preventDefault();\n\t\t\t// \tself.scroller.doMouseZoom(e.detail ? (e.detail * -120) : e.wheelDelta, e.timeStamp, e.pageX, e.pageY);\n\t\t\t// }, false);\n\t\t}\n\t};\n\n\tMagnifierImplScroller.prototype._updateButtonStates = function(){\n\t\tif (this._magnification === 1) {\n\t\t\t$(this._$el).addClass('scroller-zoom-out-max');\n\t\t\t$(this._$el).removeClass('scroller-zoom-in-max');\n\t\t} else if (this._magnification === this._options.magnification) {\n\t\t\t$(this._$el).addClass('scroller-zoom-in-max');\n\t\t\t$(this._$el).removeClass('scroller-zoom-out-max');\n\t\t} else {\n\t\t\t$(this._$el).removeClass('scroller-zoom-in-max');\n\t\t\t$(this._$el).removeClass('scroller-zoom-out-max');\n\t\t}\n\t};\n\n\tvar ex = (typeof exports === 'undefined') ? window : exports;\n\tex.Akamai = ex.Akamai || {};\n\tex.Akamai.MagnifierImplScroller = MagnifierImplScroller;\n})();\n(function(exports, $){\n\t/**\n\t * Image magnifier\n\t * @class\n\t * @alias Akamai.Magnifier\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\tvar Magnifier = Akamai.Util.component(\"Magnifier\", function(element, options){\n\t\t// compat with enlarge `disabled` option\n\t\tthis._options.disabled = !this._options.enabled;\n\t\tif (this._options.mode === Magnifier.MODE_ANIMATED_ZOOM) {\n\t\t\tthis._impl = new Akamai.MagnifierImplScroller(element, options);\n\t\t} else {\n\t\t\tthis._impl = new Akamai.MagnifierImplEnlarge(element, options);\n\t\t}\n\t});\n\n\tMagnifier.prototype._updateOptions = function(options){\n\t\tif( !options ) {\n\t\t\tthis._options = this._originalOptions;\n\t\t}\n\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// compat with enlarge `disabled` option\n\t\tthis._options.disabled = !this._options.enabled;\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Magnifier.defaultOptions, this._options, \"Magnifier\");\n\n\t\tthis._impl.updateOptions(this._options);\n\t};\n\n\t// Used in preflight to \"rename\" events based on the child component events\n\tMagnifier._componentEventMapping = {\n\t\t\"enlarge.after-zoom-in\": \"in\",\n\t\t\"enlarge.after-zoom-out\": \"out\",\n\t\t\"scroller.after-zoom-in\": \"in\",\n\t\t\"scroller.after-zoom-out\": \"out\"\n\t};\n\n\tMagnifier.MODE_HOVER_ZOOM = 'hoverzoom';\n\tMagnifier.MODE_ANIMATED_ZOOM = 'animatedzoom';\n\n\t/**\n\t * Defines the global default options for all Magnifiers on the page\n\t * @static\n\t * @property {String} mode - Set what mode to run the magnifier in. There are two modes available: `hoverzoom` and `animatedzoom`. The `hoverzoom` mode provides the ability to magnify and pan the image by just hovering your mouse over the image. It also provides a `flyout` option where a clip of the zoomed in image is displayed on a separate widget floating somewhere around (configurable) the image. The `animatedzoom` mode only does inline magnification but provides smooth animation between magnification levels, it also allows for incremental zooming.\n\t * @property {Boolean} button - Whether to show a button for toggling magnification (default: true)\n\t * @property {Boolean} enabled - Enabled/disable magnification (default: true, breakpoints supported)\n\t * @property {Integer} magnification - The scale factor to magnify the image: `2`, `3` (default), `4`, `4.5`, etc\n\n\t * @property {Array} image.widths - List of available widths for an image (to be combined with image.widthParam) - (default: [\"320\",\"640\",\"800\",\"1024\",\"2048\",\"5000\"])\n\t * @property {String} image.sizes - Value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw` (default), `200px`, `(min-width:1000px) 500px, 100vw` - (default: `100vw`, breakpoints supported)\n\t * @property {String} image.policy - Query param value for policy, appended to &policy= per image url when specified. Values: `foo`. (default: undefined)\n\n\t * @property {Integer} delay - Only applicable in `hoverzoom` mode. The time delay in milliseconds between mouse hover and magnification (default: 300, breakpoints supported)\n\t * @property {String} buttonText - Only applicable in `hoverzoom` mode. Text for the zoom button. Also used for its title attribute. (default: \"Toggle Image Magnification\")\n\t * @property {Integer} flyout.width - Only applicable in `hoverzoom` mode. Width of the flyout image (default: 200)\n\t * @property {Integer} flyout.height - Only applicable in `hoverzoom` mode. Height of the flyout image (default: 200)\n\t * @property {Boolean} hoverZoomWithoutClick - Only applicable in `hoverzoom` mode. Zoom starts on mouse hover with no click needed (default: true; false will require a click to hover-zoom)\n\t * @property {String} placement - Only applicable in `hoverzoom` mode. Placement of the magnified image: `inline` , `flyoutloupe`, `flyouttopleft`,`flyoutbottomleft` ,`flyouttopright` and `flyoutbottomright` - (default: inline, breakpoins supported)\n\n\t * @property {Float} incrementalZoomFactor - Only applicable in `animatedzoom` mode. A number by which to incrementally zoom up until the specified `magnification`, default `3`. For example a `magnification` of `4` and an `incrementalZoomFactor` of `2` will zoom the image in `2` steps\n\t * @property {Boolean} animateZoom - Only applicable in `animatedzoom` mode. Animates the magnification process. Default `true`\n\t * @property {Integer} animationDuration - Only applicable in `animatedzoom` mode. If `animateZoom` is true, this specifies the length of the animation in milliseconds. Default `250`\n\t * @property {String} zoomInBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom in button. Default `Zoom In`\n\t * @property {String} zoomOutBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom out button. Default `Zoom Out`\n\t */\n\tMagnifier.defaultOptions = {\n\t\t// general options\n\t\tmode: Magnifier.MODE_HOVER_ZOOM,\n\t\tbutton: true,\n\t\tenabled: true,\n\t\tmagnification: 3,\n\n\t\t// image options\n\t\timage: Akamai.Image.defaultConfig,\n\n\t\t// hoverzoom options\n\t\tdelay: 300,\n\t\tbuttonText: \"Toggle Image Magnification\",\n\t\tflyout: {\n\t\t\twidth: 200,\n\t\t\theight: 200\n\t\t},\n\t\thoverZoomWithoutClick: true,\n\t\tplacement: \"inline\",\n\n\t\t// animatedzoom options\n\t\tincrementalZoomFactor: 3,\n\t\tanimateZoom: true,\n\t\tanimationDuration: 250,\n\t\tzoomInBtnText: 'Zoom In',\n\t\tzoomOutBtnText: 'Zoom Out'\n\t};\n\n\t// srcset stuff\n\tAkamai.Sourceable.extendStatic(Magnifier);\n\n\tMagnifier.render = function(json, options){\n\t\tvar finalOptions = Akamai.Util.options(Magnifier.defaultOptions, options);\n\t\tvar dataAttr = Magnifier._dataAttr;\n\t\tvar fallbackSrc = Magnifier._fallbackSrc(json.url, finalOptions.image);\n\t\tvar largestSrc = Magnifier._largestSrc(json.url, finalOptions.image);\n\t\tvar srcSet = Magnifier._srcset(json.url, finalOptions.image)\n\t\tvar sizes = finalOptions.image.sizes;\n\t\tvar impl = (finalOptions.mode === Magnifier.MODE_ANIMATED_ZOOM) ? Akamai.MagnifierImplScroller : Akamai.MagnifierImplEnlarge;\n\t\treturn impl.render(finalOptions, dataAttr, fallbackSrc, largestSrc, srcSet, sizes);\n\t};\n\n\t/**\n\t * Enter magnifier mode\n\t * @method\n\t * @fires Akamai.Magnifier#akamai-magnifier-in\n\t * @returns {undefined}\n\t */\n\tMagnifier.prototype.in = function(){\n\t\tthis._impl.zoomIn();\n\t};\n\n\t/**\n\t * Exit magnifier mode\n\t * @method\n\t * @fires Akamai.Magnifier#akamai-magnifier-out\n\t * @returns {undefined}\n\t */\n\tMagnifier.prototype.out = function(){\n\t\tthis._impl.zoomOut();\n\t};\n\n\t/**\n\t * Return the current state of the magnifier\n\t * @method\n\t * @returns {Boolean}\n\t */\n\tMagnifier.prototype.isMagnified = function(){\n\t\treturn this._impl.isMagnified();\n\t};\n\n\t/**\n\t * Toggle the state of the magnifier\n\t * @method\n\t * @fires Akamai.Magnifier#akamai-magnifier-in\n\t * @fires Akamai.Magnifier#akamai-magnifier-out\n\t * @returns {undefined}\n\t */\n\tMagnifier.prototype.toggle = function(){\n\t\tthis._impl.toggleZoom();\n\t};\n\n\t/**\n\t * Triggered when the magnifier has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carouse#in}.\n\t *\n\t * @event Akamai.Magnifier#akamai-magnifier-in\n\t */\n\n\t/**\n\t * Triggered when the magnifier has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carouse#out}.\n\t *\n\t * @event Akamai.Magnifier#akamai-magnifier-out\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Magnifier}.\n\t *\n\t * @event Akamai.Magnifier#akamai-magnifier-init\n\t */\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Magnifier = Magnifier;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\tvar VideoImpl = Akamai.Util.component(\"VideoImpl\", function(element, options){\n\t\tthrow new Error('Not allowed to instantiate VideoImpl');\n\t});\n\n\tVideoImpl.render = function(json, options){\n\t\tthrow new Error('VideoImpl.render not implemented');\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.VideoImpl = VideoImpl;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\tvar VideoImplPristine = Akamai.Util.component(\"VideoImplPristine\", function(element, options){\n\t});\n\n\tVideoImplPristine.prototype = Object.create(Akamai.VideoImpl.prototype);\n\tVideoImplPristine.prototype.constructor = VideoImplPristine;\n\n\tVideoImplPristine.render = function(json, options){\n\t\tvar finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options);\n\n\t\tvar url = json.url;\n\t\tvar poster = json.poster || \"\";\n\t\tvar mime = (json.mime) ? 'type=\"' + json.mime + '\"' : '';\n\n\t\tvar attrs = [\n\t\t\tfinalOptions.loop ? \"loop\" : \"\",\n\t\t\tfinalOptions.autoplay ? \"autoplay\" : \"\",\n\t\t\tfinalOptions.controls ? \"controls\" : \"\",\n\t\t\tfinalOptions.muted ? \"muted\" : \"\",\n\t\t\t\"playsinline\"\n\t\t];\n\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t`);\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.VideoImplPristine = VideoImplPristine;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\tvar VideoImplIm = Akamai.Util.component(\"VideoImplIm\", function(element, options){\n\t\tvar finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options);\n\t\tVideoImplIm._validateOptions(finalOptions);\n\t});\n\n\tVideoImplIm.prototype = Object.create(Akamai.VideoImpl.prototype);\n\tVideoImplIm.prototype.constructor = VideoImplIm;\n\n\tVideoImplIm._validateOptions = function(options){\n\t\tif (!Array.isArray(options.sizes) || options.sizes.length !== 3) {\n\t\t\tthrow new Error('Akamai.Video sizes must have three entries');\n\t\t}\n\n\t\tif (options.sizes.some(function (size) {\n\t\t\treturn isNaN(size);\n\t\t})) {\n\t\t\tthrow new Error('Akamai.VideoImplIm all sizes must be numbers');\n\t\t}\n\t}\n\n\tVideoImplIm.render = function(json, options){\n\t\tvar finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options);\n\t\tVideoImplIm._validateOptions(finalOptions);\n\n\t\tvar url = json.url;\n\t\tvar poster = json.poster || \"\";\n\t\tvar width;\n\n\t\tvar viewPortWidth = window.innerWidth;\n\n\t\tif (viewPortWidth < 992) {\n\t\t\twidth = finalOptions.sizes[0];\n\t\t} else if (viewPortWidth < 1200) {\n\t\t\twidth = finalOptions.sizes[1];\n\t\t} else {\n\t\t\twidth = finalOptions.sizes[2];\n\t\t}\n\n\t\tvar attrs = [\n\t\t\tfinalOptions.loop ? \"loop\" : \"\",\n\t\t\tfinalOptions.autoplay ? \"autoplay\" : \"\",\n\t\t\tfinalOptions.controls ? \"controls\" : \"\",\n\t\t\tfinalOptions.muted ? \"muted\" : \"\",\n\t\t\t\"playsinline\"\n\t\t];\n\n\t\tvar joiner = url.indexOf('?') === -1 ? '?' : '&';\n\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t`);\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.VideoImplIm = VideoImplIm;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t/**\n\t * Video component\n\t * @class\n\t * @alias Akamai.Video\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\tvar Video = Akamai.Util.component(\"Video\", function(element, options){\n\t\t// TODO\n\t\tthis._comp = this._$el.length && undefined;\n\t\tthis._$videoElement = this._$el.find(\"video\");\n\t\tthis._videoElement = this._$videoElement[0];\n\n\t\tif( !this._videoElement ){\n\t\t\tthrow new Error( \"Akamai.Video requires a child HTML Video element\" );\n\t\t}\n\n\t\tif( !this._videoElement.play || !this._videoElement.pause ){\n\t\t\tthis._unsupportedAPI = true;\n\t\t\tthis._unsupported();\n\t\t\treturn;\n\t\t}\n\n\t\t// get the initial state (could be autoplaying on render)\n\t\tthis._setState(this._videoElement.paused ? Video.States.Paused : Video.States.Playing);\n\n\t\t// state bindings\n\t\tthis._$el\n\t\t\t.bind(\"akamai-video-play\", function(){\n\t\t\t\tthis._setState(Video.States.Playing);\n\t\t\t}.bind(this))\n\t\t\t.bind(\"akamai-video-pause\", function(){\n\t\t\t\tthis._setState(Video.States.Paused);\n\t\t\t}.bind(this));\n\n\t\tthis._createPlayButton();\n\t});\n\n\tVideo.MODE_IM = 'im';\n\tVideo.MODE_PRISTINE = 'pristine';\n\n\tVideo._componentEventMapping = {\n\t\t\"play\": {\n\t\t\tto: \"play\",\n\t\t\tselector: \"video\"\n\t\t},\n\n\t\t\"pause\": {\n\t\t\tto: \"pause\",\n\t\t\tselector: \"video\"\n\t\t},\n\n\t\t\"seeked\": {\n\t\t\tto: \"seek\",\n\t\t\tselector: \"video\"\n\t\t }\n\t};\n\n\tVideo.prototype._updateOptions = function(options){\n\t\tif( !options ) {\n\t\t\tthis._options = this._originalOptions;\n\t\t}\n\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Video.defaultOptions, this._options, \"Video\");\n\n\t\t// TODO see Magnifier for example\n\t};\n\n\t/**\n\t * Defines the global default options for all Spin360s on the page\n\t * @static\n\t * @property {Boolean} autoplay - Automatically play the video on load (default: false)\n\t * @property {Boolean} controls - Display the video controls (default: true)\n\t * @property {Boolean} loop - Restart the video when it reaches the end (default: false)\n\t * @property {Boolean} muted - Mute the video (default: true)\n\t * @property {String} mode - What video component to use. `im` will let you use any size pristine video and will automatically request & generate the right size when the page is loaded. `pristine` just passes through the original video into the video player and this is what will always play on the users device. Default: `pristine`\n\t * @property {Array} sizes - Video sizes (widths) to use for different screen widths. Defaults: [1920 (large screens), 1280 (tablet), 854 (mobile), ]\n\t */\n\tVideo.defaultOptions = {\n\t\tautoplay: false,\n\t\tcontrols: true,\n\t\tloop: false,\n\t\tmuted: true,\n\t\tmode: Video.MODE_PRISTINE,\n\t\tsizes: [854, 1280, 1920]\n\t};\n\n\tVideo.render = function(json, options){\n\t\tvar impl = (options && options.mode === Video.MODE_IM) ? Akamai.VideoImplIm : Akamai.VideoImplPristine;\n\t\treturn impl.render(json, options);\n\t};\n\n\tVideo.States = {\n\t\tPlaying: 0,\n\t\tPaused: 1\n\t};\n\n\tVideo.prototype._unsupported = function(){\n\t\tif( this._unsupportedAPI ){\n\t\t\tAkamai.Util.log(\"Video: video API not supported\", 'error');\n\t\t}\n\n\t\treturn this._unsupportedAPI;\n\t};\n\n\t/**\n\t * Play the video, idempotent\n\t * @method\n\t * @fires Akamai.Video#akamai-video-play\n\t * @returns {undefined}\n\t */\n\tVideo.prototype.play = function(){\n\t\tif( this._unsupported() ) { return; }\n\t\tthis._videoElement.play();\n\t};\n\n\t/**\n\t * Pause the video\n\t * @method\n\t * @fires Akamai.Video#akamai-video-pause\n\t * @returns {undefined}\n\t */\n\tVideo.prototype.pause = function(){\n\t\tif( this._unsupported() ) { return; }\n\t\tthis._videoElement.pause();\n\t};\n\n\t/**\n\t * Return the current state of the video\n\t * @example Video states\n\t * Akamai.Video.States = {\n\t *\tPlaying: 0,\n\t *\tPaused: 1\n\t * };\n\t *\n\t * @example Conditioning on states\n\t * if( video.getState() === Akamai.Video.States.Paused ) {\n\t *\t video.play()\n\t * }\n\t * @method\n\t * @returns {Akamai.Video.State}\n\t */\n\tVideo.prototype.getState = function(){\n\t\treturn this._state;\n\t};\n\n\tVideo.prototype._setState = function(value){\n\t\tthis._state = value;\n\t};\n\n\t/**\n\t * Seek to the input percentage.\n\t * @method\n\t * @fires Akamai.Video#akamai-video-seek\n\t * @param {Integer} percent - value between 0 and 100 percent for seeking\n\t * @returns {undefined}\n\t */\n\tVideo.prototype.seek = function(percent){\n\t\tif( percent < 0 || 100 < percent ) {\n\t\t\tthrow new Error( \"seek takes a an integer between 0 and 100\" );\n\t\t}\n\n\t\tvar newTime = (percent / 100) * (this._videoElement.duration || 1);\n\t\tthis._videoElement.currentTime = newTime;\n\t};\n\n\t/**\n\t * Set whether the video should loop or not\n\t * @method\n\t * @param {Boolean} value - The value true or false\n\t * @returns {undefined}\n\t */\n\tVideo.prototype.setLoop = function(value){\n\t\tthis._videoElement.loop = value;\n\t};\n\n\tVideo.prototype.getElement = function(){\n\t\treturn this._$el[0];\n\t};\n\n\t// Borrowed from https://codepen.io/chrisnager/pen/jPrJgQ\n\tVideo.prototype._createPlayButton = function(){\n\t\tvar videoPlayButton;\n\t\tvar videoWrapper = this._$el[0];\n\t\tvar video = this._videoElement;\n\n\t\tif(this._options.autoplay){\n\t\t\treturn;\n\t\t}\n\n\t\t// TODO move to render?\n\t\tvideoWrapper.insertAdjacentHTML('beforeend', `\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t`);\n\n\t\tvideo.removeAttribute('controls');\n\t\tvideoPlayButton = this._$el.find('svg')[0];\n\n\t\tvideoPlayButton.addEventListener('click', function(){\n\t\t\tvideo.play();\n\t\t\tvideoPlayButton.classList.add('is-hidden');\n\t\t\tif( this._options.controls ) {\n\t\t\t\tvideo.setAttribute('controls', 'controls');\n\t\t\t}\n\t\t}.bind(this));\n\t};\n\n\t/**\n\t * Triggered when the video has been played. See {@link Akamai.Video#play}.\n\t *\n\t * @event Akamai.Video#akamai-video-play\n\t */\n\n\t/**\n\t * Triggered when the video has been paused. See {@link Akamai.Video#pause}.\n\t *\n\t * @event Akamai.Video#akamai-video-pause\n\t */\n\n\t/**\n\t * Triggered when the video has seeked to a position. See {@link Akamai.Video#seek}.\n\t *\n\t * @event Akamai.Video#akamai-video-seek\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Video}.\n\t *\n\t * @event Akamai.Video#akamai-video-init\n\t */\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Video = Video;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t/**\n\t * Image carousel\n\t * @class\n\t * @alias Akamai.Carousel\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\tvar Carousel = Akamai.Util.component(\"Carousel\", function(element, options){\n\t\tthis._spins = Akamai.Spin360.createMany(this._$el[0], this._options.spin360);\n\t\tthis._magnifiers = Akamai.Magnifier.createMany(this._$el[0], this._options.magnifier);\n\t\tthis._videos = Akamai.Video.createMany(this._$el[0], this._options.video);\n\n\t\tthis._$el.snapper(this._options);\n\n\t\tthis._setAspectRatio();\n\t\tthis._bindAspectAttributes();\n\n\t\tif( this._options.slideshow.autostart ){\n\t\t\tsetTimeout(function(){\n\t\t\t\tthis.startSlideshow();\n\t\t\t}.bind(this), this._options.slideshow.interval);\n\t\t}\n\n\t\t// NOTE !! the following two bindings must happen in order, the\n\t\t// autoplayables binding relies on the attribut\n\n\t\t// add item attrs and maintain the active item attributes to determine the\n\t\t// active item state\n\t\tthis._activeItemAttributes();\n\t\tthis._$el.bind( \"akamai-carousel-goto\", this._activeItemAttributes.bind(this));\n\n\t\t// TODO pause autoplay videos that are not visible\n\t\tthis._handleAutoplayables();\n\t\tthis._$el.bind( \"akamai-carousel-goto\", this._handleAutoplayables.bind(this));\n\n\t\tthis._$el\n\t\t\t.bind( \"tau.touch-tracking-start\", function(){\n\t\t\t\tthis._$el.find(\".snapper_pane\").addClass(\"no-scroll\");\n\t\t\t}.bind(this))\n\t\t\t.bind( \"tau.touch-tracking-stop\", function(){\n\t\t\t\tthis._$el.find(\".snapper_pane\").removeClass(\"no-scroll\");\n\t\t\t}.bind(this));\n\n\t\t// when child components are doing things, stop the carousel from\n\t\t// automatically advancing\n\t\tthis._$el.bind( this.constructor._stopSlideshowEvents.join(\" \"), this.stopSlideshow.bind(this));\n\t});\n\n\tCarousel._stopSlideshowEvents = [\n\t\t\"akamai-magnifier-in\",\n\t\t\"akamai-magnifier-out\",\n\t\t\"akamai-spin360-goto\",\n\t\t\"akamai-video-play\",\n\t\t\"akamai-video-seek\"\n\t];\n\n\t// clearly there should be an autoplayable interface\n\tCarousel.prototype._handleAutoplayables = function(){\n\t\tvar isParentActive = function(comp){\n\t\t\treturn !!$(comp.getElement())\n\t\t\t\t.closest(\"[\" + this.constructor.activeItemAttr + \"]\")\n\t\t\t\t.length;\n\t\t}.bind(this);\n\n\t\tthis._videos.concat(this._spins).forEach(function(comp){\n\t\t\tif( isParentActive(comp) && comp._carouselWasPlaying ) {\n\t\t\t\tcomp.play();\n\t\t\t} else {\n\t\t\t\tif( comp.getState() == comp.constructor.States.Playing ){\n\t\t\t\t\tcomp._carouselWasPlaying = true;\n\t\t\t\t\tcomp.pause();\n\t\t\t\t} else {\n\t\t\t\t\tcomp._carouselWasPlaying = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}.bind(this));\n\t};\n\n\t// Unique counter for IDs\n\tCarousel.counter = 0;\n\n\tCarousel.prototype._updateOptions = function(options){\n\t\tif( !options ) {\n\t\t\tthis._options = this._originalOptions;\n\t\t}\n\n\t\t// update the current options\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// update the options for each of the subcomponents\n\t\tvar update = function(comp, name){\n\t\t\tcomp._updateOptions(this._options[name]);\n\t\t}.bind(this);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Carousel.defaultOptions, this._options, \"Carousel\");\n\t\tthis._$el.snapper( \"updateOptions\", options );\n\n\t\tthis._spins.forEach(function(c){ update(c, \"spin360\"); });\n\t\tthis._magnifiers.forEach(function(c){ update(c, \"magnifier\"); });\n\t\tthis._videos.forEach(function(c){ update(c, \"video\"); });\n\n\t\tthis._setAspectRatio();\n\t};\n\n\t// Used in preflight to \"rename\" events based on the child component events\n\tCarousel._componentEventMapping = {\n\t\t\"snapper.after-snap\": \"goto\",\n\t\t\"snapper.snap\": \"snap\",\n\t\t\"snapper.after-next\": \"next\",\n\t\t\"snapper.after-prev\": \"previous\"\n\t};\n\n\t/**\n\t * Defines the global default options for all Carousels on the page\n\t * @static\n\t * @property {Boolean} arrows - Show carousel controls - (default: true)\n\t * @property {Number} aspectratio - Specify a percentage-based height for the carousel, relative to the width. Values: `false`, `100`, `45.6`, `78` - (default `false`, breakpoints supported)\n\t * @property {Integer} slideshow.interval - Time in milliseconds between slide advances - (default: 4000, breakpoints supported)\n\t * @property {Integer} slideshow.autostart - Start the slideshow on instantiation - (default: false)\n\t * @property {String} thumbnail.placement - Placement relative to the slide container: `left`, `right`, `bottom` (default: `bottom`, breakpoints supported)\n\t * @property {String} thumbnail.type - Type of thumbnail: `dots`, `none`, `images` - (default: `images`, breakpoints supported)\n\t * @property {String} thumbnail.policy - String to use for query parameter, ex: \"&policy=\" (default: undefined)\n\t * @property {String} thumbnail.sizes - Sizes attribute value to use if thumbnail policy is set. Values: `300px`, `200px`, `(min-width:1000px) 300px, 100px` - (default: `300px`)\n\t * @property {Object} images - Defaults to the Magnifier image option's settings\n\t */\n\tCarousel.defaultOptions = {\n\t\tarrows: true,\n\n\t\taspectratio: false,\n\n\t\tslideshow: {\n\t\t\tinterval: 4000,\n\t\t\tautostart: false\n\t\t},\n\n\t\tthumbnail: {\n\t\t\tplacement: \"bottom\",\n\t\t\ttype: \"images\",\n\t\t\tpolicy: undefined,\n\t\t\tsizes: \"300px\"\n\t\t},\n\n\t\timages: Akamai.Image.defaultConfig\n\t};\n\n\tCarousel.renderMapping = {\n\t\timage: Akamai.Magnifier,\n\t\tspin360: Akamai.Spin360,\n\t\tvideo: Akamai.Video\n\t};\n\n\tCarousel._typeMapping = {\n\t\timage: \"magnifier\"\n\t};\n\n\tCarousel._uniqueItemId = function(item, i) {\n\t\treturn \"akamai-carousel-\" + Carousel.counter + \"-\" + item.type + \"-\" + i;\n\t};\n\n\t// srcset stuff\n\tAkamai.Sourceable.extendStatic(Carousel);\n\n\tCarousel._renderItem = function(item, i, options) {\n\t\tvar mappedType = Carousel._typeMapping[item.type] || item.type;\n\n\t\tif( !Carousel.renderMapping[item.type] ){\n\t\t\tthrow new Error(`\n\t\t\t\titem type ${item.type} at index ${i} must be 'image', 'spin360', or 'video'\n\t\t\t`);\n\t\t}\n\n\t\treturn `\n\t\t\t
\n\t\t\t\t${Carousel.renderMapping[item.type].render(item, options[mappedType])}\n\t\t\t
\n\t\t`;\n\t};\n\n\tCarousel._renderThumbnailAnchor = function(item, data, i, options) {\n\t\tvar thumbUrl = \"\";\n\t\tvar altText = item.alt || \"\";\n\n\t\t// use the video poster, the canonical url, or the first in a sequence\n\t\tif( item.type === 'video' ){\n\t\t\tthumbUrl = item.poster || data.reduce(function(acc, i){\n\t\t\t\treturn acc || Carousel._thumbUrl(i);\n\t\t\t}, \"\");\n\t\t} else {\n\t\t\tthumbUrl = Carousel._thumbUrl(item);\n\t\t}\n\n\t\t// TODO this sucks\n\t\toptions.images.policy = options.thumbnail.policy;\n\n\t\tvar src = Carousel._fallbackSrc( thumbUrl, options.images );\n\t\tvar srcset = Carousel._srcset( thumbUrl, options.images );\n\t\tvar sizes = options.images.sizes;\n\n\t\t// if there's a thumbnail policy, the thumbnails will be fresh image requests,\n\t\t// so they should have better sizes attribute values\n\t\tif( options.thumbnail.policy ){\n\t\t\tsizes = options.thumbnail.sizes;\n\t\t}\n\n\t\tvar attrs = [\n\t\t\t`${Carousel._dataAttr}-thumb-type=\"${item.type}\"`,\n\t\t\titem.type == \"video\" && !item.poster ? \"${Carousel._dataAttr}-thumb-noposter\" : \"\"\n\t\t];\n\n\t\treturn `\n\t\t\t\n\t\t\t\t\"${altText}\"\n\t\t\t\n\t\t`;\n\t};\n\n\tCarousel._thumbUrl = function(item){\n\t\treturn item.type === 'video' ? item.poster : item.url || (item.urls && item.urls[0]);\n\t};\n\n\tCarousel._renderThumbnails = function(data, options) {\n\t\tif(data.length <= 1 ){\n\t\t\treturn \"\";\n\t\t};\n\n\t\treturn `\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t${Akamai.Util.map(data, function(item, i){\n\t\t\t\t\t\treturn Carousel._renderThumbnailAnchor(item, data, i, options);\n\t\t\t\t\t}).join(\"\\n\")}\n\t\t\t\t
\n\t\t\t
\n\t\t`;\n\t};\n\n\tCarousel.prototype._setAspectAttributes = function( $el ){\n\t\tvar orientationAttr = `${Carousel._dataAttr}-aspectratio-orientation`;\n\t\tif( $el.height() > $el.width() ) {\n\t\t\t$el.attr( orientationAttr, \"portrait\" );\n\t\t} else if( $el.height() < $el.width() ) {\n\t\t\t$el.attr( orientationAttr, \"landscape\" );\n\t\t} else {\n\t\t\tif ($el.parent().height() >= $el.parent().width()) {\n\t\t\t\t$el.attr( orientationAttr, \"landscape\" );\n\t\t\t} else {\n\t\t\t\t$el.attr( orientationAttr, \"portrait\" );\n\t\t\t}\n\t\t}\n\t};\n\n\tCarousel.prototype._setAspectRatio = function(){\n\t\tvar value = this._options.aspectratio || 0;\n\t\tvar $items = this._$el.find( \".snapper_item\" );\n\t\t$items.css( \"padding-top\", (value / $items.length) + \"%\" );\n\t};\n\n\tCarousel.prototype._bindAspectAttributes = function(){\n\t\tif( this._options.aspectratio === false ){\n\t\t\treturn;\n\t\t}\n\n\t\tvar value = this._options.aspectratio || 0;\n\t\tvar self = this;\n\t\tvar $items = this._$el.find( \".snapper_item\" );\n\n\t\t$items.each(function(){\n\t\t\tvar loadBound;\n\t\t\tvar $item = $(this);\n\n\t\t\tvar $loadable = $item.find( \"img, video\" ).eq(0);\n\n\t\t\t$loadable.bind(\"load loadedmetadata\", loadBinding = function(){\n\t\t\t\tclearTimeout(loadBound);\n\n\t\t\t\tif( $item.is(`[${Akamai.Spin360._dataAttr}]`) ){\n\t\t\t\t\t// TODO it's not always a canvas, sometimes it's a collection of\n\t\t\t\t\t// images based on settings\n\t\t\t\t\tself._setAspectAttributes( $item.find( \"canvas\" ) );\n\t\t\t\t}\telse{\n\t\t\t\t\tself._setAspectAttributes( $loadable );\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tloadBound = setTimeout(loadBinding, 5000);\n\t\t});\n\n\t\t$items.eq(0).find( \"img,video\" ).eq(0).bind( \"load loadedmetadata\", function(){\n\t\t\tself._trigger( \"first-media-load\" );\n\t\t});\n\t};\n\n\tCarousel.render = function(data, options){\n\t\toptions = options || {};\n\n\t\t// establish the extended default options for the carousel\n\t\tvar carouselOptions = Akamai.Util.options(Carousel.defaultOptions, options);\n\n\t\tvar templateAttrs = [\n\t\t\t`${Carousel._dataAttr}`,\n\t\t\t`${Carousel._dataAttr}-item-count=\"${(data || []).length}\"`,\n\t\t\t`data-snapper-deeplinking=\"false\"`,\n\t\t\tcarouselOptions.arrows ? \"data-snapper-nextprev\" : \"\"\n\t\t];\n\n\t\tCarousel.counter++;\n\n\t\t// TODO can we do something about the `enlarge_pane` class here, seems it\n\t\t// could be conditioned on at least one regular image type in the json\n\t\t// TODO options should dictate `data-snapper-nextprev`\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t${Akamai.Util.map(data, function(item, i){\n\t\t\t\t\t\t\t\t\treturn Carousel._renderItem(item, i, carouselOptions);\n\t\t\t\t\t\t\t\t}).join(\"\\n\")}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t${Carousel._renderThumbnails(data, carouselOptions)}\n\t\t\t
\n\t\t`);\n\t};\n\n\t/**\n\t * Instantiate Carousels by looking for children matching\n\t * [data-akamai-carousel] in `element` param\n\t * @method\n\t * @static\n\t * @param {HTMLElement} element - the element to search in for\n\t * @returns {Akamai.Carousel[]}\n\t */\n\n\n\t/**\n\t * @method\n\t * @returns {Akamai.Spin360[]}\n\t */\n\tCarousel.prototype.getSpin360s = function(){\n\t\treturn this._spins;\n\t};\n\n\t/**\n\t * @method\n\t * @returns {Akamai.Magnifier[]}\n\t */\n\tCarousel.prototype.getMagnifiers = function(){\n\t\treturn this._magnifiers;\n\t};\n\n\t/**\n\t * @method\n\t * @returns {Akamai.Video[]}\n\t */\n\tCarousel.prototype.getVideos = function(){\n\t\treturn this._videos;\n\t};\n\n\t/**\n\t * Go to a particular slide.\n\t *\n\t * Note that the state of the DOM on the page and thus the index of the\n\t * carousel will not be up-to-date until the {@link\n\t * Akamai.Carousel#akamai-carousel-goto} event has been fired. That is,\n\t * calling this method and the calling `getIndex` will not necessarily result\n\t * in an updated index value. Either bind to the event or supply a callback.\n\t *\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-goto\n\t * @param {Integer} index - The zero-based slide index to go to\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.goto = function(index, callback){\n\t\tthis._$el.snapper( \"goto\", index, callback );\n\t};\n\n\tCarousel._itemAttr = `${Carousel._dataAttr}-item`;\n\tCarousel._activeItemAttr = `${Carousel._itemAttr}-active`;\n\n\t/**\n\t * Add an attribute to all carousel items,\n\t * and additionally maintain an active attribute on the active carousel item\n\t *\n\t * @method\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype._activeItemAttributes = function(){\n\t\tvar attrAllItems = this.constructor._itemAttr;\n\t\tvar attrActiveItem = this.constructor._activeItemAttr;\n\n\t\tthis._$el\n\t\t\t.find( \".snapper_item\" )\n\t\t\t.attr( attrAllItems, true )\n\t\t\t.removeAttr( attrActiveItem )\n\t\t\t.eq( this.getIndex() ).attr( attrActiveItem, true );\n\t};\n\n\t/**\n\t * Return the current slide index\n\t * @method\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.getIndex = function(){\n\t\treturn this._$el.snapper( \"getIndex\" );\n\t};\n\n\t// Extend Carousel with Advanceabl interface\n\tAkamai.Advanceable.extend(Carousel);\n\n\t/**\n\t * Advance to the next item\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-next\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.next;\n\t// required for JSDocs, implementation in Advanceable\n\n\t/**\n\t * Retreat to the previous item\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-previous\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.previous;\n\t// required for JSDocs, implementation in Advanceable\n\n\t/**\n\t * Start automatic advancement of the carousel items\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-start-slideshow\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.startSlideshow\t= function(){\n\t\tthis._$el.one(\"mousedown touchstart\", this.stopSlideshow.bind(this));\n\n\t\tthis.next(function(){\n\t\t\tthis._trigger(\"start-slideshow\");\n\t\t\tthis._slideshowTimer = setTimeout(function(){\n\t\t\t\tthis.startSlideshow();\n\n\t\t\t\t// TODO remove || when default options are added\n\t\t\t}.bind(this), this._options.slideshow.interval );\n\t\t}.bind(this));\n\t};\n\n\t/**\n\t * Start automatic advancement of the carousel items\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-stop-slideshow\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.stopSlideshow = function(){\n\t\tclearTimeout(this._slideshowTimer);\n\t\tthis._slideshowTimer = undefined;\n\t\tthis._trigger(\"stop-slideshow\");\n\t};\n\n\t/**\n\t * Triggered when the carousel has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carousel#goto}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-goto\n\t */\n\n\t/**\n\t * Triggered when the carousel has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carousel#next}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-next\n\t */\n\n\t/**\n\t * Triggered when the carousel has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carousel#previous}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-previous\n\t */\n\n\t/**\n\t * Triggered when the carousel has started the slide show due to a call to\n\t * {@link Akamai.Carousel#startSlideshow}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-start-slideshow\n\t */\n\n\t/**\n\t * Triggered when the carousel has stoped the slide show due to a call to\n\t * {@link Akamai.Carousel#stopSlideshow}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-stop-slideshow\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Carousel}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-init\n\t */\n\n\tCarousel.prototype.refresh = function(){\n\t\tthis._$el.snapper(\"updateWidths\");\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Carousel = Carousel;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t/**\n\t * Image Fullscreen\n\t * @class\n\t * @alias Akamai.Fullscreen\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\n\t// TODO JSDocs\n\t// TODO Tests\n\tvar Fullscreen = Akamai.Util.component(\"Fullscreen\", function(element, options){\n\t\tif( this._options.enabled ) {\n\t\t\tthis._init();\n\t\t}\n\t});\n\n\tFullscreen.prototype._init = function(){\n\t\tthis._$fullscreen = this._$el;\n\n\t\t// parent container does not allow widths to be set (Firefox, fullscreen), so we’ll adjust the children\n\t\tthis.addButton();\n\n\t\tthis._fullscreenApiKeys = Fullscreen._keys();\n\n\t\tif( this._options.native ) {\n\t\t\tthis._useNativeApi = !!this._fullscreenApiKeys;\n\t\t} else {\n\t\t\tthis._useNativeApi = false;\n\t\t}\n\n\t\t// Fullscreen API is disabled and the fallback behavior is active\n\t\tthis._fullscreenFallbackEnabled = false;\n\t\tthis._$placeholder = $( \"
\" );\n\n\t\tthis._isFullscreen = false;\n\t\tthis.addEvents();\n\t};\n\n\tFullscreen.classes = {\n\t\tbtn: \"akamai-fullscreen-btn\",\n\t\tbtnContainer: \"akamai-fullscreen-btncontainer\",\n\t\tenterBtn: \"akamai-fullscreen-btn-enter\",\n\t\texitBtn: \"akamai-fullscreen-btn-exit\",\n\t\tactive: \"akamai-fullscreen-active\",\n\t\tfallback: \"akamai-fullscreen-fallback\"\n\t};\n\n\tFullscreen.attr = {\n\t\twidth: \"data-akamai-fullscreen-width\",\n\t\tenterBtn: \"data-akamai-fullscreen-btn\",\n\t\texitBtn: \"data-akamai-fullscreen-exit-btn\"\n\t};\n\n\tFullscreen.defaultOptions = {\n\t\tenabled: false,\n\t\tnative: false\n\t};\n\n\tFullscreen.prototype.addButton = function(){\n\t\tthis._$widthAdjust = this._$fullscreen.children().filter(\".focused\");\n\n\t\tif( !this._$widthAdjust.length ) {\n\t\t\tthis._$widthAdjust = this._$fullscreen\n\t\t\t\t.children()\n\t\t\t\t.eq(0);\n\t\t}\n\n\t\tif( this._$widthAdjust.attr(Fullscreen.attr.width) === null ){\n\t\t\tthis._$widthAdjust.attr( Fullscreen.attr.width, \"\" );\n\t\t}\n\n\t\tif( this._$widthAdjust.find(\"[\" + Fullscreen.attr.enterBtn + \"]\").length ){\n\t\t\treturn;\n\t\t}\n\n\t\t// buttons parent\n\t\tthis._$buttonContainer = this._$widthAdjust;\n\t\tthis._$buttonContainer.addClass( Fullscreen.classes.btnContainer );\n\n\t\tthis._$buttonContainer.append(this.render());\n\t};\n\n\tFullscreen.prototype.addEvents = function() {\n\t\tthis._$fullscreen.on( \"click\", function( e ) {\n\t\t\tvar $target = $( e.target );\n\t\t\tif( !$target.is(\"[\" + Fullscreen.attr.enterBtn + \"]\") ){\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.enter();\n\t\t\te.preventDefault();\n\t\t}.bind( this ));\n\n\t\tthis._$fullscreen.on( \"click\", function( e ) {\n\t\t\tvar $target = $( e.target );\n\n\t\t\tif( !$target.is( \"[\" + Fullscreen.attr.exitBtn + \"]\" ) ){\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.exit();\n\t\t\te.preventDefault();\n\t\t}.bind( this ));\n\n\t\tif( this._useNativeApi ) {\n\t\t\tdocument.addEventListener( this._fullscreenApiKeys.onchange, function() {\n\t\t\t\t// exiting fullscreen using native method (ESC or menu option)\n\t\t\t\tif( !document[ this._fullscreenApiKeys.element ] ) {\n\t\t\t\t\tthis._exit();\n\t\t\t\t}\n\t\t\t}.bind( this ), false );\n\t\t}\n\n\t\t// ESC to close\n\t\t$( document ).on( \"keydown\", function( e ) {\n\t\t\tvar code = e.keyCode || e.which;\n\t\t\tif( code === 27 ) {\n\t\t\t\tthis._exit();\n\t\t\t}\n\t\t}.bind( this ));\n\t};\n\n\tFullscreen.prototype.render = function() {\n\t\treturn Akamai.Util.trim(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t};\n\n\tFullscreen._keyLookup = [\n\t\t{\n\t\t\tenter: \"requestFullscreen\",\n\t\t\texit: \"exitFullscreen\",\n\t\t\telement: \"fullscreenElement\",\n\t\t\tonchange: \"fullscreenchange\"\n\t\t},\n\t\t{\n\t\t\tenter: \"webkitRequestFullscreen\",\n\t\t\texit: \"webkitExitFullscreen\",\n\t\t\telement: \"webkitFullscreenElement\",\n\t\t\tonchange: \"webkitfullscreenchange\"\n\t\t},\n\t\t{\n\t\t\tenter: \"webkitRequestFullScreen\",\n\t\t\texit: \"webkitCancelFullScreen\",\n\t\t\telement: \"webkitCurrentFullScreenElement\",\n\t\t\tonchange: \"webkitfullscreenchange\"\n\t\t},\n\t\t{\n\t\t\tenter: \"mozRequestFullScreen\",\n\t\t\texit: \"mozCancelFullScreen\",\n\t\t\telement: \"mozFullScreenElement\",\n\t\t\tonchange: \"mozfullscreenchange\"\n\t\t},\n\t\t{\n\t\t\tenter: \"msRequestFullscreen\",\n\t\t\texit: \"msExitFullscreen\",\n\t\t\telement: \"msFullscreenElement\",\n\t\t\tonchange: \"MSFullscreenChange\"\n\t\t}\n\t];\n\n\tFullscreen._keys = function( el ) {\n\t\tvar keys = Fullscreen._keyLookup;\n\n\t\tvar el = document.body;\n\t\tfor( var j = 0, k = keys.length; j < k; j++ ) {\n\t\t\tif( keys[ j ].enter in el ) {\n\t\t\t\treturn keys[ j ];\n\t\t\t}\n\t\t}\n\t};\n\n\tFullscreen.prototype._maximizePlacement = function(){\n\t\tthis._fullscreenFallbackEnabled = true;\n\t\tthis._$fullscreen.addClass( Fullscreen.classes.fallback );\n\t\tthis._$placeholder.insertAfter( this._$fullscreen );\n\t\tthis._$fullscreen.appendTo( document.body );\n\t};\n\n\tFullscreen.prototype._restorePlacement = function(){\n\t\tif( !this._fullscreenFallbackEnabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._fullscreenFallbackEnabled = false;\n\t\tthis._$fullscreen.removeClass( Fullscreen.classes.fallback );\n\t\tthis._$fullscreen.insertAfter( this._$placeholder );\n\t\tthis._$placeholder.remove();\n\t};\n\n\tFullscreen.prototype._adjustWidth = function() {\n\t\tthis._$fullscreen.css( \"width\", \"auto !important\" );\n\t\t// wish this could go into the change event above, but alas the dimensions don’t measure right\n\t\tvar widthSmall = this._$fullscreen.width();\n\t\tvar heightSmall = this._$fullscreen.height();\n\n\t\tvar viewportWidth = this._useNativeApi ? window.screen.width : Math.max( document.documentElement.clientWidth, window.innerWidth || 0 );\n\t\tvar viewportHeight = this._useNativeApi ? window.screen.height : Math.max( document.documentElement.clientHeight, window.innerHeight || 0 );\n\n\t\t// calculate the maximum width we can use to fill the viewportHeight\n\t\tvar widthBig = widthSmall * viewportHeight / heightSmall;\n\n\t\t// if the width is bigger than the maximum, just set to 100% (won’t fill the entire height, but that’s ok)\n\t\tif( widthBig > viewportWidth ) {\n\t\t\twidthBig = \"100%\";\n\t\t}\n\n\t\tthis._$widthAdjust.width( widthBig );\n\t};\n\n\tFullscreen.prototype._revertWidth = function() {\n\t\tthis._$widthAdjust.css( \"width\", \"\" );\n\t};\n\n\t/**\n\t * Enter full screen mode\n\t * @method\n\t * @returns {undefined}\n\t */\n\tFullscreen.prototype.enter = function(){\n\t\tvar activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex();\n\t\tthis._$fullscreen.addClass( Fullscreen.classes.active );\n\n\n\t\tif( this._useNativeApi ) {\n\t\t\tthis._$fullscreen[ 0 ][ this._fullscreenApiKeys.enter ]();\n\t\t\tthis._adjustWidth();\n\t\t} else {\n\t\t\tthis._adjustWidth();\n\t\t\tthis._maximizePlacement();\n\t\t}\n\t\tthis._isFullscreen = true;\n\t\tthis._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex);\n\t\tthis._trigger(\"enter\");\n\t};\n\n\tFullscreen.prototype._exit = function(){\n\t\tvar activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex();\n\t\tthis._$fullscreen.removeClass( Fullscreen.classes.active );\n\n\t\tif( !this._useNativeApi ) {\n\t\t\tthis._restorePlacement();\n\t\t}\n\n\t\tthis._revertWidth();\n\t\tthis._isFullscreen = false;\n\t\tthis._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex);\n\t\tthis._trigger(\"exit\");\n\t};\n\n\t/**\n\t * Exits full screen mode\n\t * @method\n\t * @returns {undefined}\n\t */\n\tFullscreen.prototype.exit = function(){\n\t\tif( this._useNativeApi ) {\n\t\t\t// note some close behavior happens in the onchange event handler above\n\t\t\tdocument[ this._fullscreenApiKeys.exit ]();\n\t\t}\n\n\t\tthis._exit();\n\t};\n\n\tFullscreen.prototype.isFullscreen = function(){\n\t\treturn this._isFullscreen;\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Fullscreen = Fullscreen;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t/**\n\t * Akamai Viewer omni-component\n\t * @class\n\t * @alias Akamai.Viewer\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t *\n\t * @example Instatiation\n\t * var element = document.querySelector( \"[data-akamai-viewer]\" );\n\t * var viewer = Akamai.Viewer( element );\n\t */\n\tvar Viewer = Akamai.Util.component(\"Viewer\", true, function(element, options){\n\t\t// try to update the carousel and magnifier sizes options to the width of\n\t\t// the viewer for more accurate srcset selection\n\t\tthis._updateSizesOptions(true);\n\t\tthis._onResizeComplete(this._updateSizesOptions.bind(this));\n\n\t\t// move all the flattened component options (spin, zoom, video) onto the carousel\n\t\tthis._options.carousel =\n\t\t\tViewer._extendCarouselOptions(this._options);\n\n\t\tthis._withData(function(data){\n\t\t\tif( data ) {\n\t\t\t\t// check all of the urls agains the hostname and whitelist\n\t\t\t\tthis._checkJSONUrls(data);\n\n\t\t\t\t// prevent XSS / injection attacks by escaping string values\n\t\t\t\tdata = Akamai.Util.escapeJSONVals(data);\n\n\t\t\t\t// store escaped data for later reference\n\t\t\t\tthis._options.items.data = data;\n\t\t\t}\n\n\t\t\tthis._tagMapping = {};\n\n\t\t\t// TODO if the element is empty and/or options has `items` render into element\n\t\t\tif( this._$el.children().length == 0 && data ) {\n\t\t\t\tthis._tagMapping = this.constructor._tagSplit(data, this._options.items);\n\t\t\t\tthis._$el.append(this.constructor.render(data, this._options, true));\n\t\t\t}\n\n\t\t\t// intantiate all child carousels\n\t\t\tthis._carousels = Akamai.Carousel.createMany(element, this._options.carousel);\n\t\t\tthis._fullscreen = new Akamai.Fullscreen(element, this._options.fullscreen);\n\n\t\t\t// on enter and exit of fullscreen resolve the breakpoints and update options\n\t\t\tthis._$el.bind( \"akamai-fullscreen-enter akamai-fullscreen-exit\", function(){\n\t\t\t\tthis._updateOptions(this._resolveBreakpointOptions());\n\t\t\t}.bind(this));\n\n\t\t\t// use the breakpoints to set match media listeners\n\t\t\tthis._setBreakpoints();\n\n\t\t\t// have to trigger init explicitly for async constructors\n\t\t\tthis._trigger(\"init\");\n\t\t}.bind(this), function(msg){\n\t\t\tthrow new Error(msg);\n\t\t});\n\t});\n\n\tViewer.prototype._updateSizesOptions = function(localUpdateOnly){\n\t\t// update sizes option to something more specific if possible\n\t\tvar elWidth = this._$el.width();\n\t\tif( elWidth && elWidth > 0 ){\n\t\t\telWidth += \"px\";\n\t\t\tthis._options = Akamai.Util.extend(true, this._options, {\n\t\t\t\tcarousel: {\n\t\t\t\t\timages: {\n\t\t\t\t\t\tsizes: elWidth\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif( !localUpdateOnly ){\n\t\t\tthis._updateOptions(this._options);\n\t\t}\n\t};\n\n\tViewer.prototype._withData = function(after, fail){\n\t\tif(!this._options.items.uri){\n\t\t\tif( this._exceedsLimit(this._options.items.data, fail)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tafter(this._options.items.data);\n\t\t}\n\n\t\telse if (typeof(this._options.items.uri) !== 'undefined' && this._options.items.uri.length > 0)\n\t\t{\n\t\t\t$.get(this._options.items.uri, function(data){\n\t\t\t\t// make sure the data gets sorted out regardless of `get` impl\n\t\t\t\tdata = (typeof data === \"string\" ? JSON.parse(data) : data);\n\n\t\t\t\tif( this._exceedsLimit(data.items, fail) ){\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tafter(data.items);\n\t\t\t}.bind(this));\n\t\t}\n\t};\n\n\t// value in kibibytes\n\tViewer.prototype._exceedsLimit = function(data, fail){\n\t\t// TODO some tests don't provide data\n\t\tif( !data ) { return false; }\n\n\t\t// 1 unicode character = 4 bytes\n\t\t// length = # of chars\n\t\t// kibibyte = 1024 bytes\n\t\t// # of chars * 4 / 1024\n\t\tvar kb = JSON.stringify(data).length * 4 / 1024;\n\n\t\tif( kb > this._options.items.limit ) {\n\t\t\tfail = fail || function(){};\n\t\t\tfail(`JSON data size exceeds ${this._options.items.limit} KiB. Limit can be configured with items.limit Akamai.Viewer options.`);\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t};\n\n\tViewer.prototype._setBreakpoints = function(){\n\t\tthis._breakpoints = this._reduceBreakpoints(this._options.breakpoints || {});\n\t\tthis._fullscreenBreakpoints =\n\t\t\tthis._reduceBreakpoints(this._options.fullscreenBreakpoints || {});\n\n\t\tthis._bindBreakpoints();\n\t};\n\n\tViewer.prototype._reduceBreakpoints = function(breakpoints){\n\t\tvar bps = [];\n\n\t\tfor( var bp in breakpoints ){\n\t\t\tif(breakpoints.hasOwnProperty(bp)){\n\t\t\t\ttry {\n\t\t\t\t\tbps.push(parseInt(bp, 10));\n\t\t\t\t} catch(e){\n\t\t\t\t\tAkamai.Util.log(e, 'error');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tbps.sort(function(a, b){ return b < a;});\n\n\t\treturn bps;\n\t};\n\n\tViewer.prototype._bindBreakpoints = function(){\n\t\t[this._breakpoints, this._fullscreenBreakpoints].forEach(function(bps){\n\t\t\t// bind using the minwidth and the next breakpoint as the maxwidth\n\t\t\t// adds a range from 0 to the first breakpoint and from the last\n\t\t\t// breakpoint to a very large number\n\t\t\tif(!bps.length) { return; }\n\n\t\t\tfor( var i = -1; i < bps.length; i++ ){\n\t\t\t\tthis._bindMatchMedia(bps[i], bps[i+1]);\n\t\t\t}\n\t\t}.bind(this));\n\t};\n\n\t// bind a callback to run after resize completes\n\tViewer.prototype._onResizeComplete = function( callback ){\n\t\tvar cbtimer;\n\t\tvar self = this;\n\t\twindow.addEventListener( \"resize\", function(){\n\t\t\tclearTimeout( cbtimer );\n\t\t\tcbtimer = setTimeout( callback, 500 );\n\t\t});\n\t};\n\n\tViewer.prototype._bindMatchMedia = function(minWidth, maxWidth){\n\t\tminWidth = minWidth || 0;\n\n\t\t// large number so we can use one media query template\n\t\tmaxWidth = maxWidth || 1000000000;\n\n\t\tif( window.matchMedia ){\n\t\t\t// bind using the minwidth and maxwidth so we get the events at the boundaries\n\t\t\t// so we can asses which set of options applies.\n\t\t\tvar query = `(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`;\n\n\t\t\t// create a media list to bind to\n\t\t\tvar initList = window.matchMedia(query);\n\n\t\t\tif(initList && initList.addListener) {\n\t\t\t\t// handle a match on instantiation\n\t\t\t\tthis._mediaMatch(initList, minWidth, true);\n\n\t\t\t\t// bind for later changes in whether the media query matches\n\t\t\t\tinitList.addListener(function(list){\n\t\t\t\t\tthis._mediaMatch(list, minWidth);\n\t\t\t\t}.bind(this));\n\t\t\t}\n\t\t}\n\t};\n\n\tViewer.prototype._mediaMatch = function(list, minWidth, ignoreDefault){\n\t\t// If there is a match for the breakpoint (we're in the bp range)\n\t\t// Then set the options based on that breakpoint\n\t\t// Else if there is not a match and the client width is below the\n\t\t// breakpoint that's being disabled, then use the original options\n\t\tif(list.matches){\n\t\t\tvar resolvedOptions = this._resolveBreakpointOptions(minWidth);\n\t\t\tthis._updateOptions(resolvedOptions);\n\t\t}\n\t};\n\n\t// TODO a ton of duplication with bindMatchmedia\n\tViewer.prototype._getCurrentMinWidth = function(bps){\n\t\t// bind using the minwidth and the next breakpoint as the maxwidth\n\t\t// adds a range from 0 to the first breakpoint and from the last\n\t\t// breakpoint to a very large number\n\t\tfor( var i = -1; i < bps.length; i++ ){\n\t\t\tminWidth = bps[i] || 0;\n\t\t\tmaxWidth = bps[i+1] || 100000000;\n\n\t\t\tif( window.matchMedia ){\n\t\t\t\t// bind using the minwidth and maxwidth so we get the events at the boundaries\n\t\t\t\t// so we can asses which set of options applies.\n\t\t\t\tvar query = `(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`;\n\n\t\t\t\t// create a media list to bind to\n\t\t\t\tvar initList = window.matchMedia(query);\n\n\t\t\t\tif(initList && initList.matches) {\n\t\t\t\t\treturn minWidth;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t};\n\n\tViewer.prototype._resolveBreakpointOptions = function(minWidth){\n\t\t// TODO bind on the reduce callback was not working\n\t\tvar self = this;\n\t\tvar bps, bpConfig;\n\n\t\tif( this._fullscreen.isFullscreen() ) {\n\t\t\tbps = this._fullscreenBreakpoints;\n\t\t\tbpConfig = self._options.fullscreenBreakpoints;\n\t\t} else {\n\t\t\tbps = this._breakpoints;\n\t\t\tbpConfig = self._options.breakpoints;\n\t\t}\n\n\t\tif(!minWidth){\n\t\t\tminWidth = self._getCurrentMinWidth(bps);\n\t\t}\n\n\t\t// There may be no matching breakpoints when this method is called to\n\t\t// resolve the current options. If that's the case then we need to default\n\t\t// to the top level options\n\t\tif(!bps.length){ return self._originalOptions; }\n\n\t\treturn bps.reduce(function(acc, bp){\n\t\t\tvar cloned = Akamai.Util.extend(true, {}, acc);\n\t\t\tvar ptions;\n\n\n\t\t\tif(bp <= minWidth){\n\t\t\t\tcloned = Akamai.Util.extend(true, cloned, bpConfig[bp]);\n\t\t\t}\n\n\t\t\treturn cloned;\n\t\t}, Akamai.Util.extend(true, {}, this._originalOptions));\n\t};\n\n\tViewer._extendCarouselOptions = function(options){\n\t\t// clone the carousel options for the given breakpoint\n\t\tvar clonedCarousel = Akamai.Util.extend(true, {}, options.carousel || {});\n\n\t\t// TODO shared code with constructor\n\t\t// extend the cloned options with the breakpoint options so that all the\n\t\t// child component config is attached to the carousel config (also happens\n\t\t// in the constructor )\n\t\treturn Akamai.Util.extend(true, clonedCarousel, {\n\t\t\tmagnifier: options.magnifier,\n\t\t\tspin360: options.spin360,\n\t\t\tvideo: options.video,\n\t\t\tfullscreen: options.fullscreen\n\t\t});\n\t};\n\n\tViewer.prototype._updateOptions = function(options){\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// extend the carousel options for the given breakpoint\n\t\tvar carouselOptions = Viewer._extendCarouselOptions(options);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Viewer.defaultOptions, options, \"Viewer\");\n\n\t\t// pass the new options down to all child carousels\n\t\tthis._carousels.forEach(function(comp){\n\t\t\tcomp._updateOptions(carouselOptions);\n\t\t}.bind(this));\n\t};\n\n\tViewer.prototype._checkJSONUrls = function(obj){\n\t\tvar hostnames = this._options.items.hostnames;\n\n\t\treturn Akamai.Util.mapJSONVals(obj,function(val, key){\n\t\t\tif(key === \"url\" && !this._urlHostnameMatch(val)){\n\t\t\t\tthrow new Error(`The URL \\`${val}\\` does not match this page's hostname or the whitelist defined in Akamai.Viewer option \\`hostnames\\` which is:\n\n${hostnames.length ? hostnames.join(\"\\n\") : \"No hostnames\"}\n`);\n\t\t\t}\n\n\t\t\treturn val;\n\t\t}.bind(this));\n\t};\n\n\tViewer.prototype._urlHostnameMatch = function(url){\n\t\tvar parser = document.createElement('a');\n\t\tparser.href = url;\n\n\t\treturn parser.hostname === \"\" || parser.hostname === window.location.hostname ||\n\t\t\tthis._options.items.hostnames.indexOf(parser.hostname) >= 0;\n\t};\n\n\t/**\n\t * Defines the global default options for all Viewers on the page\n\t * @static\n\t * @property {Object} breakpoints - configuration changes for child components at breakpoints (no default)\n\t * @property {Object} fullscreenBreakpoints - configuration changes for child components at breakpoints when in fullscreen mode (no default)\n\t * @property {Object[]} items.data - array of items from the Akamai JSON (default: undefined)\n\t * @property {String} items.defaultTag - (default: \"akamai-untagged\")\n\t * @property {String[]} items.hostnames - whitelist of URL hostnames to check for in JSON, (default: empty array)\n\t * @property {Integer} items.limit - size limit of JSON data in kibibytes (default: 100)\n\t * @property {String} items.renderAll - (default: false)\n\t * @property {String[]} items.tags - Set of tags to match against the Akamai JSON data (default: undefined)\n\t * @property {String} items.uri - URI at which to retrieve the Akamai JSON (default: undefined)\n\t * @property {Object} carousel - child {@link Akamai.Carousel} options\n\t * @property {Object} magnifier - child {@link Akamai.Magnifier} options\n\t * @property {Object} spin360 - child {@link Akamai.Spin360} options\n\t * @property {Object} video - child {@link Akamai.Video} options\n\t * @property {Object} fullscreen - child {@link Akamai.Fullscreen} options\n\t */\n\tViewer.defaultOptions = {\n\t\tbreakpoints: {},\n\t\titems: {\n\t\t\tdata: undefined,\n\t\t\tdefaultTag: \"akamai-untagged\",\n\t\t\thostnames: [],\n\t\t\tlimit: 100,\n\t\t\trenderAll: false,\n\t\t\ttags: undefined,\n\t\t\turi: undefined\n\t\t},\n\t\tcarousel: Akamai.Carousel.defaultOptions,\n\t\tmagnifier: Akamai.Magnifier.defaultOptions,\n\t\tspin360: Akamai.Spin360.defaultOptions,\n\t\tvideo: Akamai.Video.defaultOptions,\n\t\tfullscreen: Akamai.Fullscreen.defaultOptions\n\t};\n\n\tViewer._tagSplit = function(data, options){\n\t\tvar items = options;\n\n\t\t// set all items without a tag to the default\n\t\tdata = data.map(function(item){\n\t\t\titem.tags = (item.tags && item.tags.length) ? item.tags : [items.defaultTag];\n\t\t\treturn item;\n\t\t});\n\n\t\t// if the tags option was set, filter items out that don't match\n\t\tif( items.tags ) {\n\t\t\tdata = items.data.filter(function(item){\n\t\t\t\treturn item.tags.reduce(function(acc, tag){\n\t\t\t\t\treturn acc || items.tags.indexOf(tag) >= 0;\n\t\t\t\t}, false);\n\t\t\t});\n\t\t}\n\n\t\t// from the filtered get the first (may be default tag)\n\t\tvar def = data[0].tags[0];\n\n\t\t// create a mapping from tags to items in the set\n\t\tvar tagMapping = data.reduce(function(acc, item){\n\t\t\t(item.tags || []).forEach(function(tag){\n\t\t\t\tacc[tag] = acc[tag] || [];\n\t\t\t\tacc[tag].push(item);\n\t\t\t});\n\n\t\t\treturn acc;\n\t\t}, {});\n\n\t\t// set the first (default) object to the first tag\n\t\ttagMapping[Viewer._firstRenderTag] = tagMapping[def];\n\n\t\treturn tagMapping;\n\t};\n\n\tViewer._firstRenderTag = \"akamai-first-render\";\n\tViewer._tagAttr = `${Viewer._dataAttr}-tag`;\n\n\tViewer._renderTag = function(data, options){\n\t\t// get the first item and it's tag\n\t\t// NOTE this assumes that the data has been normalized to have a default tag\n\t\tvar tag = data[0].tags[0];\n\n\t\t// TODO shoestring doesn't treat html correctly unless the leading `<` has\n\t\t// no whitepsace in front of it:\n\t\t// shoestring/issues/94\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t${Akamai.Carousel.render(data, options)}\n\t\t\t
\n\t\t`);\n\t};\n\n\t/**\n\t * Takes standard options including an `items` attribute and renders HTML\n\t * that conforms to the component expectations\n\t * @method\n\t * @static\n\t * @params {Object[]} json - Akamai JSON data\n\t * @params {Object} options - options object with an `items` attribute, see default options\n\t * @params {Boolean} options.items - configuration for how the items should be\n\t * rendered, see {@link Akamai.Viewer.defaultOptions} default options items sub-configuration\n\t * @returns { String }\n\t */\n\tViewer.render = function(json, options, unwrapped){\n\t\t// NOTE we do not extend the default options here because it has no bearing\n\t\t// on the rendering of the Viewer markup or child markup. This is contrast\n\t\t// to the carousel which does extend the passed options with defaults so the\n\t\t// markup can make use of them\n\t\tvar data = Viewer._tagSplit(json, options.items);\n\t\tvar carouselOptions = Viewer._extendCarouselOptions(options);\n\n\t\tvar childMarkup;\n\n\t\tif( options.items.renderAll ) {\n\t\t\tchildMarkup = data.map(function(acc, datum){\n\t\t\t\treturn Viewer._renderTag(datum, carouselOptions);\n\t\t\t}).join(\" \");\n\t\t} else {\n\t\t\tchildMarkup = Viewer._renderTag(data[Viewer._firstRenderTag], carouselOptions);\n\t\t}\n\n\t\tvar wrapped = `\n\t\t\t
\n\t\t\t\t${childMarkup}\n\t\t\t
\n\t\t`;\n\n\t\treturn unwrapped ? childMarkup : wrapped ;\n\t};\n\n\t/**\n\t * Accessor for child carousel components.\n\t * @method\n\t * @returns { Akamai.Carousel[] }\n\t */\n\tViewer.prototype.getCarousels = function(){\n\t\treturn this._carousels;\n\t};\n\n\t/**\n\t * Set the visible carousel based on the tag. Note the method activity does\n\t * not complete until all the images from the relevant viewer have loaded, use\n\t * the `akamai-viewer-switch-tag` event\n\t * @method\n\t * @fires Akamai.Viewer#akamai-viewer-switch-tag\n\t * @params {String} tag - The tag corresponding to the desired carousel\n\t * @returns {undefined}\n\t */\n\tViewer.prototype.switchTag = function(tag){\n\t\tvar tags = this.getTags();\n\n\t\t// if the passed tag isn't in the data set return early and log an error in\n\t\t// the console\n\t\tif( tags.indexOf(tag) == -1){\n\t\t\tAkamai.Util.log(`tag: ${tag} is not present in the data for this viewer`, 'error');\n\t\t\treturn;\n\t\t}\n\n\t\tvar selector = `[${this.constructor._tagAttr}=\"${tag}\"]`;\n\n\t\t// try to find an existing element with the tag\n\t\tvar $taggedViewer = this._$el.find(selector);\n\n\t\t// if there's no element append the newly rendered tag markup\n\t\tif( !$taggedViewer.length ){\n\t\t\t// make sure the config takes into account the current breakpoint\n\t\t\tvar options = this._resolveBreakpointOptions();\n\t\t\tvar carouselOptions = Viewer._extendCarouselOptions(options);\n\n\t\t\t// create the markup that will be inserted\n\t\t\tvar $markup = $(this.constructor._renderTag(this._tagMapping[tag], carouselOptions, true));\n\t\t\tvar $imgs = $markup.find(\"img\");\n\t\t\tvar imgCount = $imgs.length;\n\t\t\tvar loaded = 0;\n\n\t\t\t// hide the new carousel initially\n\t\t\t$markup.css(\"display\", \"none\");\n\n\t\t\t$imgs.bind(\"load\", function(){\n\t\t\t\tif( ++loaded !== imgCount ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// TODO namespace?\n\t\t\t\t$imgs.unbind(\"load\");\n\n\t\t\t\tvar carousels = Akamai.Carousel.createMany($markup[0], carouselOptions);\n\n\t\t\t\tcarousels.forEach(function(c){\n\t\t\t\t\t// TODO figure out why the carousel constructor doesn't apply the\n\t\t\t\t\t// active index class we want on instantiation, likely due to the fact\n\t\t\t\t\t// that the carousel is hidden so the \"getIndex\" calc is broken\n\t\t\t\t\t// mark the carousel item as active\n\t\t\t\t\tc.goto(c.getIndex());\n\t\t\t\t});\n\n\t\t\t\t// create and store the new carousels (should be one)\n\t\t\t\tthis._carousels = this._carousels.concat(carousels);\n\t\t\t\tthis._showViewer($markup, carousels);\n\t\t\t}.bind(this));\n\n\t\t\t// append the new markup to the existing viewer\n\t\t\tthis._$el.append($markup);\n\t\t} else {\n\t\t\tthis._showViewer($taggedViewer);\n\t\t}\n\t};\n\n\t// TODO should be handled in CSS\n\tViewer.prototype._showViewer = function($viewer, carousels){\n\t\tthis._$el\n\t\t\t.find(`[${this.constructor._tagAttr}]`)\n\t\t\t.css(\"display\", \"none\")\n\t\t\t.removeClass(\"focused\");\n\n\t\t$viewer\n\t\t\t.css(\"display\", \"block\")\n\t\t\t.addClass(\"focused\");\n\n\t\tif( carousels ){\n\t\t\tcarousels.map(function(c) { c.refresh(); });\n\t\t}\n\n\t\t// TODO the placement here seems arbitrary, probably belongs in `_showViewer`\n\t\t// TODO also sucks to be so tightly coupled\n\t\tthis._fullscreen.addButton();\n\n\t\tthis._trigger( \"switch-tag\" );\n\t};\n\n\t/**\n\t * Accessor for tags derived from Akamai JSON data\n\t * @method\n\t * @returns { String[] }\n\t */\n\tViewer.prototype.getTags = function(){\n\t\tif( this._tags ) {\n\t\t\treturn this._tags;\n\t\t};\n\n\t\tvar tags = [];\n\n\t\t// map and store all the carousel tags\n\t\tfor(var tag in this._tagMapping){\n\t\t\tif(this._tagMapping.hasOwnProperty(tag) && tag !== Viewer._firstRenderTag ) {\n\t\t\t\t// otherwise grab the list of tags\n\t\t\t\ttags.push(tag);\n\t\t\t}\n\t\t}\n\n\t\treturn this._tags = tags;\n\t};\n\n\t/**\n\t * Triggered when the viewer switches tag views. This includes waiting for\n\t * images to load for carousels dedicated to previously unviewed tags.\n\t * {@link Akamai.Viewer#switchTag}.\n\t *\n\t * @event Akamai.Viewer#akamai-viewer-switch-tag\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Viewer}.\n\t *\n\t * @event Akamai.Viewer#akamai-viewer-init\n\t */\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Viewer = Viewer;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t$.fn.akamaiViewer = function(options){\n\t\tthis.each(function(i, element){\n\t\t\tnew Akamai.Viewer(element, options);\n\t\t});\n\t};\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n"]} \ No newline at end of file diff --git a/app/html/js/akamai-viewer.unmin.js b/app/html/js/akamai-viewer.unmin.js new file mode 100644 index 0000000..a71a9ca --- /dev/null +++ b/app/html/js/akamai-viewer.unmin.js @@ -0,0 +1,8055 @@ +/*! Copyright 2019, Akamai Technologies, Inc. All Rights Reserved. akamai-viewer-0.7.2 */ /** + * Generic animation class with support for dropped frames both optional easing and duration. + * + * Optional duration is useful when the lifetime is defined by another condition than time + * e.g. speed of an animating object, etc. + * + * Dropped frame logic allows to keep using the same updater logic independent from the actual + * rendering. This eases a lot of cases where it might be pretty complex to break down a state + * based on the pure time difference. + */ +(function (global) { + var time = Date.now || function () { + return +new Date(); + }; + var desiredFrames = 60; + var millisecondsPerSecond = 1000; + var running = {}; + var counter = 1; + + // Create namespaces + if (!global.core) { + global.core = { effect: {} }; + } else if (!core.effect) { + core.effect = {}; + } + + core.effect.Animate = { + + /** + * A requestAnimationFrame wrapper / polyfill. + * + * @param callback {Function} The callback to be invoked before the next repaint. + * @param root {HTMLElement} The root element for the repaint + */ + requestAnimationFrame: function () { + + // Check for request animation Frame support + var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame; + var isNative = !!requestFrame; + + if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) { + isNative = false; + } + + if (isNative) { + return function (callback, root) { + requestFrame(callback, root); + }; + } + + var TARGET_FPS = 60; + var requests = {}; + var requestCount = 0; + var rafHandle = 1; + var intervalHandle = null; + var lastActive = +new Date(); + + return function (callback, root) { + var callbackHandle = rafHandle++; + + // Store callback + requests[callbackHandle] = callback; + requestCount++; + + // Create timeout at first request + if (intervalHandle === null) { + + intervalHandle = setInterval(function () { + + var time = +new Date(); + var currentRequests = requests; + + // Reset data structure before executing callbacks + requests = {}; + requestCount = 0; + + for (var key in currentRequests) { + if (currentRequests.hasOwnProperty(key)) { + currentRequests[key](time); + lastActive = time; + } + } + + // Disable the timeout when nothing happens for a certain + // period of time + if (time - lastActive > 2500) { + clearInterval(intervalHandle); + intervalHandle = null; + } + }, 1000 / TARGET_FPS); + } + + return callbackHandle; + }; + }(), + + /** + * Stops the given animation. + * + * @param id {Integer} Unique animation ID + * @return {Boolean} Whether the animation was stopped (aka, was running before) + */ + stop: function (id) { + var cleared = running[id] != null; + if (cleared) { + running[id] = null; + } + + return cleared; + }, + + /** + * Whether the given animation is still running. + * + * @param id {Integer} Unique animation ID + * @return {Boolean} Whether the animation is still running + */ + isRunning: function (id) { + return running[id] != null; + }, + + /** + * Start the animation. + * + * @param stepCallback {Function} Pointer to function which is executed on every step. + * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }` + * @param verifyCallback {Function} Executed before every animation step. + * Signature of the method should be `function() { return continueWithAnimation; }` + * @param completedCallback {Function} + * Signature of the method should be `function(droppedFrames, finishedAnimation) {}` + * @param duration {Integer} Milliseconds to run the animation + * @param easingMethod {Function} Pointer to easing function + * Signature of the method should be `function(percent) { return modifiedValue; }` + * @param root {Element ? document.body} Render root, when available. Used for internal + * usage of requestAnimationFrame. + * @return {Integer} Identifier of animation. Can be used to stop it any time. + */ + start: function (stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) { + + var start = time(); + var lastFrame = start; + var percent = 0; + var dropCounter = 0; + var id = counter++; + + if (!root) { + root = document.body; + } + + // Compacting running db automatically every few new animations + if (id % 20 === 0) { + var newRunning = {}; + for (var usedId in running) { + newRunning[usedId] = true; + } + running = newRunning; + } + + // This is the internal step method which is called every few milliseconds + var step = function (virtual) { + + // Normalize virtual value + var render = virtual !== true; + + // Get current time + var now = time(); + + // Verification is executed before next animation step + if (!running[id] || verifyCallback && !verifyCallback(id)) { + + running[id] = null; + completedCallback && completedCallback(desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), id, false); + return; + } + + // For the current rendering to apply let's update omitted steps in memory. + // This is important to bring internal state variables up-to-date with progress in time. + if (render) { + + var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1; + for (var j = 0; j < Math.min(droppedFrames, 4); j++) { + step(true); + dropCounter++; + } + } + + // Compute percent value + if (duration) { + percent = (now - start) / duration; + if (percent > 1) { + percent = 1; + } + } + + // Execute step callback, then... + var value = easingMethod ? easingMethod(percent) : percent; + if ((stepCallback(value, now, render) === false || percent === 1) && render) { + running[id] = null; + completedCallback && completedCallback(desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), id, percent === 1 || duration == null); + } else if (render) { + lastFrame = now; + core.effect.Animate.requestAnimationFrame(step, root); + } + }; + + // Mark as running + running[id] = true; + + // Init first step + core.effect.Animate.requestAnimationFrame(step, root); + + // Return unique animation ID + return id; + } + }; +})(this); + +var Scroller; + +(function () { + + /** + * A pure logic 'component' for 'virtual' scrolling/zooming. + */ + Scroller = function (callback, options) { + + this.__callback = callback; + + this.options = { + + /** Enable scrolling on x-axis */ + scrollingX: true, + + /** Enable scrolling on y-axis */ + scrollingY: true, + + /** Enable animations for deceleration, snap back, zooming and scrolling */ + animating: true, + + /** duration for animations triggered by scrollTo/zoomTo */ + animationDuration: 250, + + /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */ + bouncing: true, + + /** Enable locking to the main axis if user moves only slightly on one of them at start */ + locking: true, + + /** Enable pagination mode (switching between full page content panes) */ + paging: false, + + /** Enable snapping of content to a configured pixel grid */ + snapping: false, + + /** Enable zooming of content via API, fingers and mouse wheel */ + zooming: false, + + /** Minimum zoom level */ + minZoom: 0.5, + + /** Maximum zoom level */ + maxZoom: 3 + + }; + + for (var key in options) { + this.options[key] = options[key]; + } + }; + + // Easing Equations (c) 2003 Robert Penner, all rights reserved. + // Open source under the BSD License. + + /** + * @param pos {Number} position between 0 (start of effect) and 1 (end of effect) + **/ + var easeOutCubic = function (pos) { + return Math.pow(pos - 1, 3) + 1; + }; + + /** + * @param pos {Number} position between 0 (start of effect) and 1 (end of effect) + **/ + var easeInOutCubic = function (pos) { + if ((pos /= 0.5) < 1) { + return 0.5 * Math.pow(pos, 3); + } + + return 0.5 * (Math.pow(pos - 2, 3) + 2); + }; + + var members = { + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: STATUS + --------------------------------------------------------------------------- + */ + + /** {Boolean} Whether only a single finger is used in touch handling */ + __isSingleTouch: false, + + /** {Boolean} Whether a touch event sequence is in progress */ + __isTracking: false, + + /** + * {Boolean} Whether a gesture zoom/rotate event is in progress. Activates when + * a gesturestart event happens. This has higher priority than dragging. + */ + __isGesturing: false, + + /** + * {Boolean} Whether the user has moved by such a distance that we have enabled + * dragging mode. Hint: It's only enabled after some pixels of movement to + * not interrupt with clicks etc. + */ + __isDragging: false, + + /** + * {Boolean} Not touching and dragging anymore, and smoothly animating the + * touch sequence using deceleration. + */ + __isDecelerating: false, + + /** + * {Boolean} Smoothly animating the currently configured change + */ + __isAnimating: false, + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: DIMENSIONS + --------------------------------------------------------------------------- + */ + + /** {Integer} Available outer left position (from document perspective) */ + __clientLeft: 0, + + /** {Integer} Available outer top position (from document perspective) */ + __clientTop: 0, + + /** {Integer} Available outer width */ + __clientWidth: 0, + + /** {Integer} Available outer height */ + __clientHeight: 0, + + /** {Integer} Outer width of content */ + __contentWidth: 0, + + /** {Integer} Outer height of content */ + __contentHeight: 0, + + /** {Integer} Snapping width for content */ + __snapWidth: 100, + + /** {Integer} Snapping height for content */ + __snapHeight: 100, + + /** {Integer} Height to assign to refresh area */ + __refreshHeight: null, + + /** {Boolean} Whether the refresh process is enabled when the event is released now */ + __refreshActive: false, + + /** {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */ + __refreshActivate: null, + + /** {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */ + __refreshDeactivate: null, + + /** {Function} Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */ + __refreshStart: null, + + /** {Number} Zoom level */ + __zoomLevel: 1, + + /** {Number} Scroll position on x-axis */ + __scrollLeft: 0, + + /** {Number} Scroll position on y-axis */ + __scrollTop: 0, + + /** {Integer} Maximum allowed scroll position on x-axis */ + __maxScrollLeft: 0, + + /** {Integer} Maximum allowed scroll position on y-axis */ + __maxScrollTop: 0, + + /* {Number} Scheduled left position (final position when animating) */ + __scheduledLeft: 0, + + /* {Number} Scheduled top position (final position when animating) */ + __scheduledTop: 0, + + /* {Number} Scheduled zoom level (final scale when animating) */ + __scheduledZoom: 0, + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: LAST POSITIONS + --------------------------------------------------------------------------- + */ + + /** {Number} Left position of finger at start */ + __lastTouchLeft: null, + + /** {Number} Top position of finger at start */ + __lastTouchTop: null, + + /** {Date} Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */ + __lastTouchMove: null, + + /** {Array} List of positions, uses three indexes for each state: left, top, timestamp */ + __positions: null, + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: DECELERATION SUPPORT + --------------------------------------------------------------------------- + */ + + /** {Integer} Minimum left scroll position during deceleration */ + __minDecelerationScrollLeft: null, + + /** {Integer} Minimum top scroll position during deceleration */ + __minDecelerationScrollTop: null, + + /** {Integer} Maximum left scroll position during deceleration */ + __maxDecelerationScrollLeft: null, + + /** {Integer} Maximum top scroll position during deceleration */ + __maxDecelerationScrollTop: null, + + /** {Number} Current factor to modify horizontal scroll position with on every step */ + __decelerationVelocityX: null, + + /** {Number} Current factor to modify vertical scroll position with on every step */ + __decelerationVelocityY: null, + + /* + --------------------------------------------------------------------------- + PUBLIC API + --------------------------------------------------------------------------- + */ + + /** + * Configures the dimensions of the client (outer) and content (inner) elements. + * Requires the available space for the outer element and the outer size of the inner element. + * All values which are falsy (null or zero etc.) are ignored and the old value is kept. + * + * @param clientWidth {Integer ? null} Inner width of outer element + * @param clientHeight {Integer ? null} Inner height of outer element + * @param contentWidth {Integer ? null} Outer width of inner element + * @param contentHeight {Integer ? null} Outer height of inner element + */ + setDimensions: function (clientWidth, clientHeight, contentWidth, contentHeight) { + + var self = this; + + // Only update values which are defined + if (clientWidth) { + self.__clientWidth = clientWidth; + } + + if (clientHeight) { + self.__clientHeight = clientHeight; + } + + if (contentWidth) { + self.__contentWidth = contentWidth; + } + + if (contentHeight) { + self.__contentHeight = contentHeight; + } + + // Refresh maximums + self.__computeScrollMax(); + + // Refresh scroll position + self.scrollTo(self.__scrollLeft, self.__scrollTop, true); + }, + + /** + * Sets the client coordinates in relation to the document. + * + * @param left {Integer ? 0} Left position of outer element + * @param top {Integer ? 0} Top position of outer element + */ + setPosition: function (left, top) { + + var self = this; + + self.__clientLeft = left || 0; + self.__clientTop = top || 0; + }, + + /** + * Configures the snapping (when snapping is active) + * + * @param width {Integer} Snapping width + * @param height {Integer} Snapping height + */ + setSnapSize: function (width, height) { + + var self = this; + + self.__snapWidth = width; + self.__snapHeight = height; + }, + + /** + * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever + * the user event is released during visibility of this zone. This was introduced by some apps on iOS like + * the official Twitter client. + * + * @param height {Integer} Height of pull-to-refresh zone on top of rendered list + * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release. + * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled. + * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh. + */ + activatePullToRefresh: function (height, activateCallback, deactivateCallback, startCallback) { + + var self = this; + + self.__refreshHeight = height; + self.__refreshActivate = activateCallback; + self.__refreshDeactivate = deactivateCallback; + self.__refreshStart = startCallback; + }, + + /** + * Signalizes that pull-to-refresh is finished. + */ + finishPullToRefresh: function () { + + var self = this; + + self.__refreshActive = false; + if (self.__refreshDeactivate) { + self.__refreshDeactivate(); + } + + self.scrollTo(self.__scrollLeft, self.__scrollTop, true); + }, + + /** + * Returns the scroll position and zooming values + * + * @return {Map} `left` and `top` scroll position and `zoom` level + */ + getValues: function () { + + var self = this; + + return { + left: self.__scrollLeft, + top: self.__scrollTop, + zoom: self.__zoomLevel + }; + }, + + /** + * Returns the maximum scroll values + * + * @return {Map} `left` and `top` maximum scroll values + */ + getScrollMax: function () { + + var self = this; + + return { + left: self.__maxScrollLeft, + top: self.__maxScrollTop + }; + }, + + /** + * Zooms to the given level. Supports optional animation. Zooms + * the center when no coordinates are given. + * + * @param level {Number} Level to zoom to + * @param animate {Boolean ? false} Whether to use animation + * @param originLeft {Number ? null} Zoom in at given left coordinate + * @param originTop {Number ? null} Zoom in at given top coordinate + */ + zoomTo: function (level, animate, originLeft, originTop) { + + var self = this; + + if (!self.options.zooming) { + throw new Error("Zooming is not enabled!"); + } + + // Stop deceleration + if (self.__isDecelerating) { + core.effect.Animate.stop(self.__isDecelerating); + self.__isDecelerating = false; + } + + var oldLevel = self.__zoomLevel; + + // Normalize input origin to center of viewport if not defined + if (originLeft == null) { + originLeft = self.__clientWidth / 2; + } + + if (originTop == null) { + originTop = self.__clientHeight / 2; + } + + // Limit level according to configuration + level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom); + + // Recompute maximum values while temporary tweaking maximum scroll ranges + self.__computeScrollMax(level); + + // Recompute left and top coordinates based on new zoom level + var left = (originLeft + self.__scrollLeft) * level / oldLevel - originLeft; + var top = (originTop + self.__scrollTop) * level / oldLevel - originTop; + + // Limit x-axis + if (left > self.__maxScrollLeft) { + left = self.__maxScrollLeft; + } else if (left < 0) { + left = 0; + } + + // Limit y-axis + if (top > self.__maxScrollTop) { + top = self.__maxScrollTop; + } else if (top < 0) { + top = 0; + } + + // Push values out + self.__publish(left, top, level, animate); + }, + + /** + * Zooms the content by the given factor. + * + * @param factor {Number} Zoom by given factor + * @param animate {Boolean ? false} Whether to use animation + * @param originLeft {Number ? 0} Zoom in at given left coordinate + * @param originTop {Number ? 0} Zoom in at given top coordinate + */ + zoomBy: function (factor, animate, originLeft, originTop) { + + var self = this; + + self.zoomTo(self.__zoomLevel * factor, animate, originLeft, originTop); + }, + + /** + * Scrolls to the given position. Respect limitations and snapping automatically. + * + * @param left {Number?null} Horizontal scroll position, keeps current if value is null + * @param top {Number?null} Vertical scroll position, keeps current if value is null + * @param animate {Boolean?false} Whether the scrolling should happen using an animation + * @param zoom {Number?null} Zoom level to go to + */ + scrollTo: function (left, top, animate, zoom) { + + var self = this; + + // Stop deceleration + if (self.__isDecelerating) { + core.effect.Animate.stop(self.__isDecelerating); + self.__isDecelerating = false; + } + + // Correct coordinates based on new zoom level + if (zoom != null && zoom !== self.__zoomLevel) { + + if (!self.options.zooming) { + throw new Error("Zooming is not enabled!"); + } + + left *= zoom; + top *= zoom; + + // Recompute maximum values while temporary tweaking maximum scroll ranges + self.__computeScrollMax(zoom); + } else { + + // Keep zoom when not defined + zoom = self.__zoomLevel; + } + + if (!self.options.scrollingX) { + + left = self.__scrollLeft; + } else { + + if (self.options.paging) { + left = Math.round(left / self.__clientWidth) * self.__clientWidth; + } else if (self.options.snapping) { + left = Math.round(left / self.__snapWidth) * self.__snapWidth; + } + } + + if (!self.options.scrollingY) { + + top = self.__scrollTop; + } else { + + if (self.options.paging) { + top = Math.round(top / self.__clientHeight) * self.__clientHeight; + } else if (self.options.snapping) { + top = Math.round(top / self.__snapHeight) * self.__snapHeight; + } + } + + // Limit for allowed ranges + left = Math.max(Math.min(self.__maxScrollLeft, left), 0); + top = Math.max(Math.min(self.__maxScrollTop, top), 0); + + // Don't animate when no change detected, still call publish to make sure + // that rendered position is really in-sync with internal data + if (left === self.__scrollLeft && top === self.__scrollTop) { + animate = false; + } + + // Publish new values + self.__publish(left, top, zoom, animate); + }, + + /** + * Scroll by the given offset + * + * @param left {Number ? 0} Scroll x-axis by given offset + * @param top {Number ? 0} Scroll x-axis by given offset + * @param animate {Boolean ? false} Whether to animate the given change + */ + scrollBy: function (left, top, animate) { + + var self = this; + + var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft; + var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop; + + self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate); + }, + + /* + --------------------------------------------------------------------------- + EVENT CALLBACKS + --------------------------------------------------------------------------- + */ + + /** + * Mouse wheel handler for zooming support + */ + doMouseZoom: function (wheelDelta, timeStamp, pageX, pageY) { + + var self = this; + var change = wheelDelta > 0 ? 0.97 : 1.03; + + return self.zoomTo(self.__zoomLevel * change, false, pageX - self.__clientLeft, pageY - self.__clientTop); + }, + + /** + * Touch start handler for scrolling support + */ + doTouchStart: function (touches, timeStamp) { + + // Array-like check is enough here + if (touches.length == null) { + throw new Error("Invalid touch list: " + touches); + } + + if (timeStamp instanceof Date) { + timeStamp = timeStamp.valueOf(); + } + if (typeof timeStamp !== "number") { + throw new Error("Invalid timestamp value: " + timeStamp); + } + + var self = this; + + // Stop deceleration + if (self.__isDecelerating) { + core.effect.Animate.stop(self.__isDecelerating); + self.__isDecelerating = false; + } + + // Stop animation + if (self.__isAnimating) { + core.effect.Animate.stop(self.__isAnimating); + self.__isAnimating = false; + } + + // Use center point when dealing with two fingers + var currentTouchLeft, currentTouchTop; + var isSingleTouch = touches.length === 1; + if (isSingleTouch) { + currentTouchLeft = touches[0].pageX; + currentTouchTop = touches[0].pageY; + } else { + currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; + currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; + } + + // Store initial positions + self.__initialTouchLeft = currentTouchLeft; + self.__initialTouchTop = currentTouchTop; + + // Store current zoom level + self.__zoomLevelStart = self.__zoomLevel; + + // Store initial touch positions + self.__lastTouchLeft = currentTouchLeft; + self.__lastTouchTop = currentTouchTop; + + // Store initial move time stamp + self.__lastTouchMove = timeStamp; + + // Reset initial scale + self.__lastScale = 1; + + // Reset locking flags + self.__enableScrollX = !isSingleTouch && self.options.scrollingX; + self.__enableScrollY = !isSingleTouch && self.options.scrollingY; + + // Reset tracking flag + self.__isTracking = true; + + // Dragging starts directly with two fingers, otherwise lazy with an offset + self.__isDragging = !isSingleTouch; + + // Some features are disabled in multi touch scenarios + self.__isSingleTouch = isSingleTouch; + + // Clearing data structure + self.__positions = []; + }, + + /** + * Touch move handler for scrolling support + */ + doTouchMove: function (touches, timeStamp, scale) { + + // Array-like check is enough here + if (touches.length == null) { + throw new Error("Invalid touch list: " + touches); + } + + if (timeStamp instanceof Date) { + timeStamp = timeStamp.valueOf(); + } + if (typeof timeStamp !== "number") { + throw new Error("Invalid timestamp value: " + timeStamp); + } + + var self = this; + + // Ignore event when tracking is not enabled (event might be outside of element) + if (!self.__isTracking) { + return; + } + + var currentTouchLeft, currentTouchTop; + + // Compute move based around of center of fingers + if (touches.length === 2) { + currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; + currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; + } else { + currentTouchLeft = touches[0].pageX; + currentTouchTop = touches[0].pageY; + } + + var positions = self.__positions; + + // Are we already is dragging mode? + if (self.__isDragging) { + + // Compute move distance + var moveX = currentTouchLeft - self.__lastTouchLeft; + var moveY = currentTouchTop - self.__lastTouchTop; + + // Read previous scroll position and zooming + var scrollLeft = self.__scrollLeft; + var scrollTop = self.__scrollTop; + var level = self.__zoomLevel; + + // Work with scaling + if (scale != null && self.options.zooming) { + + var oldLevel = level; + + // Recompute level based on previous scale and new scale + level = level / self.__lastScale * scale; + + // Limit level according to configuration + level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom); + + // Only do further compution when change happened + if (oldLevel !== level) { + + // Compute relative event position to container + var currentTouchLeftRel = currentTouchLeft - self.__clientLeft; + var currentTouchTopRel = currentTouchTop - self.__clientTop; + + // Recompute left and top coordinates based on new zoom level + scrollLeft = (currentTouchLeftRel + scrollLeft) * level / oldLevel - currentTouchLeftRel; + scrollTop = (currentTouchTopRel + scrollTop) * level / oldLevel - currentTouchTopRel; + + // Recompute max scroll values + self.__computeScrollMax(level); + } + } + + if (self.__enableScrollX) { + + scrollLeft -= moveX; + var maxScrollLeft = self.__maxScrollLeft; + + if (scrollLeft > maxScrollLeft || scrollLeft < 0) { + + // Slow down on the edges + if (self.options.bouncing) { + + scrollLeft += moveX / 2; + } else if (scrollLeft > maxScrollLeft) { + + scrollLeft = maxScrollLeft; + } else { + + scrollLeft = 0; + } + } + } + + // Compute new vertical scroll position + if (self.__enableScrollY) { + + scrollTop -= moveY; + var maxScrollTop = self.__maxScrollTop; + + if (scrollTop > maxScrollTop || scrollTop < 0) { + + // Slow down on the edges + if (self.options.bouncing) { + + scrollTop += moveY / 2; + + // Support pull-to-refresh (only when only y is scrollable) + if (!self.__enableScrollX && self.__refreshHeight != null) { + + if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) { + + self.__refreshActive = true; + if (self.__refreshActivate) { + self.__refreshActivate(); + } + } else if (self.__refreshActive && scrollTop > -self.__refreshHeight) { + + self.__refreshActive = false; + if (self.__refreshDeactivate) { + self.__refreshDeactivate(); + } + } + } + } else if (scrollTop > maxScrollTop) { + + scrollTop = maxScrollTop; + } else { + + scrollTop = 0; + } + } + } + + // Keep list from growing infinitely (holding min 10, max 20 measure points) + if (positions.length > 60) { + positions.splice(0, 30); + } + + // Track scroll movement for decleration + positions.push(scrollLeft, scrollTop, timeStamp); + + // Sync scroll position + self.__publish(scrollLeft, scrollTop, level); + + // Otherwise figure out whether we are switching into dragging mode now. + } else { + + var minimumTrackingForScroll = self.options.locking ? 3 : 0; + var minimumTrackingForDrag = 5; + + var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft); + var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop); + + self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll; + self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll; + + positions.push(self.__scrollLeft, self.__scrollTop, timeStamp); + + self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag); + } + + // Update last touch positions and time stamp for next event + self.__lastTouchLeft = currentTouchLeft; + self.__lastTouchTop = currentTouchTop; + self.__lastTouchMove = timeStamp; + self.__lastScale = scale; + }, + + /** + * Touch end handler for scrolling support + */ + doTouchEnd: function (timeStamp) { + + if (timeStamp instanceof Date) { + timeStamp = timeStamp.valueOf(); + } + if (typeof timeStamp !== "number") { + throw new Error("Invalid timestamp value: " + timeStamp); + } + + var self = this; + + // Ignore event when tracking is not enabled (no touchstart event on element) + // This is required as this listener ('touchmove') sits on the document and not on the element itself. + if (!self.__isTracking) { + return; + } + + // Not touching anymore (when two finger hit the screen there are two touch end events) + self.__isTracking = false; + + // Be sure to reset the dragging flag now. Here we also detect whether + // the finger has moved fast enough to switch into a deceleration animation. + if (self.__isDragging) { + + // Reset dragging flag + self.__isDragging = false; + + // Start deceleration + // Verify that the last move detected was in some relevant time frame + if (self.__isSingleTouch && self.options.animating && timeStamp - self.__lastTouchMove <= 100) { + + // Then figure out what the scroll position was about 100ms ago + var positions = self.__positions; + var endPos = positions.length - 1; + var startPos = endPos; + + // Move pointer to position measured 100ms ago + for (var i = endPos; i > 0 && positions[i] > self.__lastTouchMove - 100; i -= 3) { + startPos = i; + } + + // If start and stop position is identical in a 100ms timeframe, + // we cannot compute any useful deceleration. + if (startPos !== endPos) { + + // Compute relative movement between these two points + var timeOffset = positions[endPos] - positions[startPos]; + var movedLeft = self.__scrollLeft - positions[startPos - 2]; + var movedTop = self.__scrollTop - positions[startPos - 1]; + + // Based on 50ms compute the movement to apply for each render step + self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60); + self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60); + + // How much velocity is required to start the deceleration + var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? 4 : 1; + + // Verify that we have enough velocity to start deceleration + if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) { + + // Deactivate pull-to-refresh when decelerating + if (!self.__refreshActive) { + + self.__startDeceleration(timeStamp); + } + } + } + } + } + + // If this was a slower move it is per default non decelerated, but this + // still means that we want snap back to the bounds which is done here. + // This is placed outside the condition above to improve edge case stability + // e.g. touchend fired without enabled dragging. This should normally do not + // have modified the scroll positions or even showed the scrollbars though. + if (!self.__isDecelerating) { + + if (self.__refreshActive && self.__refreshStart) { + + // Use publish instead of scrollTo to allow scrolling to out of boundary position + // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled + self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true); + + if (self.__refreshStart) { + self.__refreshStart(); + } + } else { + + self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel); + + // Directly signalize deactivation (nothing todo on refresh?) + if (self.__refreshActive) { + + self.__refreshActive = false; + if (self.__refreshDeactivate) { + self.__refreshDeactivate(); + } + } + } + } + + // Fully cleanup list + self.__positions.length = 0; + }, + + /* + --------------------------------------------------------------------------- + PRIVATE API + --------------------------------------------------------------------------- + */ + + /** + * Applies the scroll position to the content element + * + * @param left {Number} Left scroll position + * @param top {Number} Top scroll position + * @param animate {Boolean?false} Whether animation should be used to move to the new coordinates + */ + __publish: function (left, top, zoom, animate) { + + var self = this; + + // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation + var wasAnimating = self.__isAnimating; + if (wasAnimating) { + core.effect.Animate.stop(wasAnimating); + self.__isAnimating = false; + } + + if (animate && self.options.animating) { + + // Keep scheduled positions for scrollBy/zoomBy functionality + self.__scheduledLeft = left; + self.__scheduledTop = top; + self.__scheduledZoom = zoom; + + var oldLeft = self.__scrollLeft; + var oldTop = self.__scrollTop; + var oldZoom = self.__zoomLevel; + + var diffLeft = left - oldLeft; + var diffTop = top - oldTop; + var diffZoom = zoom - oldZoom; + + var step = function (percent, now, render) { + + if (render) { + + self.__scrollLeft = oldLeft + diffLeft * percent; + self.__scrollTop = oldTop + diffTop * percent; + self.__zoomLevel = oldZoom + diffZoom * percent; + + // Push values out + if (self.__callback) { + self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel); + } + } + }; + + var verify = function (id) { + return self.__isAnimating === id; + }; + + var completed = function (renderedFramesPerSecond, animationId, wasFinished) { + if (animationId === self.__isAnimating) { + self.__isAnimating = false; + } + + if (self.options.zooming) { + self.__computeScrollMax(); + } + }; + + // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out + self.__isAnimating = core.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic); + } else { + + self.__scheduledLeft = self.__scrollLeft = left; + self.__scheduledTop = self.__scrollTop = top; + self.__scheduledZoom = self.__zoomLevel = zoom; + + // Push values out + if (self.__callback) { + self.__callback(left, top, zoom); + } + + // Fix max scroll ranges + if (self.options.zooming) { + self.__computeScrollMax(); + } + } + }, + + /** + * Recomputes scroll minimum values based on client dimensions and content dimensions. + */ + __computeScrollMax: function (zoomLevel) { + + var self = this; + + if (zoomLevel == null) { + zoomLevel = self.__zoomLevel; + } + + self.__maxScrollLeft = Math.max(self.__contentWidth * zoomLevel - self.__clientWidth, 0); + self.__maxScrollTop = Math.max(self.__contentHeight * zoomLevel - self.__clientHeight, 0); + }, + + /* + --------------------------------------------------------------------------- + ANIMATION (DECELERATION) SUPPORT + --------------------------------------------------------------------------- + */ + + /** + * Called when a touch sequence end and the speed of the finger was high enough + * to switch into deceleration mode. + */ + __startDeceleration: function (timeStamp) { + + var self = this; + + if (self.options.paging) { + + var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0); + var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0); + var clientWidth = self.__clientWidth; + var clientHeight = self.__clientHeight; + + // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area. + // Each page should have exactly the size of the client area. + self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth; + self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight; + self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth; + self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight; + } else { + + self.__minDecelerationScrollLeft = 0; + self.__minDecelerationScrollTop = 0; + self.__maxDecelerationScrollLeft = self.__maxScrollLeft; + self.__maxDecelerationScrollTop = self.__maxScrollTop; + } + + // Wrap class method + var step = function (percent, now, render) { + self.__stepThroughDeceleration(render); + }; + + // How much velocity is required to keep the deceleration running + var minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1; + + // Detect whether it's still worth to continue animating steps + // If we are already slow enough to not being user perceivable anymore, we stop the whole process here. + var verify = function () { + return Math.abs(self.__decelerationVelocityX) >= minVelocityToKeepDecelerating || Math.abs(self.__decelerationVelocityY) >= minVelocityToKeepDecelerating; + }; + + var completed = function (renderedFramesPerSecond, animationId, wasFinished) { + self.__isDecelerating = false; + + // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions + self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping); + }; + + // Start animation and switch on flag + self.__isDecelerating = core.effect.Animate.start(step, verify, completed); + }, + + /** + * Called on every step of the animation + * + * @param inMemory {Boolean?false} Whether to not render the current step, but keep it in memory only. Used internally only! + */ + __stepThroughDeceleration: function (render) { + + var self = this; + + // + // COMPUTE NEXT SCROLL POSITION + // + + // Add deceleration to scroll position + var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX; + var scrollTop = self.__scrollTop + self.__decelerationVelocityY; + + // + // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE + // + + if (!self.options.bouncing) { + + var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft); + if (scrollLeftFixed !== scrollLeft) { + scrollLeft = scrollLeftFixed; + self.__decelerationVelocityX = 0; + } + + var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop); + if (scrollTopFixed !== scrollTop) { + scrollTop = scrollTopFixed; + self.__decelerationVelocityY = 0; + } + } + + // + // UPDATE SCROLL POSITION + // + + if (render) { + + self.__publish(scrollLeft, scrollTop, self.__zoomLevel); + } else { + + self.__scrollLeft = scrollLeft; + self.__scrollTop = scrollTop; + } + + // + // SLOW DOWN + // + + // Slow down velocity on every iteration + if (!self.options.paging) { + + // This is the factor applied to every iteration of the animation + // to slow down the process. This should emulate natural behavior where + // objects slow down when the initiator of the movement is removed + var frictionFactor = 0.95; + + self.__decelerationVelocityX *= frictionFactor; + self.__decelerationVelocityY *= frictionFactor; + } + + // + // BOUNCING SUPPORT + // + + if (self.options.bouncing) { + + var scrollOutsideX = 0; + var scrollOutsideY = 0; + + // This configures the amount of change applied to deceleration/acceleration when reaching boundaries + var penetrationDeceleration = 0.03; + var penetrationAcceleration = 0.08; + + // Check limits + if (scrollLeft < self.__minDecelerationScrollLeft) { + scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft; + } else if (scrollLeft > self.__maxDecelerationScrollLeft) { + scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft; + } + + if (scrollTop < self.__minDecelerationScrollTop) { + scrollOutsideY = self.__minDecelerationScrollTop - scrollTop; + } else if (scrollTop > self.__maxDecelerationScrollTop) { + scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop; + } + + // Slow down until slow enough, then flip back to snap position + if (scrollOutsideX !== 0) { + if (scrollOutsideX * self.__decelerationVelocityX <= 0) { + self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration; + } else { + self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration; + } + } + + if (scrollOutsideY !== 0) { + if (scrollOutsideY * self.__decelerationVelocityY <= 0) { + self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration; + } else { + self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration; + } + } + } + } + }; + + // Copy over members to prototype + for (var key in members) { + Scroller.prototype[key] = members[key]; + } +})(); + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['shoestring'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = factory(); + } else { + // Browser globals + factory(); + } +})(function () { + var win = typeof window !== "undefined" ? window : this; + var doc = win.document; + + /** + * The shoestring object constructor. + * + * @param {string,object} prim The selector to find or element to wrap. + * @param {object} sec The context in which to match the `prim` selector. + * @returns shoestring + * @this window + */ + function shoestring(prim, sec) { + var pType = typeof prim, + ret = [], + sel; + + // return an empty shoestring object + if (!prim) { + return new Shoestring(ret); + } + + // ready calls + if (prim.call) { + return shoestring.ready(prim); + } + + // handle re-wrapping shoestring objects + if (prim.constructor === Shoestring && !sec) { + return prim; + } + + // if string starting with <, make html + if (pType === "string" && prim.indexOf("<") === 0) { + var dfrag = doc.createElement("div"); + + dfrag.innerHTML = prim; + + // TODO depends on children (circular) + return shoestring(dfrag).children().each(function () { + dfrag.removeChild(this); + }); + } + + // if string, it's a selector, use qsa + if (pType === "string") { + if (sec) { + return shoestring(sec).find(prim); + } + + sel = doc.querySelectorAll(prim); + + return new Shoestring(sel, prim); + } + + // array like objects or node lists + if (Object.prototype.toString.call(pType) === '[object Array]' || win.NodeList && prim instanceof win.NodeList) { + + return new Shoestring(prim, prim); + } + + // if it's an array, use all the elements + if (prim.constructor === Array) { + return new Shoestring(prim, prim); + } + + // otherwise assume it's an object the we want at an index + return new Shoestring([prim], prim); + } + + var Shoestring = function (ret, prim) { + this.length = 0; + this.selector = prim; + shoestring.merge(this, ret); + }; + + // TODO only required for tests + Shoestring.prototype.reverse = [].reverse; + + // For adding element set methods + shoestring.fn = Shoestring.prototype; + + shoestring.Shoestring = Shoestring; + + // For extending objects + // TODO move to separate module when we use prototypes + shoestring.extend = function (first, second) { + for (var i in second) { + if (second.hasOwnProperty(i)) { + first[i] = second[i]; + } + } + + return first; + }; + + // taken directly from jQuery + shoestring.merge = function (first, second) { + var len, j, i; + + len = +second.length, j = 0, i = first.length; + + for (; j < len; j++) { + first[i++] = second[j]; + } + + first.length = i; + + return first; + }; + + // expose + win.shoestring = shoestring; + + /** + * Make an HTTP request to a url. + * + * **NOTE** the following options are supported: + * + * - *method* - The HTTP method used with the request. Default: `GET`. + * - *data* - Raw object with keys and values to pass with request as query params. Default `null`. + * - *headers* - Set of request headers to add. Default `{}`. + * - *async* - Whether the opened request is asynchronouse. Default `true`. + * - *success* - Callback for successful request and response. Passed the response data. + * - *error* - Callback for failed request and response. + * - *cancel* - Callback for cancelled request and response. + * + * @param {string} url The url to request. + * @param {object} options The options object, see Notes. + * @return shoestring + * @this shoestring + */ + + shoestring.ajax = function (url, options) { + var params = "", + req = new XMLHttpRequest(), + settings, + key; + + settings = shoestring.extend({}, shoestring.ajax.settings); + + if (options) { + shoestring.extend(settings, options); + } + + if (!url) { + url = settings.url; + } + + if (!req || !url) { + return; + } + + // create parameter string from data object + if (settings.data) { + for (key in settings.data) { + if (settings.data.hasOwnProperty(key)) { + if (params !== "") { + params += "&"; + } + params += encodeURIComponent(key) + "=" + encodeURIComponent(settings.data[key]); + } + } + } + + // append params to url for GET requests + if (settings.method === "GET" && params) { + + url += "?" + params; + } + + req.open(settings.method, url, settings.async); + + if (req.setRequestHeader) { + req.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + + // Set 'Content-type' header for POST requests + if (settings.method === "POST" && params) { + req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + } + + for (key in settings.headers) { + if (settings.headers.hasOwnProperty(key)) { + req.setRequestHeader(key, settings.headers[key]); + } + } + } + + req.onreadystatechange = function () { + if (req.readyState === 4) { + // Trim the whitespace so shoestring('
') works + var res = (req.responseText || '').replace(/^\s+|\s+$/g, ''); + if (req.status.toString().indexOf("0") === 0) { + return settings.cancel(res, req.status, req); + } else if (req.status.toString().match(/^(4|5)/) && RegExp.$1) { + return settings.error(res, req.status, req); + } else if (settings.success) { + return settings.success(res, req.status, req); + } + } + }; + + if (req.readyState === 4) { + return req; + } + + // Send request + if (settings.method === "POST" && params) { + req.send(params); + } else { + req.send(); + } + + return req; + }; + + shoestring.ajax.settings = { + success: function () {}, + error: function () {}, + cancel: function () {}, + method: "GET", + async: true, + data: null, + headers: {} + }; + + /** + * Helper function wrapping a call to [ajax](ajax.js.html) using the `GET` method. + * + * @param {string} url The url to GET from. + * @param {function} callback Callback to invoke on success. + * @return shoestring + * @this shoestring + */ + shoestring.get = function (url, callback) { + return shoestring.ajax(url, { success: callback }); + }; + + /** + * Load the HTML response from `url` into the current set of elements. + * + * @param {string} url The url to GET from. + * @param {function} callback Callback to invoke after HTML is inserted. + * @return shoestring + * @this shoestring + */ + shoestring.fn.load = function (url, callback) { + var self = this, + args = arguments, + intCB = function (data) { + self.each(function () { + shoestring(this).html(data); + }); + + if (callback) { + callback.apply(self, args); + } + }; + + shoestring.ajax(url, { success: intCB }); + return this; + }; + + /** + * Helper function wrapping a call to [ajax](ajax.js.html) using the `POST` method. + * + * @param {string} url The url to POST to. + * @param {object} data The data to send. + * @param {function} callback Callback to invoke on success. + * @return shoestring + * @this shoestring + */ + shoestring.post = function (url, data, callback) { + return shoestring.ajax(url, { data: data, method: "POST", success: callback }); + }; + + /** + * Iterates over `shoestring` collections. + * + * @param {function} callback The callback to be invoked on each element and index + * @return shoestring + * @this shoestring + */ + shoestring.fn.each = function (callback) { + return shoestring.each(this, callback); + }; + + shoestring.each = function (collection, callback) { + var val; + for (var i = 0, il = collection.length; i < il; i++) { + val = callback.call(collection[i], i, collection[i]); + if (val === false) { + break; + } + } + + return collection; + }; + + /** + * Check for array membership. + * + * @param {object} needle The thing to find. + * @param {object} haystack The thing to find the needle in. + * @return {boolean} + * @this window + */ + shoestring.inArray = function (needle, haystack) { + var isin = -1; + for (var i = 0, il = haystack.length; i < il; i++) { + if (haystack.hasOwnProperty(i) && haystack[i] === needle) { + isin = i; + } + } + return isin; + }; + + /** + * Bind callbacks to be run when the DOM is "ready". + * + * @param {function} fn The callback to be run + * @return shoestring + * @this shoestring + */ + shoestring.ready = function (fn) { + if (ready && fn) { + fn.call(doc); + } else if (fn) { + readyQueue.push(fn); + } else { + runReady(); + } + + return [doc]; + }; + + // TODO necessary? + shoestring.fn.ready = function (fn) { + shoestring.ready(fn); + return this; + }; + + // Empty and exec the ready queue + var ready = false, + readyQueue = [], + runReady = function () { + if (!ready) { + while (readyQueue.length) { + readyQueue.shift().call(doc); + } + ready = true; + } + }; + + // If DOM is already ready at exec time, depends on the browser. + // From: https://github.com/mobify/mobifyjs/blob/526841be5509e28fc949038021799e4223479f8d/src/capture.js#L128 + if (doc.attachEvent ? doc.readyState === "complete" : doc.readyState !== "loading") { + runReady(); + } else { + doc.addEventListener("DOMContentLoaded", runReady, false); + doc.addEventListener("readystatechange", runReady, false); + win.addEventListener("load", runReady, false); + } + + /** + * Checks the current set of elements against the selector, if one matches return `true`. + * + * @param {string} selector The selector to check. + * @return {boolean} + * @this {shoestring} + */ + shoestring.fn.is = function (selector) { + var ret = false, + self = this, + parents, + check; + + // assume a dom element + if (typeof selector !== "string") { + // array-like, ie shoestring objects or element arrays + if (selector.length && selector[0]) { + check = selector; + } else { + check = [selector]; + } + + return _checkElements(this, check); + } + + parents = this.parent(); + + if (!parents.length) { + parents = shoestring(doc); + } + + parents.each(function (i, e) { + var children; + + children = e.querySelectorAll(selector); + + ret = _checkElements(self, children); + }); + + return ret; + }; + + function _checkElements(needles, haystack) { + var ret = false; + + needles.each(function () { + var j = 0; + + while (j < haystack.length) { + if (this === haystack[j]) { + ret = true; + } + + j++; + } + }); + + return ret; + } + + /** + * Get data attached to the first element or set data values on all elements in the current set. + * + * @param {string} name The data attribute name. + * @param {any} value The value assigned to the data attribute. + * @return {any|shoestring} + * @this shoestring + */ + shoestring.fn.data = function (name, value) { + if (name !== undefined) { + if (value !== undefined) { + return this.each(function () { + if (!this.shoestringData) { + this.shoestringData = {}; + } + + this.shoestringData[name] = value; + }); + } else { + if (this[0]) { + if (this[0].shoestringData) { + return this[0].shoestringData[name]; + } + } + } + } else { + return this[0] ? this[0].shoestringData || {} : undefined; + } + }; + + /** + * Remove data associated with `name` or all the data, for each element in the current set. + * + * @param {string} name The data attribute name. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeData = function (name) { + return this.each(function () { + if (name !== undefined && this.shoestringData) { + this.shoestringData[name] = undefined; + delete this.shoestringData[name]; + } else { + this[0].shoestringData = {}; + } + }); + }; + + /** + * An alias for the `shoestring` constructor. + */ + if (typeof win.$ === "undefined") { + win.$ = shoestring; + } + + /** + * Add a class to each DOM element in the set of elements. + * + * @param {string} className The name of the class to be added. + * @return shoestring + * @this shoestring + */ + shoestring.fn.addClass = function (className) { + var classes = className.replace(/^\s+|\s+$/g, '').split(" "); + + return this.each(function () { + for (var i = 0, il = classes.length; i < il; i++) { + if (this.className !== undefined && (this.className === "" || !this.className.match(new RegExp("(^|\\s)" + classes[i] + "($|\\s)")))) { + this.className += " " + classes[i]; + } + } + }); + }; + + /** + * Add elements matching the selector to the current set. + * + * @param {string} selector The selector for the elements to add from the DOM + * @return shoestring + * @this shoestring + */ + shoestring.fn.add = function (selector) { + var ret = []; + this.each(function () { + ret.push(this); + }); + + shoestring(selector).each(function () { + ret.push(this); + }); + + return shoestring(ret); + }; + + /** + * Insert an element or HTML string after each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.after = function (fragment) { + if (typeof fragment === "string" || fragment.nodeType !== undefined) { + fragment = shoestring(fragment); + } + + if (fragment.length > 1) { + fragment = fragment.reverse(); + } + return this.each(function (i) { + for (var j = 0, jl = fragment.length; j < jl; j++) { + var insertEl = i > 0 ? fragment[j].cloneNode(true) : fragment[j]; + this.parentNode.insertBefore(insertEl, this.nextSibling); + } + }); + }; + + /** + * Insert an element or HTML string as the last child of each element in the set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.append = function (fragment) { + if (typeof fragment === "string" || fragment.nodeType !== undefined) { + fragment = shoestring(fragment); + } + + return this.each(function (i) { + for (var j = 0, jl = fragment.length; j < jl; j++) { + this.appendChild(i > 0 ? fragment[j].cloneNode(true) : fragment[j]); + } + }); + }; + + /** + * Insert the current set as the last child of the elements matching the selector. + * + * @param {string} selector The selector after which to append the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.appendTo = function (selector) { + return this.each(function () { + shoestring(selector).append(this); + }); + }; + + /** + * Get the value of the first element of the set or set the value of all the elements in the set. + * + * @param {string} name The attribute name. + * @param {string} value The new value for the attribute. + * @return {shoestring|string|undefined} + * @this {shoestring} + */ + shoestring.fn.attr = function (name, value) { + var nameStr = typeof name === "string"; + + if (value !== undefined || !nameStr) { + return this.each(function () { + if (nameStr) { + this.setAttribute(name, value); + } else { + for (var i in name) { + if (name.hasOwnProperty(i)) { + this.setAttribute(i, name[i]); + } + } + } + }); + } else { + return this[0] ? this[0].getAttribute(name) : undefined; + } + }; + + /** + * Insert an element or HTML string before each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.before = function (fragment) { + if (typeof fragment === "string" || fragment.nodeType !== undefined) { + fragment = shoestring(fragment); + } + + return this.each(function (i) { + for (var j = 0, jl = fragment.length; j < jl; j++) { + this.parentNode.insertBefore(i > 0 ? fragment[j].cloneNode(true) : fragment[j], this); + } + }); + }; + + /** + * Get the children of the current collection. + * @return shoestring + * @this shoestring + */ + shoestring.fn.children = function () { + var ret = [], + childs, + j; + this.each(function () { + childs = this.children; + j = -1; + + while (j++ < childs.length - 1) { + if (shoestring.inArray(childs[j], ret) === -1) { + ret.push(childs[j]); + } + } + }); + return shoestring(ret); + }; + + /** + * Clone and return the current set of nodes into a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.clone = function () { + var ret = []; + + this.each(function () { + ret.push(this.cloneNode(true)); + }); + + return shoestring(ret); + }; + + /** + * Find an element matching the selector in the set of the current element and its parents. + * + * @param {string} selector The selector used to identify the target element. + * @return shoestring + * @this shoestring + */ + shoestring.fn.closest = function (selector) { + var ret = []; + + if (!selector) { + return shoestring(ret); + } + + this.each(function () { + var element, + $self = shoestring(element = this); + + if ($self.is(selector)) { + ret.push(this); + return; + } + + while (element.parentElement) { + if (shoestring(element.parentElement).is(selector)) { + ret.push(element.parentElement); + break; + } + + element = element.parentElement; + } + }); + + return shoestring(ret); + }; + + shoestring.cssExceptions = { + 'float': ['cssFloat'] + }; + + (function () { + var cssExceptions = shoestring.cssExceptions; + + // marginRight instead of margin-right + function convertPropertyName(str) { + return str.replace(/\-([A-Za-z])/g, function (match, character) { + return character.toUpperCase(); + }); + } + + function _getStyle(element, property) { + return win.getComputedStyle(element, null).getPropertyValue(property); + } + + var vendorPrefixes = ['', '-webkit-', '-ms-', '-moz-', '-o-', '-khtml-']; + + /** + * Private function for getting the computed style of an element. + * + * **NOTE** Please use the [css](../css.js.html) method instead. + * + * @method _getStyle + * @param {HTMLElement} element The element we want the style property for. + * @param {string} property The css property we want the style for. + */ + shoestring._getStyle = function (element, property) { + var convert, value, j, k; + + if (cssExceptions[property]) { + for (j = 0, k = cssExceptions[property].length; j < k; j++) { + value = _getStyle(element, cssExceptions[property][j]); + + if (value) { + return value; + } + } + } + + for (j = 0, k = vendorPrefixes.length; j < k; j++) { + convert = convertPropertyName(vendorPrefixes[j] + property); + + // VendorprefixKeyName || key-name + value = _getStyle(element, convert); + + if (convert !== property) { + value = value || _getStyle(element, property); + } + + if (vendorPrefixes[j]) { + // -vendorprefix-key-name + value = value || _getStyle(element, vendorPrefixes[j] + property); + } + + if (value) { + return value; + } + } + + return undefined; + }; + })(); + + (function () { + var cssExceptions = shoestring.cssExceptions; + + // marginRight instead of margin-right + function convertPropertyName(str) { + return str.replace(/\-([A-Za-z])/g, function (match, character) { + return character.toUpperCase(); + }); + } + + /** + * Private function for setting the style of an element. + * + * **NOTE** Please use the [css](../css.js.html) method instead. + * + * @method _setStyle + * @param {HTMLElement} element The element we want to style. + * @param {string} property The property being used to style the element. + * @param {string} value The css value for the style property. + */ + shoestring._setStyle = function (element, property, value) { + var convertedProperty = convertPropertyName(property); + + element.style[property] = value; + + if (convertedProperty !== property) { + element.style[convertedProperty] = value; + } + + if (cssExceptions[property]) { + for (var j = 0, k = cssExceptions[property].length; j < k; j++) { + element.style[cssExceptions[property][j]] = value; + } + } + }; + })(); + + /** + * Get the compute style property of the first element or set the value of a style property + * on all elements in the set. + * + * @method _setStyle + * @param {string} property The property being used to style the element. + * @param {string|undefined} value The css value for the style property. + * @return {string|shoestring} + * @this shoestring + */ + shoestring.fn.css = function (property, value) { + if (!this[0]) { + return; + } + + if (typeof property === "object") { + return this.each(function () { + for (var key in property) { + if (property.hasOwnProperty(key)) { + shoestring._setStyle(this, key, property[key]); + } + } + }); + } else { + // assignment else retrieve first + if (value !== undefined) { + return this.each(function () { + shoestring._setStyle(this, property, value); + }); + } + + return shoestring._getStyle(this[0], property); + } + }; + + /** + * Returns the indexed element wrapped in a new `shoestring` object. + * + * @param {integer} index The index of the element to wrap and return. + * @return shoestring + * @this shoestring + */ + shoestring.fn.eq = function (index) { + if (this[index]) { + return shoestring(this[index]); + } + + return shoestring([]); + }; + + /** + * Filter out the current set if they do *not* match the passed selector or + * the supplied callback returns false + * + * @param {string,function} selector The selector or boolean return value callback used to filter the elements. + * @return shoestring + * @this shoestring + */ + shoestring.fn.filter = function (selector) { + var ret = []; + + this.each(function (index) { + var wsel; + + if (typeof selector === 'function') { + if (selector.call(this, index) !== false) { + ret.push(this); + } + // document node + } else if (this.nodeType === 9) { + if (this === selector) { + ret.push(this); + } + } else { + if (!this.parentNode) { + var context = shoestring(doc.createDocumentFragment()); + + context[0].appendChild(this); + wsel = shoestring(selector, context); + } else { + wsel = shoestring(selector, this.parentNode); + } + + if (shoestring.inArray(this, wsel) > -1) { + ret.push(this); + } + } + }); + + return shoestring(ret); + }; + + /** + * Find descendant elements of the current collection. + * + * @param {string} selector The selector used to find the children + * @return shoestring + * @this shoestring + */ + shoestring.fn.find = function (selector) { + var ret = [], + finds; + this.each(function () { + finds = this.querySelectorAll(selector); + + for (var i = 0, il = finds.length; i < il; i++) { + ret = ret.concat(finds[i]); + } + }); + return shoestring(ret); + }; + + /** + * Returns the first element of the set wrapped in a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.first = function () { + return this.eq(0); + }; + + /** + * Returns the raw DOM node at the passed index. + * + * @param {integer} index The index of the element to wrap and return. + * @return {HTMLElement|undefined|array} + * @this shoestring + */ + shoestring.fn.get = function (index) { + + // return an array of elements if index is undefined + if (index === undefined) { + var elements = []; + + for (var i = 0; i < this.length; i++) { + elements.push(this[i]); + } + + return elements; + } else { + return this[index]; + } + }; + + /** + * Private function for setting/getting the offset property for height/width. + * + * **NOTE** Please use the [width](width.js.html) or [height](height.js.html) methods instead. + * + * @param {shoestring} set The set of elements. + * @param {string} name The string "height" or "width". + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this window + */ + shoestring._dimension = function (set, name, value) { + var offsetName; + + if (value === undefined) { + offsetName = name.replace(/^[a-z]/, function (letter) { + return letter.toUpperCase(); + }); + + return set[0]["offset" + offsetName]; + } else { + // support integer values as pixels + value = typeof value === "string" ? value : value + "px"; + + return set.each(function () { + this.style[name] = value; + }); + } + }; + + /** + * Gets the height value of the first element or sets the height for the whole set. + * + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.height = function (value) { + return shoestring._dimension(this, "height", value); + }; + + var set = function (html) { + if (typeof html === "string" || typeof html === "number") { + return this.each(function () { + this.innerHTML = "" + html; + }); + } else { + var h = ""; + if (typeof html.length !== "undefined") { + for (var i = 0, l = html.length; i < l; i++) { + h += html[i].outerHTML; + } + } else { + h = html.outerHTML; + } + return this.each(function () { + this.innerHTML = h; + }); + } + }; + /** + * Gets or sets the `innerHTML` from all the elements in the set. + * + * @param {string|undefined} html The html to assign + * @return {string|shoestring} + * @this shoestring + */ + shoestring.fn.html = function (html) { + if (typeof html !== "undefined") { + return set.call(this, html); + } else { + // get + var pile = ""; + + this.each(function () { + pile += this.innerHTML; + }); + + return pile; + } + }; + + (function () { + function _getIndex(set, test) { + var i, result, element; + + for (i = result = 0; i < set.length; i++) { + element = set.item ? set.item(i) : set[i]; + + if (test(element)) { + return result; + } + + // ignore text nodes, etc + // NOTE may need to be more permissive + if (element.nodeType === 1) { + result++; + } + } + + return -1; + } + + /** + * Find the index in the current set for the passed selector. + * Without a selector it returns the index of the first node within the array of its siblings. + * + * @param {string|undefined} selector The selector used to search for the index. + * @return {integer} + * @this {shoestring} + */ + shoestring.fn.index = function (selector) { + var self, children; + + self = this; + + // no arg? check the children, otherwise check each element that matches + if (selector === undefined) { + children = (this[0] && this[0].parentNode || doc.documentElement).childNodes; + + // check if the element matches the first of the set + return _getIndex(children, function (element) { + return self[0] === element; + }); + } else { + + // check if the element matches the first selected node from the parent + return _getIndex(self, function (element) { + return element === shoestring(selector, element.parentNode)[0]; + }); + } + }; + })(); + + /** + * Insert the current set after the elements matching the selector. + * + * @param {string} selector The selector after which to insert the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.insertAfter = function (selector) { + return this.each(function () { + shoestring(selector).after(this); + }); + }; + + /** + * Insert the current set before the elements matching the selector. + * + * @param {string} selector The selector before which to insert the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.insertBefore = function (selector) { + return this.each(function () { + shoestring(selector).before(this); + }); + }; + + /** + * Returns the last element of the set wrapped in a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.last = function () { + return this.eq(this.length - 1); + }; + + /** + * Returns a `shoestring` object with the set of siblings of each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.next = function () { + + var result = []; + + // TODO need to implement map + this.each(function () { + var children, item, found; + + // get the child nodes for this member of the set + children = shoestring(this.parentNode)[0].childNodes; + + for (var i = 0; i < children.length; i++) { + item = children.item(i); + + // found the item we needed (found) which means current item value is + // the next node in the list, as long as it's viable grab it + // NOTE may need to be more permissive + if (found && item.nodeType === 1) { + result.push(item); + break; + } + + // find the current item and mark it as found + if (item === this) { + found = true; + } + } + }); + + return shoestring(result); + }; + + /** + * Removes elements from the current set. + * + * @param {string} selector The selector to use when removing the elements. + * @return shoestring + * @this shoestring + */ + shoestring.fn.not = function (selector) { + var ret = []; + + this.each(function () { + var found = shoestring(selector, this.parentNode); + + if (shoestring.inArray(this, found) === -1) { + ret.push(this); + } + }); + + return shoestring(ret); + }; + + /** + * Returns an object with the `top` and `left` properties corresponging to the first elements offsets. + * + * @return object + * @this shoestring + */ + shoestring.fn.offset = function () { + return { + top: this[0].offsetTop, + left: this[0].offsetLeft + }; + }; + + /** + * Returns the set of first parents for each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.parent = function () { + var ret = [], + parent; + + this.each(function () { + // no parent node, assume top level + // jQuery parent: return the document object for or the parent node if it exists + parent = this === doc.documentElement ? doc : this.parentNode; + + // if there is a parent and it's not a document fragment + if (parent && parent.nodeType !== 11) { + ret.push(parent); + } + }); + + return shoestring(ret); + }; + + /** + * Returns the set of all parents matching the selector if provided for each element in the current set. + * + * @param {string} selector The selector to check the parents with. + * @return shoestring + * @this shoestring + */ + shoestring.fn.parents = function (selector) { + var ret = []; + + this.each(function () { + var curr = this, + match; + + while (curr.parentElement && !match) { + curr = curr.parentElement; + + if (selector) { + if (curr === shoestring(selector)[0]) { + match = true; + + if (shoestring.inArray(curr, ret) === -1) { + ret.push(curr); + } + } + } else { + if (shoestring.inArray(curr, ret) === -1) { + ret.push(curr); + } + } + } + }); + + return shoestring(ret); + }; + + /** + * Add an HTML string or element before the children of each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML string or element to add. + * @return shoestring + * @this shoestring + */ + shoestring.fn.prepend = function (fragment) { + if (typeof fragment === "string" || fragment.nodeType !== undefined) { + fragment = shoestring(fragment); + } + + return this.each(function (i) { + + for (var j = 0, jl = fragment.length; j < jl; j++) { + var insertEl = i > 0 ? fragment[j].cloneNode(true) : fragment[j]; + if (this.firstChild) { + this.insertBefore(insertEl, this.firstChild); + } else { + this.appendChild(insertEl); + } + } + }); + }; + + /** + * Add each element of the current set before the children of the selected elements. + * + * @param {string} selector The selector for the elements to add the current set to.. + * @return shoestring + * @this shoestring + */ + shoestring.fn.prependTo = function (selector) { + return this.each(function () { + shoestring(selector).prepend(this); + }); + }; + + /** + * Returns a `shoestring` object with the set of *one* siblingx before each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.prev = function () { + + var result = []; + + // TODO need to implement map + this.each(function () { + var children, item, found; + + // get the child nodes for this member of the set + children = shoestring(this.parentNode)[0].childNodes; + + for (var i = children.length - 1; i >= 0; i--) { + item = children.item(i); + + // found the item we needed (found) which means current item value is + // the next node in the list, as long as it's viable grab it + // NOTE may need to be more permissive + if (found && item.nodeType === 1) { + result.push(item); + break; + } + + // find the current item and mark it as found + if (item === this) { + found = true; + } + } + }); + + return shoestring(result); + }; + + /** + * Returns a `shoestring` object with the set of *all* siblings before each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.prevAll = function () { + + var result = []; + + this.each(function () { + var $previous = shoestring(this).prev(); + + while ($previous.length) { + result.push($previous[0]); + $previous = $previous.prev(); + } + }); + + return shoestring(result); + }; + + // Property normalization, a subset taken from jQuery src + shoestring.propFix = { + "class": "className", + contenteditable: "contentEditable", + "for": "htmlFor", + readonly: "readOnly", + tabindex: "tabIndex" + }; + + /** + * Gets the property value from the first element or sets the property value on all elements of the currrent set. + * + * @param {string} name The property name. + * @param {any} value The property value. + * @return {any|shoestring} + * @this shoestring + */ + shoestring.fn.prop = function (name, value) { + if (!this[0]) { + return; + } + + name = shoestring.propFix[name] || name; + + if (value !== undefined) { + return this.each(function () { + this[name] = value; + }); + } else { + return this[0][name]; + } + }; + + /** + * Remove an attribute from each element in the current set. + * + * @param {string} name The name of the attribute. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeAttr = function (name) { + return this.each(function () { + this.removeAttribute(name); + }); + }; + + /** + * Remove a class from each DOM element in the set of elements. + * + * @param {string} className The name of the class to be removed. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeClass = function (cname) { + var classes = cname.replace(/^\s+|\s+$/g, '').split(" "); + + return this.each(function () { + var newClassName, regex; + + for (var i = 0, il = classes.length; i < il; i++) { + if (this.className !== undefined) { + regex = new RegExp("(^|\\s)" + classes[i] + "($|\\s)", "gmi"); + newClassName = this.className.replace(regex, " "); + + this.className = newClassName.replace(/^\s+|\s+$/g, ''); + } + } + }); + }; + + /** + * Remove the current set of elements from the DOM. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.remove = function () { + return this.each(function () { + if (this.parentNode) { + this.parentNode.removeChild(this); + } + }); + }; + + /** + * Remove a proprety from each element in the current set. + * + * @param {string} name The name of the property. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeProp = function (property) { + var name = shoestring.propFix[property] || property; + + return this.each(function () { + this[name] = undefined; + delete this[name]; + }); + }; + + /** + * Replace each element in the current set with that argument HTML string or HTMLElement. + * + * @param {string|HTMLElement} fragment The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.replaceWith = function (fragment) { + if (typeof fragment === "string") { + fragment = shoestring(fragment); + } + + var ret = []; + + if (fragment.length > 1) { + fragment = fragment.reverse(); + } + this.each(function (i) { + var clone = this.cloneNode(true), + insertEl; + ret.push(clone); + + // If there is no parentNode, this is pointless, drop it. + if (!this.parentNode) { + return; + } + + if (fragment.length === 1) { + insertEl = i > 0 ? fragment[0].cloneNode(true) : fragment[0]; + this.parentNode.replaceChild(insertEl, this); + } else { + for (var j = 0, jl = fragment.length; j < jl; j++) { + insertEl = i > 0 ? fragment[j].cloneNode(true) : fragment[j]; + this.parentNode.insertBefore(insertEl, this.nextSibling); + } + this.parentNode.removeChild(this); + } + }); + + return shoestring(ret); + }; + + shoestring.inputTypes = ["text", "hidden", "password", "color", "date", "datetime", + // "datetime\-local" matched by datetime + "email", "month", "number", "range", "search", "tel", "time", "url", "week"]; + + shoestring.inputTypeTest = new RegExp(shoestring.inputTypes.join("|")); + + /** + * Serialize child input element values into an object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.serialize = function () { + var data = {}; + + shoestring("input, select", this).each(function () { + var type = this.type, + name = this.name, + value = this.value; + + if (shoestring.inputTypeTest.test(type) || (type === "checkbox" || type === "radio") && this.checked) { + + data[name] = value; + } else if (this.nodeName === "SELECT") { + data[name] = this.options[this.selectedIndex].nodeValue; + } + }); + + return data; + }; + + /** + * Get all of the sibling elements for each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.siblings = function () { + + if (!this.length) { + return shoestring([]); + } + + var sibs = [], + el = this[0].parentNode.firstChild; + + do { + if (el.nodeType === 1 && el !== this[0]) { + sibs.push(el); + } + + el = el.nextSibling; + } while (el); + + return shoestring(sibs); + }; + + var getText = function (elem) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if (!nodeType) { + // If no nodeType, this is expected to be an array + while (node = elem[i++]) { + // Do not traverse comment nodes + ret += getText(node); + } + } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if (typeof elem.textContent === "string") { + return elem.textContent; + } else { + // Traverse its children + for (elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText(elem); + } + } + } else if (nodeType === 3 || nodeType === 4) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; + }; + + /** + * Recursively retrieve the text content of the each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.text = function () { + + return getText(this); + }; + + /** + * Get the value of the first element or set the value of all elements in the current set. + * + * @param {string} value The value to set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.val = function (value) { + var el; + if (value !== undefined) { + return this.each(function () { + if (this.tagName === "SELECT") { + var optionSet, + option, + options = this.options, + values = [], + i = options.length, + newIndex; + + values[0] = value; + while (i--) { + option = options[i]; + if (option.selected = shoestring.inArray(option.value, values) >= 0) { + optionSet = true; + newIndex = i; + } + } + // force browsers to behave consistently when non-matching value is set + if (!optionSet) { + this.selectedIndex = -1; + } else { + this.selectedIndex = newIndex; + } + } else { + this.value = value; + } + }); + } else { + el = this[0]; + + if (el.tagName === "SELECT") { + if (el.selectedIndex < 0) { + return ""; + } + return el.options[el.selectedIndex].value; + } else { + return el.value; + } + } + }; + + /** + * Gets the width value of the first element or sets the width for the whole set. + * + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.width = function (value) { + return shoestring._dimension(this, "width", value); + }; + + /** + * Wraps the child elements in the provided HTML. + * + * @param {string} html The wrapping HTML. + * @return shoestring + * @this shoestring + */ + shoestring.fn.wrapInner = function (html) { + return this.each(function () { + var inH = this.innerHTML; + + this.innerHTML = ""; + shoestring(this).append(shoestring(html).html(inH)); + }); + }; + + function initEventCache(el, evt) { + if (!el.shoestringData) { + el.shoestringData = {}; + } + if (!el.shoestringData.events) { + el.shoestringData.events = {}; + } + if (!el.shoestringData.loop) { + el.shoestringData.loop = {}; + } + if (!el.shoestringData.events[evt]) { + el.shoestringData.events[evt] = []; + } + } + + function addToEventCache(el, evt, eventInfo) { + var obj = {}; + obj.isCustomEvent = eventInfo.isCustomEvent; + obj.callback = eventInfo.callfunc; + obj.originalCallback = eventInfo.originalCallback; + obj.namespace = eventInfo.namespace; + + el.shoestringData.events[evt].push(obj); + + if (eventInfo.customEventLoop) { + el.shoestringData.loop[evt] = eventInfo.customEventLoop; + } + } + + /** + * Bind a callback to an event for the currrent set of elements. + * + * @param {string} evt The event(s) to watch for. + * @param {object,function} data Data to be included with each event or the callback. + * @param {function} originalCallback Callback to be invoked when data is define.d. + * @return shoestring + * @this shoestring + */ + shoestring.fn.bind = function (evt, data, originalCallback) { + + if (typeof data === "function") { + originalCallback = data; + data = null; + } + + var evts = evt.split(" "); + + // NOTE the `triggeredElement` is purely for custom events from IE + function encasedCallback(e, namespace, triggeredElement) { + var result; + + if (e._namespace && e._namespace !== namespace) { + return; + } + + e.data = data; + e.namespace = e._namespace; + + var returnTrue = function () { + return true; + }; + + e.isDefaultPrevented = function () { + return false; + }; + + var originalPreventDefault = e.preventDefault; + var preventDefaultConstructor = function () { + if (originalPreventDefault) { + return function () { + e.isDefaultPrevented = returnTrue; + originalPreventDefault.call(e); + }; + } else { + return function () { + e.isDefaultPrevented = returnTrue; + e.returnValue = false; + }; + } + }; + + // thanks https://github.com/jonathantneal/EventListener + e.target = triggeredElement || e.target || e.srcElement; + e.preventDefault = preventDefaultConstructor(); + e.stopPropagation = e.stopPropagation || function () { + e.cancelBubble = true; + }; + + result = originalCallback.apply(this, [e].concat(e._args)); + + if (result === false) { + e.preventDefault(); + e.stopPropagation(); + } + + return result; + } + + return this.each(function () { + var domEventCallback, + customEventCallback, + customEventLoop, + oEl = this; + + for (var i = 0, il = evts.length; i < il; i++) { + var split = evts[i].split("."), + evt = split[0], + namespace = split.length > 0 ? split[1] : null; + + domEventCallback = function (originalEvent) { + if (oEl.ssEventTrigger) { + originalEvent._namespace = oEl.ssEventTrigger._namespace; + originalEvent._args = oEl.ssEventTrigger._args; + + oEl.ssEventTrigger = null; + } + return encasedCallback.call(oEl, originalEvent, namespace); + }; + customEventCallback = null; + customEventLoop = null; + + initEventCache(this, evt); + + this.addEventListener(evt, domEventCallback, false); + + addToEventCache(this, evt, { + callfunc: customEventCallback || domEventCallback, + isCustomEvent: !!customEventCallback, + customEventLoop: customEventLoop, + originalCallback: originalCallback, + namespace: namespace + }); + } + }); + }; + + shoestring.fn.on = shoestring.fn.bind; + + /** + * Unbind a previous bound callback for an event. + * + * @param {string} event The event(s) the callback was bound to.. + * @param {function} callback Callback to unbind. + * @return shoestring + * @this shoestring + */ + shoestring.fn.unbind = function (event, callback) { + + var evts = event ? event.split(" ") : []; + + return this.each(function () { + if (!this.shoestringData || !this.shoestringData.events) { + return; + } + + if (!evts.length) { + unbindAll.call(this); + } else { + var split, evt, namespace; + for (var i = 0, il = evts.length; i < il; i++) { + split = evts[i].split("."), evt = split[0], namespace = split.length > 0 ? split[1] : null; + + if (evt) { + unbind.call(this, evt, namespace, callback); + } else { + unbindAll.call(this, namespace, callback); + } + } + } + }); + }; + + function unbind(evt, namespace, callback) { + var bound = this.shoestringData.events[evt]; + if (!(bound && bound.length)) { + return; + } + + var matched = [], + j, + jl; + for (j = 0, jl = bound.length; j < jl; j++) { + if (!namespace || namespace === bound[j].namespace) { + if (callback === undefined || callback === bound[j].originalCallback) { + this.removeEventListener(evt, bound[j].callback, false); + matched.push(j); + } + } + } + + for (j = 0, jl = matched.length; j < jl; j++) { + this.shoestringData.events[evt].splice(j, 1); + } + } + + function unbindAll(namespace, callback) { + for (var evtKey in this.shoestringData.events) { + unbind.call(this, evtKey, namespace, callback); + } + } + + shoestring.fn.off = shoestring.fn.unbind; + + /** + * Bind a callback to an event for the currrent set of elements, unbind after one occurence. + * + * @param {string} event The event(s) to watch for. + * @param {function} callback Callback to invoke on the event. + * @return shoestring + * @this shoestring + */ + shoestring.fn.one = function (event, callback) { + var evts = event.split(" "); + + return this.each(function () { + var thisevt, + cbs = {}, + $t = shoestring(this); + + for (var i = 0, il = evts.length; i < il; i++) { + thisevt = evts[i]; + + cbs[thisevt] = function (e) { + var $t = shoestring(this); + + for (var j in cbs) { + $t.unbind(j, cbs[j]); + } + + return callback.apply(this, [e].concat(e._args)); + }; + + $t.bind(thisevt, cbs[thisevt]); + } + }); + }; + + /** + * Trigger an event on the first element in the set, no bubbling, no defaults. + * + * @param {string} event The event(s) to trigger. + * @param {object} args Arguments to append to callback invocations. + * @return shoestring + * @this shoestring + */ + shoestring.fn.triggerHandler = function (event, args) { + var e = event.split(" ")[0], + el = this[0], + ret; + + // See this.fireEvent( 'on' + evts[ i ], document.createEventObject() ); instead of click() etc in trigger. + if (doc.createEvent && el.shoestringData && el.shoestringData.events && el.shoestringData.events[e]) { + var bindings = el.shoestringData.events[e]; + for (var i in bindings) { + if (bindings.hasOwnProperty(i)) { + event = doc.createEvent("Event"); + event.initEvent(e, true, true); + event._args = args; + args.unshift(event); + + ret = bindings[i].originalCallback.apply(event.target, args); + } + } + } + + return ret; + }; + + /** + * Trigger an event on each of the DOM elements in the current set. + * + * @param {string} event The event(s) to trigger. + * @param {object} args Arguments to append to callback invocations. + * @return shoestring + * @this shoestring + */ + shoestring.fn.trigger = function (event, args) { + var evts = event.split(" "); + + return this.each(function () { + var split, evt, namespace; + for (var i = 0, il = evts.length; i < il; i++) { + split = evts[i].split("."), evt = split[0], namespace = split.length > 0 ? split[1] : null; + + if (evt === "click") { + if (this.tagName === "INPUT" && this.type === "checkbox" && this.click) { + this.click(); + return false; + } + } + + if (doc.createEvent) { + var event = doc.createEvent("Event"); + event.initEvent(evt, true, true); + event._args = args; + event._namespace = namespace; + + this.dispatchEvent(event); + } + } + }); + }; + + return shoestring; +}); + +if (!window.jQuery) { + window.jQuery = window.jQuery || window.shoestring; +} + +(function (exports) { + /** + * The Akamai component namespace contains the {@link Akamai.Viewer}, {@link + * Akamai.Spin360}, {@link Akamai.Carousel}, and {@link Akamai.Zoom} components. + * The {@link Akamai.Viewer} component manages the other components depending on + * the state of the markup it is provided, start there for more. + * + * @example On-ready automatic initialization with jQuery + * $(function(){ + * $( "[data-akamai-viewer]" ).each(function(i, element){ + * var viewer = Akamai.Viewer( element ); + * ... + * // example API use, stoping autoration of first {@link Akamai.Spin360} + * viewer.getCarousels()[0].getSpin360s()[0].stopSpin(); + * }) + * }) + * + * @namespace Akamai + */ + exports.Akamai = exports.Akamai || {}; + window.componentNamespace = "Akamai"; +})(typeof exports === 'undefined' ? window : exports); + +(function (window, $) { + var $window, $doc; + + $window = $(window); + $doc = $(document.documentElement); + + var ns = window.componentNamespace = window.componentNamespace || "FG"; + window[ns] = window[ns] || {}; + + Function.prototype.bind = Function.prototype.bind || function (context) { + var self = this; + + return function () { + self.apply(context, arguments); + }; + }; + + var Tau = window[ns].Tau = function (element, options) { + var startIndex, reducedStepSize; + + this.element = element; + this.options = options || {}; + this.$element = $(element); + this.$initial = this.$element.find("img"); + this.$loading = this.$element.find(".loading"); + this.index = 0; + + // frame count by order of precendence + // 1. initial frames when they are specified explicitly + // 2. the data attribute on the initial image + // 3. the configured number of frames + this.frames = this.$initial.length > 1 ? this.$initial.length : parseInt(this.$initial.attr("data-frames"), 10) || this.options.frames; + + // grab the user specified step size for when the browser is less-abled + reducedStepSize = parseInt(this.$initial.attr("data-reduced-step-size"), 10) || 4; + + // TODO sort out a better qualification for the full set of images? + this.stepSize = window.requestAnimationFrame ? 1 : reducedStepSize; + + // grab the user specified auto start delay + this.autoRotateStartDelay = (this.options.autoplay || {}).delay || parseInt(this.$initial.attr("data-auto-rotate-delay"), 10) || Tau.autoRotateStartDelay; + + this.mouseMoveBinding = this.rotateEvent.bind(this); + this.touchMoveBinding = this.rotateEvent.bind(this); + + this.path = new Tau.Path(); + + // make sure the initial image stays visible after enhance + this.$initial.first().addClass("focused"); + + // hide all other images + this.$element.addClass("tau-enhanced"); + + // create a rendering spot to force decoding in IE and prevent blinking + this.$render = $("
").css("position", "absolute").css("left", "0").css("top", "0").prependTo(this.element); + + if (this.options.canvas !== false) { + this.canvas = $("").prependTo(this.element)[0]; + + if (this.canvas.getContext) { + this.canvasCtx = this.canvas.getContext("2d"); + this.$element.addClass("tau-canvas"); + + $(window).bind("resize", function () { + clearTimeout(this.canvasResizeTimeout); + this.canvasResizeTimeout = setTimeout(this.renderCanvas.bind(this), 100); + }.bind(this)); + } + } + + if (this.options.controls) { + this.options.controls.text = this.options.controls.text || { + play: "Spin Object", + left: "Rotate Left", + right: "Rotate Right" + }; + + this.createControls(); + } + + // create the rest of the images + this.createImages(); + + // set the initial index and image + if (this.options.autoplay && this.options.autoplay.enabled) { + // start the automatic rotation + this.autostartTimeout = setTimeout(this.autoRotate.bind(this), this.autoRotateStartDelay); + } + + // setup the event bindings for touch drag and mouse drag rotation + this.bind(); + }; + + Tau.autoRotateTraversalTime = 4500; + Tau.autoRotateStartDelay = 100; + Tau.verticalScrollRatio = 4; + // Tau.decelTimeStep = Tau.autoRotateDelay / 2; + // Tau.decel = Tau.decelTimeStep / 8; + Tau.maxVelocity = 60; + + Tau.prototype.createControls = function () { + this.$controls = $("
"); + + if (this.options.controls.play) { + this.$controls.append(this.controlAnchorMarkup("play")); + } + + if (this.options.controls.arrows) { + this.$controls.prepend(this.controlAnchorMarkup("left")).append(this.controlAnchorMarkup("right")); + } + + this.$controls.bind("mousedown touchstart", this.onControlDown.bind(this)); + this.$controls.bind("mouseup", this.onControlUp.bind(this)); + + // prevent link clicks from bubbling + this.$controls.bind("click", function (event) { + if ($(event.target).is("a")) { + event.preventDefault(); + } + }); + + this.$element.append(this.$controls); + }; + + Tau.prototype.controlAnchorMarkup = function (name) { + var text = this.options.controls.text[name]; + + return "" + text + ""; + }; + + Tau.prototype.onControlDown = function (event) { + var $link = $(event.target).closest("a"); + + switch ($link.attr("data-tau-controls")) { + case "left": + this.$element.addClass("control-left-down"); + this.stopAutoRotate(); + this.autoRotate(); + break; + case "right": + this.$element.addClass("control-right-down"); + this.stopAutoRotate(); + this.autoRotate(true); + break; + } + }; + + Tau.prototype.onControlUp = function (event) { + var $link = $(event.target).closest("a"); + + switch ($link.attr("data-tau-controls")) { + case "left": + case "right": + this.$element.removeClass("control-left-down"); + this.$element.removeClass("control-right-down"); + this.stopAutoRotate(); + break; + case "play": + if (this.autoInterval) { + this.stopAutoRotate(); + } else { + this.autoRotate(); + } + break; + } + }; + + Tau.prototype.change = function (delta) { + this.goto(this.options.reverse ? this.index - delta : this.index + delta); + }; + + Tau.prototype.goto = function (index) { + var $next, + normalizedIndex, + imageCount = this.$images.length; + + index = index % imageCount; + + // stay within the bounds of the array + normalizedIndex = (imageCount + index) % imageCount; + + // set the next image that's going to be shown/focused + $next = this.$images.eq(normalizedIndex); + + // skip this action if the desired image isn't loaded yet + // TODO do something fancier here instead of just throwing up hands + if (!$next[0].tauImageLoaded) { + this.showLoading(); + return false; + } + + // hide any image that happens to be visible (initial image when canvas) + if (this.$current) { + this.$current.removeClass("focused"); + } else { + this.$images.removeClass("focused"); + } + + // record the current focused image and make it visible + this.$current = $next; + + // record the updated index only after advancing is possible + this.index = normalizedIndex; + + if (this.canvasCtx) { + return this.renderCanvas(); + } else { + // show the new focused image + this.$current.addClass("focused"); + return true; + } + }; + + Tau.prototype.renderCanvas = function () { + var $img = this.$current; + var img = $img[0]; + + var width = img.naturalWidth; + var height = img.naturalHeight; + + var parentWidth = this.element.clientWidth; + var calcHeight = parentWidth / width * height; + + if (!width || !height || !img.complete) { + return false; + } + + if (this.canvas.width !== parentWidth || this.canvas.height !== calcHeight || parentWidth && calcHeight) { + this.canvas.width = parentWidth; + this.canvas.height = calcHeight; + } + + this.canvasCtx.drawImage(img, 0, 0, parentWidth, calcHeight); + + return true; + }; + + // TODO transplant the attributes from the initial image + Tau.prototype.createImages = function () { + var src, frames, html, $new, boundImageLoaded; + + // if there are no image elements, raise an exception + if (this.$initial.length < 1) { + throw new Error("At least one image required"); + } + + this.loadedCount = 0; + + // if there is only one image element, assume it's a template + if (this.$initial.length == 1) { + this.markImageLoaded(this.$initial[0]); + + src = this.options.template || this.$initial.attr("data-src-template"); + + var imgs = []; + for (var i = this.stepSize + 1; i <= this.frames; i += this.stepSize) { + html = ""; + $new = $(html); + imgs.push($new); + } + + $.each(imgs, function (i, e) { + var $img = $(e); + + $img.bind("load error", function (e) { + this.imageLoaded(i, e.target, e); + }.bind(this)); + + this.$element.append($img); + this.$render.append($img.html()); + }.bind(this)); + + // take all the child images and use them as frames of the rotation + this.$images = this.$element.children().filter("img"); + this.$current = this.$images; + this.goto(0); + } else { + // take all the child images and use them as frames of the rotation + this.$images = this.$element.children().filter("img"); + + this.$images.each(function (i, e) { + // if the image height is greater than zero we assume the image is loaded + // otherwise we bind to onload and pray that we win the race + if ($(e).height() > 0) { + this.imageLoaded(i, e); + } else { + $(e).bind("load error", function (event) { + this.imageLoaded(i, event.target, event); + }.bind(this)); + } + }.bind(this)); + } + }; + + Tau.prototype.imageLoaded = function (index, element, event) { + var initTriggered = false; + this.markImageLoaded(element); + + // if the isn't going to play automatically and the first image is + // loaded make sure to render it + if (this.$element.find("img")[0] == element && (!event || event.type !== "error") && (!this.options.autoplay || !this.options.autoplay.enabled)) { + this.goto(0); + this.$element.trigger("tau.init"); + initTriggered = true; + } + + this.loadedCount++; + + if (this.loadedCount >= this.frames - 1) { + this.hideLoading(); + + if (!initTriggered) { + this.$element.trigger("tau.init"); + this.initialized = true; + } + } + }; + + Tau.prototype.markImageLoaded = function (element) { + element.tauImageLoaded = true; + }; + + Tau.prototype.bind = function () { + this.$element.bind("mousedown touchstart", this.track.bind(this)); + }; + + Tau.prototype.autoRotate = function (right) { + // already rotating + if (this.autoInterval) { + return; + } + + this.$element.addClass("spinning"); + + // move once initially + this.change(right ? -1 : 1); + + // move after the interval + this.autoInterval = setInterval(function () { + this.change(right ? -1 : 1); + }.bind(this), this.autoRotateDelay() * this.stepSize); + + this.$element.trigger("tau.auto-rotate-start"); + }; + + Tau.prototype.autoRotateDelay = function () { + return (this.options.interval || Tau.autoRotateTraversalTime) / this.frames; + }; + + Tau.prototype.stopAutoRotate = function () { + clearInterval(this.autoInterval); + clearInterval(this.autostartTimeout); + this.$element.removeClass("spinning"); + this.autoInterval = undefined; + this.$element.trigger("tau.auto-rotate-stop"); + }; + + Tau.prototype.track = function (event) { + var point; + + // ignore tracking on control clicks + if ($(event.target).closest(".tau-controls").length) { + return; + } + + // prevent dragging behavior for mousedown + if (event.type === "mousedown") { + event.preventDefault(); + } + + if (event.type === "touchstart") { + this.$element.trigger("tau.touch-tracking-start"); + } else { + this.$element.trigger("tau.mouse-tracking-start"); + } + + if (this.tracking) { + return; + } + + $doc.one("mouseup", this.release.bind(this)); + $doc.one("touchend", this.release.bind(this)); + + this.tracking = true; + + // clean out the path since we'll need a new one for decel + this.path.reset(); + + // show the cursor as grabbing + this.cursorGrab(); + + // By default the number of pixels required to move the carousel by one + // frame is the ratio of the tau element width to the number of frames. That + // is, by default the user should be able to see the full rotation by moving + // their input device from one side of the tau element to the other. + var defaultThreshold = this.$element[0].clientWidth / this.frames; + + // divide the default by the sensitivity. If the senstivity is greater than + // 1 it will require less effort (smaller distance) to advance the rotation + // by a single slide. If the sensitivity is less than 1 it will require more + // effort + this.rotateThreshold = defaultThreshold / (this.options.sensitivity || 1); + + // record the x for threshold calculations + point = this.getPoint(event); + this.downX = point.x; + this.downY = point.y; + this.downIndex = this.index; + + $doc.bind("mousemove", this.mouseMoveBinding); + $doc.bind("touchmove", this.touchMoveBinding); + }; + + Tau.prototype.slow = function () { + // if the path gets broken during the decel just stop + if (!this.path.isSufficient()) { + this.clearSlowInterval(); + return; + } + + this.rotate({ + x: this.path.last().x + this.velocity, + y: this.path.last().y + }); + + if (this.velocity > 0) { + this.velocity = this.velocity - this.decelVal(); + + if (this.velocity <= 0) { + this.clearSlowInterval(); + } + } else { + this.velocity = this.velocity + this.decelVal(); + + if (this.velocity >= 0) { + this.clearSlowInterval(); + } + } + }; + + Tau.prototype.decelVal = function () { + return this.decelTimeStep() / 8; + }; + + Tau.prototype.clearSlowInterval = function () { + clearInterval(this.slowInterval); + this.velocity = 0; + this.slowInterval = undefined; + }; + + Tau.prototype.decel = function () { + var velocity, sign; + + // if we don't have two points of mouse or touch tracking this won't work + if (!this.path.isSufficient()) { + return; + } + + // determine the starting velocity based on the traced path + velocity = this.path.velocity(this.decelTimeStep()); + + // borrowed from http://stackoverflow.com/questions/7624920/number-sign-in-javascript + sign = velocity > 0 ? 1 : velocity < 0 ? -1 : 0; + + // keep a lid on how fast the rotation spins out + if (Math.abs(velocity) > Tau.maxVelocity) { + velocity = sign * Tau.maxVelocity; + } + + this.velocity = velocity; + this.slowInterval = setInterval(this.slow.bind(this), this.decelTimeStep()); + }; + + Tau.prototype.decelTimeStep = function () { + return this.autoRotateDelay() / 2; + }; + + Tau.prototype.release = function (event) { + if ($(event.target).closest(".tau-controls").length) { + return; + } + + if (!this.tracking) { + return; + } + + if (event.type === "touchend") { + this.$element.trigger("tau.touch-tracking-stop"); + } else { + this.$element.trigger("tau.mouse-tracking-stop"); + } + + this.decel(); + + this.cursorRelease(); + + // TODO sort out why shoestring borks when unbinding with a string split list + $doc.unbind("mousemove", this.mouseMoveBinding); + $doc.unbind("touchmove", this.touchMoveBinding); + + this.tracking = false; + }; + + Tau.prototype.cursorGrab = function () { + $doc.addClass("grabbing"); + }; + + Tau.prototype.cursorRelease = function () { + $doc.removeClass("grabbing"); + }; + + Tau.prototype.showLoading = function () { + this.$loading.attr("style", "display: block"); + }; + + Tau.prototype.hideLoading = function () { + this.$loading.attr("style", "display: none"); + }; + + Tau.prototype.getPoint = function (event) { + var touch = event.touches || event.originalEvent && event.originalEvent.touches; + + if (touch) { + return { + x: touch[0].pageX, + y: touch[0].pageY + }; + } + + return { + x: event.pageX || event.clientX, + y: event.pageY || event.clientY + }; + }; + + Tau.prototype.rotateEvent = function (event) { + // NOTE it might be better to prevent when the rotation returns anything BUT false + // so that slow drags still get the scroll prevented + if (this.rotate(this.getPoint(event))) { + event.preventDefault(); + } + }; + + Tau.prototype.rotate = function (point) { + var deltaX, deltaY; + + deltaX = point.x - this.downX; + deltaY = point.y - this.downY; + + // if the movement on the Y dominates X then skip and allow scroll + if (Math.abs(deltaY) / Math.abs(deltaX) >= Tau.verticalScrollRatio) { + return false; + } + + // since we're rotating record the point for decel + this.path.record(point); + + // NOTE to reverse the spin direction add the delta/thresh to the downIndex + if (Math.abs(deltaX) >= this.rotateThreshold) { + // NOTE works better on mousedown, here allows autorotate to continue + this.stopAutoRotate(); + + var index; + + if (this.options.reverse) { + index = this.downIndex + Math.round(deltaX / this.rotateThreshold); + } else { + index = this.downIndex - Math.round(deltaX / this.rotateThreshold); + } + + this.goto(index); + + return true; + } + }; +})(this, jQuery); + +(function (window, $) { + var ns = window.componentNamespace = window.componentNamespace || "FG"; + window[ns] = window[ns] || {}; + + // IE 8 + Date.now = Date.now || function now() { + return new Date().getTime(); + }; + + var Path = window[ns].Tau.Path = function () { + this.reset(); + }; + + Path.prototype.isSufficient = function () { + return !!this.prevPoint && this.prevPrevPoint; + }; + + Path.prototype.distance = function () { + return this.prevPoint.x - this.prevPrevPoint.x; + }; + + Path.prototype.duration = function () { + return this.prevTime - this.prevPrevTime; + }; + + // TODO sort out variable names + Path.prototype.record = function (point) { + this.prevPrevTime = this.prevTime; + this.prevPrevPoint = this.prevPoint; + + // record the most recent drag point for decel on release + this.prevTime = Date.now(); + this.prevPoint = point; + }; + + Path.prototype.velocity = function (timeStep) { + var distance, time; + + distance = this.distance(); + time = this.duration(); + + return distance / (time / timeStep); + }; + + Path.prototype.reset = function () { + this.prevPoint = undefined; + this.prevTime = undefined; + + this.prevPrevTime = undefined; + this.prevPrevPoint = undefined; + }; + + Path.prototype.last = function () { + return this.prevPoint; + }; +})(this, jQuery); + +(function (exports, $) { + exports.Akamai = exports.Akamai || {}; + var Util = exports.Akamai.Util = {}; + + Util.preflight = function (context, element, options, name) { + name = name || context.constructor.name; + + context._$el = $(element); + + if (!context._$el.length) { + throw new Error(name + " component requires element"); + } + + // If the element has already been instantiated with this component, skip + if (context._$el.data(name)) { + return context._$el.data(name); + } + + // Store the instance for access and to prevent double init above + context._$el.data(name, context); + + // make sure the element has the data attribute for CSS that keys off it + context._$el.attr(context.constructor._dataAttr, true); + + context._options = Util.options(context.constructor.defaultOptions || {}, options || {}, context._$el, name); + + // retain the options decided at instantiation for reseting the options + // when the breakpoint options don't apply (are "backed out") + context._originalOptions = Util.extend(true, {}, context._options); + + // map all the child component events to the wrapper component events + Util.mapEvents(context); + + return false; + }; + + Util.component = function (name, async, init) { + if (!init) { + init = async; + async = false; + } + + var constr = function (element, options) { + // do the preflight + var existing = Util.preflight(this, element, options, name); + + // return on double init + if (existing) { + this._trigger("init"); + return existing; + } + + // call the init code + init.apply(this, arguments); + + // trigger the init event + if (!async) { + this._trigger("init"); + } + }; + + constr.name = name; + + constr._dataAttr = "data-akamai-" + name.toLowerCase(); + + // static constructor for many possible objects + constr.createMany = Util.createMany(constr); + + // component prefixed event trigger + constr.prototype._trigger = Util.trigger(name); + + return constr; + }; + + Util.mapEvents = function (context) { + var mapping = context.constructor._componentEventMapping || {}; + + for (var event in mapping) { + if (mapping.hasOwnProperty(event)) { + var mappedTo = mapping[event].to || mapping[event]; + var $el = mapping[event].selector ? context._$el.find(mapping[event].selector) : undefined; + + Util.mapEvent(context, event, mappedTo, $el); + } + } + }; + + Util.mapEvent = function (context, from, to, $el) { + ($el || context._$el).bind(from, function () { + context._trigger(to); + }.bind(context)); + }; + + Util.createMany = function (constructor, selector) { + selector = selector || "[" + constructor._dataAttr + "]"; + + return function (element, options) { + var $comps = $(element).find(selector); + + // sigh, shoestring needs map + var objects = []; + + $comps.each(function (i, el) { + objects.push(new constructor(el, options)); + }); + + return objects; + }; + }; + + /** + * Helper for building assertions into methods based on property requirements + * @private + * @static + * @param {prop} prop - the property required for the function to operate correctly + * @param {fn} function - the function definition + */ + Util.propRequired = function (prop, msg, fn) { + return function () { + var args = arguments; + + if (!this[prop]) { + throw new Error(msg); + } + + return fn.apply(this, args); + }; + }; + + Util.trigger = function (componentName) { + componentName = componentName.toLowerCase(); + return function (event) { + var fullEvent = "akamai-" + componentName + "-" + event; + + // NOTE we assume here that if jQuery is present we will only want to trigger + // with jQuery and that we are using the same DOM lib across the library + this._$el.trigger(fullEvent); + }; + }; + + Util.camelToKabob = function (string) { + return string.replace(/([A-Z]+)/g, function (m) { + return "-" + m.toLowerCase(); + }); + }; + + Util.options = function (defaults, options, $element, name) { + var dataAttrOptions = {}; + // make sure to make a new copy of the defaults so we don't mess with the original + var cloneDefaults = Util.extend(true, {}, defaults); + + if ($element && name) { + dataAttrOptions = Util.getDataAttrOptions($element, defaults, "data-akamai-" + name.toLowerCase()); + } + + // use the following precedence, prefering settings further to the right + // defaults -> options -> datattributes + var finalOptions = Util.extend(true, Util.extend(true, cloneDefaults, dataAttrOptions), options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + if ($element && name) { + Util.setDataAttrOptions($element, defaults, finalOptions, name); + } + + return finalOptions; + }; + + Util.getDataAttrOptions = function ($element, defaults, prefix) { + var dataAttrOptions = {}; + + for (var prop in defaults) { + if (defaults.hasOwnProperty(prop)) { + var dashedProp = Util.camelToKabob(prop); + var currentDataProp = prefix + "-" + dashedProp; + + if (/object/.test(typeof defaults[prop]) && !(defaults[prop] instanceof Array)) { + var subDefaults = defaults[prop]; + var newPrefix = currentDataProp; + + var subConfig = Util.getDataAttrOptions($element, subDefaults, newPrefix); + dataAttrOptions[prop] = subConfig; + } else { + var value = $element.attr(currentDataProp); + value = Util.coerceAttrVal(value); + + if (value !== undefined && value !== null) { + // set the config + dataAttrOptions[prop] = value; + } + } + } + } + + return dataAttrOptions; + }; + + Util.setDataAttrOptions = function ($element, defaults, options, name) { + Util.setDataAttrOptionsPrefix($element, defaults, options, "data-akamai-" + name.toLowerCase()); + }; + + Util.setDataAttrOptionsPrefix = function ($element, defaults, options, prefix) { + defaults = defaults || {}; + + for (var prop in options) { + if (options.hasOwnProperty(prop) && prop !== "breakpoints") { + var dashedProp = Util.camelToKabob(prop); + var currentDataProp = prefix + "-" + dashedProp; + + if (/object/.test(typeof options[prop])) { + var subOptions = options[prop]; + var newPrefix = currentDataProp; + + Util.setDataAttrOptionsPrefix($element, defaults[prop], subOptions, newPrefix); + } else { + + // if the $element had the attribute already or the property exists + // as one of the defaults then we can write it to the element. + if ($element.attr(currentDataProp) || defaults[prop] !== undefined) { + $element.attr(currentDataProp, options[prop]); + } + } + } + } + }; + + Util.coerceAttrVal = function (value) { + if (value === "true") { + return true; + } + if (value === "false") { + return false; + } + if (/^[0-9]+$/.test(value)) { + return parseInt(value, 10); + } + if (/^[0-9]+.[0-9]+$/.test(value)) { + return parseFloat(value, 10); + } + + return value; + }; + + // Due to jQuery, but with less argument handling + Util.extend = function (deep, target, options) { + var name, src, copy, copyIsArray, clone; + + // Only deal with non-null/undefined values + if (options != null) { + + // Extend the base object + for (name in options) { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target === copy) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if (deep && copy && Util.isPlainObject(copy)) { + + clone = src && Util.isPlainObject(src) ? src : {}; + + // Never move original objects, clone them + target[name] = Util.extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[name] = copy; + } + } + } + + // Return the modified object + return target; + }; + + // Due to jQuery + Util.isPlainObject = function (obj) { + var proto, Ctor; + + // taken from https://github.com/jupiter/simple-mock/pull/3/files + if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") { + return false; + } + + return true; + }; + + // Due to jQuery + var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + Util.trim = function (text) { + return text == null ? "" : (text + "").replace(rtrim, ""); + }; + + Util.map = function (array, fn) { + return Util.reduce(array, function (acc, val, i) { + acc.push(fn(val, i)); + return acc; + }, []); + }; + + Util.reduce = function (array, fn, acc) { + for (var i = 0; i < array.length; i++) { + acc = fn(acc, array[i], i); + } + + return acc; + }; + + Util.log = function (msg, type) { + type = type || "log"; + + if (console) { + console[type](msg); + } + }; + + Util.mapJSONVals = function (obj, callback, key) { + var result = {}; + + if (obj.map) { + return obj.map(function (o) { + return Util.mapJSONVals(o, callback); + }); + } + + if (["string", "number", "boolean"].indexOf(typeof obj) >= 0) { + return callback(obj, key); + } + + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + result[prop] = Util.mapJSONVals(obj[prop], callback, prop); + } + } + + return result; + }; + + Util.escapeJSONVals = function (obj) { + return Util.mapJSONVals(obj, function (val) { + if (typeof val === "string") { + return escapeHTML(val); + } + + return val; + }); + }; + + // https://github.com/janl/mustache.js/blob/23beb3a8805c9a857e3ea777431481599fab503e/mustache.js#L60 + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + function escapeHTML(string) { + return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap(s) { + return entityMap[s]; + }); + } +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (exports, $) { + var Advanceable = { + extension: { + next: function (callback) { + this.goto(this.getIndex() + 1, function () { + if (callback) { + callback(); + }; + this._trigger("next"); + }.bind(this)); + }, + + previous: function (callback) { + this.goto(this.getIndex() - 1, function () { + if (callback) { + callback(); + }; + this._trigger("previous"); + }.bind(this)); + } + }, + + extend: function (constr) { + if (!constr.prototype.goto || !constr.prototype.getIndex) { + throw new Error("Advanceable mixin requires `goto` and `getIndex` methods"); + } + + $.extend(constr.prototype, this.extension); + } + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Advanceable = Advanceable; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +// TODO this should be handled in an image component +(function (exports, $) { + var Sourceable = { + extension: { + _srcArray: function (url, options, includeW) { + var policy = options.policy || options.thumbnail && options.thumbnail.policy; + var widthParam = options.widthParam; + + return options.widths.map(function (w) { + var src = url + (url.indexOf("?") === -1 ? "?" : "&") + widthParam + "=" + w; + + if (policy) { + src += "&impolicy=" + policy; + } + + //note src width for srcset (eg ' 500w') + if (includeW) { + src += " " + w + "w"; + } + + return src; + }); + }, + + _srcset: function (url, options) { + return this._srcArray(url, options, true).join(", "); + }, + + _fallbackSrc: function (url, options) { + return this._srcArray(url, options)[0]; + }, + + _largestSrc: function (url, options) { + return this._srcArray(url, options).pop(); + } + }, + + extendStatic: function (constr) { + Akamai.Util.extend(false, constr, this.extension); + } + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Sourceable = Sourceable; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (w, undefined) { + + // requestAnimationFrame pfill + var raf = function () { + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { + w.setTimeout(callback, 1000 / 60); + }; + }(); + + /* toss scrolls and element with easing + // elem is the element to scroll + // options hash: + * left is the desired horizontal scroll. Default is "+0". For relative distances, pass a string with "+" or "-" in front. + * top is the desired vertical scroll. Default is "+0". For relative distances, pass a string with "+" or "-" in front. + * duration is the number of milliseconds the throw will take. Default is 100. + * easing is an optional custom easing function. Default is w.toss.easing. Must follow the easing function signature + */ + w.toss = function (elem, options) { + toss.tossing(elem, false); + var i = 0, + sLeft = elem.scrollLeft, + sTop = elem.scrollTop, + + // Toss defaults + op = { + top: "+0", + left: "+0", + duration: 200, + easing: toss.easing, + finished: function () {} + }, + endLeft, + endTop; + + // Mixin based on predefined defaults + if (options) { + for (var j in op) { + if (options[j] !== undefined) { + op[j] = options[j]; + } + } + } + + // Convert relative values to ints + // First the left val + if (typeof op.left === "string") { + op.left = parseFloat(op.left); + endLeft = op.left + sLeft; + } else { + endLeft = op.left; + op.left = op.left - sLeft; + } + // Then the top val + if (typeof op.top === "string") { + + op.top = parseFloat(op.top); + endTop = op.top + sTop; + } else { + endTop = op.top; + op.top = op.top - sTop; + } + + toss.tossing(elem, true); + var startTime = new Date().getTime(); + var endTime = startTime + op.duration; + var run = function () { + var curTime = new Date().getTime(); + // if tossing is suddenly not true, return the callback + if (!toss.tossing(elem)) { + if (op.finished) { + op.finished(); + } + } + // if the time is still less than the end of duration, keep scrolling + else if (curTime < endTime) { + i = (curTime - startTime) / op.duration * op.duration; + elem.scrollLeft = op.easing(i, sLeft, op.left, op.duration); + elem.scrollTop = op.easing(i, sTop, op.top, op.duration); + return raf(run); + } + // if time is up, + else { + elem.scrollLeft = endLeft; + elem.scrollTop = endTop; + if (op.finished) { + op.finished(); + } + toss.tossing(elem, false); + } + }; + + raf(run); + + // Return the values, post-mixin, with end values specified + return { top: endTop, left: endLeft, duration: op.duration, easing: op.easing }; + }; + + // tossing object keeps track of currently tossing elements. true during a programatic scroll + var tossingElems = {}; + toss.tossing = function (elem, state) { + if (state !== undefined) { + tossingElems[elem] = state; + } + return tossingElems[elem]; + }; + + // Easing can use any of Robert Penner's equations (http://www.robertpenner.com/easing_terms_of_use.html). By default, toss includes ease-out-cubic + // arguments: t = current iteration, b = initial value, c = end value, d = total iterations + // use w.toss.easing to provide a custom function externally, or pass an easing function as a callback to the toss method + toss.easing = function (t, b, c, d) { + return c * ((t = t / d - 1) * t * t + 1) + b; + }; + + //retain old api + toss.toss = toss; +})(this); + +;(function (w, $) { + var pluginName = "snapper"; + $.fn[pluginName] = function (optionsOrMethod) { + var pluginArgs = arguments; + var scrollListening = true; + + // css snap points feature test. + // even if this test passes, several behaviors will still be polyfilled, such as snapping after resize, and animated advancing of slides with anchor links or next/prev links + var testProp = "scroll-snap-type"; + // test latest spec first. then fallback older + var snapSupported = w.CSS && w.CSS.supports && (w.CSS.supports(testProp, "x mandatory") || w.CSS.supports(testProp, "mandatory") || w.CSS.supports("-webkit-" + testProp, "mandatory") || w.CSS.supports("-ms-" + testProp, "mandatory")); + + // get the snapper_item elements whose left offsets fall within the scroll pane. Returns a wrapped array. + function itemsAtOffset(elem, offset) { + var $childNodes = $(elem).find("." + pluginName + "_item"); + var containWidth = $(elem).width(); + var activeItems = []; + + $childNodes.each(function (i) { + if (this.offsetLeft >= offset - 5 && this.offsetLeft < offset + containWidth - 5) { + activeItems.push(this); + } + }); + return $(activeItems); + } + + function outerWidth($elem) { + return $elem.width() + parseFloat($elem.css("margin-left")) + parseFloat($elem.css("margin-right")); + } + + function outerHeight($elem) { + return $elem.height() + parseFloat($elem.css("margin-bottom")) + parseFloat($elem.css("margin-top")); + } + + // snapEvent dispatches the "snapper.snap" event. + // The snapper_item elements with left offsets that are inside the scroll viewport are listed in an array in the second callback argument's activeSlides property. + // use like this: $( ".snapper" ).bind( "snapper.snap", function( event, data ){ console.log( data.activeSlides ); } ); + function snapEvent(elem, x, prefix) { + prefix = prefix ? prefix + "-" : ""; + var activeSlides = itemsAtOffset(elem, x); + $(elem).trigger(pluginName + "." + prefix + "snap", { activeSlides: activeSlides }); + } + + // optional: include toss() in your page to get a smooth scroll, otherwise it'll just jump to the slide + function goto(elem, x, nothrow, callback) { + scrollListening = false; + snapEvent(elem, x); + + var after = function () { + elem.scrollLeft = x; + $(elem).closest("." + pluginName).removeClass(pluginName + "-looping"); + $(elem).trigger(pluginName + ".after-goto", { + activeSlides: itemsAtOffset(elem, x) + }); + + if (callback) { + callback(); + }; + snapEvent(elem, x, "after"); + scrollListening = true; + }; + + // backport to old toss for compat + if (!w.toss && w.overthrow) { + w.toss = w.overthrow.toss; + } + if (typeof w.toss !== "undefined" && !nothrow) { + w.toss(elem, { left: x, finished: after }); + } else { + elem.scrollLeft = x; + after(); + } + } + + var result, innerResult; + + // Loop through snapper elements and enhance/bind events + result = this.each(function () { + if (innerResult !== undefined) { + return; + } + + var self = this; + var $self = $(self); + var addNextPrev = $self.is("[data-" + pluginName + "-nextprev]"); + var autoTimeout; + var $slider = $("." + pluginName + "_pane", self); + var enhancedClass = pluginName + "-enhanced"; + var $itemsContain = $slider.find("." + pluginName + "_items"); + var $items = $itemsContain.children(); + $items.addClass(pluginName + "_item"); + var numItems = $items.length; + var $nav = $("." + pluginName + "_nav", self); + var navSelectedClass = pluginName + "_nav_item-selected"; + var useDeepLinking = $self.attr("data-snapper-deeplinking") !== "false"; + + if (typeof optionsOrMethod === "string") { + var args = Array.prototype.slice.call(pluginArgs, 1); + var index; + var itemWidth = $itemsContain.width() / numItems; + + switch (optionsOrMethod) { + case "goto": + index = args[0] % numItems; + + // width / items * index to make sure it goes + offset = itemWidth * index; + goto($slider[0], offset, false, function () { + // snap the scroll to the right position + snapScroll(); + + // invoke the callback if it was supplied + if (typeof args[1] === "function") { + args[1](); + } + }); + break; + case "getIndex": + // NOTE make the scroll left value large enough to overcome + // subpixel widths + innerResult = Math.floor(($slider[0].scrollLeft + 1) / itemWidth); + break; + case "updateWidths": + updateWidths(); + break; + } + + return; + } + + // avoid double enhance activities + if ($self.attr("data-" + pluginName + "-enhanced")) { + return; + } + + // NOTE all state manipulation has to come after method invocation to + // avoid monkeying with the DOM when it's unwarranted + var $navInner = $nav.find("." + pluginName + "_nav_inner"); + if (!$navInner.length) { + $navInner = $('
').append($nav.children()).appendTo($nav); + } + + // give the pane a tabindex for arrow key handling + $slider.attr("tabindex", "0"); + + function getAutoplayInterval() { + var autoTiming = $self.attr("data-autoplay") || $self.attr("data-snapper-autoplay"); + var parseError = false; + + if (autoTiming) { + try { + autoTiming = parseInt(autoTiming, 10); + } catch (e) { + parseError = true; + } + + // if NaN or there was an error throw an exception + if (!autoTiming || parseError) { + var msg = "Snapper: `data-autoplay` must have an natural number value."; + throw new Error(msg); + } + } + + return autoTiming; + } + + // this function updates the widths of the items within the slider, and their container. + // It factors in margins and converts those to values that make sense when all items are placed in a long row + function updateWidths() { + var itemsContainStyle = $itemsContain.attr("style"); + $itemsContain.attr("style", ""); + var itemStyle = $items.eq(0).attr("style"); + $items.eq(0).attr("style", ""); + var sliderWidth = $slider.width(); + var itemWidth = $items.eq(0).width(); + var computed = w.getComputedStyle($items[0], null); + var itemLeftMargin = parseFloat(computed.getPropertyValue("margin-left")); + var itemRightMargin = parseFloat(computed.getPropertyValue("margin-right")); + var outerItemWidth = itemWidth + itemLeftMargin + itemRightMargin; + $items.eq(0).attr("style", itemStyle); + $itemsContain.attr("style", itemsContainStyle); + var parentWidth = numItems / Math.round(sliderWidth / outerItemWidth) * 100; + var iPercentWidth = itemWidth / sliderWidth * 100; + var iPercentRightMargin = itemRightMargin / sliderWidth * 100; + var iPercentLeftMargin = itemLeftMargin / sliderWidth * 100; + var outerPercentWidth = iPercentWidth + iPercentLeftMargin + iPercentRightMargin; + var percentAsWidth = iPercentWidth / outerPercentWidth; + var percentAsRightMargin = iPercentRightMargin / outerPercentWidth; + var percentAsLeftMargin = iPercentLeftMargin / outerPercentWidth; + $itemsContain.css("width", parentWidth + "%"); + $items.css("width", 100 / numItems * percentAsWidth + "%"); + $items.css("margin-left", 100 / numItems * percentAsLeftMargin + "%"); + $items.css("margin-right", 100 / numItems * percentAsRightMargin + "%"); + } + + updateWidths(); + $(self).addClass(enhancedClass); + + // if the nextprev option is set, add the nextprev nav + if (addNextPrev) { + var $nextprev = $(''); + var $nextprevContain = $(".snapper_nextprev_contain", self); + if (!$nextprevContain.length) { + $nextprevContain = $(self); + } + $nextprev.appendTo($nextprevContain); + } + + // This click binding will allow deep-linking to slides without causing the page to scroll to the carousel container + // this also supports click handling for generated next/prev links + $("a", this).bind("click", function (e) { + clearTimeout(autoTimeout); + var slideID = $(this).attr("href"); + + if ($(this).is(".snapper_nextprev_next")) { + e.preventDefault(); + return arrowNavigate(true); + } else if ($(this).is(".snapper_nextprev_prev")) { + e.preventDefault(); + return arrowNavigate(false); + } + // internal links to slides + else if (slideID.indexOf("#") === 0 && slideID.length > 1) { + e.preventDefault(); + + var $slide = $(slideID, self); + if ($slide.length) { + goto($slider[0], $slide[0].offsetLeft); + if (useDeepLinking && "replaceState" in w.history) { + w.history.replaceState({}, document.title, slideID); + } + } + } + }); + + // arrow key bindings for next/prev + $(this).bind("keydown", function (e) { + if (e.keyCode === 37 || e.keyCode === 38) { + clearTimeout(autoTimeout); + e.preventDefault(); + e.stopImmediatePropagation(); + arrowNavigate(false); + } + if (e.keyCode === 39 || e.keyCode === 40) { + clearTimeout(autoTimeout); + e.preventDefault(); + e.stopImmediatePropagation(); + arrowNavigate(true); + } + }); + + var snapScrollCancelled = false; + + // snap to nearest slide. Useful after a scroll stops, for polyfilling snap points + function snapScroll() { + if (isTouched) { + snapScrollCancelled = true; + return; + } + + var currScroll = $slider[0].scrollLeft; + var width = $itemsContain.width(); + var itemWidth = $items[1] ? $items[1].offsetLeft : outerWidth($items.eq(0)); + var roundedScroll = Math.round(currScroll / itemWidth) * itemWidth; + var maxScroll = width - $slider.width(); + if (roundedScroll > maxScroll) { + roundedScroll = maxScroll; + } + if (currScroll !== roundedScroll) { + if (snapSupported) { + snapEvent($slider[0], roundedScroll); + snapEvent($slider[0], roundedScroll, "after"); + } else { + goto($slider[0], roundedScroll); + } + } else { + goto($slider[0], roundedScroll); + } + + snapScrollCancelled = false; + } + + // retain snapping on resize (necessary even in scroll-snap supporting browsers currently, unfortunately) + var startSlide; + var afterResize; + function snapStay() { + var currScroll = $slider[0].scrollLeft; + var numItems = $items.length; + var width = $itemsContain.width(); + if (startSlide === undefined) { + startSlide = Math.round(currScroll / width * numItems); + } + if (afterResize) { + clearTimeout(afterResize); + } + afterResize = setTimeout(function () { + updateWidths(); + goto($slider[0], $items[startSlide].offsetLeft, true); + startSlide = afterResize = undefined; + }, 50); + } + $(w).bind("resize", snapStay); + + // next/prev links or arrows should loop back to the other end when an extreme is reached + function arrowNavigate(forward) { + var currScroll = $slider[0].scrollLeft; + var width = $itemsContain.width(); + var itemWidth = outerWidth($slider); + var maxScroll = width - itemWidth - 5; + if (forward) { + if (currScroll >= maxScroll) { + $self.addClass(pluginName + "-looping"); + return first(); + } else { + return next(); + } + } else { + if (currScroll === 0) { + $self.addClass(pluginName + "-looping"); + return last(); + } else { + return prev(); + } + } + } + + // advance slide one full scrollpane's width forward + function next() { + goto($slider[0], $slider[0].scrollLeft + $itemsContain.width() / numItems, false, function () { + $slider.trigger(pluginName + ".after-next"); + }); + } + + // advance slide one full scrollpane's width backwards + function prev() { + goto($slider[0], $slider[0].scrollLeft - $itemsContain.width() / numItems, false, function () { + $slider.trigger(pluginName + ".after-prev"); + }); + } + + // go to first slide + function first() { + goto($slider[0], 0); + } + + // go to last slide + function last() { + goto($slider[0], $itemsContain.width() - $slider.width()); + } + + // update thumbnail state on pane scroll + if ($nav.length) { + // function for scrolling to the xy of the active thumbnail + function scrollNav(elem, x, y) { + if (typeof w.toss !== "undefined") { + w.toss(elem, { left: x, top: y }); + } else { + elem.scrollLeft = x; + elem.scrollTop = y; + } + } + var lastActiveItem; + function activeItem(force) { + var currTime = new Date().getTime(); + if (!force && lastActiveItem && currTime - lastActiveItem < 200) { + return; + } + lastActiveItem = currTime; + var currScroll = $slider[0].scrollLeft; + var width = outerWidth($itemsContain); + var navWidth = outerWidth($nav); + var navHeight = outerHeight($nav); + var activeIndex = Math.round(currScroll / width * numItems) || 0; + var childs = $nav.find("a").removeClass(navSelectedClass); + var activeChild = childs.eq(activeIndex).addClass(navSelectedClass); + + var thumbX = activeChild[0].offsetLeft - navWidth / 2; + var thumbY = activeChild[0].offsetTop - navHeight / 2; + + scrollNav($navInner[0], thumbX, thumbY); + } + + // set active item on init + activeItem(); + + $slider.bind("scroll", activeItem); + } + + // apply snapping after scroll, in browsers that don't support CSS scroll-snap + var scrollStop; + var scrolling; + var lastScroll = 0; + + $slider.bind("scroll", function (e) { + lastScroll = new Date().getTime(); + scrolling = true; + }); + + setInterval(function () { + if (scrolling && lastScroll <= new Date().getTime() - 150) { + snapScroll(); + if (activeItem) { + activeItem(true); + } + scrolling = false; + } + }, 150); + + var isTouched = false; + + // if a touch event is fired on the snapper we know the user is trying to + // interact with it and we should disable the auto play + $slider.bind("touchstart", function () { + clearTimeout(autoTimeout); + + isTouched = true; + }); + + $slider.bind("touchend", function () { + isTouched = false; + + if (snapScrollCancelled && !scrolling) { + snapScroll(); + scrolling = false; + } + }); + + // if the `data-autoplay` attribute is assigned a natural number value + // use it to make the slides cycle until there is a user interaction + function autoplay(autoTiming) { + if (autoTiming) { + // autoTimeout is cleared in each user interaction binding + autoTimeout = setTimeout(function () { + var timeout = getAutoplayInterval(); + if (timeout) { + arrowNavigate(true); + autoplay(timeout); + } + }, autoTiming); + } + } + + autoplay(getAutoplayInterval()); + $self.attr("data-" + pluginName + "-enhanced", true); + }); + + return innerResult !== undefined ? innerResult : result; + }; +})(this, jQuery); + +;(function (w) { + var enlarge = function () { + var $ = w.jQuery; + var pluginName = "enlarge"; + $.fn[pluginName] = function (options) { + var pluginArgs = arguments; + + // options + var o = $(this).data("options") || { + button: true, + hoverZoomWithoutClick: true, + delay: 300, + flyout: { + width: 200, + height: 200 + }, + placement: "inline", + magnification: 3 + }; + + if (typeof options !== "string") { + // extend with passed options + o = $.extend(o, options); + $(this).data("options", o); + } + + var internalResult; + + var result = this.each(function () { + var $element = $(this); + + var self = this; + var testimg = w.document.createElement("img"); + var srcsetSupported = "srcset" in testimg; + var srcsetSizesSupported = srcsetSupported && "sizes" in testimg; + var $anchor = $(this).find("a"); + + if (!$anchor.length) { + throw new Error(pluginName + ": requires an anchor element with `href` for the enlarged image source"); + } + + // find image within container + var initialImg = $element.find("img")[0]; + var targetImg = initialImg; + var imgOriginalSrc = targetImg.src; + var srcset = $(targetImg).attr("srcset"); + var imgOriginalSizes = $(targetImg).attr("sizes"); + var imgZoomSrc = $anchor[0].href; + var initialText = $anchor[0].innerText; + var zoomClass = pluginName + "-zoomed"; + var delayClass = pluginName + "-delay"; + var $contain = $(targetImg).closest(".enlarge_contain"); + var $zoomContain = $contain; + var $parentPane = $(targetImg).closest(".enlarge_pane") || $element; + + var $zoomParent = $(this).data("zoomParent") || $parentPane; + $(this).data("zoomParent", $zoomParent); + + var zoomed = $element.data("zoomed") || false; + $element.data("zoomed", zoomed); + + $element.data("lockedZoom", $element.data("lockedZoom") || false); + + var lockZoomClass = pluginName + "-locked"; + + if (!$contain.length) { + throw new Error(pluginName + ": requires an element above the image marked with the class `enlarge_contain`"); + } + + // this allows for methods and changing options in subsequent calls to the plugin + if (typeof options === "string") { + var args = Array.prototype.slice.call(pluginArgs, 1); + + switch (options) { + case "in": + if (!$element.data("zoomed")) { + standardToggleZoom(); + } + break; + case "out": + if ($element.data("zoomed")) { + standardToggleZoom(); + } + break; + case "isZoomed": + internalResult = $element.data("zoomed"); + break; + case "updateOptions": + $element.data("updateOptions")(args[0]); + break; + } + return; + } + + // to toggle between inline and flyout modes, we change the elements that + // the targetImg, zoomParent, and zoomContain vars refer to + function updatePlacement() { + if (o.placement === "inline") { + targetImg = initialImg; + $zoomParent = $parentPane; + $element.data("zoomParent", $zoomParent); + $zoomContain = $contain; + } else { + targetImg = $flyout.find("img")[0]; + $zoomParent = $zoomContain = $flyout; + $element.data("zoomParent", $zoomParent); + } + } + + // this positions the loupe or side flyout element either according to mouse/touch coordinates + // or the sides of the viewer specified + function positionFlyout() { + + // set flyout width and height + $flyout.css({ + "width": o.flyout.width + "px", + "height": o.flyout.height + "px", + top: "", + left: "", + "margin-left": "", + "margin-top": "" + }); + + // set negative left or right value to match width + var flyoutSide = o.placement.match(/left|right/); + + if (flyoutSide) { + $flyout.css(flyoutSide[0], -o.flyout.width + "px"); + } + // if loupe mode, center offset + var loupe = o.placement.match(/loupe/); + + if (loupe) { + // loupe + $flyout.css({ + "margin-left": -o.flyout.width / 2 + "px", + "margin-top": -o.flyout.height / 2 + "px" + }); + } + + // add class to specify positioning spot for static css to apply + $flyout[0].className = $flyout[0].className.replace(/enlarge_flyout\-[^$\s]+/, ' '); + $flyout.addClass("enlarge_flyout-" + o.placement); + } + + function disable() { + if (o.disabled) { + $element.addClass("enlarge_disabled"); + } else { + $element.removeClass("enlarge_disabled"); + } + } + + disable(); + + // this allows for subsequent calls to the plugin to pass an updateOptions method and object, + // which will pass through to the existing viewer on that element + $element.data("updateOptions", function (opts) { + o = $.extend(o, opts); + $(this).data("options", o); + + updatePlacement(); + positionFlyout(); + hoverEnabled = o.hoverZoomWithoutClick; + if (o.image && o.image.sizes) { + imgOriginalSizes = o.image.sizes; + toggleImgSrc(); + } + + disable(); + + if (o.disabled && $element.data("zoomed")) { + standardToggleZoom(); + } + }); + + // loader div holds a new image while its new source is loading + // we insert this into the dom so that srcset/sizes can calculate a best source + function addLoader() { + $contain.append(''); + } + + // zoom state toggle boolean + function toggleZoomState() { + zoomed = !$element.data("zoomed"); + $element.data("zoomed", zoomed); + } + + // toggle the image source bigger or smaller + // ideally, this toggles the sizes attribute and allows the browser to select a new source from srcset + // if srcset isn't supported or sizes attribute is not provided, the link href is used for the larger source + function toggleImgSrc(after) { + after = after || function () {}; + + if (!zoomed) { + targetImg.sizes = imgOriginalSizes; + if (!srcsetSizesSupported) { + targetImg.src = imgOriginalSrc; + } + after(); + } else { + // if the zooming is disabled do not replace with the larger source + // NOTE we don't prevent switching to the original source because we + // always want to allow the plugin to back out of the zoomed state + // when disabled + if (o.disabled) { + after();return false; + } + + var zoomimg = new Image(); + zoomimg.className = "enlarge_img-loading"; + zoomimg.onload = function () { + targetImg.sizes = zoomimg.sizes; + if (!srcsetSizesSupported || !srcset) { + targetImg.src = imgZoomSrc; + } + $(zoomimg).remove(); + + after(); + }; + + zoomimg.sizes = imgZoomWidth() + "px"; + + if (!srcsetSizesSupported || !srcset) { + zoomimg.src = imgZoomSrc; + } else if (srcset) { + zoomimg.srcset = srcset; + } + + $(zoomimg).insertBefore(targetImg); + } + } + + // scroll to the center of the zoomed image + function scrollToCenter() { + var pw = $zoomContain.width(); + var ph = $zoomContain.height(); + var w = targetImg.offsetWidth; + var h = targetImg.offsetHeight; + $zoomContain[0].scrollLeft = w / 2 - pw / 2; + $zoomContain[0].scrollTop = h / 2 - ph / 2; + } + + // lock zoom mode allows for scrolling around normally without a cursor-follow behavior + function toggleLockZoom() { + if (!$element.data("lockedZoom")) { + // NOTE we allow the image to zoom out if functionality gets disabled + // when it's in a zoomed state + if (o.disabled) { + return false; + } + + $parentPane.add($zoomParent).addClass(lockZoomClass); + $element.data("lockedZoom", lockedZoom = true); + $zoomContain.attr("tabindex", "0"); + $zoomContain[0].focus(); + } else { + $parentPane.add($zoomParent).removeClass(lockZoomClass); + $element.data("lockedZoom", lockedZoom = false); + $zoomContain.removeAttr("tabindex"); + } + } + + function imgZoomWidth() { + return $parentPane[0].offsetWidth * o.magnification; + } + + // toggle magnification of image + function toggleImgZoom() { + if ($element.data("zoomed")) { + // NOTE we allow the image to zoom out if functionality gets disabled + // when it's in a zoomed state + if (o.disabled) { + return false; + } + + if (o.placement === "inline") { + $contain.add($parentPane).css({ "width": $parentPane[0].offsetWidth + "px", "height": parseFloat(getComputedStyle($parentPane[0]).height) + "px" }); + } + $zoomParent.addClass(zoomClass); + $(targetImg).css("width", imgZoomWidth() + "px"); + + $(self).trigger(pluginName + ".after-zoom-in"); + } else { + $zoomParent.removeClass(zoomClass); + if (o.placement === "inline") { + $contain.add($parentPane).css({ "width": "", "height": "" }); + } + $(targetImg).css("width", ""); + + $(self).trigger(pluginName + ".after-zoom-out"); + } + } + + function forceInlineMode() { + var oldO = o.placement; + if (oldO !== "inline") { + function resetPlacement() { + o.placement = oldO; + updatePlacement(); + $(self).unbind(pluginName + ".after-zoom-out", resetPlacement); + } + $(self).bind(pluginName + ".after-zoom-out", resetPlacement); + o.placement = "inline"; + updatePlacement(); + } + } + + // lock zoom mode toggle + function standardToggleZoom() { + // NOTE if the current is zoomed out and it's disabled prevent toggling + if (o.disabled && !$element.data("zoomed")) { + return false; + } + toggleZoomState(); + toggleImgSrc(function () { + toggleLockZoom(); + toggleImgZoom(); + scrollToCenter(); + }); + } + + var trackingOn; + var trackingTimer; + var mouseEntered; + var touchStarted; + var hoverEnabled = o.hoverZoomWithoutClick; + var lastMouseMove; + + // mouseenter or touchstart handler for dragging image + function startTrackingDelay(e) { + if (e.type === "touchstart") { + touchStarted = true; + } + if (touchStarted && e.type === "mouseenter" || e.type === "mouseenter" && !touchStarted && !hoverEnabled || $element.data("lockedZoom") || mouseEntered) { + return; + } + mouseEntered = true; + $contain.addClass(delayClass); + trackingTimer = setTimeout(function () { + $contain.removeClass(delayClass); + toggleZoomState(); + toggleImgSrc(function () { + toggleImgZoom(); + trackingOn = true; + if (lastMouseMove) { + scrollWithMouse(lastMouseMove); + } else { + scrollWithMouse(e); + } + }); + }, o.delay); + } + + // mouseleave or touchend after a drag + function stopTrackingDelay(e) { + $contain.removeClass(delayClass); + clearTimeout(trackingTimer); + trackingOn = false; + if (o.hoverZoomWithoutClick === false && !touchStarted) { + hoverEnabled = false; + } + if (touchStarted && e.type === "mouseleave") { + touchStarted = false; + } + } + + // mousemove or touch-drag image placement + function scrollWithMouse(e) { + // if tracking's not on yet, ignore. This allows the delay to work + if (trackingOn) { + // if the move was touch-started, and the event is mousemove, it can be ignored + // (mouse events fire along with touch events and we just want the touch) + if (touchStarted && e.type === "mousemove") { + return; + } + // normalize ev to touch or mouse + var ev = e.touches ? e.touches[0] : e; + e.preventDefault(); + var x = ev.clientX - $contain[0].getBoundingClientRect().left; + var y = ev.clientY - $contain[0].getBoundingClientRect().top; + + if (o.placement.match(/loupe/)) { + // offset the loupe a little differently for touch so that it's not directly beneath a finger + var mLeft = (e.touches ? -o.flyout.width / 1.3 : -o.flyout.width / 2) + "px"; + var mTop = (e.touches ? -o.flyout.height / 1.3 : -o.flyout.height / 2) + "px"; + requestAnimationFrame(function () { + $flyout.css({ + top: y + "px", + left: x + "px", + "margin-left": mLeft, + "margin-top": mTop + }); + }); + } + + var containWidth = $contain[0].offsetWidth; + var containHeight = $contain[0].offsetHeight; + var containScrollWidth = targetImg.offsetWidth; + var containScrollHeight = targetImg.offsetHeight; + var zoomContainWidth = $zoomContain[0].offsetWidth; + var zoomContainHeight = $zoomContain[0].offsetHeight; + var widthFactor = containWidth / zoomContainWidth; + var heightFactor = containHeight / zoomContainHeight; + + $zoomContain[0].scrollLeft = x / containWidth * (containScrollWidth - zoomContainWidth); + $zoomContain[0].scrollLeft += (x / containWidth - 0.5) * zoomContainWidth; + $zoomContain[0].scrollTop = y / containHeight * (containScrollHeight - zoomContainHeight); + $zoomContain[0].scrollTop += (y / containHeight - 0.5) * zoomContainHeight; + } else { + lastMouseMove = e; + } + } + + // add flyout for flyout and loupe modes + // flyout is always present, yet hidden when inline mode is active + var $flyout = $('
').append($contain.clone()); + $flyout.insertAfter($parentPane); + + // add loader div + addLoader(); + updatePlacement(); + positionFlyout(); + + // clicking the magnify anchor toggles lock-zoom mode + $anchor.bind("keydown", function (e) { + if (e.keyCode === 13 || e.keyCode === 32) { + forceInlineMode(); + } + // spacebar triggers click too + if (e.keyCode === 32) { + e.preventDefault(); // don't scroll the new focused area + $(this).trigger("click"); + } + }).bind("click", function (e) { + e.preventDefault(); + standardToggleZoom(); + }); + + // on resize, if in lock zoom mode, un zoom + $(w).bind("resize", function (e) { + if ($element.data("lockedZoom")) { + standardToggleZoom(); + } + }); + + // on click-out on the page, if in locked zoom mode, zoom out + $(w.document).bind("mouseup", function (e) { + if ($element.data("lockedZoom") && !$(e.target).closest($parentPane).length) { + standardToggleZoom(); + } + }); + + // mouse hover and touch-drag gestures for a cursor-tracked zoom behavior + $(initialImg).bind("mouseenter touchstart", startTrackingDelay).bind("mousemove touchmove", scrollWithMouse).bind("mouseleave touchend", function (e) { + mouseEntered = false; + if (zoomed && !$element.data("lockedZoom")) { + toggleZoomState(); + toggleImgSrc(function () { + toggleImgZoom(); + }); + } + stopTrackingDelay(e); + }) + // tapping the image should trigger a lock zoom + // click will not fire after a touch-drag so it works as a tap for our needs here + .bind("click", function (e) { + e.preventDefault(); + // if the click was started with a touchstart event, + // and placement is inline + // toggle the locked zoom mode + if (touchStarted && o.placement === "inline") { + standardToggleZoom(); + } + + if (o.hoverZoomWithoutClick === false && !touchStarted) { + hoverEnabled = !hoverEnabled; + if (hoverEnabled) { + $(this).trigger("mouseenter"); + } else { + $(this).trigger("mouseleave"); + } + } + }); + + // keyboard handling for arrows in zoom mode + $(this).bind("keydown keyup", function (e) { + if (e.keyCode === 37 || e.keyCode === 38 || e.keyCode === 39 || e.keyCode === 40) { + e.stopImmediatePropagation(); + if (!$element.data("lockedZoom")) { + e.preventDefault(); + } + } else if (e.type === "keyup" && $(this).data("lockedZoom") && e.keyCode === 27) { + //esc or backspace closes zoom + standardToggleZoom(); + $anchor[0].focus(); + e.stopImmediatePropagation(); + } + }); + + // on scroll, zoom out + $parentPane.bind("scroll", function () { + if ($element.data("zoomed")) { + toggleZoomState(); + toggleImgSrc(function () { + if ($element.data("lockedZoom")) { + toggleLockZoom(); + } + toggleImgZoom(); + }); + } + }); + }); + + return internalResult !== undefined ? internalResult : result; + }; + }; + + if (typeof module !== "undefined") { + module.exports = enlarge; + } else { + enlarge(); + } +})(typeof global !== "undefined" ? global : this); + +(function (exports) { + var Image = { + defaultConfig: { + widths: ["320", "640", "800", "1024", "2048", "5000"], + + sizes: "100vw", + policy: undefined, + widthParam: "imwidth" + } + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Image = Image; +})(typeof exports === 'undefined' ? window : exports); + +(function (exports, $) { + /** + * 360 degree viewer + * @class + * @alias Akamai.Spin360 + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Spin360 = Akamai.Util.component("Spin360", function (element, options) { + this._comp = new Akamai.Tau(element, this._options); + }); + + // srcset stuff + Akamai.Sourceable.extendStatic(Spin360); + + // Used in preflight to "rename" events based on the child component events + Spin360._componentEventMapping = { + "tau.auto-rotate-start": "play", + "tau.auto-rotate-stop": "pause" + }; + + Spin360.prototype._updateOptions = function (options) { + if (!options) { + this._options = this._originalOptions; + } + + this._options = Akamai.Util.extend(true, this._options, options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Spin360.defaultOptions, this._options, "Spin360"); + + // TODO actually update the options + }; + + Spin360._renderImg = function (url, options) { + return "\n\t\t\t\n\t\t"; + }; + + Spin360.render = function (json, options) { + var finalOptions = Akamai.Util.options(Spin360.defaultOptions, options); + + // TODO fix the string problem in shoestring + return Akamai.Util.trim("\n\t\t\t
\n\t\t\t\t" + Akamai.Util.map(json.urls, function (url) { + return Spin360._renderImg(url, finalOptions); + }).join("\n") + "\n\t\t\t
\n\t\t"); + }; + + /** + * Defines the global default options for all Spin360s on the page + * @static + * @property {Boolean} autoplay.enabled - Enable autoplay (default: false) + * @property {Integer} autoplay.delay - Delay in milliseconds after initialization before spinning begins (default: 1000) + * @property {Boolean} controls.arrows - Render controls to spin left and right (default: false) + * @property {Boolean} controls.play - Render controls to enable and disable automatic spinning (default: false) + * @property {String} controls.text.left - Left rotation control title and text (default: "Rotate Left") + * @property {String} controls.text.right - Right rotation control title and text (default: "Rotate Right") + * @property {String} controls.text.play - Spin control title and text (default: "Spin Object") + * @property {Array} images.widths - list of available widths for an image (to be combined with image.widthParam), Default: ["320","640","800","1024","2048","5000"] + * @property {String} images.sizes - value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw`, `200px`, `(min-width:1000px) 500px, 100vw`. + * @property {String} images.policy - query param value for policy, appended to &policy= per image url when specified. Values: `foo`. Default: undefined. + * @property {String} images.widthParam - query string name to use for setting each url width. Default urls will be ?imwidth=320 for example. Values: `imwidth` (default), `w`, `width`, etc. + * @property {Integer} interval - The full rotation interval in milliseconds, determines physics, (default: 3000) + * @property {Boolean} reverse - Reverse the direction of the spin (default: false) + * @property {Float} sensitivity - The speed at which the object rotates relative to user input (default: 1). At the default value of `1` the object will complete a full 360 rotation when you drag across the entire width of the spin360 component. To require less effort to complete a rotation, change this value to a higher number. For example, setting `sensitivity: 2` would complete a full 360 rotation by dragging halfway (ex. from the center to the edge). + */ + Spin360.defaultOptions = { + autoplay: { + enabled: false, + delay: 1000 + }, + + controls: { + arrows: false, + play: false, + text: { + left: "Rotate Left", + right: "Rotate Right", + play: "Spin Object" + } + }, + + // unsupported, the number of frame images to create using the configured + // template + frames: 72, + images: Akamai.Image.defaultConfig, + interval: 3000, + reverse: false, + + sensitivity: 1, + + // unsupported, template used to generate urls when only one image is + // present in the 360 viewer + template: undefined + }; + + /** + * Goto to a particular frame of the spining image + * @method + * @param {Integer} index - the frame to advance to + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Spin360.prototype.goto = function (index, callback) { + this._comp.goto(index); + // NOTE goto must come before the callback because the callback will be + // used to trigger the `next` and `previous` events. The order should be + // maintained consistently as `goto` -> `next`/`previous` + this._trigger("goto"); + if (callback) { + callback(); + } + }; + + /** + * Return the current frame index + * @method + * @returns {Integer} + */ + Spin360.prototype.getIndex = function () { + return this._comp.index; + }; + + // Extend Carousel with Advanceable interface + Akamai.Advanceable.extend(Spin360); + + /** + * Go to the next frame + * @method + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Spin360.prototype.next; + // required for JSDocs + + /** + * Go to the previous frame + * @method + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Spin360.prototype.previous; + // required for JSDocs + + /** + * Begin the automatic rotation of the images + * @todo support passing in an interval or speed? + * @method + * @fires Akamai.Spin360#akamai-spin360-play + * @returns {undefined} + */ + Spin360.prototype.play = function () { + this._comp.autoRotate(); + }; + + /** + * Stop the automatic rotation of the images + * @method + * @fires Akamai.Spin360#akamai-spin360-pause + * @returns {undefined} + */ + Spin360.prototype.pause = function () { + this._comp.stopAutoRotate(); + }; + + Spin360.prototype.getElement = function () { + return this._$el[0]; + }; + + Spin360.States = { + Playing: 0, + Paused: 1 + }; + + /** + * Return the current state of the spin360 + * @example Spin360 states + * Akamai.Spin360.States = { + * Playing: 0, + * Paused: 1 + * }; + * + * @example Conditioning on states + * if( spin360.getState() === Akamai.Spin360.States.Paused ) { + * spin360.play() + * } + * @method + * @returns {Akamai.Spin360.State} + */ + Spin360.prototype.getState = function () { + // TODO expose using method in Tau + return !!this._comp.autoInterval ? Spin360.States.Playing : Spin360.States.Paused; + }; + + /** + * Triggered when the spin360 has stoped automatically spinning + * {@link Akamai.Spin360#pause}. + * + * @event Akamai.Spin360#akamai-spin360-stop-spin + */ + + /** + * Triggered when the spin360 has started automatically spinning + * {@link Akamai.Spin360#play}. + * + * @event Akamai.Spin360#akamai-spin360-start-spin + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Spin360}. + * + * @event Akamai.Spin360#akamai-spin360-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Spin360 = Spin360; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function () { + function MagnifierImpl(element, options) { + throw new Error('Not allowed to instantiate MagnifierImpl'); + } + + MagnifierImpl.prototype.updateOptions = function (options) { + throw new Error('magnifier updateOptions not implemented'); + }; + + MagnifierImpl.prototype.zoomIn = function () { + throw new Error('magnifier zoomIn not implemented'); + }; + + MagnifierImpl.prototype.zoomOut = function () { + throw new Error('magnifier zoomOut not implemented'); + }; + + MagnifierImpl.prototype.isMagnified = function () { + throw new Error('magnifier isMagnified not implemented'); + }; + + MagnifierImpl.prototype.toggleZoom = function () { + throw new Error('magnifier toggleZoom not implemented'); + }; + + MagnifierImpl.prototype.render = function (options, dataAttr, fallbackSrc, largestSrc, srcSet) { + throw new Error('magnifier render not implemented'); + }; + + var ex = typeof exports === 'undefined' ? window : exports; + ex.Akamai = ex.Akamai || {}; + ex.Akamai.MagnifierImpl = MagnifierImpl; +})(); +(function () { + function MagnifierImplEnlarge(element, options) { + this._$el = $(element); + this._$el.enlarge(options); + } + + MagnifierImplEnlarge.prototype = Object.create(Akamai.MagnifierImpl.prototype); + MagnifierImplEnlarge.prototype.constructor = MagnifierImplEnlarge; + + MagnifierImplEnlarge.prototype.updateOptions = function (options) { + this._$el.enlarge("updateOptions", options); + }; + + MagnifierImplEnlarge.prototype.zoomIn = function () { + this._$el.enlarge("in"); + }; + + MagnifierImplEnlarge.prototype.zoomOut = function () { + this._$el.enlarge("out"); + }; + + MagnifierImplEnlarge.prototype.isMagnified = function () { + return this._$el.enlarge("isZoomed"); + }; + + MagnifierImplEnlarge.prototype.toggleZoom = function () { + if (this.isMagnified()) { + this.zoomOut(); + } else { + this.zoomIn(); + } + }; + + MagnifierImplEnlarge.render = function (options, dataAttr, fallbackSrc, largestSrc, srcSet) { + return "\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t" + options.buttonText + "\n\t\t\t\t\n\t\t\t
\n\t\t"; + }; + + var ex = typeof exports === 'undefined' ? window : exports; + ex.Akamai = ex.Akamai || {}; + ex.Akamai.MagnifierImplEnlarge = MagnifierImplEnlarge; +})(); +(function () { + function ClickTracker(element, handler) { + var self = this; + self.element = element; + self.handler = handler; + this.reset(); + } + + ClickTracker.prototype.onDown = function (e) { + if (e.target == this.element) { + if (this.isDown) { + this.reset(); + } else { + this.down.x = e.clientX; + this.down.y = e.clientY; + this.down.timestamp = new Date(); + this.isDown = true; + } + } + }; + + ClickTracker.prototype.onMove = function () { + this.isMoved = true; + }; + + ClickTracker.prototype.onUp = function (e) { + if (this.isDown) { + this.up.x = e.clientX; + this.up.y = e.clientY; + this.up.timestamp = new Date(); + + if (!this.isMoved && this.down.x === this.up.x && this.down.y === this.up.y && this.up.timestamp.getTime() - this.down.timestamp.getTime() <= 500) { + this.handler(); + } + } + + this.reset(); + }; + + ClickTracker.prototype.reset = function (x, y) { + this.down = { + x: -Infinity, + y: -Infinity, + timestamp: new Date(0) + }; + + this.isDown = false; + this.isMoved = false; + + this.up = { + x: Infinity, + y: Infinity, + timestamp: new Date() + }; + }; + + function MagnifierImplScroller(element, options) { + this._$el = $(element); + this._options = options; + this._container = this._$el.get(0); + this._content = this._$el.children().get(0); + this._imgCast = $(this._content).find('div').get(0); + this._zoomInBtn = this._$el.find("a.scroller-zoom-in").get(0); + this._zoomOutBtn = this._$el.find("a.scroller-zoom-out").get(0); + this._magnification = 1; + + var self = this; + + // Initialize Scroller + this.scroller = new Scroller(this._renderer(), { + zooming: options.enabled, + minZoom: 1, + maxZoom: options.magnification, + animationDuration: options.animationDuration + }); + + var rect = this._container.getBoundingClientRect(); + this.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop); + + this._installEventHandlers(); + this._updateButtonStates(); + setTimeout(function () { + self._onResize(); + }); + } + + MagnifierImplScroller.prototype = Object.create(Akamai.MagnifierImpl.prototype); + MagnifierImplScroller.prototype.constructor = MagnifierImplScroller; + + MagnifierImplScroller.prototype.updateOptions = function (options) { + var rect = this._container.getBoundingClientRect(); + this.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop); + this._onResize(); + this._zoomBy(1 / this._options.magnification, false); + }; + + MagnifierImplScroller.prototype.zoomIn = function () { + this._zoomBy(this._options.incrementalZoomFactor); + }; + + MagnifierImplScroller.prototype.cyclicZoom = function () { + var self = this; + if (this._magnification >= this._options.magnification) { + this._zoomBy(1 / this._options.magnification); + } else { + this.zoomIn(); + } + }; + + MagnifierImplScroller.prototype.zoomOut = function () { + this._zoomBy(1 / this._options.incrementalZoomFactor); + }; + + MagnifierImplScroller.prototype.isMagnified = function () { + return this._magnification > 1; + }; + + MagnifierImplScroller.prototype.toggleZoom = function () { + throw new Error('zoom toggle behavior is undefined in scroller mode'); + }; + + MagnifierImplScroller.render = function (options, dataAttr, fallbackSrc, largestSrc, srcSet) { + return "\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t" + options.zoomInBtnText + "\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t" + options.zoomOutBtnText + "\n\t\t\t\t\n\t\t\t
\n\t\t"; + }; + + MagnifierImplScroller.prototype._zoomBy = function (magnification, animate) { + var self = this, + magnification = Number(magnification); + + if (typeof animate === "undefined") { + animate = this._options.animateZoom; + } + + if (magnification === 1 || // if we are magnifying by 1 OR + this._magnification === 1 && magnification < 1 || // fully zoomed out and still attempting a zoom out OR + this._magnification === this._options.magnification && magnification > 1) { + // fully zoomed in and still attempting a zoom in + return; + } + + this.scroller.zoomTo(this._magnification * magnification, this._options.animateZoom); + + setTimeout(function () { + self._onZoom(); + }, this._options.animationDuration); + }; + + MagnifierImplScroller.prototype._onZoom = function () { + var oldMagnification = this._magnification; + + this._magnification = Number(this.scroller.getValues().zoom.toFixed(2)); + this._updateButtonStates(); + + if (oldMagnification > this._magnification) { + this._$el.trigger("scroller.after-zoom-out"); + } else if (oldMagnification < this._magnification) { + this._$el.trigger("scroller.after-zoom-in"); + } + }; + + MagnifierImplScroller.prototype._renderer = function () { + var docStyle = document.documentElement.style, + self = this; + + var engine; + if (window.opera && Object.prototype.toString.call(opera) === '[object Opera]') { + engine = 'presto'; + } else if ('MozAppearance' in docStyle) { + engine = 'gecko'; + } else if ('WebkitAppearance' in docStyle) { + engine = 'webkit'; + } else if (typeof navigator.cpuClass === 'string') { + engine = 'trident'; + } + + var vendorPrefix = { + trident: 'ms', + gecko: 'Moz', + webkit: 'Webkit', + presto: 'O' + }[engine]; + + var helperElem = document.createElement("div"); + var undef; + + var perspectiveProperty = vendorPrefix + "Perspective"; + var transformProperty = vendorPrefix + "Transform"; + + if (helperElem.style[perspectiveProperty] !== undef) { + return function (left, top, zoom) { + self._content.style[transformProperty] = 'translate3d(' + -left + 'px,' + -top + 'px,0) scale(' + zoom + ')'; + }; + } else if (helperElem.style[transformProperty] !== undef) { + return function (left, top, zoom) { + self._content.style[transformProperty] = 'translate(' + -left + 'px,' + -top + 'px) scale(' + zoom + ')'; + }; + } else { + return function (left, top, zoom) { + self._content.style.marginLeft = left ? -left / zoom + 'px' : ''; + self._content.style.marginTop = top ? -top / zoom + 'px' : ''; + self._content.style.zoom = zoom || ''; + }; + } + }; + + MagnifierImplScroller.prototype._onResize = function () { + this.scroller.setDimensions(this._container.clientWidth, this._container.clientHeight, this._container.clientWidth, this._container.clientHeight); + }; + + MagnifierImplScroller.prototype._installEventHandlers = function () { + var self = this, + clickTracker = new ClickTracker(this._imgCast, function () { + self.cyclicZoom(); + }); + + window.addEventListener("resize", function (e) { + self._onResize(e); + }, false); + + this._zoomInBtn.addEventListener("click", function (e) { + e.preventDefault(); + self.zoomIn(); + }, false); + + this._zoomOutBtn.addEventListener("click", function (e) { + e.preventDefault(); + self.zoomOut(); + }, false); + + if ('ontouchstart' in window) { + self._container.addEventListener("touchstart", function (e) { + // Don't react if initial down happens on one of the zoom buttons + if (e.touches[0].target == self._$el.find('a.scroller-zoom-in').get(0) || e.touches[0].target == self._$el.find('a.scroller-zoom-out').get(0)) { + return; + } + + // Don't react if initial down happens on a form element + if (e.touches[0] && e.touches[0].target && e.touches[0].target.tagName.match(/input|textarea|select/i)) { + return; + } + + self.scroller.doTouchStart(e.touches, e.timeStamp); + + for (var i = 0; i < e.touches.length; i++) { + clickTracker.onDown(e.touches[i]); + } + + e.preventDefault(); + }, false); + + document.addEventListener("touchmove", function (e) { + self.scroller.doTouchMove(e.touches, e.timeStamp, e.scale); + clickTracker.onMove(); + }, false); + + document.addEventListener("touchend", function (e) { + self.scroller.doTouchEnd(e.timeStamp); + + // handle any zoom that may have occured + self._onZoom(); + + for (var i = 0; i < e.changedTouches.length; i++) { + clickTracker.onUp(e.changedTouches[i]); + } + }, false); + + document.addEventListener("touchcancel", function (e) { + self.scroller.doTouchEnd(e.timeStamp); + // handle any zoom that may have occured + self._onZoom(); + + for (var i = 0; i < e.changedTouches.length; i++) { + clickTracker.onUp(e.changedTouches[i]); + } + }, false); + } else { + self._container.addEventListener("mousedown", function (e) { + if (e.target.tagName.match(/input|textarea|select/i)) { + return; + } + + if (self.scroller.__clientWidth === 0) { + self._onResize(); + } + self.scroller.doTouchStart([{ + pageX: e.pageX, + pageY: e.pageY + }], e.timeStamp); + + clickTracker.onDown(e); + }, false); + + document.addEventListener("mousemove", function (e) { + if (!clickTracker.isDown) { + return; + } + + self.scroller.doTouchMove([{ + pageX: e.pageX, + pageY: e.pageY + }], e.timeStamp); + + clickTracker.onMove(); + }, false); + + document.addEventListener("mouseup", function (e) { + if (!clickTracker.isDown) { + return; + } + + self.scroller.doTouchEnd(e.timeStamp); + clickTracker.onUp(e); + }, false); + + // self._container.addEventListener(navigator.userAgent.indexOf("Firefox") > -1 ? "DOMMouseScroll" : "mousewheel", function(e) { + // e.preventDefault(); + // self.scroller.doMouseZoom(e.detail ? (e.detail * -120) : e.wheelDelta, e.timeStamp, e.pageX, e.pageY); + // }, false); + } + }; + + MagnifierImplScroller.prototype._updateButtonStates = function () { + if (this._magnification === 1) { + $(this._$el).addClass('scroller-zoom-out-max'); + $(this._$el).removeClass('scroller-zoom-in-max'); + } else if (this._magnification === this._options.magnification) { + $(this._$el).addClass('scroller-zoom-in-max'); + $(this._$el).removeClass('scroller-zoom-out-max'); + } else { + $(this._$el).removeClass('scroller-zoom-in-max'); + $(this._$el).removeClass('scroller-zoom-out-max'); + } + }; + + var ex = typeof exports === 'undefined' ? window : exports; + ex.Akamai = ex.Akamai || {}; + ex.Akamai.MagnifierImplScroller = MagnifierImplScroller; +})(); +(function (exports, $) { + /** + * Image magnifier + * @class + * @alias Akamai.Magnifier + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Magnifier = Akamai.Util.component("Magnifier", function (element, options) { + // compat with enlarge `disabled` option + this._options.disabled = !this._options.enabled; + if (this._options.mode === Magnifier.MODE_ANIMATED_ZOOM) { + this._impl = new Akamai.MagnifierImplScroller(element, options); + } else { + this._impl = new Akamai.MagnifierImplEnlarge(element, options); + } + }); + + Magnifier.prototype._updateOptions = function (options) { + if (!options) { + this._options = this._originalOptions; + } + + this._options = Akamai.Util.extend(true, this._options, options); + + // compat with enlarge `disabled` option + this._options.disabled = !this._options.enabled; + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Magnifier.defaultOptions, this._options, "Magnifier"); + + this._impl.updateOptions(this._options); + }; + + // Used in preflight to "rename" events based on the child component events + Magnifier._componentEventMapping = { + "enlarge.after-zoom-in": "in", + "enlarge.after-zoom-out": "out", + "scroller.after-zoom-in": "in", + "scroller.after-zoom-out": "out" + }; + + Magnifier.MODE_HOVER_ZOOM = 'hoverzoom'; + Magnifier.MODE_ANIMATED_ZOOM = 'animatedzoom'; + + /** + * Defines the global default options for all Magnifiers on the page + * @static + * @property {String} mode - Set what mode to run the magnifier in. There are two modes available: `hoverzoom` and `animatedzoom`. The `hoverzoom` mode provides the ability to magnify and pan the image by just hovering your mouse over the image. It also provides a `flyout` option where a clip of the zoomed in image is displayed on a separate widget floating somewhere around (configurable) the image. The `animatedzoom` mode only does inline magnification but provides smooth animation between magnification levels, it also allows for incremental zooming. + * @property {Boolean} button - Whether to show a button for toggling magnification (default: true) + * @property {Boolean} enabled - Enabled/disable magnification (default: true, breakpoints supported) + * @property {Integer} magnification - The scale factor to magnify the image: `2`, `3` (default), `4`, `4.5`, etc + * @property {Array} image.widths - List of available widths for an image (to be combined with image.widthParam) - (default: ["320","640","800","1024","2048","5000"]) + * @property {String} image.sizes - Value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw` (default), `200px`, `(min-width:1000px) 500px, 100vw` - (default: `100vw`, breakpoints supported) + * @property {String} image.policy - Query param value for policy, appended to &policy= per image url when specified. Values: `foo`. (default: undefined) + * @property {Integer} delay - Only applicable in `hoverzoom` mode. The time delay in milliseconds between mouse hover and magnification (default: 300, breakpoints supported) + * @property {String} buttonText - Only applicable in `hoverzoom` mode. Text for the zoom button. Also used for its title attribute. (default: "Toggle Image Magnification") + * @property {Integer} flyout.width - Only applicable in `hoverzoom` mode. Width of the flyout image (default: 200) + * @property {Integer} flyout.height - Only applicable in `hoverzoom` mode. Height of the flyout image (default: 200) + * @property {Boolean} hoverZoomWithoutClick - Only applicable in `hoverzoom` mode. Zoom starts on mouse hover with no click needed (default: true; false will require a click to hover-zoom) + * @property {String} placement - Only applicable in `hoverzoom` mode. Placement of the magnified image: `inline` , `flyoutloupe`, `flyouttopleft`,`flyoutbottomleft` ,`flyouttopright` and `flyoutbottomright` - (default: inline, breakpoins supported) + * @property {Float} incrementalZoomFactor - Only applicable in `animatedzoom` mode. A number by which to incrementally zoom up until the specified `magnification`, default `3`. For example a `magnification` of `4` and an `incrementalZoomFactor` of `2` will zoom the image in `2` steps + * @property {Boolean} animateZoom - Only applicable in `animatedzoom` mode. Animates the magnification process. Default `true` + * @property {Integer} animationDuration - Only applicable in `animatedzoom` mode. If `animateZoom` is true, this specifies the length of the animation in milliseconds. Default `250` + * @property {String} zoomInBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom in button. Default `Zoom In` + * @property {String} zoomOutBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom out button. Default `Zoom Out` + */ + Magnifier.defaultOptions = { + // general options + mode: Magnifier.MODE_HOVER_ZOOM, + button: true, + enabled: true, + magnification: 3, + + // image options + image: Akamai.Image.defaultConfig, + + // hoverzoom options + delay: 300, + buttonText: "Toggle Image Magnification", + flyout: { + width: 200, + height: 200 + }, + hoverZoomWithoutClick: true, + placement: "inline", + + // animatedzoom options + incrementalZoomFactor: 3, + animateZoom: true, + animationDuration: 250, + zoomInBtnText: 'Zoom In', + zoomOutBtnText: 'Zoom Out' + }; + + // srcset stuff + Akamai.Sourceable.extendStatic(Magnifier); + + Magnifier.render = function (json, options) { + var finalOptions = Akamai.Util.options(Magnifier.defaultOptions, options); + var dataAttr = Magnifier._dataAttr; + var fallbackSrc = Magnifier._fallbackSrc(json.url, finalOptions.image); + var largestSrc = Magnifier._largestSrc(json.url, finalOptions.image); + var srcSet = Magnifier._srcset(json.url, finalOptions.image); + var sizes = finalOptions.image.sizes; + var impl = finalOptions.mode === Magnifier.MODE_ANIMATED_ZOOM ? Akamai.MagnifierImplScroller : Akamai.MagnifierImplEnlarge; + return impl.render(finalOptions, dataAttr, fallbackSrc, largestSrc, srcSet, sizes); + }; + + /** + * Enter magnifier mode + * @method + * @fires Akamai.Magnifier#akamai-magnifier-in + * @returns {undefined} + */ + Magnifier.prototype.in = function () { + this._impl.zoomIn(); + }; + + /** + * Exit magnifier mode + * @method + * @fires Akamai.Magnifier#akamai-magnifier-out + * @returns {undefined} + */ + Magnifier.prototype.out = function () { + this._impl.zoomOut(); + }; + + /** + * Return the current state of the magnifier + * @method + * @returns {Boolean} + */ + Magnifier.prototype.isMagnified = function () { + return this._impl.isMagnified(); + }; + + /** + * Toggle the state of the magnifier + * @method + * @fires Akamai.Magnifier#akamai-magnifier-in + * @fires Akamai.Magnifier#akamai-magnifier-out + * @returns {undefined} + */ + Magnifier.prototype.toggle = function () { + this._impl.toggleZoom(); + }; + + /** + * Triggered when the magnifier has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carouse#in}. + * + * @event Akamai.Magnifier#akamai-magnifier-in + */ + + /** + * Triggered when the magnifier has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carouse#out}. + * + * @event Akamai.Magnifier#akamai-magnifier-out + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Magnifier}. + * + * @event Akamai.Magnifier#akamai-magnifier-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Magnifier = Magnifier; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (exports, $) { + var VideoImpl = Akamai.Util.component("VideoImpl", function (element, options) { + throw new Error('Not allowed to instantiate VideoImpl'); + }); + + VideoImpl.render = function (json, options) { + throw new Error('VideoImpl.render not implemented'); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.VideoImpl = VideoImpl; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (exports, $) { + var VideoImplPristine = Akamai.Util.component("VideoImplPristine", function (element, options) {}); + + VideoImplPristine.prototype = Object.create(Akamai.VideoImpl.prototype); + VideoImplPristine.prototype.constructor = VideoImplPristine; + + VideoImplPristine.render = function (json, options) { + var finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options); + + var url = json.url; + var poster = json.poster || ""; + var mime = json.mime ? 'type="' + json.mime + '"' : ''; + + var attrs = [finalOptions.loop ? "loop" : "", finalOptions.autoplay ? "autoplay" : "", finalOptions.controls ? "controls" : "", finalOptions.muted ? "muted" : "", "playsinline"]; + + return Akamai.Util.trim("\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t"); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.VideoImplPristine = VideoImplPristine; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (exports, $) { + var VideoImplIm = Akamai.Util.component("VideoImplIm", function (element, options) { + var finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options); + VideoImplIm._validateOptions(finalOptions); + }); + + VideoImplIm.prototype = Object.create(Akamai.VideoImpl.prototype); + VideoImplIm.prototype.constructor = VideoImplIm; + + VideoImplIm._validateOptions = function (options) { + if (!Array.isArray(options.sizes) || options.sizes.length !== 3) { + throw new Error('Akamai.Video sizes must have three entries'); + } + + if (options.sizes.some(function (size) { + return isNaN(size); + })) { + throw new Error('Akamai.VideoImplIm all sizes must be numbers'); + } + }; + + VideoImplIm.render = function (json, options) { + var finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options); + VideoImplIm._validateOptions(finalOptions); + + var url = json.url; + var poster = json.poster || ""; + var width; + + var viewPortWidth = window.innerWidth; + + if (viewPortWidth < 992) { + width = finalOptions.sizes[0]; + } else if (viewPortWidth < 1200) { + width = finalOptions.sizes[1]; + } else { + width = finalOptions.sizes[2]; + } + + var attrs = [finalOptions.loop ? "loop" : "", finalOptions.autoplay ? "autoplay" : "", finalOptions.controls ? "controls" : "", finalOptions.muted ? "muted" : "", "playsinline"]; + + var joiner = url.indexOf('?') === -1 ? '?' : '&'; + + return Akamai.Util.trim("\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t"); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.VideoImplIm = VideoImplIm; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (exports, $) { + /** + * Video component + * @class + * @alias Akamai.Video + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Video = Akamai.Util.component("Video", function (element, options) { + // TODO + this._comp = this._$el.length && undefined; + this._$videoElement = this._$el.find("video"); + this._videoElement = this._$videoElement[0]; + + if (!this._videoElement) { + throw new Error("Akamai.Video requires a child HTML Video element"); + } + + if (!this._videoElement.play || !this._videoElement.pause) { + this._unsupportedAPI = true; + this._unsupported(); + return; + } + + // get the initial state (could be autoplaying on render) + this._setState(this._videoElement.paused ? Video.States.Paused : Video.States.Playing); + + // state bindings + this._$el.bind("akamai-video-play", function () { + this._setState(Video.States.Playing); + }.bind(this)).bind("akamai-video-pause", function () { + this._setState(Video.States.Paused); + }.bind(this)); + + this._createPlayButton(); + }); + + Video.MODE_IM = 'im'; + Video.MODE_PRISTINE = 'pristine'; + + Video._componentEventMapping = { + "play": { + to: "play", + selector: "video" + }, + + "pause": { + to: "pause", + selector: "video" + }, + + "seeked": { + to: "seek", + selector: "video" + } + }; + + Video.prototype._updateOptions = function (options) { + if (!options) { + this._options = this._originalOptions; + } + + this._options = Akamai.Util.extend(true, this._options, options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Video.defaultOptions, this._options, "Video"); + + // TODO see Magnifier for example + }; + + /** + * Defines the global default options for all Spin360s on the page + * @static + * @property {Boolean} autoplay - Automatically play the video on load (default: false) + * @property {Boolean} controls - Display the video controls (default: true) + * @property {Boolean} loop - Restart the video when it reaches the end (default: false) + * @property {Boolean} muted - Mute the video (default: true) + * @property {String} mode - What video component to use. `im` will let you use any size pristine video and will automatically request & generate the right size when the page is loaded. `pristine` just passes through the original video into the video player and this is what will always play on the users device. Default: `pristine` + * @property {Array} sizes - Video sizes (widths) to use for different screen widths. Defaults: [1920 (large screens), 1280 (tablet), 854 (mobile), ] + */ + Video.defaultOptions = { + autoplay: false, + controls: true, + loop: false, + muted: true, + mode: Video.MODE_PRISTINE, + sizes: [854, 1280, 1920] + }; + + Video.render = function (json, options) { + var impl = options && options.mode === Video.MODE_IM ? Akamai.VideoImplIm : Akamai.VideoImplPristine; + return impl.render(json, options); + }; + + Video.States = { + Playing: 0, + Paused: 1 + }; + + Video.prototype._unsupported = function () { + if (this._unsupportedAPI) { + Akamai.Util.log("Video: video API not supported", 'error'); + } + + return this._unsupportedAPI; + }; + + /** + * Play the video, idempotent + * @method + * @fires Akamai.Video#akamai-video-play + * @returns {undefined} + */ + Video.prototype.play = function () { + if (this._unsupported()) { + return; + } + this._videoElement.play(); + }; + + /** + * Pause the video + * @method + * @fires Akamai.Video#akamai-video-pause + * @returns {undefined} + */ + Video.prototype.pause = function () { + if (this._unsupported()) { + return; + } + this._videoElement.pause(); + }; + + /** + * Return the current state of the video + * @example Video states + * Akamai.Video.States = { + * Playing: 0, + * Paused: 1 + * }; + * + * @example Conditioning on states + * if( video.getState() === Akamai.Video.States.Paused ) { + * video.play() + * } + * @method + * @returns {Akamai.Video.State} + */ + Video.prototype.getState = function () { + return this._state; + }; + + Video.prototype._setState = function (value) { + this._state = value; + }; + + /** + * Seek to the input percentage. + * @method + * @fires Akamai.Video#akamai-video-seek + * @param {Integer} percent - value between 0 and 100 percent for seeking + * @returns {undefined} + */ + Video.prototype.seek = function (percent) { + if (percent < 0 || 100 < percent) { + throw new Error("seek takes a an integer between 0 and 100"); + } + + var newTime = percent / 100 * (this._videoElement.duration || 1); + this._videoElement.currentTime = newTime; + }; + + /** + * Set whether the video should loop or not + * @method + * @param {Boolean} value - The value true or false + * @returns {undefined} + */ + Video.prototype.setLoop = function (value) { + this._videoElement.loop = value; + }; + + Video.prototype.getElement = function () { + return this._$el[0]; + }; + + // Borrowed from https://codepen.io/chrisnager/pen/jPrJgQ + Video.prototype._createPlayButton = function () { + var videoPlayButton; + var videoWrapper = this._$el[0]; + var video = this._videoElement; + + if (this._options.autoplay) { + return; + } + + // TODO move to render? + videoWrapper.insertAdjacentHTML('beforeend', "\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t"); + + video.removeAttribute('controls'); + videoPlayButton = this._$el.find('svg')[0]; + + videoPlayButton.addEventListener('click', function () { + video.play(); + videoPlayButton.classList.add('is-hidden'); + if (this._options.controls) { + video.setAttribute('controls', 'controls'); + } + }.bind(this)); + }; + + /** + * Triggered when the video has been played. See {@link Akamai.Video#play}. + * + * @event Akamai.Video#akamai-video-play + */ + + /** + * Triggered when the video has been paused. See {@link Akamai.Video#pause}. + * + * @event Akamai.Video#akamai-video-pause + */ + + /** + * Triggered when the video has seeked to a position. See {@link Akamai.Video#seek}. + * + * @event Akamai.Video#akamai-video-seek + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Video}. + * + * @event Akamai.Video#akamai-video-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Video = Video; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (exports, $) { + /** + * Image carousel + * @class + * @alias Akamai.Carousel + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Carousel = Akamai.Util.component("Carousel", function (element, options) { + this._spins = Akamai.Spin360.createMany(this._$el[0], this._options.spin360); + this._magnifiers = Akamai.Magnifier.createMany(this._$el[0], this._options.magnifier); + this._videos = Akamai.Video.createMany(this._$el[0], this._options.video); + + this._$el.snapper(this._options); + + this._setAspectRatio(); + this._bindAspectAttributes(); + + if (this._options.slideshow.autostart) { + setTimeout(function () { + this.startSlideshow(); + }.bind(this), this._options.slideshow.interval); + } + + // NOTE !! the following two bindings must happen in order, the + // autoplayables binding relies on the attribut + + // add item attrs and maintain the active item attributes to determine the + // active item state + this._activeItemAttributes(); + this._$el.bind("akamai-carousel-goto", this._activeItemAttributes.bind(this)); + + // TODO pause autoplay videos that are not visible + this._handleAutoplayables(); + this._$el.bind("akamai-carousel-goto", this._handleAutoplayables.bind(this)); + + this._$el.bind("tau.touch-tracking-start", function () { + this._$el.find(".snapper_pane").addClass("no-scroll"); + }.bind(this)).bind("tau.touch-tracking-stop", function () { + this._$el.find(".snapper_pane").removeClass("no-scroll"); + }.bind(this)); + + // when child components are doing things, stop the carousel from + // automatically advancing + this._$el.bind(this.constructor._stopSlideshowEvents.join(" "), this.stopSlideshow.bind(this)); + }); + + Carousel._stopSlideshowEvents = ["akamai-magnifier-in", "akamai-magnifier-out", "akamai-spin360-goto", "akamai-video-play", "akamai-video-seek"]; + + // clearly there should be an autoplayable interface + Carousel.prototype._handleAutoplayables = function () { + var isParentActive = function (comp) { + return !!$(comp.getElement()).closest("[" + this.constructor.activeItemAttr + "]").length; + }.bind(this); + + this._videos.concat(this._spins).forEach(function (comp) { + if (isParentActive(comp) && comp._carouselWasPlaying) { + comp.play(); + } else { + if (comp.getState() == comp.constructor.States.Playing) { + comp._carouselWasPlaying = true; + comp.pause(); + } else { + comp._carouselWasPlaying = false; + } + } + }.bind(this)); + }; + + // Unique counter for IDs + Carousel.counter = 0; + + Carousel.prototype._updateOptions = function (options) { + if (!options) { + this._options = this._originalOptions; + } + + // update the current options + this._options = Akamai.Util.extend(true, this._options, options); + + // update the options for each of the subcomponents + var update = function (comp, name) { + comp._updateOptions(this._options[name]); + }.bind(this); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Carousel.defaultOptions, this._options, "Carousel"); + this._$el.snapper("updateOptions", options); + + this._spins.forEach(function (c) { + update(c, "spin360"); + }); + this._magnifiers.forEach(function (c) { + update(c, "magnifier"); + }); + this._videos.forEach(function (c) { + update(c, "video"); + }); + + this._setAspectRatio(); + }; + + // Used in preflight to "rename" events based on the child component events + Carousel._componentEventMapping = { + "snapper.after-snap": "goto", + "snapper.snap": "snap", + "snapper.after-next": "next", + "snapper.after-prev": "previous" + }; + + /** + * Defines the global default options for all Carousels on the page + * @static + * @property {Boolean} arrows - Show carousel controls - (default: true) + * @property {Number} aspectratio - Specify a percentage-based height for the carousel, relative to the width. Values: `false`, `100`, `45.6`, `78` - (default `false`, breakpoints supported) + * @property {Integer} slideshow.interval - Time in milliseconds between slide advances - (default: 4000, breakpoints supported) + * @property {Integer} slideshow.autostart - Start the slideshow on instantiation - (default: false) + * @property {String} thumbnail.placement - Placement relative to the slide container: `left`, `right`, `bottom` (default: `bottom`, breakpoints supported) + * @property {String} thumbnail.type - Type of thumbnail: `dots`, `none`, `images` - (default: `images`, breakpoints supported) + * @property {String} thumbnail.policy - String to use for query parameter, ex: "&policy=" (default: undefined) + * @property {String} thumbnail.sizes - Sizes attribute value to use if thumbnail policy is set. Values: `300px`, `200px`, `(min-width:1000px) 300px, 100px` - (default: `300px`) + * @property {Object} images - Defaults to the Magnifier image option's settings + */ + Carousel.defaultOptions = { + arrows: true, + + aspectratio: false, + + slideshow: { + interval: 4000, + autostart: false + }, + + thumbnail: { + placement: "bottom", + type: "images", + policy: undefined, + sizes: "300px" + }, + + images: Akamai.Image.defaultConfig + }; + + Carousel.renderMapping = { + image: Akamai.Magnifier, + spin360: Akamai.Spin360, + video: Akamai.Video + }; + + Carousel._typeMapping = { + image: "magnifier" + }; + + Carousel._uniqueItemId = function (item, i) { + return "akamai-carousel-" + Carousel.counter + "-" + item.type + "-" + i; + }; + + // srcset stuff + Akamai.Sourceable.extendStatic(Carousel); + + Carousel._renderItem = function (item, i, options) { + var mappedType = Carousel._typeMapping[item.type] || item.type; + + if (!Carousel.renderMapping[item.type]) { + throw new Error("\n\t\t\t\titem type " + item.type + " at index " + i + " must be 'image', 'spin360', or 'video'\n\t\t\t"); + } + + return "\n\t\t\t
\n\t\t\t\t" + Carousel.renderMapping[item.type].render(item, options[mappedType]) + "\n\t\t\t
\n\t\t"; + }; + + Carousel._renderThumbnailAnchor = function (item, data, i, options) { + var thumbUrl = ""; + var altText = item.alt || ""; + + // use the video poster, the canonical url, or the first in a sequence + if (item.type === 'video') { + thumbUrl = item.poster || data.reduce(function (acc, i) { + return acc || Carousel._thumbUrl(i); + }, ""); + } else { + thumbUrl = Carousel._thumbUrl(item); + } + + // TODO this sucks + options.images.policy = options.thumbnail.policy; + + var src = Carousel._fallbackSrc(thumbUrl, options.images); + var srcset = Carousel._srcset(thumbUrl, options.images); + var sizes = options.images.sizes; + + // if there's a thumbnail policy, the thumbnails will be fresh image requests, + // so they should have better sizes attribute values + if (options.thumbnail.policy) { + sizes = options.thumbnail.sizes; + } + + var attrs = [Carousel._dataAttr + "-thumb-type=\"" + item.type + "\"", item.type == "video" && !item.poster ? "${Carousel._dataAttr}-thumb-noposter" : ""]; + + return "\n\t\t\t\n\t\t\t\t\""\n\t\t\t\n\t\t"; + }; + + Carousel._thumbUrl = function (item) { + return item.type === 'video' ? item.poster : item.url || item.urls && item.urls[0]; + }; + + Carousel._renderThumbnails = function (data, options) { + if (data.length <= 1) { + return ""; + }; + + return "\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t" + Akamai.Util.map(data, function (item, i) { + return Carousel._renderThumbnailAnchor(item, data, i, options); + }).join("\n") + "\n\t\t\t\t
\n\t\t\t
\n\t\t"; + }; + + Carousel.prototype._setAspectAttributes = function ($el) { + var orientationAttr = Carousel._dataAttr + "-aspectratio-orientation"; + if ($el.height() > $el.width()) { + $el.attr(orientationAttr, "portrait"); + } else if ($el.height() < $el.width()) { + $el.attr(orientationAttr, "landscape"); + } else { + if ($el.parent().height() >= $el.parent().width()) { + $el.attr(orientationAttr, "landscape"); + } else { + $el.attr(orientationAttr, "portrait"); + } + } + }; + + Carousel.prototype._setAspectRatio = function () { + var value = this._options.aspectratio || 0; + var $items = this._$el.find(".snapper_item"); + $items.css("padding-top", value / $items.length + "%"); + }; + + Carousel.prototype._bindAspectAttributes = function () { + if (this._options.aspectratio === false) { + return; + } + + var value = this._options.aspectratio || 0; + var self = this; + var $items = this._$el.find(".snapper_item"); + + $items.each(function () { + var loadBound; + var $item = $(this); + + var $loadable = $item.find("img, video").eq(0); + + $loadable.bind("load loadedmetadata", loadBinding = function () { + clearTimeout(loadBound); + + if ($item.is("[" + Akamai.Spin360._dataAttr + "]")) { + // TODO it's not always a canvas, sometimes it's a collection of + // images based on settings + self._setAspectAttributes($item.find("canvas")); + } else { + self._setAspectAttributes($loadable); + } + }); + + loadBound = setTimeout(loadBinding, 5000); + }); + + $items.eq(0).find("img,video").eq(0).bind("load loadedmetadata", function () { + self._trigger("first-media-load"); + }); + }; + + Carousel.render = function (data, options) { + options = options || {}; + + // establish the extended default options for the carousel + var carouselOptions = Akamai.Util.options(Carousel.defaultOptions, options); + + var templateAttrs = ["" + Carousel._dataAttr, Carousel._dataAttr + "-item-count=\"" + (data || []).length + "\"", "data-snapper-deeplinking=\"false\"", carouselOptions.arrows ? "data-snapper-nextprev" : ""]; + + Carousel.counter++; + + // TODO can we do something about the `enlarge_pane` class here, seems it + // could be conditioned on at least one regular image type in the json + // TODO options should dictate `data-snapper-nextprev` + return Akamai.Util.trim("\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t" + Akamai.Util.map(data, function (item, i) { + return Carousel._renderItem(item, i, carouselOptions); + }).join("\n") + "\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t" + Carousel._renderThumbnails(data, carouselOptions) + "\n\t\t\t
\n\t\t"); + }; + + /** + * Instantiate Carousels by looking for children matching + * [data-akamai-carousel] in `element` param + * @method + * @static + * @param {HTMLElement} element - the element to search in for + * @returns {Akamai.Carousel[]} + */ + + /** + * @method + * @returns {Akamai.Spin360[]} + */ + Carousel.prototype.getSpin360s = function () { + return this._spins; + }; + + /** + * @method + * @returns {Akamai.Magnifier[]} + */ + Carousel.prototype.getMagnifiers = function () { + return this._magnifiers; + }; + + /** + * @method + * @returns {Akamai.Video[]} + */ + Carousel.prototype.getVideos = function () { + return this._videos; + }; + + /** + * Go to a particular slide. + * + * Note that the state of the DOM on the page and thus the index of the + * carousel will not be up-to-date until the {@link + * Akamai.Carousel#akamai-carousel-goto} event has been fired. That is, + * calling this method and the calling `getIndex` will not necessarily result + * in an updated index value. Either bind to the event or supply a callback. + * + * @method + * @fires Akamai.Carousel#akamai-carousel-goto + * @param {Integer} index - The zero-based slide index to go to + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Carousel.prototype.goto = function (index, callback) { + this._$el.snapper("goto", index, callback); + }; + + Carousel._itemAttr = Carousel._dataAttr + "-item"; + Carousel._activeItemAttr = Carousel._itemAttr + "-active"; + + /** + * Add an attribute to all carousel items, + * and additionally maintain an active attribute on the active carousel item + * + * @method + * @returns {undefined} + */ + Carousel.prototype._activeItemAttributes = function () { + var attrAllItems = this.constructor._itemAttr; + var attrActiveItem = this.constructor._activeItemAttr; + + this._$el.find(".snapper_item").attr(attrAllItems, true).removeAttr(attrActiveItem).eq(this.getIndex()).attr(attrActiveItem, true); + }; + + /** + * Return the current slide index + * @method + * @returns {undefined} + */ + Carousel.prototype.getIndex = function () { + return this._$el.snapper("getIndex"); + }; + + // Extend Carousel with Advanceabl interface + Akamai.Advanceable.extend(Carousel); + + /** + * Advance to the next item + * @method + * @fires Akamai.Carousel#akamai-carousel-next + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Carousel.prototype.next; + // required for JSDocs, implementation in Advanceable + + /** + * Retreat to the previous item + * @method + * @fires Akamai.Carousel#akamai-carousel-previous + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Carousel.prototype.previous; + // required for JSDocs, implementation in Advanceable + + /** + * Start automatic advancement of the carousel items + * @method + * @fires Akamai.Carousel#akamai-carousel-start-slideshow + * @returns {undefined} + */ + Carousel.prototype.startSlideshow = function () { + this._$el.one("mousedown touchstart", this.stopSlideshow.bind(this)); + + this.next(function () { + this._trigger("start-slideshow"); + this._slideshowTimer = setTimeout(function () { + this.startSlideshow(); + + // TODO remove || when default options are added + }.bind(this), this._options.slideshow.interval); + }.bind(this)); + }; + + /** + * Start automatic advancement of the carousel items + * @method + * @fires Akamai.Carousel#akamai-carousel-stop-slideshow + * @returns {undefined} + */ + Carousel.prototype.stopSlideshow = function () { + clearTimeout(this._slideshowTimer); + this._slideshowTimer = undefined; + this._trigger("stop-slideshow"); + }; + + /** + * Triggered when the carousel has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carousel#goto}. + * + * @event Akamai.Carousel#akamai-carousel-goto + */ + + /** + * Triggered when the carousel has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carousel#next}. + * + * @event Akamai.Carousel#akamai-carousel-next + */ + + /** + * Triggered when the carousel has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carousel#previous}. + * + * @event Akamai.Carousel#akamai-carousel-previous + */ + + /** + * Triggered when the carousel has started the slide show due to a call to + * {@link Akamai.Carousel#startSlideshow}. + * + * @event Akamai.Carousel#akamai-carousel-start-slideshow + */ + + /** + * Triggered when the carousel has stoped the slide show due to a call to + * {@link Akamai.Carousel#stopSlideshow}. + * + * @event Akamai.Carousel#akamai-carousel-stop-slideshow + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Carousel}. + * + * @event Akamai.Carousel#akamai-carousel-init + */ + + Carousel.prototype.refresh = function () { + this._$el.snapper("updateWidths"); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Carousel = Carousel; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (exports, $) { + /** + * Image Fullscreen + * @class + * @alias Akamai.Fullscreen + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + + // TODO JSDocs + // TODO Tests + var Fullscreen = Akamai.Util.component("Fullscreen", function (element, options) { + if (this._options.enabled) { + this._init(); + } + }); + + Fullscreen.prototype._init = function () { + this._$fullscreen = this._$el; + + // parent container does not allow widths to be set (Firefox, fullscreen), so we’ll adjust the children + this.addButton(); + + this._fullscreenApiKeys = Fullscreen._keys(); + + if (this._options.native) { + this._useNativeApi = !!this._fullscreenApiKeys; + } else { + this._useNativeApi = false; + } + + // Fullscreen API is disabled and the fallback behavior is active + this._fullscreenFallbackEnabled = false; + this._$placeholder = $("
"); + + this._isFullscreen = false; + this.addEvents(); + }; + + Fullscreen.classes = { + btn: "akamai-fullscreen-btn", + btnContainer: "akamai-fullscreen-btncontainer", + enterBtn: "akamai-fullscreen-btn-enter", + exitBtn: "akamai-fullscreen-btn-exit", + active: "akamai-fullscreen-active", + fallback: "akamai-fullscreen-fallback" + }; + + Fullscreen.attr = { + width: "data-akamai-fullscreen-width", + enterBtn: "data-akamai-fullscreen-btn", + exitBtn: "data-akamai-fullscreen-exit-btn" + }; + + Fullscreen.defaultOptions = { + enabled: false, + native: false + }; + + Fullscreen.prototype.addButton = function () { + this._$widthAdjust = this._$fullscreen.children().filter(".focused"); + + if (!this._$widthAdjust.length) { + this._$widthAdjust = this._$fullscreen.children().eq(0); + } + + if (this._$widthAdjust.attr(Fullscreen.attr.width) === null) { + this._$widthAdjust.attr(Fullscreen.attr.width, ""); + } + + if (this._$widthAdjust.find("[" + Fullscreen.attr.enterBtn + "]").length) { + return; + } + + // buttons parent + this._$buttonContainer = this._$widthAdjust; + this._$buttonContainer.addClass(Fullscreen.classes.btnContainer); + + this._$buttonContainer.append(this.render()); + }; + + Fullscreen.prototype.addEvents = function () { + this._$fullscreen.on("click", function (e) { + var $target = $(e.target); + if (!$target.is("[" + Fullscreen.attr.enterBtn + "]")) { + return; + } + + this.enter(); + e.preventDefault(); + }.bind(this)); + + this._$fullscreen.on("click", function (e) { + var $target = $(e.target); + + if (!$target.is("[" + Fullscreen.attr.exitBtn + "]")) { + return; + } + + this.exit(); + e.preventDefault(); + }.bind(this)); + + if (this._useNativeApi) { + document.addEventListener(this._fullscreenApiKeys.onchange, function () { + // exiting fullscreen using native method (ESC or menu option) + if (!document[this._fullscreenApiKeys.element]) { + this._exit(); + } + }.bind(this), false); + } + + // ESC to close + $(document).on("keydown", function (e) { + var code = e.keyCode || e.which; + if (code === 27) { + this._exit(); + } + }.bind(this)); + }; + + Fullscreen.prototype.render = function () { + return Akamai.Util.trim("\n\t\t\t\n\t\t\t\n\t\t"); + }; + + Fullscreen._keyLookup = [{ + enter: "requestFullscreen", + exit: "exitFullscreen", + element: "fullscreenElement", + onchange: "fullscreenchange" + }, { + enter: "webkitRequestFullscreen", + exit: "webkitExitFullscreen", + element: "webkitFullscreenElement", + onchange: "webkitfullscreenchange" + }, { + enter: "webkitRequestFullScreen", + exit: "webkitCancelFullScreen", + element: "webkitCurrentFullScreenElement", + onchange: "webkitfullscreenchange" + }, { + enter: "mozRequestFullScreen", + exit: "mozCancelFullScreen", + element: "mozFullScreenElement", + onchange: "mozfullscreenchange" + }, { + enter: "msRequestFullscreen", + exit: "msExitFullscreen", + element: "msFullscreenElement", + onchange: "MSFullscreenChange" + }]; + + Fullscreen._keys = function (el) { + var keys = Fullscreen._keyLookup; + + var el = document.body; + for (var j = 0, k = keys.length; j < k; j++) { + if (keys[j].enter in el) { + return keys[j]; + } + } + }; + + Fullscreen.prototype._maximizePlacement = function () { + this._fullscreenFallbackEnabled = true; + this._$fullscreen.addClass(Fullscreen.classes.fallback); + this._$placeholder.insertAfter(this._$fullscreen); + this._$fullscreen.appendTo(document.body); + }; + + Fullscreen.prototype._restorePlacement = function () { + if (!this._fullscreenFallbackEnabled) { + return; + } + + this._fullscreenFallbackEnabled = false; + this._$fullscreen.removeClass(Fullscreen.classes.fallback); + this._$fullscreen.insertAfter(this._$placeholder); + this._$placeholder.remove(); + }; + + Fullscreen.prototype._adjustWidth = function () { + this._$fullscreen.css("width", "auto !important"); + // wish this could go into the change event above, but alas the dimensions don’t measure right + var widthSmall = this._$fullscreen.width(); + var heightSmall = this._$fullscreen.height(); + + var viewportWidth = this._useNativeApi ? window.screen.width : Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + var viewportHeight = this._useNativeApi ? window.screen.height : Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + + // calculate the maximum width we can use to fill the viewportHeight + var widthBig = widthSmall * viewportHeight / heightSmall; + + // if the width is bigger than the maximum, just set to 100% (won’t fill the entire height, but that’s ok) + if (widthBig > viewportWidth) { + widthBig = "100%"; + } + + this._$widthAdjust.width(widthBig); + }; + + Fullscreen.prototype._revertWidth = function () { + this._$widthAdjust.css("width", ""); + }; + + /** + * Enter full screen mode + * @method + * @returns {undefined} + */ + Fullscreen.prototype.enter = function () { + var activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex(); + this._$fullscreen.addClass(Fullscreen.classes.active); + + if (this._useNativeApi) { + this._$fullscreen[0][this._fullscreenApiKeys.enter](); + this._adjustWidth(); + } else { + this._adjustWidth(); + this._maximizePlacement(); + } + this._isFullscreen = true; + this._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex); + this._trigger("enter"); + }; + + Fullscreen.prototype._exit = function () { + var activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex(); + this._$fullscreen.removeClass(Fullscreen.classes.active); + + if (!this._useNativeApi) { + this._restorePlacement(); + } + + this._revertWidth(); + this._isFullscreen = false; + this._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex); + this._trigger("exit"); + }; + + /** + * Exits full screen mode + * @method + * @returns {undefined} + */ + Fullscreen.prototype.exit = function () { + if (this._useNativeApi) { + // note some close behavior happens in the onchange event handler above + document[this._fullscreenApiKeys.exit](); + } + + this._exit(); + }; + + Fullscreen.prototype.isFullscreen = function () { + return this._isFullscreen; + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Fullscreen = Fullscreen; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (exports, $) { + /** + * Akamai Viewer omni-component + * @class + * @alias Akamai.Viewer + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + * + * @example Instatiation + * var element = document.querySelector( "[data-akamai-viewer]" ); + * var viewer = Akamai.Viewer( element ); + */ + var Viewer = Akamai.Util.component("Viewer", true, function (element, options) { + // try to update the carousel and magnifier sizes options to the width of + // the viewer for more accurate srcset selection + this._updateSizesOptions(true); + this._onResizeComplete(this._updateSizesOptions.bind(this)); + + // move all the flattened component options (spin, zoom, video) onto the carousel + this._options.carousel = Viewer._extendCarouselOptions(this._options); + + this._withData(function (data) { + if (data) { + // check all of the urls agains the hostname and whitelist + this._checkJSONUrls(data); + + // prevent XSS / injection attacks by escaping string values + data = Akamai.Util.escapeJSONVals(data); + + // store escaped data for later reference + this._options.items.data = data; + } + + this._tagMapping = {}; + + // TODO if the element is empty and/or options has `items` render into element + if (this._$el.children().length == 0 && data) { + this._tagMapping = this.constructor._tagSplit(data, this._options.items); + this._$el.append(this.constructor.render(data, this._options, true)); + } + + // intantiate all child carousels + this._carousels = Akamai.Carousel.createMany(element, this._options.carousel); + this._fullscreen = new Akamai.Fullscreen(element, this._options.fullscreen); + + // on enter and exit of fullscreen resolve the breakpoints and update options + this._$el.bind("akamai-fullscreen-enter akamai-fullscreen-exit", function () { + this._updateOptions(this._resolveBreakpointOptions()); + }.bind(this)); + + // use the breakpoints to set match media listeners + this._setBreakpoints(); + + // have to trigger init explicitly for async constructors + this._trigger("init"); + }.bind(this), function (msg) { + throw new Error(msg); + }); + }); + + Viewer.prototype._updateSizesOptions = function (localUpdateOnly) { + // update sizes option to something more specific if possible + var elWidth = this._$el.width(); + if (elWidth && elWidth > 0) { + elWidth += "px"; + this._options = Akamai.Util.extend(true, this._options, { + carousel: { + images: { + sizes: elWidth + } + } + }); + } + + if (!localUpdateOnly) { + this._updateOptions(this._options); + } + }; + + Viewer.prototype._withData = function (after, fail) { + if (!this._options.items.uri) { + if (this._exceedsLimit(this._options.items.data, fail)) { + return; + } + + after(this._options.items.data); + } else if (typeof this._options.items.uri !== 'undefined' && this._options.items.uri.length > 0) { + $.get(this._options.items.uri, function (data) { + // make sure the data gets sorted out regardless of `get` impl + data = typeof data === "string" ? JSON.parse(data) : data; + + if (this._exceedsLimit(data.items, fail)) { + return; + } + + after(data.items); + }.bind(this)); + } + }; + + // value in kibibytes + Viewer.prototype._exceedsLimit = function (data, fail) { + // TODO some tests don't provide data + if (!data) { + return false; + } + + // 1 unicode character = 4 bytes + // length = # of chars + // kibibyte = 1024 bytes + // # of chars * 4 / 1024 + var kb = JSON.stringify(data).length * 4 / 1024; + + if (kb > this._options.items.limit) { + fail = fail || function () {}; + fail("JSON data size exceeds " + this._options.items.limit + " KiB. Limit can be configured with items.limit Akamai.Viewer options."); + return true; + } + + return false; + }; + + Viewer.prototype._setBreakpoints = function () { + this._breakpoints = this._reduceBreakpoints(this._options.breakpoints || {}); + this._fullscreenBreakpoints = this._reduceBreakpoints(this._options.fullscreenBreakpoints || {}); + + this._bindBreakpoints(); + }; + + Viewer.prototype._reduceBreakpoints = function (breakpoints) { + var bps = []; + + for (var bp in breakpoints) { + if (breakpoints.hasOwnProperty(bp)) { + try { + bps.push(parseInt(bp, 10)); + } catch (e) { + Akamai.Util.log(e, 'error'); + } + } + } + + bps.sort(function (a, b) { + return b < a; + }); + + return bps; + }; + + Viewer.prototype._bindBreakpoints = function () { + [this._breakpoints, this._fullscreenBreakpoints].forEach(function (bps) { + // bind using the minwidth and the next breakpoint as the maxwidth + // adds a range from 0 to the first breakpoint and from the last + // breakpoint to a very large number + if (!bps.length) { + return; + } + + for (var i = -1; i < bps.length; i++) { + this._bindMatchMedia(bps[i], bps[i + 1]); + } + }.bind(this)); + }; + + // bind a callback to run after resize completes + Viewer.prototype._onResizeComplete = function (callback) { + var cbtimer; + var self = this; + window.addEventListener("resize", function () { + clearTimeout(cbtimer); + cbtimer = setTimeout(callback, 500); + }); + }; + + Viewer.prototype._bindMatchMedia = function (minWidth, maxWidth) { + minWidth = minWidth || 0; + + // large number so we can use one media query template + maxWidth = maxWidth || 1000000000; + + if (window.matchMedia) { + // bind using the minwidth and maxwidth so we get the events at the boundaries + // so we can asses which set of options applies. + var query = "(min-width: " + minWidth + "px) and (max-width: " + maxWidth + "px)"; + + // create a media list to bind to + var initList = window.matchMedia(query); + + if (initList && initList.addListener) { + // handle a match on instantiation + this._mediaMatch(initList, minWidth, true); + + // bind for later changes in whether the media query matches + initList.addListener(function (list) { + this._mediaMatch(list, minWidth); + }.bind(this)); + } + } + }; + + Viewer.prototype._mediaMatch = function (list, minWidth, ignoreDefault) { + // If there is a match for the breakpoint (we're in the bp range) + // Then set the options based on that breakpoint + // Else if there is not a match and the client width is below the + // breakpoint that's being disabled, then use the original options + if (list.matches) { + var resolvedOptions = this._resolveBreakpointOptions(minWidth); + this._updateOptions(resolvedOptions); + } + }; + + // TODO a ton of duplication with bindMatchmedia + Viewer.prototype._getCurrentMinWidth = function (bps) { + // bind using the minwidth and the next breakpoint as the maxwidth + // adds a range from 0 to the first breakpoint and from the last + // breakpoint to a very large number + for (var i = -1; i < bps.length; i++) { + minWidth = bps[i] || 0; + maxWidth = bps[i + 1] || 100000000; + + if (window.matchMedia) { + // bind using the minwidth and maxwidth so we get the events at the boundaries + // so we can asses which set of options applies. + var query = "(min-width: " + minWidth + "px) and (max-width: " + maxWidth + "px)"; + + // create a media list to bind to + var initList = window.matchMedia(query); + + if (initList && initList.matches) { + return minWidth; + } + } + } + + return false; + }; + + Viewer.prototype._resolveBreakpointOptions = function (minWidth) { + // TODO bind on the reduce callback was not working + var self = this; + var bps, bpConfig; + + if (this._fullscreen.isFullscreen()) { + bps = this._fullscreenBreakpoints; + bpConfig = self._options.fullscreenBreakpoints; + } else { + bps = this._breakpoints; + bpConfig = self._options.breakpoints; + } + + if (!minWidth) { + minWidth = self._getCurrentMinWidth(bps); + } + + // There may be no matching breakpoints when this method is called to + // resolve the current options. If that's the case then we need to default + // to the top level options + if (!bps.length) { + return self._originalOptions; + } + + return bps.reduce(function (acc, bp) { + var cloned = Akamai.Util.extend(true, {}, acc); + var ptions; + + if (bp <= minWidth) { + cloned = Akamai.Util.extend(true, cloned, bpConfig[bp]); + } + + return cloned; + }, Akamai.Util.extend(true, {}, this._originalOptions)); + }; + + Viewer._extendCarouselOptions = function (options) { + // clone the carousel options for the given breakpoint + var clonedCarousel = Akamai.Util.extend(true, {}, options.carousel || {}); + + // TODO shared code with constructor + // extend the cloned options with the breakpoint options so that all the + // child component config is attached to the carousel config (also happens + // in the constructor ) + return Akamai.Util.extend(true, clonedCarousel, { + magnifier: options.magnifier, + spin360: options.spin360, + video: options.video, + fullscreen: options.fullscreen + }); + }; + + Viewer.prototype._updateOptions = function (options) { + this._options = Akamai.Util.extend(true, this._options, options); + + // extend the carousel options for the given breakpoint + var carouselOptions = Viewer._extendCarouselOptions(options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Viewer.defaultOptions, options, "Viewer"); + + // pass the new options down to all child carousels + this._carousels.forEach(function (comp) { + comp._updateOptions(carouselOptions); + }.bind(this)); + }; + + Viewer.prototype._checkJSONUrls = function (obj) { + var hostnames = this._options.items.hostnames; + + return Akamai.Util.mapJSONVals(obj, function (val, key) { + if (key === "url" && !this._urlHostnameMatch(val)) { + throw new Error("The URL `" + val + "` does not match this page's hostname or the whitelist defined in Akamai.Viewer option `hostnames` which is:\n\n" + (hostnames.length ? hostnames.join("\n") : "No hostnames") + "\n"); + } + + return val; + }.bind(this)); + }; + + Viewer.prototype._urlHostnameMatch = function (url) { + var parser = document.createElement('a'); + parser.href = url; + + return parser.hostname === "" || parser.hostname === window.location.hostname || this._options.items.hostnames.indexOf(parser.hostname) >= 0; + }; + + /** + * Defines the global default options for all Viewers on the page + * @static + * @property {Object} breakpoints - configuration changes for child components at breakpoints (no default) + * @property {Object} fullscreenBreakpoints - configuration changes for child components at breakpoints when in fullscreen mode (no default) + * @property {Object[]} items.data - array of items from the Akamai JSON (default: undefined) + * @property {String} items.defaultTag - (default: "akamai-untagged") + * @property {String[]} items.hostnames - whitelist of URL hostnames to check for in JSON, (default: empty array) + * @property {Integer} items.limit - size limit of JSON data in kibibytes (default: 100) + * @property {String} items.renderAll - (default: false) + * @property {String[]} items.tags - Set of tags to match against the Akamai JSON data (default: undefined) + * @property {String} items.uri - URI at which to retrieve the Akamai JSON (default: undefined) + * @property {Object} carousel - child {@link Akamai.Carousel} options + * @property {Object} magnifier - child {@link Akamai.Magnifier} options + * @property {Object} spin360 - child {@link Akamai.Spin360} options + * @property {Object} video - child {@link Akamai.Video} options + * @property {Object} fullscreen - child {@link Akamai.Fullscreen} options + */ + Viewer.defaultOptions = { + breakpoints: {}, + items: { + data: undefined, + defaultTag: "akamai-untagged", + hostnames: [], + limit: 100, + renderAll: false, + tags: undefined, + uri: undefined + }, + carousel: Akamai.Carousel.defaultOptions, + magnifier: Akamai.Magnifier.defaultOptions, + spin360: Akamai.Spin360.defaultOptions, + video: Akamai.Video.defaultOptions, + fullscreen: Akamai.Fullscreen.defaultOptions + }; + + Viewer._tagSplit = function (data, options) { + var items = options; + + // set all items without a tag to the default + data = data.map(function (item) { + item.tags = item.tags && item.tags.length ? item.tags : [items.defaultTag]; + return item; + }); + + // if the tags option was set, filter items out that don't match + if (items.tags) { + data = items.data.filter(function (item) { + return item.tags.reduce(function (acc, tag) { + return acc || items.tags.indexOf(tag) >= 0; + }, false); + }); + } + + // from the filtered get the first (may be default tag) + var def = data[0].tags[0]; + + // create a mapping from tags to items in the set + var tagMapping = data.reduce(function (acc, item) { + (item.tags || []).forEach(function (tag) { + acc[tag] = acc[tag] || []; + acc[tag].push(item); + }); + + return acc; + }, {}); + + // set the first (default) object to the first tag + tagMapping[Viewer._firstRenderTag] = tagMapping[def]; + + return tagMapping; + }; + + Viewer._firstRenderTag = "akamai-first-render"; + Viewer._tagAttr = Viewer._dataAttr + "-tag"; + + Viewer._renderTag = function (data, options) { + // get the first item and it's tag + // NOTE this assumes that the data has been normalized to have a default tag + var tag = data[0].tags[0]; + + // TODO shoestring doesn't treat html correctly unless the leading `<` has + // no whitepsace in front of it: + // shoestring/issues/94 + return Akamai.Util.trim("\n\t\t\t
\n\t\t\t\t" + Akamai.Carousel.render(data, options) + "\n\t\t\t
\n\t\t"); + }; + + /** + * Takes standard options including an `items` attribute and renders HTML + * that conforms to the component expectations + * @method + * @static + * @params {Object[]} json - Akamai JSON data + * @params {Object} options - options object with an `items` attribute, see default options + * @params {Boolean} options.items - configuration for how the items should be + * rendered, see {@link Akamai.Viewer.defaultOptions} default options items sub-configuration + * @returns { String } + */ + Viewer.render = function (json, options, unwrapped) { + // NOTE we do not extend the default options here because it has no bearing + // on the rendering of the Viewer markup or child markup. This is contrast + // to the carousel which does extend the passed options with defaults so the + // markup can make use of them + var data = Viewer._tagSplit(json, options.items); + var carouselOptions = Viewer._extendCarouselOptions(options); + + var childMarkup; + + if (options.items.renderAll) { + childMarkup = data.map(function (acc, datum) { + return Viewer._renderTag(datum, carouselOptions); + }).join(" "); + } else { + childMarkup = Viewer._renderTag(data[Viewer._firstRenderTag], carouselOptions); + } + + var wrapped = "\n\t\t\t
\n\t\t\t\t" + childMarkup + "\n\t\t\t
\n\t\t"; + + return unwrapped ? childMarkup : wrapped; + }; + + /** + * Accessor for child carousel components. + * @method + * @returns { Akamai.Carousel[] } + */ + Viewer.prototype.getCarousels = function () { + return this._carousels; + }; + + /** + * Set the visible carousel based on the tag. Note the method activity does + * not complete until all the images from the relevant viewer have loaded, use + * the `akamai-viewer-switch-tag` event + * @method + * @fires Akamai.Viewer#akamai-viewer-switch-tag + * @params {String} tag - The tag corresponding to the desired carousel + * @returns {undefined} + */ + Viewer.prototype.switchTag = function (tag) { + var tags = this.getTags(); + + // if the passed tag isn't in the data set return early and log an error in + // the console + if (tags.indexOf(tag) == -1) { + Akamai.Util.log("tag: " + tag + " is not present in the data for this viewer", 'error'); + return; + } + + var selector = "[" + this.constructor._tagAttr + "=\"" + tag + "\"]"; + + // try to find an existing element with the tag + var $taggedViewer = this._$el.find(selector); + + // if there's no element append the newly rendered tag markup + if (!$taggedViewer.length) { + // make sure the config takes into account the current breakpoint + var options = this._resolveBreakpointOptions(); + var carouselOptions = Viewer._extendCarouselOptions(options); + + // create the markup that will be inserted + var $markup = $(this.constructor._renderTag(this._tagMapping[tag], carouselOptions, true)); + var $imgs = $markup.find("img"); + var imgCount = $imgs.length; + var loaded = 0; + + // hide the new carousel initially + $markup.css("display", "none"); + + $imgs.bind("load", function () { + if (++loaded !== imgCount) { + return; + } + + // TODO namespace? + $imgs.unbind("load"); + + var carousels = Akamai.Carousel.createMany($markup[0], carouselOptions); + + carousels.forEach(function (c) { + // TODO figure out why the carousel constructor doesn't apply the + // active index class we want on instantiation, likely due to the fact + // that the carousel is hidden so the "getIndex" calc is broken + // mark the carousel item as active + c.goto(c.getIndex()); + }); + + // create and store the new carousels (should be one) + this._carousels = this._carousels.concat(carousels); + this._showViewer($markup, carousels); + }.bind(this)); + + // append the new markup to the existing viewer + this._$el.append($markup); + } else { + this._showViewer($taggedViewer); + } + }; + + // TODO should be handled in CSS + Viewer.prototype._showViewer = function ($viewer, carousels) { + this._$el.find("[" + this.constructor._tagAttr + "]").css("display", "none").removeClass("focused"); + + $viewer.css("display", "block").addClass("focused"); + + if (carousels) { + carousels.map(function (c) { + c.refresh(); + }); + } + + // TODO the placement here seems arbitrary, probably belongs in `_showViewer` + // TODO also sucks to be so tightly coupled + this._fullscreen.addButton(); + + this._trigger("switch-tag"); + }; + + /** + * Accessor for tags derived from Akamai JSON data + * @method + * @returns { String[] } + */ + Viewer.prototype.getTags = function () { + if (this._tags) { + return this._tags; + }; + + var tags = []; + + // map and store all the carousel tags + for (var tag in this._tagMapping) { + if (this._tagMapping.hasOwnProperty(tag) && tag !== Viewer._firstRenderTag) { + // otherwise grab the list of tags + tags.push(tag); + } + } + + return this._tags = tags; + }; + + /** + * Triggered when the viewer switches tag views. This includes waiting for + * images to load for carousels dedicated to previously unviewed tags. + * {@link Akamai.Viewer#switchTag}. + * + * @event Akamai.Viewer#akamai-viewer-switch-tag + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Viewer}. + * + * @event Akamai.Viewer#akamai-viewer-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Viewer = Viewer; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); + +(function (exports, $) { + $.fn.akamaiViewer = function (options) { + this.each(function (i, element) { + new Akamai.Viewer(element, options); + }); + }; +})(typeof exports === 'undefined' ? window : exports, this.jQuery); +//# sourceMappingURL=akamai-viewer.unmin.js.map diff --git a/app/html/js/akamai-viewer.unmin.js.map b/app/html/js/akamai-viewer.unmin.js.map new file mode 100644 index 0000000..d7f1909 --- /dev/null +++ b/app/html/js/akamai-viewer.unmin.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["akamai-viewer.es2015"],"names":["global","time","Date","now","desiredFrames","millisecondsPerSecond","running","counter","core","effect","Animate","requestAnimationFrame","requestFrame","webkitRequestAnimationFrame","mozRequestAnimationFrame","oRequestAnimationFrame","isNative","test","toString","callback","root","TARGET_FPS","requests","requestCount","rafHandle","intervalHandle","lastActive","callbackHandle","setInterval","currentRequests","key","hasOwnProperty","clearInterval","stop","id","cleared","isRunning","start","stepCallback","verifyCallback","completedCallback","duration","easingMethod","lastFrame","percent","dropCounter","document","body","newRunning","usedId","step","virtual","render","droppedFrames","Math","round","j","min","value","Scroller","options","__callback","scrollingX","scrollingY","animating","animationDuration","bouncing","locking","paging","snapping","zooming","minZoom","maxZoom","easeOutCubic","pos","pow","easeInOutCubic","members","__isSingleTouch","__isTracking","__isGesturing","__isDragging","__isDecelerating","__isAnimating","__clientLeft","__clientTop","__clientWidth","__clientHeight","__contentWidth","__contentHeight","__snapWidth","__snapHeight","__refreshHeight","__refreshActive","__refreshActivate","__refreshDeactivate","__refreshStart","__zoomLevel","__scrollLeft","__scrollTop","__maxScrollLeft","__maxScrollTop","__scheduledLeft","__scheduledTop","__scheduledZoom","__lastTouchLeft","__lastTouchTop","__lastTouchMove","__positions","__minDecelerationScrollLeft","__minDecelerationScrollTop","__maxDecelerationScrollLeft","__maxDecelerationScrollTop","__decelerationVelocityX","__decelerationVelocityY","setDimensions","clientWidth","clientHeight","contentWidth","contentHeight","self","__computeScrollMax","scrollTo","setPosition","left","top","setSnapSize","width","height","activatePullToRefresh","activateCallback","deactivateCallback","startCallback","finishPullToRefresh","getValues","zoom","getScrollMax","zoomTo","level","animate","originLeft","originTop","Error","oldLevel","max","__publish","zoomBy","factor","scrollBy","startLeft","startTop","doMouseZoom","wheelDelta","timeStamp","pageX","pageY","change","doTouchStart","touches","length","valueOf","currentTouchLeft","currentTouchTop","isSingleTouch","abs","__initialTouchLeft","__initialTouchTop","__zoomLevelStart","__lastScale","__enableScrollX","__enableScrollY","doTouchMove","scale","positions","moveX","moveY","scrollLeft","scrollTop","currentTouchLeftRel","currentTouchTopRel","maxScrollLeft","maxScrollTop","splice","push","minimumTrackingForScroll","minimumTrackingForDrag","distanceX","distanceY","doTouchEnd","endPos","startPos","i","timeOffset","movedLeft","movedTop","minVelocityToStartDeceleration","__startDeceleration","wasAnimating","oldLeft","oldTop","oldZoom","diffLeft","diffTop","diffZoom","verify","completed","renderedFramesPerSecond","animationId","wasFinished","zoomLevel","floor","ceil","__stepThroughDeceleration","minVelocityToKeepDecelerating","scrollLeftFixed","scrollTopFixed","frictionFactor","scrollOutsideX","scrollOutsideY","penetrationDeceleration","penetrationAcceleration","prototype","factory","define","amd","module","exports","win","window","doc","shoestring","prim","sec","pType","ret","sel","Shoestring","call","ready","constructor","indexOf","dfrag","createElement","innerHTML","children","each","removeChild","find","querySelectorAll","Object","NodeList","Array","selector","merge","reverse","fn","extend","first","second","len","ajax","url","params","req","XMLHttpRequest","settings","data","encodeURIComponent","method","open","async","setRequestHeader","headers","onreadystatechange","readyState","res","responseText","replace","status","cancel","match","RegExp","$1","error","success","send","get","load","args","arguments","intCB","html","apply","post","collection","val","il","inArray","needle","haystack","isin","readyQueue","runReady","shift","attachEvent","addEventListener","is","parents","check","_checkElements","parent","e","needles","name","undefined","shoestringData","removeData","$","addClass","className","classes","split","add","after","fragment","nodeType","jl","insertEl","cloneNode","parentNode","insertBefore","nextSibling","append","appendChild","appendTo","attr","nameStr","setAttribute","getAttribute","before","childs","clone","closest","element","$self","parentElement","cssExceptions","convertPropertyName","str","character","toUpperCase","_getStyle","property","getComputedStyle","getPropertyValue","vendorPrefixes","convert","k","_setStyle","convertedProperty","style","css","eq","index","filter","wsel","context","createDocumentFragment","finds","concat","elements","_dimension","set","offsetName","letter","h","l","outerHTML","pile","_getIndex","result","item","documentElement","childNodes","insertAfter","last","next","found","not","offset","offsetTop","offsetLeft","curr","prepend","firstChild","prependTo","prev","prevAll","$previous","propFix","contenteditable","readonly","tabindex","prop","removeAttr","removeAttribute","removeClass","cname","newClassName","regex","remove","removeProp","replaceWith","replaceChild","inputTypes","inputTypeTest","join","serialize","type","checked","nodeName","selectedIndex","nodeValue","siblings","sibs","el","getText","elem","node","textContent","text","tagName","optionSet","option","values","newIndex","selected","wrapInner","inH","initEventCache","evt","events","loop","addToEventCache","eventInfo","obj","isCustomEvent","callfunc","originalCallback","namespace","customEventLoop","bind","evts","encasedCallback","triggeredElement","_namespace","returnTrue","isDefaultPrevented","originalPreventDefault","preventDefault","preventDefaultConstructor","returnValue","target","srcElement","stopPropagation","cancelBubble","_args","domEventCallback","customEventCallback","oEl","originalEvent","ssEventTrigger","on","unbind","event","unbindAll","bound","matched","removeEventListener","evtKey","off","one","thisevt","cbs","$t","triggerHandler","createEvent","bindings","initEvent","unshift","trigger","click","dispatchEvent","jQuery","Akamai","componentNamespace","$window","$doc","ns","Function","Tau","startIndex","reducedStepSize","$element","$initial","$loading","frames","parseInt","stepSize","autoRotateStartDelay","autoplay","delay","mouseMoveBinding","rotateEvent","touchMoveBinding","path","Path","$render","canvas","getContext","canvasCtx","clearTimeout","canvasResizeTimeout","setTimeout","renderCanvas","controls","play","right","createControls","createImages","enabled","autostartTimeout","autoRotate","autoRotateTraversalTime","verticalScrollRatio","maxVelocity","$controls","controlAnchorMarkup","arrows","onControlDown","onControlUp","$link","stopAutoRotate","autoInterval","delta","goto","$next","normalizedIndex","imageCount","$images","tauImageLoaded","showLoading","$current","$img","img","naturalWidth","naturalHeight","parentWidth","calcHeight","complete","drawImage","src","$new","boundImageLoaded","loadedCount","markImageLoaded","template","imgs","imageLoaded","initTriggered","hideLoading","initialized","track","autoRotateDelay","interval","point","tracking","release","reset","cursorGrab","defaultThreshold","rotateThreshold","sensitivity","getPoint","downX","x","downY","y","downIndex","slow","isSufficient","clearSlowInterval","rotate","velocity","decelVal","decelTimeStep","slowInterval","decel","sign","cursorRelease","touch","clientX","clientY","deltaX","deltaY","record","getTime","prevPoint","prevPrevPoint","distance","prevTime","prevPrevTime","timeStep","Util","preflight","_$el","_dataAttr","_options","defaultOptions","_originalOptions","mapEvents","component","init","constr","existing","_trigger","toLowerCase","createMany","mapping","_componentEventMapping","mappedTo","to","$el","mapEvent","from","$comps","objects","propRequired","msg","componentName","fullEvent","camelToKabob","string","m","defaults","dataAttrOptions","cloneDefaults","getDataAttrOptions","finalOptions","setDataAttrOptions","prefix","dashedProp","currentDataProp","subDefaults","newPrefix","subConfig","coerceAttrVal","setDataAttrOptionsPrefix","subOptions","parseFloat","deep","copy","copyIsArray","isPlainObject","proto","Ctor","rtrim","trim","map","array","reduce","acc","log","console","mapJSONVals","o","escapeJSONVals","escapeHTML","entityMap","String","fromEntityMap","s","Advanceable","extension","getIndex","previous","Sourceable","_srcArray","includeW","policy","thumbnail","widthParam","widths","w","_srcset","_fallbackSrc","_largestSrc","pop","extendStatic","raf","toss","tossing","sLeft","sTop","op","easing","finished","endLeft","endTop","startTime","endTime","run","curTime","tossingElems","state","t","b","c","d","pluginName","optionsOrMethod","pluginArgs","scrollListening","testProp","snapSupported","CSS","supports","itemsAtOffset","$childNodes","containWidth","activeItems","outerWidth","$elem","outerHeight","snapEvent","activeSlides","nothrow","overthrow","innerResult","addNextPrev","autoTimeout","$slider","enhancedClass","$itemsContain","$items","numItems","$nav","navSelectedClass","useDeepLinking","slice","itemWidth","snapScroll","updateWidths","$navInner","getAutoplayInterval","autoTiming","parseError","itemsContainStyle","itemStyle","sliderWidth","computed","itemLeftMargin","itemRightMargin","outerItemWidth","iPercentWidth","iPercentRightMargin","iPercentLeftMargin","outerPercentWidth","percentAsWidth","percentAsRightMargin","percentAsLeftMargin","$nextprev","$nextprevContain","slideID","arrowNavigate","$slide","history","replaceState","title","keyCode","stopImmediatePropagation","snapScrollCancelled","isTouched","currScroll","roundedScroll","maxScroll","startSlide","afterResize","snapStay","forward","scrollNav","lastActiveItem","activeItem","force","currTime","navWidth","navHeight","activeIndex","activeChild","thumbX","thumbY","scrollStop","scrolling","lastScroll","timeout","enlarge","button","hoverZoomWithoutClick","flyout","placement","magnification","internalResult","testimg","srcsetSupported","srcsetSizesSupported","$anchor","initialImg","targetImg","imgOriginalSrc","srcset","imgOriginalSizes","imgZoomSrc","href","initialText","innerText","zoomClass","delayClass","$contain","$zoomContain","$parentPane","$zoomParent","zoomed","lockZoomClass","standardToggleZoom","updatePlacement","$flyout","positionFlyout","flyoutSide","loupe","disable","disabled","opts","hoverEnabled","image","sizes","toggleImgSrc","addLoader","toggleZoomState","zoomimg","Image","onload","imgZoomWidth","scrollToCenter","pw","ph","offsetWidth","offsetHeight","toggleLockZoom","lockedZoom","focus","toggleImgZoom","forceInlineMode","oldO","resetPlacement","trackingOn","trackingTimer","mouseEntered","touchStarted","lastMouseMove","startTrackingDelay","scrollWithMouse","stopTrackingDelay","ev","getBoundingClientRect","mLeft","mTop","containHeight","containScrollWidth","containScrollHeight","zoomContainWidth","zoomContainHeight","widthFactor","heightFactor","defaultConfig","Spin360","_comp","_updateOptions","_renderImg","images","json","alt","urls","pause","getElement","States","Playing","Paused","getState","MagnifierImpl","updateOptions","zoomIn","zoomOut","isMagnified","toggleZoom","dataAttr","fallbackSrc","largestSrc","srcSet","ex","MagnifierImplEnlarge","create","buttonText","ClickTracker","handler","onDown","isDown","down","timestamp","onMove","isMoved","onUp","up","Infinity","MagnifierImplScroller","_container","_content","_imgCast","_zoomInBtn","_zoomOutBtn","_magnification","scroller","_renderer","rect","clientLeft","clientTop","_installEventHandlers","_updateButtonStates","_onResize","_zoomBy","incrementalZoomFactor","cyclicZoom","zoomInBtnText","zoomOutBtnText","Number","animateZoom","_onZoom","oldMagnification","toFixed","docStyle","engine","opera","navigator","cpuClass","vendorPrefix","trident","gecko","webkit","presto","helperElem","undef","perspectiveProperty","transformProperty","marginLeft","marginTop","clickTracker","changedTouches","Magnifier","mode","MODE_ANIMATED_ZOOM","_impl","MODE_HOVER_ZOOM","impl","in","out","toggle","VideoImpl","VideoImplPristine","Video","poster","mime","attrs","muted","VideoImplIm","_validateOptions","isArray","some","size","isNaN","viewPortWidth","innerWidth","joiner","_$videoElement","_videoElement","_unsupportedAPI","_unsupported","_setState","paused","_createPlayButton","MODE_IM","MODE_PRISTINE","_state","seek","newTime","currentTime","setLoop","videoPlayButton","videoWrapper","video","insertAdjacentHTML","classList","Carousel","_spins","spin360","_magnifiers","magnifier","_videos","snapper","_setAspectRatio","_bindAspectAttributes","slideshow","autostart","startSlideshow","_activeItemAttributes","_handleAutoplayables","_stopSlideshowEvents","stopSlideshow","isParentActive","comp","activeItemAttr","forEach","_carouselWasPlaying","update","aspectratio","renderMapping","_typeMapping","_uniqueItemId","_renderItem","mappedType","_renderThumbnailAnchor","thumbUrl","altText","_thumbUrl","_renderThumbnails","_setAspectAttributes","orientationAttr","loadBound","$item","$loadable","loadBinding","carouselOptions","templateAttrs","getSpin360s","getMagnifiers","getVideos","_itemAttr","_activeItemAttr","attrAllItems","attrActiveItem","_slideshowTimer","refresh","Fullscreen","_init","_$fullscreen","addButton","_fullscreenApiKeys","_keys","native","_useNativeApi","_fullscreenFallbackEnabled","_$placeholder","_isFullscreen","addEvents","btn","btnContainer","enterBtn","exitBtn","active","fallback","_$widthAdjust","_$buttonContainer","$target","enter","exit","onchange","_exit","code","which","_keyLookup","keys","_maximizePlacement","_restorePlacement","_adjustWidth","widthSmall","heightSmall","viewportWidth","screen","viewportHeight","innerHeight","widthBig","_revertWidth","Viewer","_carousels","isFullscreen","_updateSizesOptions","_onResizeComplete","carousel","_extendCarouselOptions","_withData","_checkJSONUrls","items","_tagMapping","_tagSplit","_fullscreen","fullscreen","_resolveBreakpointOptions","_setBreakpoints","localUpdateOnly","elWidth","fail","uri","_exceedsLimit","JSON","parse","kb","stringify","limit","_breakpoints","_reduceBreakpoints","breakpoints","_fullscreenBreakpoints","fullscreenBreakpoints","_bindBreakpoints","bps","bp","sort","a","_bindMatchMedia","cbtimer","minWidth","maxWidth","matchMedia","query","initList","addListener","_mediaMatch","list","ignoreDefault","matches","resolvedOptions","_getCurrentMinWidth","bpConfig","cloned","ptions","clonedCarousel","hostnames","_urlHostnameMatch","parser","hostname","location","defaultTag","renderAll","tags","tag","def","tagMapping","_firstRenderTag","_tagAttr","_renderTag","unwrapped","childMarkup","datum","wrapped","getCarousels","switchTag","getTags","$taggedViewer","$markup","$imgs","imgCount","loaded","carousels","_showViewer","$viewer","_tags","akamaiViewer"],"mappings":"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","file":"akamai-viewer.unmin.js","sourcesContent":["/*! Copyright 2019, Akamai Technologies, Inc. All Rights Reserved. akamai-viewer-0.7.2 *//**\n * Generic animation class with support for dropped frames both optional easing and duration.\n *\n * Optional duration is useful when the lifetime is defined by another condition than time\n * e.g. speed of an animating object, etc.\n *\n * Dropped frame logic allows to keep using the same updater logic independent from the actual\n * rendering. This eases a lot of cases where it might be pretty complex to break down a state\n * based on the pure time difference.\n */\n(function(global) {\n\tvar time = Date.now || function() {\n\t\treturn +new Date();\n\t};\n\tvar desiredFrames = 60;\n\tvar millisecondsPerSecond = 1000;\n\tvar running = {};\n\tvar counter = 1;\n\n\t// Create namespaces\n\tif (!global.core) {\n\t\tglobal.core = { effect : {} };\n\n\t} else if (!core.effect) {\n\t\tcore.effect = {};\n\t}\n\n\tcore.effect.Animate = {\n\n\t\t/**\n\t\t * A requestAnimationFrame wrapper / polyfill.\n\t\t *\n\t\t * @param callback {Function} The callback to be invoked before the next repaint.\n\t\t * @param root {HTMLElement} The root element for the repaint\n\t\t */\n\t\trequestAnimationFrame: (function() {\n\n\t\t\t// Check for request animation Frame support\n\t\t\tvar requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;\n\t\t\tvar isNative = !!requestFrame;\n\n\t\t\tif (requestFrame && !/requestAnimationFrame\\(\\)\\s*\\{\\s*\\[native code\\]\\s*\\}/i.test(requestFrame.toString())) {\n\t\t\t\tisNative = false;\n\t\t\t}\n\n\t\t\tif (isNative) {\n\t\t\t\treturn function(callback, root) {\n\t\t\t\t\trequestFrame(callback, root)\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tvar TARGET_FPS = 60;\n\t\t\tvar requests = {};\n\t\t\tvar requestCount = 0;\n\t\t\tvar rafHandle = 1;\n\t\t\tvar intervalHandle = null;\n\t\t\tvar lastActive = +new Date();\n\n\t\t\treturn function(callback, root) {\n\t\t\t\tvar callbackHandle = rafHandle++;\n\n\t\t\t\t// Store callback\n\t\t\t\trequests[callbackHandle] = callback;\n\t\t\t\trequestCount++;\n\n\t\t\t\t// Create timeout at first request\n\t\t\t\tif (intervalHandle === null) {\n\n\t\t\t\t\tintervalHandle = setInterval(function() {\n\n\t\t\t\t\t\tvar time = +new Date();\n\t\t\t\t\t\tvar currentRequests = requests;\n\n\t\t\t\t\t\t// Reset data structure before executing callbacks\n\t\t\t\t\t\trequests = {};\n\t\t\t\t\t\trequestCount = 0;\n\n\t\t\t\t\t\tfor(var key in currentRequests) {\n\t\t\t\t\t\t\tif (currentRequests.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tcurrentRequests[key](time);\n\t\t\t\t\t\t\t\tlastActive = time;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Disable the timeout when nothing happens for a certain\n\t\t\t\t\t\t// period of time\n\t\t\t\t\t\tif (time - lastActive > 2500) {\n\t\t\t\t\t\t\tclearInterval(intervalHandle);\n\t\t\t\t\t\t\tintervalHandle = null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}, 1000 / TARGET_FPS);\n\t\t\t\t}\n\n\t\t\t\treturn callbackHandle;\n\t\t\t};\n\n\t\t})(),\n\n\n\t\t/**\n\t\t * Stops the given animation.\n\t\t *\n\t\t * @param id {Integer} Unique animation ID\n\t\t * @return {Boolean} Whether the animation was stopped (aka, was running before)\n\t\t */\n\t\tstop: function(id) {\n\t\t\tvar cleared = running[id] != null;\n\t\t\tif (cleared) {\n\t\t\t\trunning[id] = null;\n\t\t\t}\n\n\t\t\treturn cleared;\n\t\t},\n\n\n\t\t/**\n\t\t * Whether the given animation is still running.\n\t\t *\n\t\t * @param id {Integer} Unique animation ID\n\t\t * @return {Boolean} Whether the animation is still running\n\t\t */\n\t\tisRunning: function(id) {\n\t\t\treturn running[id] != null;\n\t\t},\n\n\n\t\t/**\n\t\t * Start the animation.\n\t\t *\n\t\t * @param stepCallback {Function} Pointer to function which is executed on every step.\n\t\t * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`\n\t\t * @param verifyCallback {Function} Executed before every animation step.\n\t\t * Signature of the method should be `function() { return continueWithAnimation; }`\n\t\t * @param completedCallback {Function}\n\t\t * Signature of the method should be `function(droppedFrames, finishedAnimation) {}`\n\t\t * @param duration {Integer} Milliseconds to run the animation\n\t\t * @param easingMethod {Function} Pointer to easing function\n\t\t * Signature of the method should be `function(percent) { return modifiedValue; }`\n\t\t * @param root {Element ? document.body} Render root, when available. Used for internal\n\t\t * usage of requestAnimationFrame.\n\t\t * @return {Integer} Identifier of animation. Can be used to stop it any time.\n\t\t */\n\t\tstart: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {\n\n\t\t\tvar start = time();\n\t\t\tvar lastFrame = start;\n\t\t\tvar percent = 0;\n\t\t\tvar dropCounter = 0;\n\t\t\tvar id = counter++;\n\n\t\t\tif (!root) {\n\t\t\t\troot = document.body;\n\t\t\t}\n\n\t\t\t// Compacting running db automatically every few new animations\n\t\t\tif (id % 20 === 0) {\n\t\t\t\tvar newRunning = {};\n\t\t\t\tfor (var usedId in running) {\n\t\t\t\t\tnewRunning[usedId] = true;\n\t\t\t\t}\n\t\t\t\trunning = newRunning;\n\t\t\t}\n\n\t\t\t// This is the internal step method which is called every few milliseconds\n\t\t\tvar step = function(virtual) {\n\n\t\t\t\t// Normalize virtual value\n\t\t\t\tvar render = virtual !== true;\n\n\t\t\t\t// Get current time\n\t\t\t\tvar now = time();\n\n\t\t\t\t// Verification is executed before next animation step\n\t\t\t\tif (!running[id] || (verifyCallback && !verifyCallback(id))) {\n\n\t\t\t\t\trunning[id] = null;\n\t\t\t\t\tcompletedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);\n\t\t\t\t\treturn;\n\n\t\t\t\t}\n\n\t\t\t\t// For the current rendering to apply let's update omitted steps in memory.\n\t\t\t\t// This is important to bring internal state variables up-to-date with progress in time.\n\t\t\t\tif (render) {\n\n\t\t\t\t\tvar droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;\n\t\t\t\t\tfor (var j = 0; j < Math.min(droppedFrames, 4); j++) {\n\t\t\t\t\t\tstep(true);\n\t\t\t\t\t\tdropCounter++;\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t\t// Compute percent value\n\t\t\t\tif (duration) {\n\t\t\t\t\tpercent = (now - start) / duration;\n\t\t\t\t\tif (percent > 1) {\n\t\t\t\t\t\tpercent = 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Execute step callback, then...\n\t\t\t\tvar value = easingMethod ? easingMethod(percent) : percent;\n\t\t\t\tif ((stepCallback(value, now, render) === false || percent === 1) && render) {\n\t\t\t\t\trunning[id] = null;\n\t\t\t\t\tcompletedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null);\n\t\t\t\t} else if (render) {\n\t\t\t\t\tlastFrame = now;\n\t\t\t\t\tcore.effect.Animate.requestAnimationFrame(step, root);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Mark as running\n\t\t\trunning[id] = true;\n\n\t\t\t// Init first step\n\t\t\tcore.effect.Animate.requestAnimationFrame(step, root);\n\n\t\t\t// Return unique animation ID\n\t\t\treturn id;\n\t\t}\n\t};\n})(this);\n\n\nvar Scroller;\n\n(function() {\n\t\n\t/**\n\t * A pure logic 'component' for 'virtual' scrolling/zooming.\n\t */\n\tScroller = function(callback, options) {\n\n\t\tthis.__callback = callback;\n\n\t\tthis.options = {\n\t\t\t\n\t\t\t/** Enable scrolling on x-axis */\n\t\t\tscrollingX: true,\n\n\t\t\t/** Enable scrolling on y-axis */\n\t\t\tscrollingY: true,\n\n\t\t\t/** Enable animations for deceleration, snap back, zooming and scrolling */\n\t\t\tanimating: true,\n\n\t\t\t/** duration for animations triggered by scrollTo/zoomTo */\n\t\t\tanimationDuration: 250,\n\n\t\t\t/** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */\n\t\t\tbouncing: true,\n\n\t\t\t/** Enable locking to the main axis if user moves only slightly on one of them at start */\n\t\t\tlocking: true,\n\n\t\t\t/** Enable pagination mode (switching between full page content panes) */\n\t\t\tpaging: false,\n\n\t\t\t/** Enable snapping of content to a configured pixel grid */\n\t\t\tsnapping: false,\n\n\t\t\t/** Enable zooming of content via API, fingers and mouse wheel */\n\t\t\tzooming: false,\n\n\t\t\t/** Minimum zoom level */\n\t\t\tminZoom: 0.5,\n\n\t\t\t/** Maximum zoom level */\n\t\t\tmaxZoom: 3\n\t\t\t\n\t\t};\n\n\t\tfor (var key in options) {\n\t\t\tthis.options[key] = options[key];\n\t\t}\n\n\t};\n\t\n\t\n\t// Easing Equations (c) 2003 Robert Penner, all rights reserved.\n\t// Open source under the BSD License.\n\t\n\t/**\n\t * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)\n\t**/\n\tvar easeOutCubic = function(pos) {\n\t\treturn (Math.pow((pos - 1), 3) + 1);\n\t};\n\n\t/**\n\t * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)\n\t**/\n\tvar easeInOutCubic = function(pos) {\n\t\tif ((pos /= 0.5) < 1) {\n\t\t\treturn 0.5 * Math.pow(pos, 3);\n\t\t}\n\n\t\treturn 0.5 * (Math.pow((pos - 2), 3) + 2);\n\t};\n\t\n\t\n\tvar members = {\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tINTERNAL FIELDS :: STATUS\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/** {Boolean} Whether only a single finger is used in touch handling */\n\t\t__isSingleTouch: false,\n\n\t\t/** {Boolean} Whether a touch event sequence is in progress */\n\t\t__isTracking: false,\n\n\t\t/**\n\t\t * {Boolean} Whether a gesture zoom/rotate event is in progress. Activates when\n\t\t * a gesturestart event happens. This has higher priority than dragging.\n\t\t */\n\t\t__isGesturing: false,\n\n\t\t/**\n\t\t * {Boolean} Whether the user has moved by such a distance that we have enabled\n\t\t * dragging mode. Hint: It's only enabled after some pixels of movement to\n\t\t * not interrupt with clicks etc.\n\t\t */\n\t\t__isDragging: false,\n\n\t\t/**\n\t\t * {Boolean} Not touching and dragging anymore, and smoothly animating the\n\t\t * touch sequence using deceleration.\n\t\t */\n\t\t__isDecelerating: false,\n\n\t\t/**\n\t\t * {Boolean} Smoothly animating the currently configured change\n\t\t */\n\t\t__isAnimating: false,\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tINTERNAL FIELDS :: DIMENSIONS\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/** {Integer} Available outer left position (from document perspective) */\n\t\t__clientLeft: 0,\n\n\t\t/** {Integer} Available outer top position (from document perspective) */\n\t\t__clientTop: 0,\n\n\t\t/** {Integer} Available outer width */\n\t\t__clientWidth: 0,\n\n\t\t/** {Integer} Available outer height */\n\t\t__clientHeight: 0,\n\n\t\t/** {Integer} Outer width of content */\n\t\t__contentWidth: 0,\n\n\t\t/** {Integer} Outer height of content */\n\t\t__contentHeight: 0,\n\n\t\t/** {Integer} Snapping width for content */\n\t\t__snapWidth: 100,\n\n\t\t/** {Integer} Snapping height for content */\n\t\t__snapHeight: 100,\n\n\t\t/** {Integer} Height to assign to refresh area */\n\t\t__refreshHeight: null,\n\t\t\n\t\t/** {Boolean} Whether the refresh process is enabled when the event is released now */\n\t\t__refreshActive: false,\n\t\t\n\t\t/** {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */\n\t\t__refreshActivate: null,\n\n\t\t/** {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */\n\t\t__refreshDeactivate: null,\n\t\t\n\t\t/** {Function} Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */\n\t\t__refreshStart: null,\n\n\t\t/** {Number} Zoom level */\n\t\t__zoomLevel: 1,\n\n\t\t/** {Number} Scroll position on x-axis */\n\t\t__scrollLeft: 0,\n\n\t\t/** {Number} Scroll position on y-axis */\n\t\t__scrollTop: 0,\n\n\t\t/** {Integer} Maximum allowed scroll position on x-axis */\n\t\t__maxScrollLeft: 0,\n\n\t\t/** {Integer} Maximum allowed scroll position on y-axis */\n\t\t__maxScrollTop: 0,\n\n\t\t/* {Number} Scheduled left position (final position when animating) */\n\t\t__scheduledLeft: 0,\n\n\t\t/* {Number} Scheduled top position (final position when animating) */\n\t\t__scheduledTop: 0,\n\n\t\t/* {Number} Scheduled zoom level (final scale when animating) */\n\t\t__scheduledZoom: 0,\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tINTERNAL FIELDS :: LAST POSITIONS\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/** {Number} Left position of finger at start */\n\t\t__lastTouchLeft: null,\n\n\t\t/** {Number} Top position of finger at start */\n\t\t__lastTouchTop: null,\n\n\t\t/** {Date} Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */\n\t\t__lastTouchMove: null,\n\t\t\n\t\t/** {Array} List of positions, uses three indexes for each state: left, top, timestamp */\n\t\t__positions: null,\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tINTERNAL FIELDS :: DECELERATION SUPPORT\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/** {Integer} Minimum left scroll position during deceleration */\n\t\t__minDecelerationScrollLeft: null,\n\n\t\t/** {Integer} Minimum top scroll position during deceleration */\n\t\t__minDecelerationScrollTop: null,\n\n\t\t/** {Integer} Maximum left scroll position during deceleration */\n\t\t__maxDecelerationScrollLeft: null,\n\n\t\t/** {Integer} Maximum top scroll position during deceleration */\n\t\t__maxDecelerationScrollTop: null,\n\n\t\t/** {Number} Current factor to modify horizontal scroll position with on every step */\n\t\t__decelerationVelocityX: null,\n\n\t\t/** {Number} Current factor to modify vertical scroll position with on every step */\n\t\t__decelerationVelocityY: null,\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tPUBLIC API\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/**\n\t\t * Configures the dimensions of the client (outer) and content (inner) elements.\n\t\t * Requires the available space for the outer element and the outer size of the inner element.\n\t\t * All values which are falsy (null or zero etc.) are ignored and the old value is kept.\n\t\t *\n\t\t * @param clientWidth {Integer ? null} Inner width of outer element\n\t\t * @param clientHeight {Integer ? null} Inner height of outer element\n\t\t * @param contentWidth {Integer ? null} Outer width of inner element\n\t\t * @param contentHeight {Integer ? null} Outer height of inner element\n\t\t */\n\t\tsetDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) {\n\n\t\t\tvar self = this;\n\n\t\t\t// Only update values which are defined\n\t\t\tif (clientWidth) {\n\t\t\t\tself.__clientWidth = clientWidth;\n\t\t\t}\n\n\t\t\tif (clientHeight) {\n\t\t\t\tself.__clientHeight = clientHeight;\n\t\t\t}\n\n\t\t\tif (contentWidth) {\n\t\t\t\tself.__contentWidth = contentWidth;\n\t\t\t}\n\n\t\t\tif (contentHeight) {\n\t\t\t\tself.__contentHeight = contentHeight;\n\t\t\t}\n\n\t\t\t// Refresh maximums\n\t\t\tself.__computeScrollMax();\n\n\t\t\t// Refresh scroll position\n\t\t\tself.scrollTo(self.__scrollLeft, self.__scrollTop, true);\n\t\t\t\n\t\t},\n\n\n\t\t/**\n\t\t * Sets the client coordinates in relation to the document.\n\t\t *\n\t\t * @param left {Integer ? 0} Left position of outer element\n\t\t * @param top {Integer ? 0} Top position of outer element\n\t\t */\n\t\tsetPosition: function(left, top) {\n\n\t\t\tvar self = this;\n\n\t\t\tself.__clientLeft = left || 0;\n\t\t\tself.__clientTop = top || 0;\n\n\t\t},\n\n\n\t\t/**\n\t\t * Configures the snapping (when snapping is active)\n\t\t *\n\t\t * @param width {Integer} Snapping width\n\t\t * @param height {Integer} Snapping height\n\t\t */\n\t\tsetSnapSize: function(width, height) {\n\n\t\t\tvar self = this;\n\n\t\t\tself.__snapWidth = width;\n\t\t\tself.__snapHeight = height;\n\n\t\t},\n\n\n\t\t/**\n\t\t * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever\n\t\t * the user event is released during visibility of this zone. This was introduced by some apps on iOS like\n\t\t * the official Twitter client.\n\t\t *\n\t\t * @param height {Integer} Height of pull-to-refresh zone on top of rendered list\n\t\t * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release.\n\t\t * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled.\n\t\t * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.\n\t\t */\n\t\tactivatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback) {\n\n\t\t\tvar self = this;\n\n\t\t\tself.__refreshHeight = height;\n\t\t\tself.__refreshActivate = activateCallback;\n\t\t\tself.__refreshDeactivate = deactivateCallback;\n\t\t\tself.__refreshStart = startCallback;\n\n\t\t},\n\t\t\n\t\t\n\t\t/**\n\t\t * Signalizes that pull-to-refresh is finished. \n\t\t */\n\t\tfinishPullToRefresh: function() {\n\t\t\t\n\t\t\tvar self = this;\n\t\t\t\n\t\t\tself.__refreshActive = false;\n\t\t\tif (self.__refreshDeactivate) {\n\t\t\t\tself.__refreshDeactivate();\n\t\t\t}\n\t\t\t\n\t\t\tself.scrollTo(self.__scrollLeft, self.__scrollTop, true);\n\t\t\t\n\t\t},\n\n\n\t\t/**\n\t\t * Returns the scroll position and zooming values\n\t\t *\n\t\t * @return {Map} `left` and `top` scroll position and `zoom` level\n\t\t */\n\t\tgetValues: function() {\n\n\t\t\tvar self = this;\n\n\t\t\treturn {\n\t\t\t\tleft: self.__scrollLeft,\n\t\t\t\ttop: self.__scrollTop,\n\t\t\t\tzoom: self.__zoomLevel\n\t\t\t};\n\n\t\t},\n\t\t\n\t\t\n\t\t/**\n\t\t * Returns the maximum scroll values\n\t\t *\n\t\t * @return {Map} `left` and `top` maximum scroll values\n\t\t */\n\t\tgetScrollMax: function() {\n\t\t\t\n\t\t\tvar self = this;\n\t\t\t\n\t\t\treturn {\n\t\t\t\tleft: self.__maxScrollLeft,\n\t\t\t\ttop: self.__maxScrollTop\n\t\t\t};\n\t\t\t\n\t\t},\n\n\n\t\t/**\n\t\t * Zooms to the given level. Supports optional animation. Zooms\n\t\t * the center when no coordinates are given.\n\t\t *\n\t\t * @param level {Number} Level to zoom to\n\t\t * @param animate {Boolean ? false} Whether to use animation\n\t\t * @param originLeft {Number ? null} Zoom in at given left coordinate\n\t\t * @param originTop {Number ? null} Zoom in at given top coordinate\n\t\t */\n\t\tzoomTo: function(level, animate, originLeft, originTop) {\n\n\t\t\tvar self = this;\n\n\t\t\tif (!self.options.zooming) {\n\t\t\t\tthrow new Error(\"Zooming is not enabled!\");\n\t\t\t}\n\n\t\t\t// Stop deceleration\n\t\t\tif (self.__isDecelerating) {\n\t\t\t\tcore.effect.Animate.stop(self.__isDecelerating);\n\t\t\t\tself.__isDecelerating = false;\n\t\t\t}\n\n\t\t\tvar oldLevel = self.__zoomLevel;\n\n\t\t\t// Normalize input origin to center of viewport if not defined\n\t\t\tif (originLeft == null) {\n\t\t\t\toriginLeft = self.__clientWidth / 2;\n\t\t\t}\n\n\t\t\tif (originTop == null) {\n\t\t\t\toriginTop = self.__clientHeight / 2;\n\t\t\t}\n\n\t\t\t// Limit level according to configuration\n\t\t\tlevel = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);\n\n\t\t\t// Recompute maximum values while temporary tweaking maximum scroll ranges\n\t\t\tself.__computeScrollMax(level);\n\n\t\t\t// Recompute left and top coordinates based on new zoom level\n\t\t\tvar left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft;\n\t\t\tvar top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop;\n\n\t\t\t// Limit x-axis\n\t\t\tif (left > self.__maxScrollLeft) {\n\t\t\t\tleft = self.__maxScrollLeft;\n\t\t\t} else if (left < 0) {\n\t\t\t\tleft = 0;\n\t\t\t}\n\n\t\t\t// Limit y-axis\n\t\t\tif (top > self.__maxScrollTop) {\n\t\t\t\ttop = self.__maxScrollTop;\n\t\t\t} else if (top < 0) {\n\t\t\t\ttop = 0;\n\t\t\t}\n\n\t\t\t// Push values out\n\t\t\tself.__publish(left, top, level, animate);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Zooms the content by the given factor.\n\t\t *\n\t\t * @param factor {Number} Zoom by given factor\n\t\t * @param animate {Boolean ? false} Whether to use animation\n\t\t * @param originLeft {Number ? 0} Zoom in at given left coordinate\n\t\t * @param originTop {Number ? 0} Zoom in at given top coordinate\n\t\t */\n\t\tzoomBy: function(factor, animate, originLeft, originTop) {\n\n\t\t\tvar self = this;\n\n\t\t\tself.zoomTo(self.__zoomLevel * factor, animate, originLeft, originTop);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Scrolls to the given position. Respect limitations and snapping automatically.\n\t\t *\n\t\t * @param left {Number?null} Horizontal scroll position, keeps current if value is null\n\t\t * @param top {Number?null} Vertical scroll position, keeps current if value is null\n\t\t * @param animate {Boolean?false} Whether the scrolling should happen using an animation\n\t\t * @param zoom {Number?null} Zoom level to go to\n\t\t */\n\t\tscrollTo: function(left, top, animate, zoom) {\n\n\t\t\tvar self = this;\n\t\t\t\n\t\t\t// Stop deceleration\n\t\t\tif (self.__isDecelerating) {\n\t\t\t\tcore.effect.Animate.stop(self.__isDecelerating);\n\t\t\t\tself.__isDecelerating = false;\n\t\t\t}\n\t\t\t\n\t\t\t// Correct coordinates based on new zoom level\n\t\t\tif (zoom != null && zoom !== self.__zoomLevel) {\n\t\t\t\t\n\t\t\t\tif (!self.options.zooming) {\n\t\t\t\t\tthrow new Error(\"Zooming is not enabled!\");\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tleft *= zoom;\n\t\t\t\ttop *= zoom;\n\t\t\t\t\n\t\t\t\t// Recompute maximum values while temporary tweaking maximum scroll ranges\n\t\t\t\tself.__computeScrollMax(zoom);\n\t\t\t\t\n\t\t\t} else {\n\t\t\t\t\n\t\t\t\t// Keep zoom when not defined\n\t\t\t\tzoom = self.__zoomLevel;\n\t\t\t\t\n\t\t\t}\n\n\t\t\tif (!self.options.scrollingX) {\n\n\t\t\t\tleft = self.__scrollLeft;\n\n\t\t\t} else {\n\n\t\t\t\tif (self.options.paging) {\n\t\t\t\t\tleft = Math.round(left / self.__clientWidth) * self.__clientWidth;\n\t\t\t\t} else if (self.options.snapping) {\n\t\t\t\t\tleft = Math.round(left / self.__snapWidth) * self.__snapWidth;\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif (!self.options.scrollingY) {\n\n\t\t\t\ttop = self.__scrollTop;\n\n\t\t\t} else {\n\n\t\t\t\tif (self.options.paging) {\n\t\t\t\t\ttop = Math.round(top / self.__clientHeight) * self.__clientHeight;\n\t\t\t\t} else if (self.options.snapping) {\n\t\t\t\t\ttop = Math.round(top / self.__snapHeight) * self.__snapHeight;\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t// Limit for allowed ranges\n\t\t\tleft = Math.max(Math.min(self.__maxScrollLeft, left), 0);\n\t\t\ttop = Math.max(Math.min(self.__maxScrollTop, top), 0);\n\n\t\t\t// Don't animate when no change detected, still call publish to make sure\n\t\t\t// that rendered position is really in-sync with internal data\n\t\t\tif (left === self.__scrollLeft && top === self.__scrollTop) {\n\t\t\t\tanimate = false;\n\t\t\t}\n\t\t\t\n\t\t\t// Publish new values\n\t\t\tself.__publish(left, top, zoom, animate);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Scroll by the given offset\n\t\t *\n\t\t * @param left {Number ? 0} Scroll x-axis by given offset\n\t\t * @param top {Number ? 0} Scroll x-axis by given offset\n\t\t * @param animate {Boolean ? false} Whether to animate the given change\n\t\t */\n\t\tscrollBy: function(left, top, animate) {\n\n\t\t\tvar self = this;\n\n\t\t\tvar startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;\n\t\t\tvar startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;\n\n\t\t\tself.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);\n\n\t\t},\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tEVENT CALLBACKS\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/**\n\t\t * Mouse wheel handler for zooming support\n\t\t */\n\t\tdoMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) {\n\n\t\t\tvar self = this;\n\t\t\tvar change = wheelDelta > 0 ? 0.97 : 1.03;\n\n\t\t\treturn self.zoomTo(self.__zoomLevel * change, false, pageX - self.__clientLeft, pageY - self.__clientTop);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Touch start handler for scrolling support\n\t\t */\n\t\tdoTouchStart: function(touches, timeStamp) {\n\n\t\t\t// Array-like check is enough here\n\t\t\tif (touches.length == null) {\n\t\t\t\tthrow new Error(\"Invalid touch list: \" + touches);\n\t\t\t}\n\n\t\t\tif (timeStamp instanceof Date) {\n\t\t\t\ttimeStamp = timeStamp.valueOf();\n\t\t\t}\n\t\t\tif (typeof timeStamp !== \"number\") {\n\t\t\t\tthrow new Error(\"Invalid timestamp value: \" + timeStamp);\n\t\t\t}\n\t\t\t\n\t\t\tvar self = this;\n\n\t\t\t// Stop deceleration\n\t\t\tif (self.__isDecelerating) {\n\t\t\t\tcore.effect.Animate.stop(self.__isDecelerating);\n\t\t\t\tself.__isDecelerating = false;\n\t\t\t}\n\n\t\t\t// Stop animation\n\t\t\tif (self.__isAnimating) {\n\t\t\t\tcore.effect.Animate.stop(self.__isAnimating);\n\t\t\t\tself.__isAnimating = false;\n\t\t\t}\n\n\t\t\t// Use center point when dealing with two fingers\n\t\t\tvar currentTouchLeft, currentTouchTop;\n\t\t\tvar isSingleTouch = touches.length === 1;\n\t\t\tif (isSingleTouch) {\n\t\t\t\tcurrentTouchLeft = touches[0].pageX;\n\t\t\t\tcurrentTouchTop = touches[0].pageY;\n\t\t\t} else {\n\t\t\t\tcurrentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;\n\t\t\t\tcurrentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;\n\t\t\t}\n\n\t\t\t// Store initial positions\n\t\t\tself.__initialTouchLeft = currentTouchLeft;\n\t\t\tself.__initialTouchTop = currentTouchTop;\n\n\t\t\t// Store current zoom level\n\t\t\tself.__zoomLevelStart = self.__zoomLevel;\n\n\t\t\t// Store initial touch positions\n\t\t\tself.__lastTouchLeft = currentTouchLeft;\n\t\t\tself.__lastTouchTop = currentTouchTop;\n\n\t\t\t// Store initial move time stamp\n\t\t\tself.__lastTouchMove = timeStamp;\n\n\t\t\t// Reset initial scale\n\t\t\tself.__lastScale = 1;\n\n\t\t\t// Reset locking flags\n\t\t\tself.__enableScrollX = !isSingleTouch && self.options.scrollingX;\n\t\t\tself.__enableScrollY = !isSingleTouch && self.options.scrollingY;\n\n\t\t\t// Reset tracking flag\n\t\t\tself.__isTracking = true;\n\n\t\t\t// Dragging starts directly with two fingers, otherwise lazy with an offset\n\t\t\tself.__isDragging = !isSingleTouch;\n\n\t\t\t// Some features are disabled in multi touch scenarios\n\t\t\tself.__isSingleTouch = isSingleTouch;\n\n\t\t\t// Clearing data structure\n\t\t\tself.__positions = [];\n\n\t\t},\n\n\n\t\t/**\n\t\t * Touch move handler for scrolling support\n\t\t */\n\t\tdoTouchMove: function(touches, timeStamp, scale) {\n\n\t\t\t// Array-like check is enough here\n\t\t\tif (touches.length == null) {\n\t\t\t\tthrow new Error(\"Invalid touch list: \" + touches);\n\t\t\t}\n\n\t\t\tif (timeStamp instanceof Date) {\n\t\t\t\ttimeStamp = timeStamp.valueOf();\n\t\t\t}\n\t\t\tif (typeof timeStamp !== \"number\") {\n\t\t\t\tthrow new Error(\"Invalid timestamp value: \" + timeStamp);\n\t\t\t}\n\t\t\t\n\t\t\tvar self = this;\n\n\t\t\t// Ignore event when tracking is not enabled (event might be outside of element)\n\t\t\tif (!self.__isTracking) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t\n\t\t\t\n\t\t\tvar currentTouchLeft, currentTouchTop;\n\n\t\t\t// Compute move based around of center of fingers\n\t\t\tif (touches.length === 2) {\n\t\t\t\tcurrentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;\n\t\t\t\tcurrentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;\n\t\t\t} else {\n\t\t\t\tcurrentTouchLeft = touches[0].pageX;\n\t\t\t\tcurrentTouchTop = touches[0].pageY;\n\t\t\t}\n\n\t\t\tvar positions = self.__positions;\n\n\t\t\t// Are we already is dragging mode?\n\t\t\tif (self.__isDragging) {\n\n\t\t\t\t// Compute move distance\n\t\t\t\tvar moveX = currentTouchLeft - self.__lastTouchLeft;\n\t\t\t\tvar moveY = currentTouchTop - self.__lastTouchTop;\n\n\t\t\t\t// Read previous scroll position and zooming\n\t\t\t\tvar scrollLeft = self.__scrollLeft;\n\t\t\t\tvar scrollTop = self.__scrollTop;\n\t\t\t\tvar level = self.__zoomLevel;\n\n\t\t\t\t// Work with scaling\n\t\t\t\tif (scale != null && self.options.zooming) {\n\n\t\t\t\t\tvar oldLevel = level;\n\n\t\t\t\t\t// Recompute level based on previous scale and new scale\n\t\t\t\t\tlevel = level / self.__lastScale * scale;\n\n\t\t\t\t\t// Limit level according to configuration\n\t\t\t\t\tlevel = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);\n\n\t\t\t\t\t// Only do further compution when change happened\n\t\t\t\t\tif (oldLevel !== level) {\n\n\t\t\t\t\t\t// Compute relative event position to container\n\t\t\t\t\t\tvar currentTouchLeftRel = currentTouchLeft - self.__clientLeft;\n\t\t\t\t\t\tvar currentTouchTopRel = currentTouchTop - self.__clientTop;\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Recompute left and top coordinates based on new zoom level\n\t\t\t\t\t\tscrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel;\n\t\t\t\t\t\tscrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel;\n\n\t\t\t\t\t\t// Recompute max scroll values\n\t\t\t\t\t\tself.__computeScrollMax(level);\n\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (self.__enableScrollX) {\n\n\t\t\t\t\tscrollLeft -= moveX;\n\t\t\t\t\tvar maxScrollLeft = self.__maxScrollLeft;\n\n\t\t\t\t\tif (scrollLeft > maxScrollLeft || scrollLeft < 0) {\n\n\t\t\t\t\t\t// Slow down on the edges\n\t\t\t\t\t\tif (self.options.bouncing) {\n\n\t\t\t\t\t\t\tscrollLeft += (moveX / 2);\n\n\t\t\t\t\t\t} else if (scrollLeft > maxScrollLeft) {\n\n\t\t\t\t\t\t\tscrollLeft = maxScrollLeft;\n\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\tscrollLeft = 0;\n\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Compute new vertical scroll position\n\t\t\t\tif (self.__enableScrollY) {\n\n\t\t\t\t\tscrollTop -= moveY;\n\t\t\t\t\tvar maxScrollTop = self.__maxScrollTop;\n\n\t\t\t\t\tif (scrollTop > maxScrollTop || scrollTop < 0) {\n\n\t\t\t\t\t\t// Slow down on the edges\n\t\t\t\t\t\tif (self.options.bouncing) {\n\n\t\t\t\t\t\t\tscrollTop += (moveY / 2);\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// Support pull-to-refresh (only when only y is scrollable)\n\t\t\t\t\t\t\tif (!self.__enableScrollX && self.__refreshHeight != null) {\n\n\t\t\t\t\t\t\t\tif (!self.__refreshActive && scrollTop <= -self.__refreshHeight) {\n\n\t\t\t\t\t\t\t\t\tself.__refreshActive = true;\n\t\t\t\t\t\t\t\t\tif (self.__refreshActivate) {\n\t\t\t\t\t\t\t\t\t\tself.__refreshActivate();\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t} else if (self.__refreshActive && scrollTop > -self.__refreshHeight) {\n\n\t\t\t\t\t\t\t\t\tself.__refreshActive = false;\n\t\t\t\t\t\t\t\t\tif (self.__refreshDeactivate) {\n\t\t\t\t\t\t\t\t\t\tself.__refreshDeactivate();\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t} else if (scrollTop > maxScrollTop) {\n\n\t\t\t\t\t\t\tscrollTop = maxScrollTop;\n\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\tscrollTop = 0;\n\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Keep list from growing infinitely (holding min 10, max 20 measure points)\n\t\t\t\tif (positions.length > 60) {\n\t\t\t\t\tpositions.splice(0, 30);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Track scroll movement for decleration\n\t\t\t\tpositions.push(scrollLeft, scrollTop, timeStamp);\n\n\t\t\t\t// Sync scroll position\n\t\t\t\tself.__publish(scrollLeft, scrollTop, level);\n\n\t\t\t// Otherwise figure out whether we are switching into dragging mode now.\n\t\t\t} else {\n\n\t\t\t\tvar minimumTrackingForScroll = self.options.locking ? 3 : 0;\n\t\t\t\tvar minimumTrackingForDrag = 5;\n\n\t\t\t\tvar distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft);\n\t\t\t\tvar distanceY = Math.abs(currentTouchTop - self.__initialTouchTop);\n\n\t\t\t\tself.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll;\n\t\t\t\tself.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll;\n\t\t\t\t\n\t\t\t\tpositions.push(self.__scrollLeft, self.__scrollTop, timeStamp);\n\n\t\t\t\tself.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag);\n\n\t\t\t}\n\n\t\t\t// Update last touch positions and time stamp for next event\n\t\t\tself.__lastTouchLeft = currentTouchLeft;\n\t\t\tself.__lastTouchTop = currentTouchTop;\n\t\t\tself.__lastTouchMove = timeStamp;\n\t\t\tself.__lastScale = scale;\n\n\t\t},\n\n\n\t\t/**\n\t\t * Touch end handler for scrolling support\n\t\t */\n\t\tdoTouchEnd: function(timeStamp) {\n\t\t\t\n\t\t\tif (timeStamp instanceof Date) {\n\t\t\t\ttimeStamp = timeStamp.valueOf();\n\t\t\t}\n\t\t\tif (typeof timeStamp !== \"number\") {\n\t\t\t\tthrow new Error(\"Invalid timestamp value: \" + timeStamp);\n\t\t\t}\n\t\t\t\n\t\t\tvar self = this;\n\n\t\t\t// Ignore event when tracking is not enabled (no touchstart event on element)\n\t\t\t// This is required as this listener ('touchmove') sits on the document and not on the element itself.\n\t\t\tif (!self.__isTracking) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t\n\t\t\t// Not touching anymore (when two finger hit the screen there are two touch end events)\n\t\t\tself.__isTracking = false;\n\n\t\t\t// Be sure to reset the dragging flag now. Here we also detect whether\n\t\t\t// the finger has moved fast enough to switch into a deceleration animation.\n\t\t\tif (self.__isDragging) {\n\n\t\t\t\t// Reset dragging flag\n\t\t\t\tself.__isDragging = false;\n\n\t\t\t\t// Start deceleration\n\t\t\t\t// Verify that the last move detected was in some relevant time frame\n\t\t\t\tif (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) {\n\n\t\t\t\t\t// Then figure out what the scroll position was about 100ms ago\n\t\t\t\t\tvar positions = self.__positions;\n\t\t\t\t\tvar endPos = positions.length - 1;\n\t\t\t\t\tvar startPos = endPos;\n\t\t\t\t\t\n\t\t\t\t\t// Move pointer to position measured 100ms ago\n\t\t\t\t\tfor (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) {\n\t\t\t\t\t\tstartPos = i;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// If start and stop position is identical in a 100ms timeframe, \n\t\t\t\t\t// we cannot compute any useful deceleration.\n\t\t\t\t\tif (startPos !== endPos) {\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Compute relative movement between these two points\n\t\t\t\t\t\tvar timeOffset = positions[endPos] - positions[startPos];\n\t\t\t\t\t\tvar movedLeft = self.__scrollLeft - positions[startPos - 2];\n\t\t\t\t\t\tvar movedTop = self.__scrollTop - positions[startPos - 1];\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Based on 50ms compute the movement to apply for each render step\n\t\t\t\t\t\tself.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60);\n\t\t\t\t\t\tself.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60);\n\n\t\t\t\t\t\t// How much velocity is required to start the deceleration\n\t\t\t\t\t\tvar minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? 4 : 1;\n\n\t\t\t\t\t\t// Verify that we have enough velocity to start deceleration\n\t\t\t\t\t\tif (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) {\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// Deactivate pull-to-refresh when decelerating\n\t\t\t\t\t\t\tif (!self.__refreshActive) {\n\n\t\t\t\t\t\t\t\tself.__startDeceleration(timeStamp);\n\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If this was a slower move it is per default non decelerated, but this\n\t\t\t// still means that we want snap back to the bounds which is done here.\n\t\t\t// This is placed outside the condition above to improve edge case stability\n\t\t\t// e.g. touchend fired without enabled dragging. This should normally do not\n\t\t\t// have modified the scroll positions or even showed the scrollbars though.\n\t\t\tif (!self.__isDecelerating) {\n\n\t\t\t\tif (self.__refreshActive && self.__refreshStart) {\n\t\t\t\t\t\n\t\t\t\t\t// Use publish instead of scrollTo to allow scrolling to out of boundary position\n\t\t\t\t\t// We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled\n\t\t\t\t\tself.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true);\n\t\t\t\t\t\n\t\t\t\t\tif (self.__refreshStart) {\n\t\t\t\t\t\tself.__refreshStart();\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t} else {\n\t\t\t\t\t\n\t\t\t\t\tself.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel);\n\t\t\t\t\t\n\t\t\t\t\t// Directly signalize deactivation (nothing todo on refresh?)\n\t\t\t\t\tif (self.__refreshActive) {\n\t\t\t\t\t\t\n\t\t\t\t\t\tself.__refreshActive = false;\n\t\t\t\t\t\tif (self.__refreshDeactivate) {\n\t\t\t\t\t\t\tself.__refreshDeactivate();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// Fully cleanup list\n\t\t\tself.__positions.length = 0;\n\n\t\t},\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tPRIVATE API\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/**\n\t\t * Applies the scroll position to the content element\n\t\t *\n\t\t * @param left {Number} Left scroll position\n\t\t * @param top {Number} Top scroll position\n\t\t * @param animate {Boolean?false} Whether animation should be used to move to the new coordinates\n\t\t */\n\t\t__publish: function(left, top, zoom, animate) {\n\n\t\t\tvar self = this;\n\t\t\t\n\t\t\t// Remember whether we had an animation, then we try to continue based on the current \"drive\" of the animation\n\t\t\tvar wasAnimating = self.__isAnimating;\n\t\t\tif (wasAnimating) {\n\t\t\t\tcore.effect.Animate.stop(wasAnimating);\n\t\t\t\tself.__isAnimating = false;\n\t\t\t}\n\n\t\t\tif (animate && self.options.animating) {\n\n\t\t\t\t// Keep scheduled positions for scrollBy/zoomBy functionality\n\t\t\t\tself.__scheduledLeft = left;\n\t\t\t\tself.__scheduledTop = top;\n\t\t\t\tself.__scheduledZoom = zoom;\n\n\t\t\t\tvar oldLeft = self.__scrollLeft;\n\t\t\t\tvar oldTop = self.__scrollTop;\n\t\t\t\tvar oldZoom = self.__zoomLevel;\n\n\t\t\t\tvar diffLeft = left - oldLeft;\n\t\t\t\tvar diffTop = top - oldTop;\n\t\t\t\tvar diffZoom = zoom - oldZoom;\n\n\t\t\t\tvar step = function(percent, now, render) {\n\n\t\t\t\t\tif (render) {\n\n\t\t\t\t\t\tself.__scrollLeft = oldLeft + (diffLeft * percent);\n\t\t\t\t\t\tself.__scrollTop = oldTop + (diffTop * percent);\n\t\t\t\t\t\tself.__zoomLevel = oldZoom + (diffZoom * percent);\n\n\t\t\t\t\t\t// Push values out\n\t\t\t\t\t\tif (self.__callback) {\n\t\t\t\t\t\t\tself.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tvar verify = function(id) {\n\t\t\t\t\treturn self.__isAnimating === id;\n\t\t\t\t};\n\n\t\t\t\tvar completed = function(renderedFramesPerSecond, animationId, wasFinished) {\n\t\t\t\t\tif (animationId === self.__isAnimating) {\n\t\t\t\t\t\tself.__isAnimating = false;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif (self.options.zooming) {\n\t\t\t\t\t\tself.__computeScrollMax();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\t\n\t\t\t\t// When continuing based on previous animation we choose an ease-out animation instead of ease-in-out\n\t\t\t\tself.__isAnimating = core.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic);\n\n\t\t\t} else {\n\n\t\t\t\tself.__scheduledLeft = self.__scrollLeft = left;\n\t\t\t\tself.__scheduledTop = self.__scrollTop = top;\n\t\t\t\tself.__scheduledZoom = self.__zoomLevel = zoom;\n\n\t\t\t\t// Push values out\n\t\t\t\tif (self.__callback) {\n\t\t\t\t\tself.__callback(left, top, zoom);\n\t\t\t\t}\n\n\t\t\t\t// Fix max scroll ranges\n\t\t\t\tif (self.options.zooming) {\n\t\t\t\t\tself.__computeScrollMax();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\n\t\t/**\n\t\t * Recomputes scroll minimum values based on client dimensions and content dimensions.\n\t\t */\n\t\t__computeScrollMax: function(zoomLevel) {\n\n\t\t\tvar self = this;\n\t\t\t\n\t\t\tif (zoomLevel == null) {\n\t\t\t\tzoomLevel = self.__zoomLevel;\n\t\t\t}\n\n\t\t\tself.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0);\n\t\t\tself.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0);\n\t\t\t\n\t\t},\n\n\n\n\t\t/*\n\t\t---------------------------------------------------------------------------\n\t\t\tANIMATION (DECELERATION) SUPPORT\n\t\t---------------------------------------------------------------------------\n\t\t*/\n\n\t\t/**\n\t\t * Called when a touch sequence end and the speed of the finger was high enough\n\t\t * to switch into deceleration mode.\n\t\t */\n\t\t__startDeceleration: function(timeStamp) {\n\n\t\t\tvar self = this;\n\n\t\t\tif (self.options.paging) {\n\n\t\t\t\tvar scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0);\n\t\t\t\tvar scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0);\n\t\t\t\tvar clientWidth = self.__clientWidth;\n\t\t\t\tvar clientHeight = self.__clientHeight;\n\n\t\t\t\t// We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area.\n\t\t\t\t// Each page should have exactly the size of the client area.\n\t\t\t\tself.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth;\n\t\t\t\tself.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight;\n\t\t\t\tself.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth;\n\t\t\t\tself.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight;\n\n\t\t\t} else {\n\n\t\t\t\tself.__minDecelerationScrollLeft = 0;\n\t\t\t\tself.__minDecelerationScrollTop = 0;\n\t\t\t\tself.__maxDecelerationScrollLeft = self.__maxScrollLeft;\n\t\t\t\tself.__maxDecelerationScrollTop = self.__maxScrollTop;\n\n\t\t\t}\n\n\t\t\t// Wrap class method\n\t\t\tvar step = function(percent, now, render) {\n\t\t\t\tself.__stepThroughDeceleration(render);\n\t\t\t};\n\n\t\t\t// How much velocity is required to keep the deceleration running\n\t\t\tvar minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1;\n\n\t\t\t// Detect whether it's still worth to continue animating steps\n\t\t\t// If we are already slow enough to not being user perceivable anymore, we stop the whole process here.\n\t\t\tvar verify = function() {\n\t\t\t\treturn Math.abs(self.__decelerationVelocityX) >= minVelocityToKeepDecelerating || Math.abs(self.__decelerationVelocityY) >= minVelocityToKeepDecelerating;\n\t\t\t};\n\n\t\t\tvar completed = function(renderedFramesPerSecond, animationId, wasFinished) {\n\t\t\t\tself.__isDecelerating = false;\n\n\t\t\t\t// Animate to grid when snapping is active, otherwise just fix out-of-boundary positions\n\t\t\t\tself.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping);\n\t\t\t};\n\n\t\t\t// Start animation and switch on flag\n\t\t\tself.__isDecelerating = core.effect.Animate.start(step, verify, completed);\n\n\t\t},\n\n\n\t\t/**\n\t\t * Called on every step of the animation\n\t\t *\n\t\t * @param inMemory {Boolean?false} Whether to not render the current step, but keep it in memory only. Used internally only!\n\t\t */\n\t\t__stepThroughDeceleration: function(render) {\n\n\t\t\tvar self = this;\n\n\n\t\t\t//\n\t\t\t// COMPUTE NEXT SCROLL POSITION\n\t\t\t//\n\n\t\t\t// Add deceleration to scroll position\n\t\t\tvar scrollLeft = self.__scrollLeft + self.__decelerationVelocityX;\n\t\t\tvar scrollTop = self.__scrollTop + self.__decelerationVelocityY;\n\n\n\t\t\t//\n\t\t\t// HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE\n\t\t\t//\n\n\t\t\tif (!self.options.bouncing) {\n\n\t\t\t\tvar scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft);\n\t\t\t\tif (scrollLeftFixed !== scrollLeft) {\n\t\t\t\t\tscrollLeft = scrollLeftFixed;\n\t\t\t\t\tself.__decelerationVelocityX = 0;\n\t\t\t\t}\n\n\t\t\t\tvar scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop);\n\t\t\t\tif (scrollTopFixed !== scrollTop) {\n\t\t\t\t\tscrollTop = scrollTopFixed;\n\t\t\t\t\tself.__decelerationVelocityY = 0;\n\t\t\t\t}\n\n\t\t\t}\n\n\n\t\t\t//\n\t\t\t// UPDATE SCROLL POSITION\n\t\t\t//\n\n\t\t\tif (render) {\n\n\t\t\t\tself.__publish(scrollLeft, scrollTop, self.__zoomLevel);\n\n\t\t\t} else {\n\n\t\t\t\tself.__scrollLeft = scrollLeft;\n\t\t\t\tself.__scrollTop = scrollTop;\n\n\t\t\t}\n\n\n\t\t\t//\n\t\t\t// SLOW DOWN\n\t\t\t//\n\n\t\t\t// Slow down velocity on every iteration\n\t\t\tif (!self.options.paging) {\n\n\t\t\t\t// This is the factor applied to every iteration of the animation\n\t\t\t\t// to slow down the process. This should emulate natural behavior where\n\t\t\t\t// objects slow down when the initiator of the movement is removed\n\t\t\t\tvar frictionFactor = 0.95;\n\n\t\t\t\tself.__decelerationVelocityX *= frictionFactor;\n\t\t\t\tself.__decelerationVelocityY *= frictionFactor;\n\n\t\t\t}\n\n\n\t\t\t//\n\t\t\t// BOUNCING SUPPORT\n\t\t\t//\n\n\t\t\tif (self.options.bouncing) {\n\n\t\t\t\tvar scrollOutsideX = 0;\n\t\t\t\tvar scrollOutsideY = 0;\n\n\t\t\t\t// This configures the amount of change applied to deceleration/acceleration when reaching boundaries\n\t\t\t\tvar penetrationDeceleration = 0.03;\n\t\t\t\tvar penetrationAcceleration = 0.08;\n\n\t\t\t\t// Check limits\n\t\t\t\tif (scrollLeft < self.__minDecelerationScrollLeft) {\n\t\t\t\t\tscrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft;\n\t\t\t\t} else if (scrollLeft > self.__maxDecelerationScrollLeft) {\n\t\t\t\t\tscrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft;\n\t\t\t\t}\n\n\t\t\t\tif (scrollTop < self.__minDecelerationScrollTop) {\n\t\t\t\t\tscrollOutsideY = self.__minDecelerationScrollTop - scrollTop;\n\t\t\t\t} else if (scrollTop > self.__maxDecelerationScrollTop) {\n\t\t\t\t\tscrollOutsideY = self.__maxDecelerationScrollTop - scrollTop;\n\t\t\t\t}\n\n\t\t\t\t// Slow down until slow enough, then flip back to snap position\n\t\t\t\tif (scrollOutsideX !== 0) {\n\t\t\t\t\tif (scrollOutsideX * self.__decelerationVelocityX <= 0) {\n\t\t\t\t\t\tself.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (scrollOutsideY !== 0) {\n\t\t\t\t\tif (scrollOutsideY * self.__decelerationVelocityY <= 0) {\n\t\t\t\t\t\tself.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\t\n\t// Copy over members to prototype\n\tfor (var key in members) {\n\t\tScroller.prototype[key] = members[key];\n\t}\n\t\t\n})();\n\n(function( factory ) {\n\tif( typeof define === 'function' && define.amd ) {\n\t\t\t// AMD. Register as an anonymous module.\n\t\t\tdefine( [ 'shoestring' ], factory );\n\t} else if (typeof module === 'object' && module.exports) {\n\t\t// Node/CommonJS\n\t\tmodule.exports = factory();\n\t} else {\n\t\t// Browser globals\n\t\tfactory();\n\t}\n}(function () {\n\tvar win = typeof window !== \"undefined\" ? window : this;\n\tvar doc = win.document;\n\n\n\t/**\n\t * The shoestring object constructor.\n\t *\n\t * @param {string,object} prim The selector to find or element to wrap.\n\t * @param {object} sec The context in which to match the `prim` selector.\n\t * @returns shoestring\n\t * @this window\n\t */\n\tfunction shoestring( prim, sec ){\n\t\tvar pType = typeof( prim ),\n\t\t\t\tret = [],\n\t\t\t\tsel;\n\n\t\t// return an empty shoestring object\n\t\tif( !prim ){\n\t\t\treturn new Shoestring( ret );\n\t\t}\n\n\t\t// ready calls\n\t\tif( prim.call ){\n\t\t\treturn shoestring.ready( prim );\n\t\t}\n\n\t\t// handle re-wrapping shoestring objects\n\t\tif( prim.constructor === Shoestring && !sec ){\n\t\t\treturn prim;\n\t\t}\n\n\t\t// if string starting with <, make html\n\t\tif( pType === \"string\" && prim.indexOf( \"<\" ) === 0 ){\n\t\t\tvar dfrag = doc.createElement( \"div\" );\n\n\t\t\tdfrag.innerHTML = prim;\n\n\t\t\t// TODO depends on children (circular)\n\t\t\treturn shoestring( dfrag ).children().each(function(){\n\t\t\t\tdfrag.removeChild( this );\n\t\t\t});\n\t\t}\n\n\t\t// if string, it's a selector, use qsa\n\t\tif( pType === \"string\" ){\n\t\t\tif( sec ){\n\t\t\t\treturn shoestring( sec ).find( prim );\n\t\t\t}\n\n\t\t\t\tsel = doc.querySelectorAll( prim );\n\n\t\t\treturn new Shoestring( sel, prim );\n\t\t}\n\n\t\t// array like objects or node lists\n\t\tif( Object.prototype.toString.call( pType ) === '[object Array]' ||\n\t\t\t\t(win.NodeList && prim instanceof win.NodeList) ){\n\n\t\t\treturn new Shoestring( prim, prim );\n\t\t}\n\n\t\t// if it's an array, use all the elements\n\t\tif( prim.constructor === Array ){\n\t\t\treturn new Shoestring( prim, prim );\n\t\t}\n\n\t\t// otherwise assume it's an object the we want at an index\n\t\treturn new Shoestring( [prim], prim );\n\t}\n\n\tvar Shoestring = function( ret, prim ) {\n\t\tthis.length = 0;\n\t\tthis.selector = prim;\n\t\tshoestring.merge(this, ret);\n\t};\n\n\t// TODO only required for tests\n\tShoestring.prototype.reverse = [].reverse;\n\n\t// For adding element set methods\n\tshoestring.fn = Shoestring.prototype;\n\n\tshoestring.Shoestring = Shoestring;\n\n\t// For extending objects\n\t// TODO move to separate module when we use prototypes\n\tshoestring.extend = function( first, second ){\n\t\tfor( var i in second ){\n\t\t\tif( second.hasOwnProperty( i ) ){\n\t\t\t\tfirst[ i ] = second[ i ];\n\t\t\t}\n\t\t}\n\n\t\treturn first;\n\t};\n\n\t// taken directly from jQuery\n\tshoestring.merge = function( first, second ) {\n\t\tvar len, j, i;\n\n\t\tlen = +second.length,\n\t\tj = 0,\n\t\ti = first.length;\n\n\t\tfor ( ; j < len; j++ ) {\n\t\t\tfirst[ i++ ] = second[ j ];\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t};\n\n\t// expose\n\twin.shoestring = shoestring;\n\n\n\n\t/**\n\t * Make an HTTP request to a url.\n\t *\n\t * **NOTE** the following options are supported:\n\t *\n\t * - *method* - The HTTP method used with the request. Default: `GET`.\n\t * - *data* - Raw object with keys and values to pass with request as query params. Default `null`.\n\t * - *headers* - Set of request headers to add. Default `{}`.\n\t * - *async* - Whether the opened request is asynchronouse. Default `true`.\n\t * - *success* - Callback for successful request and response. Passed the response data.\n\t * - *error* - Callback for failed request and response.\n\t * - *cancel* - Callback for cancelled request and response.\n\t *\n\t * @param {string} url The url to request.\n\t * @param {object} options The options object, see Notes.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\n\tshoestring.ajax = function( url, options ) {\n\t\tvar params = \"\", req = new XMLHttpRequest(), settings, key;\n\n\t\tsettings = shoestring.extend( {}, shoestring.ajax.settings );\n\n\t\tif( options ){\n\t\t\tshoestring.extend( settings, options );\n\t\t}\n\n\t\tif( !url ){\n\t\t\turl = settings.url;\n\t\t}\n\n\t\tif( !req || !url ){\n\t\t\treturn;\n\t\t}\n\n\t\t// create parameter string from data object\n\t\tif( settings.data ){\n\t\t\tfor( key in settings.data ){\n\t\t\t\tif( settings.data.hasOwnProperty( key ) ){\n\t\t\t\t\tif( params !== \"\" ){\n\t\t\t\t\t\tparams += \"&\";\n\t\t\t\t\t}\n\t\t\t\t\tparams += encodeURIComponent( key ) + \"=\" +\n\t\t\t\t\t\tencodeURIComponent( settings.data[key] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// append params to url for GET requests\n\t\tif( settings.method === \"GET\" && params ){\n\t\t\t\n\t\t\turl += \"?\" + params;\n\t\t}\n\n\t\treq.open( settings.method, url, settings.async );\n\n\t\tif( req.setRequestHeader ){\n\t\t\treq.setRequestHeader( \"X-Requested-With\", \"XMLHttpRequest\" );\n\n\t\t\t// Set 'Content-type' header for POST requests\n\t\t\tif( settings.method === \"POST\" && params ){\n\t\t\t\treq.setRequestHeader( \"Content-type\", \"application/x-www-form-urlencoded\" );\n\t\t\t}\n\n\t\t\tfor( key in settings.headers ){\n\t\t\t\tif( settings.headers.hasOwnProperty( key ) ){\n\t\t\t\t\treq.setRequestHeader(key, settings.headers[ key ]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treq.onreadystatechange = function () {\n\t\t\tif( req.readyState === 4 ){\n\t\t\t\t// Trim the whitespace so shoestring('
') works\n\t\t\t\tvar res = (req.responseText || '').replace(/^\\s+|\\s+$/g, '');\n\t\t\t\tif( req.status.toString().indexOf( \"0\" ) === 0 ){\n\t\t\t\t\treturn settings.cancel( res, req.status, req );\n\t\t\t\t}\n\t\t\t\telse if ( req.status.toString().match( /^(4|5)/ ) && RegExp.$1 ){\n\t\t\t\t\treturn settings.error( res, req.status, req );\n\t\t\t\t}\n\t\t\t\telse if (settings.success) {\n\t\t\t\t\treturn settings.success( res, req.status, req );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tif( req.readyState === 4 ){\n\t\t\treturn req;\n\t\t}\n\n\t\t// Send request\n\t\tif( settings.method === \"POST\" && params ){\n\t\t\treq.send( params );\n\t\t} else {\n\t\t\treq.send();\n\t\t}\n\n\t\treturn req;\n\t};\n\n\tshoestring.ajax.settings = {\n\t\tsuccess: function(){},\n\t\terror: function(){},\n\t\tcancel: function(){},\n\t\tmethod: \"GET\",\n\t\tasync: true,\n\t\tdata: null,\n\t\theaders: {}\n\t};\n\n\n\n\t/**\n\t * Helper function wrapping a call to [ajax](ajax.js.html) using the `GET` method.\n\t *\n\t * @param {string} url The url to GET from.\n\t * @param {function} callback Callback to invoke on success.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.get = function( url, callback ){\n\t\treturn shoestring.ajax( url, { success: callback } );\n\t};\n\n\n\n /**\n\t * Load the HTML response from `url` into the current set of elements.\n\t *\n\t * @param {string} url The url to GET from.\n\t * @param {function} callback Callback to invoke after HTML is inserted.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.load = function( url, callback ){\n\t\tvar self = this,\n\t\t\targs = arguments,\n\t\t\tintCB = function( data ){\n\t\t\t\tself.each(function(){\n\t\t\t\t\tshoestring( this ).html( data );\n\t\t\t\t});\n\n\t\t\t\tif( callback ){\n\t\t\t\t\tcallback.apply( self, args );\n\t\t\t\t}\n\t\t };\n\n\t\tshoestring.ajax( url, { success: intCB } );\n\t\treturn this;\n\t};\n\n\n\n\t/**\n\t * Helper function wrapping a call to [ajax](ajax.js.html) using the `POST` method.\n\t *\n\t * @param {string} url The url to POST to.\n\t * @param {object} data The data to send.\n\t * @param {function} callback Callback to invoke on success.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.post = function( url, data, callback ){\n\t\treturn shoestring.ajax( url, { data: data, method: \"POST\", success: callback } );\n\t};\n\n\n\n\t/**\n\t * Iterates over `shoestring` collections.\n\t *\n\t * @param {function} callback The callback to be invoked on each element and index\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.each = function( callback ){\n\t\treturn shoestring.each( this, callback );\n\t};\n\n\tshoestring.each = function( collection, callback ) {\n\t\tvar val;\n\t\tfor( var i = 0, il = collection.length; i < il; i++ ){\n\t\t\tval = callback.call( collection[i], i, collection[i] );\n\t\t\tif( val === false ){\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn collection;\n\t};\n\n\n\n /**\n\t * Check for array membership.\n\t *\n\t * @param {object} needle The thing to find.\n\t * @param {object} haystack The thing to find the needle in.\n\t * @return {boolean}\n\t * @this window\n\t */\n\tshoestring.inArray = function( needle, haystack ){\n\t\tvar isin = -1;\n\t\tfor( var i = 0, il = haystack.length; i < il; i++ ){\n\t\t\tif( haystack.hasOwnProperty( i ) && haystack[ i ] === needle ){\n\t\t\t\tisin = i;\n\t\t\t}\n\t\t}\n\t\treturn isin;\n\t};\n\n\n\n /**\n\t * Bind callbacks to be run when the DOM is \"ready\".\n\t *\n\t * @param {function} fn The callback to be run\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.ready = function( fn ){\n\t\tif( ready && fn ){\n\t\t\tfn.call( doc );\n\t\t}\n\t\telse if( fn ){\n\t\t\treadyQueue.push( fn );\n\t\t}\n\t\telse {\n\t\t\trunReady();\n\t\t}\n\n\t\treturn [doc];\n\t};\n\n\t// TODO necessary?\n\tshoestring.fn.ready = function( fn ){\n\t\tshoestring.ready( fn );\n\t\treturn this;\n\t};\n\n\t// Empty and exec the ready queue\n\tvar ready = false,\n\t\treadyQueue = [],\n\t\trunReady = function(){\n\t\t\tif( !ready ){\n\t\t\t\twhile( readyQueue.length ){\n\t\t\t\t\treadyQueue.shift().call( doc );\n\t\t\t\t}\n\t\t\t\tready = true;\n\t\t\t}\n\t\t};\n\n\t// If DOM is already ready at exec time, depends on the browser.\n\t// From: https://github.com/mobify/mobifyjs/blob/526841be5509e28fc949038021799e4223479f8d/src/capture.js#L128\n\tif (doc.attachEvent ? doc.readyState === \"complete\" : doc.readyState !== \"loading\") {\n\t\trunReady();\n\t} else {\n\t\tdoc.addEventListener( \"DOMContentLoaded\", runReady, false );\n\t\tdoc.addEventListener( \"readystatechange\", runReady, false );\n\t\twin.addEventListener( \"load\", runReady, false );\n\t}\n\n\n\n /**\n\t * Checks the current set of elements against the selector, if one matches return `true`.\n\t *\n\t * @param {string} selector The selector to check.\n\t * @return {boolean}\n\t * @this {shoestring}\n\t */\n\tshoestring.fn.is = function( selector ){\n\t\tvar ret = false, self = this, parents, check;\n\n\t\t// assume a dom element\n\t\tif( typeof selector !== \"string\" ){\n\t\t\t// array-like, ie shoestring objects or element arrays\n\t\t\tif( selector.length && selector[0] ){\n\t\t\t\tcheck = selector;\n\t\t\t} else {\n\t\t\t\tcheck = [selector];\n\t\t\t}\n\n\t\t\treturn _checkElements(this, check);\n\t\t}\n\n\t\tparents = this.parent();\n\n\t\tif( !parents.length ){\n\t\t\tparents = shoestring( doc );\n\t\t}\n\n\t\tparents.each(function( i, e ) {\n\t\t\tvar children;\n\n\t\t\t\t\tchildren = e.querySelectorAll( selector );\n\n\t\t\tret = _checkElements( self, children );\n\t\t});\n\n\t\treturn ret;\n\t};\n\n\tfunction _checkElements(needles, haystack){\n\t\tvar ret = false;\n\n\t\tneedles.each(function() {\n\t\t\tvar j = 0;\n\n\t\t\twhile( j < haystack.length ){\n\t\t\t\tif( this === haystack[j] ){\n\t\t\t\t\tret = true;\n\t\t\t\t}\n\n\t\t\t\tj++;\n\t\t\t}\n\t\t});\n\n\t\treturn ret;\n\t}\n\n\n\n\t/**\n\t * Get data attached to the first element or set data values on all elements in the current set.\n\t *\n\t * @param {string} name The data attribute name.\n\t * @param {any} value The value assigned to the data attribute.\n\t * @return {any|shoestring}\n\t * @this shoestring\n\t */\n\tshoestring.fn.data = function( name, value ){\n\t\tif( name !== undefined ){\n\t\t\tif( value !== undefined ){\n\t\t\t\treturn this.each(function(){\n\t\t\t\t\tif( !this.shoestringData ){\n\t\t\t\t\t\tthis.shoestringData = {};\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.shoestringData[ name ] = value;\n\t\t\t\t});\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif( this[ 0 ] ) {\n\t\t\t\t\tif( this[ 0 ].shoestringData ) {\n\t\t\t\t\t\treturn this[ 0 ].shoestringData[ name ];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\treturn this[ 0 ] ? this[ 0 ].shoestringData || {} : undefined;\n\t\t}\n\t};\n\n\n\t/**\n\t * Remove data associated with `name` or all the data, for each element in the current set.\n\t *\n\t * @param {string} name The data attribute name.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.removeData = function( name ){\n\t\treturn this.each(function(){\n\t\t\tif( name !== undefined && this.shoestringData ){\n\t\t\t\tthis.shoestringData[ name ] = undefined;\n\t\t\t\tdelete this.shoestringData[ name ];\n\t\t\t}\telse {\n\t\t\t\tthis[ 0 ].shoestringData = {};\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * An alias for the `shoestring` constructor.\n\t */\n\tif (typeof(win.$) === \"undefined\") { win.$ = shoestring; }\n\n\n\n\t/**\n\t * Add a class to each DOM element in the set of elements.\n\t *\n\t * @param {string} className The name of the class to be added.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.addClass = function( className ){\n\t\tvar classes = className.replace(/^\\s+|\\s+$/g, '').split( \" \" );\n\n\t\treturn this.each(function(){\n\t\t\tfor( var i = 0, il = classes.length; i < il; i++ ){\n\t\t\t\tif( this.className !== undefined &&\n\t\t\t\t\t\t(this.className === \"\" ||\n\t\t\t\t\t\t!this.className.match( new RegExp( \"(^|\\\\s)\" + classes[ i ] + \"($|\\\\s)\"))) ){\n\t\t\t\t\tthis.className += \" \" + classes[ i ];\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\n\n /**\n\t * Add elements matching the selector to the current set.\n\t *\n\t * @param {string} selector The selector for the elements to add from the DOM\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.add = function( selector ){\n\t\tvar ret = [];\n\t\tthis.each(function(){\n\t\t\tret.push( this );\n\t\t});\n\n\t\tshoestring( selector ).each(function(){\n\t\t\tret.push( this );\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Insert an element or HTML string after each element in the current set.\n\t *\n\t * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.after = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" || fragment.nodeType !== undefined ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\tif( fragment.length > 1 ){\n\t\t\tfragment = fragment.reverse();\n\t\t}\n\t\treturn this.each(function( i ){\n\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\tvar insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ];\n\t\t\t\tthis.parentNode.insertBefore( insertEl, this.nextSibling );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Insert an element or HTML string as the last child of each element in the set.\n\t *\n\t * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.append = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" || fragment.nodeType !== undefined ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\treturn this.each(function( i ){\n\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\tthis.appendChild( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ] );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Insert the current set as the last child of the elements matching the selector.\n\t *\n\t * @param {string} selector The selector after which to append the current set.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.appendTo = function( selector ){\n\t\treturn this.each(function(){\n\t\t\tshoestring( selector ).append( this );\n\t\t});\n\t};\n\n\n\n /**\n\t * Get the value of the first element of the set or set the value of all the elements in the set.\n\t *\n\t * @param {string} name The attribute name.\n\t * @param {string} value The new value for the attribute.\n\t * @return {shoestring|string|undefined}\n\t * @this {shoestring}\n\t */\n\tshoestring.fn.attr = function( name, value ){\n\t\tvar nameStr = typeof( name ) === \"string\";\n\n\t\tif( value !== undefined || !nameStr ){\n\t\t\treturn this.each(function(){\n\t\t\t\tif( nameStr ){\n\t\t\t\t\tthis.setAttribute( name, value );\n\t\t\t\t}\telse {\n\t\t\t\t\tfor( var i in name ){\n\t\t\t\t\t\tif( name.hasOwnProperty( i ) ){\n\t\t\t\t\t\t\tthis.setAttribute( i, name[ i ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\treturn this[ 0 ] ? this[ 0 ].getAttribute( name ) : undefined;\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Insert an element or HTML string before each element in the current set.\n\t *\n\t * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.before = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" || fragment.nodeType !== undefined ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\treturn this.each(function( i ){\n\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\tthis.parentNode.insertBefore( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ], this );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Get the children of the current collection.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.children = function(){\n\t\t\t\tvar ret = [],\n\t\t\tchilds,\n\t\t\tj;\n\t\tthis.each(function(){\n\t\t\tchilds = this.children;\n\t\t\tj = -1;\n\n\t\t\twhile( j++ < childs.length-1 ){\n\t\t\t\tif( shoestring.inArray( childs[ j ], ret ) === -1 ){\n\t\t\t\t\tret.push( childs[ j ] );\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\treturn shoestring(ret);\n\t};\n\n\n\n\t/**\n\t * Clone and return the current set of nodes into a new `shoestring` object.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.clone = function() {\n\t\tvar ret = [];\n\n\t\tthis.each(function() {\n\t\t\tret.push( this.cloneNode( true ) );\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Find an element matching the selector in the set of the current element and its parents.\n\t *\n\t * @param {string} selector The selector used to identify the target element.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.closest = function( selector ){\n\t\tvar ret = [];\n\n\t\tif( !selector ){\n\t\t\treturn shoestring( ret );\n\t\t}\n\n\t\tthis.each(function(){\n\t\t\tvar element, $self = shoestring( element = this );\n\n\t\t\tif( $self.is(selector) ){\n\t\t\t\tret.push( this );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\twhile( element.parentElement ) {\n\t\t\t\tif( shoestring(element.parentElement).is(selector) ){\n\t\t\t\t\tret.push( element.parentElement );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\telement = element.parentElement;\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n shoestring.cssExceptions = {\n\t\t'float': [ 'cssFloat' ]\n\t};\n\n\n\n\t(function() {\n\t\tvar cssExceptions = shoestring.cssExceptions;\n\n\t\t// marginRight instead of margin-right\n\t\tfunction convertPropertyName( str ) {\n\t\t\treturn str.replace( /\\-([A-Za-z])/g, function ( match, character ) {\n\t\t\t\treturn character.toUpperCase();\n\t\t\t});\n\t\t}\n\n\t\tfunction _getStyle( element, property ) {\n\t\t\treturn win.getComputedStyle( element, null ).getPropertyValue( property );\n\t\t}\n\n\t\tvar vendorPrefixes = [ '', '-webkit-', '-ms-', '-moz-', '-o-', '-khtml-' ];\n\n\t\t/**\n\t\t * Private function for getting the computed style of an element.\n\t\t *\n\t\t * **NOTE** Please use the [css](../css.js.html) method instead.\n\t\t *\n\t\t * @method _getStyle\n\t\t * @param {HTMLElement} element The element we want the style property for.\n\t\t * @param {string} property The css property we want the style for.\n\t\t */\n\t\tshoestring._getStyle = function( element, property ) {\n\t\t\tvar convert, value, j, k;\n\n\t\t\tif( cssExceptions[ property ] ) {\n\t\t\t\tfor( j = 0, k = cssExceptions[ property ].length; j < k; j++ ) {\n\t\t\t\t\tvalue = _getStyle( element, cssExceptions[ property ][ j ] );\n\n\t\t\t\t\tif( value ) {\n\t\t\t\t\t\treturn value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor( j = 0, k = vendorPrefixes.length; j < k; j++ ) {\n\t\t\t\tconvert = convertPropertyName( vendorPrefixes[ j ] + property );\n\n\t\t\t\t// VendorprefixKeyName || key-name\n\t\t\t\tvalue = _getStyle( element, convert );\n\n\t\t\t\tif( convert !== property ) {\n\t\t\t\t\tvalue = value || _getStyle( element, property );\n\t\t\t\t}\n\n\t\t\t\tif( vendorPrefixes[ j ] ) {\n\t\t\t\t\t// -vendorprefix-key-name\n\t\t\t\t\tvalue = value || _getStyle( element, vendorPrefixes[ j ] + property );\n\t\t\t\t}\n\n\t\t\t\tif( value ) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t};\n\t})();\n\n\n\n\t(function() {\n\t\tvar cssExceptions = shoestring.cssExceptions;\n\n\t\t// marginRight instead of margin-right\n\t\tfunction convertPropertyName( str ) {\n\t\t\treturn str.replace( /\\-([A-Za-z])/g, function ( match, character ) {\n\t\t\t\treturn character.toUpperCase();\n\t\t\t});\n\t\t}\n\n\t\t/**\n\t\t * Private function for setting the style of an element.\n\t\t *\n\t\t * **NOTE** Please use the [css](../css.js.html) method instead.\n\t\t *\n\t\t * @method _setStyle\n\t\t * @param {HTMLElement} element The element we want to style.\n\t\t * @param {string} property The property being used to style the element.\n\t\t * @param {string} value The css value for the style property.\n\t\t */\n\t\tshoestring._setStyle = function( element, property, value ) {\n\t\t\tvar convertedProperty = convertPropertyName(property);\n\n\t\t\telement.style[ property ] = value;\n\n\t\t\tif( convertedProperty !== property ) {\n\t\t\t\telement.style[ convertedProperty ] = value;\n\t\t\t}\n\n\t\t\tif( cssExceptions[ property ] ) {\n\t\t\t\tfor( var j = 0, k = cssExceptions[ property ].length; j -1 ){\n\t\t\t\t\tret.push( this );\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Find descendant elements of the current collection.\n\t *\n\t * @param {string} selector The selector used to find the children\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.find = function( selector ){\n\t\tvar ret = [],\n\t\t\tfinds;\n\t\tthis.each(function(){\n\t\t\t\tfinds = this.querySelectorAll( selector );\n\n\t\t\tfor( var i = 0, il = finds.length; i < il; i++ ){\n\t\t\t\tret = ret.concat( finds[i] );\n\t\t\t}\n\t\t});\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Returns the first element of the set wrapped in a new `shoestring` object.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.first = function(){\n\t\treturn this.eq( 0 );\n\t};\n\n\n\n\t/**\n\t * Returns the raw DOM node at the passed index.\n\t *\n\t * @param {integer} index The index of the element to wrap and return.\n\t * @return {HTMLElement|undefined|array}\n\t * @this shoestring\n\t */\n\tshoestring.fn.get = function( index ){\n\n\t\t// return an array of elements if index is undefined\n\t\tif( index === undefined ){\n\t\t\tvar elements = [];\n\n\t\t\tfor( var i = 0; i < this.length; i++ ){\n\t\t\t\telements.push( this[ i ] );\n\t\t\t}\n\n\t\t\treturn elements;\n\t\t} else {\n\t\t\treturn this[ index ];\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Private function for setting/getting the offset property for height/width.\n\t *\n\t * **NOTE** Please use the [width](width.js.html) or [height](height.js.html) methods instead.\n\t *\n\t * @param {shoestring} set The set of elements.\n\t * @param {string} name The string \"height\" or \"width\".\n\t * @param {float|undefined} value The value to assign.\n\t * @return shoestring\n\t * @this window\n\t */\n\tshoestring._dimension = function( set, name, value ){\n\t\tvar offsetName;\n\n\t\tif( value === undefined ){\n\t\t\toffsetName = name.replace(/^[a-z]/, function( letter ) {\n\t\t\t\treturn letter.toUpperCase();\n\t\t\t});\n\n\t\t\treturn set[ 0 ][ \"offset\" + offsetName ];\n\t\t} else {\n\t\t\t// support integer values as pixels\n\t\t\tvalue = typeof value === \"string\" ? value : value + \"px\";\n\n\t\t\treturn set.each(function(){\n\t\t\t\tthis.style[ name ] = value;\n\t\t\t});\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Gets the height value of the first element or sets the height for the whole set.\n\t *\n\t * @param {float|undefined} value The value to assign.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.height = function( value ){\n\t\treturn shoestring._dimension( this, \"height\", value );\n\t};\n\n\n\n\tvar set = function( html ){\n\t\tif( typeof html === \"string\" || typeof html === \"number\" ){\n\t\t\treturn this.each(function(){\n\t\t\t\tthis.innerHTML = \"\" + html;\n\t\t\t});\n\t\t} else {\n\t\t\tvar h = \"\";\n\t\t\tif( typeof html.length !== \"undefined\" ){\n\t\t\t\tfor( var i = 0, l = html.length; i < l; i++ ){\n\t\t\t\t\th += html[i].outerHTML;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\th = html.outerHTML;\n\t\t\t}\n\t\t\treturn this.each(function(){\n\t\t\t\tthis.innerHTML = h;\n\t\t\t});\n\t\t}\n\t};\n\t/**\n\t * Gets or sets the `innerHTML` from all the elements in the set.\n\t *\n\t * @param {string|undefined} html The html to assign\n\t * @return {string|shoestring}\n\t * @this shoestring\n\t */\n\tshoestring.fn.html = function( html ){\n\t\t\t\tif( typeof html !== \"undefined\" ){\n\t\t\treturn set.call( this, html );\n\t\t} else { // get\n\t\t\tvar pile = \"\";\n\n\t\t\tthis.each(function(){\n\t\t\t\tpile += this.innerHTML;\n\t\t\t});\n\n\t\t\treturn pile;\n\t\t}\n\t};\n\n\n\n\t(function() {\n\t\tfunction _getIndex( set, test ) {\n\t\t\tvar i, result, element;\n\n\t\t\tfor( i = result = 0; i < set.length; i++ ) {\n\t\t\t\telement = set.item ? set.item(i) : set[i];\n\n\t\t\t\tif( test(element) ){\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\t// ignore text nodes, etc\n\t\t\t\t// NOTE may need to be more permissive\n\t\t\t\tif( element.nodeType === 1 ){\n\t\t\t\t\tresult++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn -1;\n\t\t}\n\n\t\t/**\n\t\t * Find the index in the current set for the passed selector.\n\t\t * Without a selector it returns the index of the first node within the array of its siblings.\n\t\t *\n\t\t * @param {string|undefined} selector The selector used to search for the index.\n\t\t * @return {integer}\n\t\t * @this {shoestring}\n\t\t */\n\t\tshoestring.fn.index = function( selector ){\n\t\t\tvar self, children;\n\n\t\t\tself = this;\n\n\t\t\t// no arg? check the children, otherwise check each element that matches\n\t\t\tif( selector === undefined ){\n\t\t\t\tchildren = ( ( this[ 0 ] && this[0].parentNode ) || doc.documentElement).childNodes;\n\n\t\t\t\t// check if the element matches the first of the set\n\t\t\t\treturn _getIndex(children, function( element ) {\n\t\t\t\t\treturn self[0] === element;\n\t\t\t\t});\n\t\t\t} else {\n\n\t\t\t\t// check if the element matches the first selected node from the parent\n\t\t\t\treturn _getIndex(self, function( element ) {\n\t\t\t\t\treturn element === (shoestring( selector, element.parentNode )[ 0 ]);\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t})();\n\n\n\n\t/**\n\t * Insert the current set after the elements matching the selector.\n\t *\n\t * @param {string} selector The selector after which to insert the current set.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.insertAfter = function( selector ){\n\t\treturn this.each(function(){\n\t\t\tshoestring( selector ).after( this );\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Insert the current set before the elements matching the selector.\n\t *\n\t * @param {string} selector The selector before which to insert the current set.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.insertBefore = function( selector ){\n\t\treturn this.each(function(){\n\t\t\tshoestring( selector ).before( this );\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Returns the last element of the set wrapped in a new `shoestring` object.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.last = function(){\n\t\treturn this.eq( this.length - 1 );\n\t};\n\n\n\n\t/**\n\t * Returns a `shoestring` object with the set of siblings of each element in the original set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.next = function(){\n\t\t\n\t\tvar result = [];\n\n\t\t// TODO need to implement map\n\t\tthis.each(function() {\n\t\t\tvar children, item, found;\n\n\t\t\t// get the child nodes for this member of the set\n\t\t\tchildren = shoestring( this.parentNode )[0].childNodes;\n\n\t\t\tfor( var i = 0; i < children.length; i++ ){\n\t\t\t\titem = children.item( i );\n\n\t\t\t\t// found the item we needed (found) which means current item value is\n\t\t\t\t// the next node in the list, as long as it's viable grab it\n\t\t\t\t// NOTE may need to be more permissive\n\t\t\t\tif( found && item.nodeType === 1 ){\n\t\t\t\t\tresult.push( item );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// find the current item and mark it as found\n\t\t\t\tif( item === this ){\n\t\t\t\t\tfound = true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( result );\n\t};\n\n\n\n\t/**\n\t * Removes elements from the current set.\n\t *\n\t * @param {string} selector The selector to use when removing the elements.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.not = function( selector ){\n\t\tvar ret = [];\n\n\t\tthis.each(function(){\n\t\t\tvar found = shoestring( selector, this.parentNode );\n\n\t\t\tif( shoestring.inArray(this, found) === -1 ){\n\t\t\t\tret.push( this );\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\t/**\n\t * Returns an object with the `top` and `left` properties corresponging to the first elements offsets.\n\t *\n\t * @return object\n\t * @this shoestring\n\t */\n\tshoestring.fn.offset = function(){\n\t\treturn {\n\t\t\ttop: this[ 0 ].offsetTop,\n\t\t\tleft: this[ 0 ].offsetLeft\n\t\t};\n\t};\n\n\n\n\t/**\n\t * Returns the set of first parents for each element in the current set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.parent = function(){\n\t\tvar ret = [],\n\t\t\tparent;\n\n\t\tthis.each(function(){\n\t\t\t// no parent node, assume top level\n\t\t\t// jQuery parent: return the document object for or the parent node if it exists\n\t\t\tparent = (this === doc.documentElement ? doc : this.parentNode);\n\n\t\t\t// if there is a parent and it's not a document fragment\n\t\t\tif( parent && parent.nodeType !== 11 ){\n\t\t\t\tret.push( parent );\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring(ret);\n\t};\n\n\n\n\t/**\n\t * Returns the set of all parents matching the selector if provided for each element in the current set.\n\t *\n\t * @param {string} selector The selector to check the parents with.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.parents = function( selector ){\n\t\tvar ret = [];\n\n\t\tthis.each(function(){\n\t\t\tvar curr = this, match;\n\n\t\t\twhile( curr.parentElement && !match ){\n\t\t\t\tcurr = curr.parentElement;\n\n\t\t\t\tif( selector ){\n\t\t\t\t\tif( curr === shoestring( selector )[0] ){\n\t\t\t\t\t\tmatch = true;\n\n\t\t\t\t\t\tif( shoestring.inArray( curr, ret ) === -1 ){\n\t\t\t\t\t\t\tret.push( curr );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif( shoestring.inArray( curr, ret ) === -1 ){\n\t\t\t\t\t\tret.push( curr );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring(ret);\n\t};\n\n\n\n\t/**\n\t * Add an HTML string or element before the children of each element in the current set.\n\t *\n\t * @param {string|HTMLElement} fragment The HTML string or element to add.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.prepend = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" || fragment.nodeType !== undefined ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\treturn this.each(function( i ){\n\n\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\tvar insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ];\n\t\t\t\tif ( this.firstChild ){\n\t\t\t\t\tthis.insertBefore( insertEl, this.firstChild );\n\t\t\t\t} else {\n\t\t\t\t\tthis.appendChild( insertEl );\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Add each element of the current set before the children of the selected elements.\n\t *\n\t * @param {string} selector The selector for the elements to add the current set to..\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.prependTo = function( selector ){\n\t\treturn this.each(function(){\n\t\t\tshoestring( selector ).prepend( this );\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Returns a `shoestring` object with the set of *one* siblingx before each element in the original set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.prev = function(){\n\t\t\n\t\tvar result = [];\n\n\t\t// TODO need to implement map\n\t\tthis.each(function() {\n\t\t\tvar children, item, found;\n\n\t\t\t// get the child nodes for this member of the set\n\t\t\tchildren = shoestring( this.parentNode )[0].childNodes;\n\n\t\t\tfor( var i = children.length -1; i >= 0; i-- ){\n\t\t\t\titem = children.item( i );\n\n\t\t\t\t// found the item we needed (found) which means current item value is\n\t\t\t\t// the next node in the list, as long as it's viable grab it\n\t\t\t\t// NOTE may need to be more permissive\n\t\t\t\tif( found && item.nodeType === 1 ){\n\t\t\t\t\tresult.push( item );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// find the current item and mark it as found\n\t\t\t\tif( item === this ){\n\t\t\t\t\tfound = true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( result );\n\t};\n\n\n\n\t/**\n\t * Returns a `shoestring` object with the set of *all* siblings before each element in the original set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.prevAll = function(){\n\t\t\n\t\tvar result = [];\n\n\t\tthis.each(function() {\n\t\t\tvar $previous = shoestring( this ).prev();\n\n\t\t\twhile( $previous.length ){\n\t\t\t\tresult.push( $previous[0] );\n\t\t\t\t$previous = $previous.prev();\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( result );\n\t};\n\n\n\n\t// Property normalization, a subset taken from jQuery src\n\tshoestring.propFix = {\n\t\t\"class\": \"className\",\n\t\tcontenteditable: \"contentEditable\",\n\t\t\"for\": \"htmlFor\",\n\t\treadonly: \"readOnly\",\n\t\ttabindex: \"tabIndex\"\n\t};\n\n\n\n\t/**\n\t * Gets the property value from the first element or sets the property value on all elements of the currrent set.\n *\n\t * @param {string} name The property name.\n * @param {any} value The property value.\n\t * @return {any|shoestring}\n\t * @this shoestring\n\t */\n\tshoestring.fn.prop = function( name, value ){\n\t\tif( !this[0] ){\n\t\t\treturn;\n\t\t}\n\n\t\tname = shoestring.propFix[ name ] || name;\n\n\t\tif( value !== undefined ){\n\t\t\treturn this.each(function(){\n\t\t\t\tthis[ name ] = value;\n\t\t\t});\n\t\t}\telse {\n\t\t\treturn this[ 0 ][ name ];\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Remove an attribute from each element in the current set.\n\t *\n\t * @param {string} name The name of the attribute.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.removeAttr = function( name ){\n\t\treturn this.each(function(){\n\t\t\tthis.removeAttribute( name );\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Remove a class from each DOM element in the set of elements.\n\t *\n\t * @param {string} className The name of the class to be removed.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.removeClass = function( cname ){\n\t\tvar classes = cname.replace(/^\\s+|\\s+$/g, '').split( \" \" );\n\n\t\treturn this.each(function(){\n\t\t\tvar newClassName, regex;\n\n\t\t\tfor( var i = 0, il = classes.length; i < il; i++ ){\n\t\t\t\tif( this.className !== undefined ){\n\t\t\t\t\tregex = new RegExp( \"(^|\\\\s)\" + classes[ i ] + \"($|\\\\s)\", \"gmi\" );\n\t\t\t\t\tnewClassName = this.className.replace( regex, \" \" );\n\n\t\t\t\t\tthis.className = newClassName.replace(/^\\s+|\\s+$/g, '');\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Remove the current set of elements from the DOM.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.remove = function(){\n\t\treturn this.each(function(){\n\t\t\tif( this.parentNode ) {\n\t\t\t\tthis.parentNode.removeChild( this );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Remove a proprety from each element in the current set.\n\t *\n\t * @param {string} name The name of the property.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.removeProp = function( property ){\n\t\tvar name = shoestring.propFix[ property ] || property;\n\n\t\treturn this.each(function(){\n\t\t\tthis[ name ] = undefined;\n\t\t\tdelete this[ name ];\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Replace each element in the current set with that argument HTML string or HTMLElement.\n\t *\n\t * @param {string|HTMLElement} fragment The value to assign.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.replaceWith = function( fragment ){\n\t\tif( typeof( fragment ) === \"string\" ){\n\t\t\tfragment = shoestring( fragment );\n\t\t}\n\n\t\tvar ret = [];\n\n\t\tif( fragment.length > 1 ){\n\t\t\tfragment = fragment.reverse();\n\t\t}\n\t\tthis.each(function( i ){\n\t\t\tvar clone = this.cloneNode( true ),\n\t\t\t\tinsertEl;\n\t\t\tret.push( clone );\n\n\t\t\t// If there is no parentNode, this is pointless, drop it.\n\t\t\tif( !this.parentNode ){ return; }\n\n\t\t\tif( fragment.length === 1 ){\n\t\t\t\tinsertEl = i > 0 ? fragment[ 0 ].cloneNode( true ) : fragment[ 0 ];\n\t\t\t\tthis.parentNode.replaceChild( insertEl, this );\n\t\t\t} else {\n\t\t\t\tfor( var j = 0, jl = fragment.length; j < jl; j++ ){\n\t\t\t\t\tinsertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ];\n\t\t\t\t\tthis.parentNode.insertBefore( insertEl, this.nextSibling );\n\t\t\t\t}\n\t\t\t\tthis.parentNode.removeChild( this );\n\t\t\t}\n\t\t});\n\n\t\treturn shoestring( ret );\n\t};\n\n\n\n\tshoestring.inputTypes = [\n\t\t\"text\",\n\t\t\"hidden\",\n\t\t\"password\",\n\t\t\"color\",\n\t\t\"date\",\n\t\t\"datetime\",\n\t\t// \"datetime\\-local\" matched by datetime\n\t\t\"email\",\n\t\t\"month\",\n\t\t\"number\",\n\t\t\"range\",\n\t\t\"search\",\n\t\t\"tel\",\n\t\t\"time\",\n\t\t\"url\",\n\t\t\"week\"\n\t];\n\n\tshoestring.inputTypeTest = new RegExp( shoestring.inputTypes.join( \"|\" ) );\n\n\n\t/**\n\t * Serialize child input element values into an object.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.serialize = function(){\n\t\tvar data = {};\n\n\t\tshoestring( \"input, select\", this ).each(function(){\n\t\t\tvar type = this.type, name = this.name,\tvalue = this.value;\n\n\t\t\tif( shoestring.inputTypeTest.test( type ) ||\n\t\t\t\t\t( type === \"checkbox\" || type === \"radio\" ) &&\n\t\t\t\t\tthis.checked ){\n\n\t\t\t\tdata[ name ] = value;\n\t\t\t}\telse if( this.nodeName === \"SELECT\" ){\n\t\t\t\tdata[ name ] = this.options[ this.selectedIndex ].nodeValue;\n\t\t\t}\n\t\t});\n\n\t\treturn data;\n\t};\n\n\n\n /**\n\t * Get all of the sibling elements for each element in the current set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.siblings = function(){\n\t\t\n\t\tif( !this.length ) {\n\t\t\treturn shoestring( [] );\n\t\t}\n\n\t\tvar sibs = [], el = this[ 0 ].parentNode.firstChild;\n\n\t\tdo {\n\t\t\tif( el.nodeType === 1 && el !== this[ 0 ] ) {\n\t\t\t\tsibs.push( el );\n\t\t\t}\n\n el = el.nextSibling;\n\t\t} while( el );\n\n\t\treturn shoestring( sibs );\n\t};\n\n\n\n\tvar getText = function( elem ){\n\t\tvar node,\n\t\t\tret = \"\",\n\t\t\ti = 0,\n\t\t\tnodeType = elem.nodeType;\n\n\t\tif ( !nodeType ) {\n\t\t\t// If no nodeType, this is expected to be an array\n\t\t\twhile ( (node = elem[i++]) ) {\n\t\t\t\t// Do not traverse comment nodes\n\t\t\t\tret += getText( node );\n\t\t\t}\n\t\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\t\t\t// Use textContent for elements\n\t\t\t// innerText usage removed for consistency of new lines (jQuery #11153)\n\t\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\t\treturn elem.textContent;\n\t\t\t} else {\n\t\t\t\t// Traverse its children\n\t\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\t\tret += getText( elem );\n\t\t\t\t}\n\t\t\t}\n\t\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\t\treturn elem.nodeValue;\n\t\t}\n\t\t// Do not include comment or processing instruction nodes\n\n\t\treturn ret;\n\t};\n\n /**\n\t * Recursively retrieve the text content of the each element in the current set.\n\t *\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.text = function() {\n\t\t\n\t\treturn getText( this );\n\t};\n\n\n\n\n\t/**\n\t * Get the value of the first element or set the value of all elements in the current set.\n\t *\n\t * @param {string} value The value to set.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.val = function( value ){\n\t\tvar el;\n\t\tif( value !== undefined ){\n\t\t\treturn this.each(function(){\n\t\t\t\tif( this.tagName === \"SELECT\" ){\n\t\t\t\t\tvar optionSet, option,\n\t\t\t\t\t\toptions = this.options,\n\t\t\t\t\t\tvalues = [],\n\t\t\t\t\t\ti = options.length,\n\t\t\t\t\t\tnewIndex;\n\n\t\t\t\t\tvalues[0] = value;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\toption = options[ i ];\n\t\t\t\t\t\tif ( (option.selected = shoestring.inArray( option.value, values ) >= 0) ) {\n\t\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t\t\tnewIndex = i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// force browsers to behave consistently when non-matching value is set\n\t\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\t\tthis.selectedIndex = -1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.selectedIndex = newIndex;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.value = value;\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tel = this[0];\n\n\t\t\tif( el.tagName === \"SELECT\" ){\n\t\t\t\tif( el.selectedIndex < 0 ){ return \"\"; }\n\t\t\t\treturn el.options[ el.selectedIndex ].value;\n\t\t\t} else {\n\t\t\t\treturn el.value;\n\t\t\t}\n\t\t}\n\t};\n\n\n\n\t/**\n\t * Gets the width value of the first element or sets the width for the whole set.\n\t *\n\t * @param {float|undefined} value The value to assign.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.width = function( value ){\n\t\treturn shoestring._dimension( this, \"width\", value );\n\t};\n\n\n\n\t/**\n\t * Wraps the child elements in the provided HTML.\n\t *\n\t * @param {string} html The wrapping HTML.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.wrapInner = function( html ){\n\t\treturn this.each(function(){\n\t\t\tvar inH = this.innerHTML;\n\n\t\t\tthis.innerHTML = \"\";\n\t\t\tshoestring( this ).append( shoestring( html ).html( inH ) );\n\t\t});\n\t};\n\n\n\n\tfunction initEventCache( el, evt ) {\n\t\tif ( !el.shoestringData ) {\n\t\t\tel.shoestringData = {};\n\t\t}\n\t\tif ( !el.shoestringData.events ) {\n\t\t\tel.shoestringData.events = {};\n\t\t}\n\t\tif ( !el.shoestringData.loop ) {\n\t\t\tel.shoestringData.loop = {};\n\t\t}\n\t\tif ( !el.shoestringData.events[ evt ] ) {\n\t\t\tel.shoestringData.events[ evt ] = [];\n\t\t}\n\t}\n\n\tfunction addToEventCache( el, evt, eventInfo ) {\n\t\tvar obj = {};\n\t\tobj.isCustomEvent = eventInfo.isCustomEvent;\n\t\tobj.callback = eventInfo.callfunc;\n\t\tobj.originalCallback = eventInfo.originalCallback;\n\t\tobj.namespace = eventInfo.namespace;\n\n\t\tel.shoestringData.events[ evt ].push( obj );\n\n\t\tif( eventInfo.customEventLoop ) {\n\t\t\tel.shoestringData.loop[ evt ] = eventInfo.customEventLoop;\n\t\t}\n\t}\n\n\t/**\n\t * Bind a callback to an event for the currrent set of elements.\n\t *\n\t * @param {string} evt The event(s) to watch for.\n\t * @param {object,function} data Data to be included with each event or the callback.\n\t * @param {function} originalCallback Callback to be invoked when data is define.d.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.bind = function( evt, data, originalCallback ){\n\n\t\t\t\tif( typeof data === \"function\" ){\n\t\t\toriginalCallback = data;\n\t\t\tdata = null;\n\t\t}\n\n\t\tvar evts = evt.split( \" \" );\n\n\t\t// NOTE the `triggeredElement` is purely for custom events from IE\n\t\tfunction encasedCallback( e, namespace, triggeredElement ){\n\t\t\tvar result;\n\n\t\t\tif( e._namespace && e._namespace !== namespace ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\te.data = data;\n\t\t\te.namespace = e._namespace;\n\n\t\t\tvar returnTrue = function(){\n\t\t\t\treturn true;\n\t\t\t};\n\n\t\t\te.isDefaultPrevented = function(){\n\t\t\t\treturn false;\n\t\t\t};\n\n\t\t\tvar originalPreventDefault = e.preventDefault;\n\t\t\tvar preventDefaultConstructor = function(){\n\t\t\t\tif( originalPreventDefault ) {\n\t\t\t\t\treturn function(){\n\t\t\t\t\t\te.isDefaultPrevented = returnTrue;\n\t\t\t\t\t\toriginalPreventDefault.call(e);\n\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\treturn function(){\n\t\t\t\t\t\te.isDefaultPrevented = returnTrue;\n\t\t\t\t\t\te.returnValue = false;\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// thanks https://github.com/jonathantneal/EventListener\n\t\t\te.target = triggeredElement || e.target || e.srcElement;\n\t\t\te.preventDefault = preventDefaultConstructor();\n\t\t\te.stopPropagation = e.stopPropagation || function () {\n\t\t\t\te.cancelBubble = true;\n\t\t\t};\n\n\t\t\tresult = originalCallback.apply(this, [ e ].concat( e._args ) );\n\n\t\t\tif( result === false ){\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\treturn this.each(function(){\n\t\t\tvar domEventCallback,\n\t\t\t\tcustomEventCallback,\n\t\t\t\tcustomEventLoop,\n\t\t\t\toEl = this;\n\n\t\t\tfor( var i = 0, il = evts.length; i < il; i++ ){\n\t\t\t\tvar split = evts[ i ].split( \".\" ),\n\t\t\t\t\tevt = split[ 0 ],\n\t\t\t\t\tnamespace = split.length > 0 ? split[ 1 ] : null;\n\n\t\t\t\tdomEventCallback = function( originalEvent ) {\n\t\t\t\t\tif( oEl.ssEventTrigger ) {\n\t\t\t\t\t\toriginalEvent._namespace = oEl.ssEventTrigger._namespace;\n\t\t\t\t\t\toriginalEvent._args = oEl.ssEventTrigger._args;\n\n\t\t\t\t\t\toEl.ssEventTrigger = null;\n\t\t\t\t\t}\n\t\t\t\t\treturn encasedCallback.call( oEl, originalEvent, namespace );\n\t\t\t\t};\n\t\t\t\tcustomEventCallback = null;\n\t\t\t\tcustomEventLoop = null;\n\n\t\t\t\tinitEventCache( this, evt );\n\n\t\t\t\tthis.addEventListener( evt, domEventCallback, false );\n\n\t\t\t\taddToEventCache( this, evt, {\n\t\t\t\t\tcallfunc: customEventCallback || domEventCallback,\n\t\t\t\t\tisCustomEvent: !!customEventCallback,\n\t\t\t\t\tcustomEventLoop: customEventLoop,\n\t\t\t\t\toriginalCallback: originalCallback,\n\t\t\t\t\tnamespace: namespace\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t};\n\n\tshoestring.fn.on = shoestring.fn.bind;\n\n\t\n\n\n\t/**\n\t * Unbind a previous bound callback for an event.\n\t *\n\t * @param {string} event The event(s) the callback was bound to..\n\t * @param {function} callback Callback to unbind.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.unbind = function( event, callback ){\n\n\t\t\n\t\tvar evts = event ? event.split( \" \" ) : [];\n\n\t\treturn this.each(function(){\n\t\t\tif( !this.shoestringData || !this.shoestringData.events ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif( !evts.length ) {\n\t\t\t\tunbindAll.call( this );\n\t\t\t} else {\n\t\t\t\tvar split, evt, namespace;\n\t\t\t\tfor( var i = 0, il = evts.length; i < il; i++ ){\n\t\t\t\t\tsplit = evts[ i ].split( \".\" ),\n\t\t\t\t\tevt = split[ 0 ],\n\t\t\t\t\tnamespace = split.length > 0 ? split[ 1 ] : null;\n\n\t\t\t\t\tif( evt ) {\n\t\t\t\t\t\tunbind.call( this, evt, namespace, callback );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tunbindAll.call( this, namespace, callback );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\tfunction unbind( evt, namespace, callback ) {\n\t\tvar bound = this.shoestringData.events[ evt ];\n\t\tif( !(bound && bound.length) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar matched = [], j, jl;\n\t\tfor( j = 0, jl = bound.length; j < jl; j++ ) {\n\t\t\tif( !namespace || namespace === bound[ j ].namespace ) {\n\t\t\t\tif( callback === undefined || callback === bound[ j ].originalCallback ) {\n\t\t\t\t\tthis.removeEventListener( evt, bound[ j ].callback, false );\n\t\t\t\t\tmatched.push( j );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor( j = 0, jl = matched.length; j < jl; j++ ) {\n\t\t\tthis.shoestringData.events[ evt ].splice( j, 1 );\n\t\t}\n\t}\n\n\tfunction unbindAll( namespace, callback ) {\n\t\tfor( var evtKey in this.shoestringData.events ) {\n\t\t\tunbind.call( this, evtKey, namespace, callback );\n\t\t}\n\t}\n\n\tshoestring.fn.off = shoestring.fn.unbind;\n\n\n\t/**\n\t * Bind a callback to an event for the currrent set of elements, unbind after one occurence.\n\t *\n\t * @param {string} event The event(s) to watch for.\n\t * @param {function} callback Callback to invoke on the event.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.one = function( event, callback ){\n\t\tvar evts = event.split( \" \" );\n\n\t\treturn this.each(function(){\n\t\t\tvar thisevt, cbs = {},\t$t = shoestring( this );\n\n\t\t\tfor( var i = 0, il = evts.length; i < il; i++ ){\n\t\t\t\tthisevt = evts[ i ];\n\n\t\t\t\tcbs[ thisevt ] = function( e ){\n\t\t\t\t\tvar $t = shoestring( this );\n\n\t\t\t\t\tfor( var j in cbs ) {\n\t\t\t\t\t\t$t.unbind( j, cbs[ j ] );\n\t\t\t\t\t}\n\n\t\t\t\t\treturn callback.apply( this, [ e ].concat( e._args ) );\n\t\t\t\t};\n\n\t\t\t\t$t.bind( thisevt, cbs[ thisevt ] );\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\t/**\n\t * Trigger an event on the first element in the set, no bubbling, no defaults.\n\t *\n\t * @param {string} event The event(s) to trigger.\n\t * @param {object} args Arguments to append to callback invocations.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.triggerHandler = function( event, args ){\n\t\tvar e = event.split( \" \" )[ 0 ],\n\t\t\tel = this[ 0 ],\n\t\t\tret;\n\n\t\t// See this.fireEvent( 'on' + evts[ i ], document.createEventObject() ); instead of click() etc in trigger.\n\t\tif( doc.createEvent && el.shoestringData && el.shoestringData.events && el.shoestringData.events[ e ] ){\n\t\t\tvar bindings = el.shoestringData.events[ e ];\n\t\t\tfor (var i in bindings ){\n\t\t\t\tif( bindings.hasOwnProperty( i ) ){\n\t\t\t\t\tevent = doc.createEvent( \"Event\" );\n\t\t\t\t\tevent.initEvent( e, true, true );\n\t\t\t\t\tevent._args = args;\n\t\t\t\t\targs.unshift( event );\n\n\t\t\t\t\tret = bindings[ i ].originalCallback.apply( event.target, args );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t};\n\n\n\n\t/**\n\t * Trigger an event on each of the DOM elements in the current set.\n\t *\n\t * @param {string} event The event(s) to trigger.\n\t * @param {object} args Arguments to append to callback invocations.\n\t * @return shoestring\n\t * @this shoestring\n\t */\n\tshoestring.fn.trigger = function( event, args ){\n\t\tvar evts = event.split( \" \" );\n\n\t\treturn this.each(function(){\n\t\t\tvar split, evt, namespace;\n\t\t\tfor( var i = 0, il = evts.length; i < il; i++ ){\n\t\t\t\tsplit = evts[ i ].split( \".\" ),\n\t\t\t\tevt = split[ 0 ],\n\t\t\t\tnamespace = split.length > 0 ? split[ 1 ] : null;\n\n\t\t\t\tif( evt === \"click\" ){\n\t\t\t\t\tif( this.tagName === \"INPUT\" && this.type === \"checkbox\" && this.click ){\n\t\t\t\t\t\tthis.click();\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif( doc.createEvent ){\n\t\t\t\t\tvar event = doc.createEvent( \"Event\" );\n\t\t\t\t\tevent.initEvent( evt, true, true );\n\t\t\t\t\tevent._args = args;\n\t\t\t\t\tevent._namespace = namespace;\n\n\t\t\t\t\tthis.dispatchEvent( event );\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\n\n\treturn shoestring;\n}));\n\nif( !window.jQuery ){\n window.jQuery = window.jQuery || window.shoestring;\n}\n\n(function(exports){\n\t/**\n\t * The Akamai component namespace contains the {@link Akamai.Viewer}, {@link\n\t * Akamai.Spin360}, {@link Akamai.Carousel}, and {@link Akamai.Zoom} components.\n\t * The {@link Akamai.Viewer} component manages the other components depending on\n\t * the state of the markup it is provided, start there for more.\n\t *\n\t * @example On-ready automatic initialization with jQuery\n\t * $(function(){\n\t *\t $( \"[data-akamai-viewer]\" ).each(function(i, element){\n\t *\t\t var viewer = Akamai.Viewer( element );\n\t *\t\t ...\n\t *\t\t // example API use, stoping autoration of first {@link Akamai.Spin360}\n\t *\t\t viewer.getCarousels()[0].getSpin360s()[0].stopSpin();\n\t *\t })\n\t * })\n\t *\n\t * @namespace Akamai\n\t */\n\texports.Akamai = exports.Akamai || {};\n\twindow.componentNamespace = \"Akamai\";\n})(typeof exports === 'undefined'? window : exports);\n\n(function( window, $ ) {\n var $window, $doc;\n\n $window = $(window);\n $doc = $( document.documentElement );\n\n var ns = window.componentNamespace = window.componentNamespace || \"FG\";\n window[ns] = window[ns] || {};\n\n Function.prototype.bind = Function.prototype.bind || function( context ) {\n var self = this;\n\n return function() {\n self.apply( context, arguments );\n };\n };\n\n var Tau = window[ns].Tau = function( element, options ) {\n var startIndex, reducedStepSize;\n\n this.element = element;\n this.options = options || {};\n this.$element = $( element );\n this.$initial = this.$element.find( \"img\" );\n this.$loading = this.$element.find( \".loading\" );\n this.index = 0;\n\n // frame count by order of precendence\n // 1. initial frames when they are specified explicitly\n // 2. the data attribute on the initial image\n // 3. the configured number of frames\n this.frames =\n this.$initial.length > 1 ? this.$initial.length :\n parseInt( this.$initial.attr(\"data-frames\"), 10 ) ||\n this.options.frames;\n\n // grab the user specified step size for when the browser is less-abled\n reducedStepSize = parseInt( this.$initial.attr(\"data-reduced-step-size\"), 10 ) || 4;\n\n // TODO sort out a better qualification for the full set of images?\n this.stepSize = window.requestAnimationFrame ? 1 : reducedStepSize;\n\n // grab the user specified auto start delay\n this.autoRotateStartDelay =\n (this.options.autoplay || {}).delay ||\n parseInt( this.$initial.attr(\"data-auto-rotate-delay\"), 10 ) ||\n Tau.autoRotateStartDelay;\n\n this.mouseMoveBinding = this.rotateEvent.bind(this);\n this.touchMoveBinding = this.rotateEvent.bind(this);\n\n this.path = new Tau.Path();\n\n // make sure the initial image stays visible after enhance\n this.$initial.first().addClass( \"focused\" );\n\n // hide all other images\n this.$element.addClass( \"tau-enhanced\" );\n\n // create a rendering spot to force decoding in IE and prevent blinking\n this.$render = $( \"
\" )\n .css( \"position\", \"absolute\" )\n .css( \"left\", \"0\" )\n .css( \"top\", \"0\" )\n .prependTo( this.element );\n\n if( this.options.canvas !== false ){\n this.canvas = $( \"\").prependTo( this.element )[0];\n\n if(this.canvas.getContext ){\n this.canvasCtx = this.canvas.getContext(\"2d\");\n this.$element.addClass( \"tau-canvas\" );\n\n $(window).bind(\"resize\", function(){\n clearTimeout(this.canvasResizeTimeout);\n this.canvasResizeTimeout = setTimeout(this.renderCanvas.bind(this), 100);\n }.bind(this));\n }\n }\n\n if( this.options.controls ){\n this.options.controls.text = this.options.controls.text || {\n play: \"Spin Object\",\n left: \"Rotate Left\",\n right: \"Rotate Right\"\n };\n\n this.createControls();\n }\n\n // create the rest of the images\n this.createImages();\n\n // set the initial index and image\n if( this.options.autoplay && this.options.autoplay.enabled ){\n // start the automatic rotation\n this.autostartTimeout = setTimeout(this.autoRotate.bind(this), this.autoRotateStartDelay);\n }\n\n // setup the event bindings for touch drag and mouse drag rotation\n this.bind();\n };\n\n Tau.autoRotateTraversalTime = 4500;\n Tau.autoRotateStartDelay = 100;\n Tau.verticalScrollRatio = 4;\n // Tau.decelTimeStep = Tau.autoRotateDelay / 2;\n // Tau.decel = Tau.decelTimeStep / 8;\n Tau.maxVelocity = 60;\n\n Tau.prototype.createControls = function(){\n this.$controls = $(\"
\");\n\n if(this.options.controls.play){\n this.$controls.append(this.controlAnchorMarkup(\"play\"));\n }\n\n if(this.options.controls.arrows){\n this.$controls\n .prepend(this.controlAnchorMarkup(\"left\"))\n .append(this.controlAnchorMarkup(\"right\"));\n }\n\n this.$controls.bind(\"mousedown touchstart\", this.onControlDown.bind(this));\n this.$controls.bind(\"mouseup\", this.onControlUp.bind(this));\n\n // prevent link clicks from bubbling\n this.$controls.bind(\"click\", function(event){\n if( $(event.target).is(\"a\") ){\n event.preventDefault();\n }\n });\n\n this.$element.append(this.$controls);\n };\n\n Tau.prototype.controlAnchorMarkup = function(name){\n var text = this.options.controls.text[name];\n\n return \"\" + text +\n \"\";\n };\n\n Tau.prototype.onControlDown = function(event){\n var $link = $(event.target).closest(\"a\");\n\n switch($link.attr(\"data-tau-controls\")){\n case \"left\":\n this.$element.addClass(\"control-left-down\");\n this.stopAutoRotate();\n this.autoRotate();\n break;\n case \"right\":\n this.$element.addClass(\"control-right-down\");\n this.stopAutoRotate();\n this.autoRotate(true);\n break;\n }\n };\n\n Tau.prototype.onControlUp = function(event){\n var $link = $(event.target).closest(\"a\");\n\n switch($link.attr(\"data-tau-controls\")){\n case \"left\":\n case \"right\":\n this.$element.removeClass(\"control-left-down\");\n this.$element.removeClass(\"control-right-down\");\n this.stopAutoRotate();\n break;\n case \"play\":\n if( this.autoInterval ){\n this.stopAutoRotate();\n } else {\n this.autoRotate();\n }\n break;\n }\n };\n\n Tau.prototype.change = function( delta ) {\n this.goto( this.options.reverse ? this.index - delta : this.index + delta );\n };\n\n Tau.prototype.goto = function( index ) {\n var $next, normalizedIndex, imageCount = this.$images.length;\n\n index = index % imageCount;\n\n // stay within the bounds of the array\n normalizedIndex = (imageCount + index) % imageCount;\n\n // set the next image that's going to be shown/focused\n $next = this.$images.eq( normalizedIndex );\n\n // skip this action if the desired image isn't loaded yet\n // TODO do something fancier here instead of just throwing up hands\n if( !$next[0].tauImageLoaded ) {\n this.showLoading();\n return false;\n }\n\n // hide any image that happens to be visible (initial image when canvas)\n if( this.$current ) {\n this.$current.removeClass( \"focused\" );\n } else {\n this.$images.removeClass( \"focused\" );\n }\n\n // record the current focused image and make it visible\n this.$current = $next;\n\n // record the updated index only after advancing is possible\n this.index = normalizedIndex;\n\n if( this.canvasCtx ) {\n return this.renderCanvas();\n } else {\n // show the new focused image\n this.$current.addClass( \"focused\" );\n return true;\n }\n };\n\n Tau.prototype.renderCanvas = function() {\n var $img = this.$current;\n var img = $img[0];\n\n var width = img.naturalWidth;\n var height = img.naturalHeight;\n\n var parentWidth = this.element.clientWidth;\n var calcHeight = (parentWidth/width) * height;\n\n if(!width || !height || !img.complete){\n return false;\n }\n\n if( this.canvas.width !== parentWidth ||\n this.canvas.height !== calcHeight || (parentWidth && calcHeight) ) {\n this.canvas.width = parentWidth;\n this.canvas.height = calcHeight;\n }\n\n this.canvasCtx.drawImage(img, 0, 0, parentWidth, calcHeight);\n\n return true;\n };\n\n // TODO transplant the attributes from the initial image\n Tau.prototype.createImages = function() {\n var src, frames, html, $new, boundImageLoaded;\n\n // if there are no image elements, raise an exception\n if( this.$initial.length < 1 ){\n throw new Error( \"At least one image required\" );\n }\n\n this.loadedCount = 0;\n\n // if there is only one image element, assume it's a template\n if( this.$initial.length == 1 ) {\n this.markImageLoaded( this.$initial[0] );\n\n src =\n this.options.template ||\n this.$initial.attr( \"data-src-template\" );\n\n var imgs = [];\n for( var i = this.stepSize + 1; i <= this.frames; i+= this.stepSize ) {\n html = \"\";\n $new = $( html );\n imgs.push($new);\n }\n\n $.each(imgs, function(i, e){\n var $img = $(e);\n\n $img.bind(\"load error\", function(e){ this.imageLoaded(i, e.target, e); }.bind(this));\n\n this.$element.append( $img );\n this.$render.append( $img.html() );\n }.bind(this));\n\n // take all the child images and use them as frames of the rotation\n this.$images = this.$element.children().filter( \"img\" );\n this.$current = this.$images;\n this.goto(0);\n } else {\n // take all the child images and use them as frames of the rotation\n this.$images = this.$element.children().filter( \"img\" );\n\n this.$images.each(function(i, e){\n // if the image height is greater than zero we assume the image is loaded\n // otherwise we bind to onload and pray that we win the race\n if( $(e).height() > 0 ){\n this.imageLoaded( i, e );\n } else {\n $(e).bind(\"load error\", function(event){\n this.imageLoaded( i, event.target, event );\n }.bind(this));\n }\n }.bind(this));\n }\n };\n\n\n Tau.prototype.imageLoaded = function( index, element, event ) {\n var initTriggered = false;\n this.markImageLoaded( element );\n\n // if the isn't going to play automatically and the first image is\n // loaded make sure to render it\n if( this.$element.find(\"img\")[0] == element &&\n (!event || event.type !== \"error\") &&\n (!this.options.autoplay || !this.options.autoplay.enabled) ){\n this.goto(0);\n this.$element.trigger(\"tau.init\");\n initTriggered = true;\n }\n\n this.loadedCount++;\n\n if( this.loadedCount >= this.frames - 1) {\n this.hideLoading();\n\n if(!initTriggered) {\n this.$element.trigger(\"tau.init\");\n this.initialized = true;\n }\n }\n };\n\n Tau.prototype.markImageLoaded = function( element ) {\n element.tauImageLoaded = true;\n };\n\n Tau.prototype.bind = function() {\n this.$element.bind( \"mousedown touchstart\", this.track.bind(this) );\n };\n\n Tau.prototype.autoRotate = function( right ) {\n // already rotating\n if( this.autoInterval ) {\n return;\n }\n\n this.$element.addClass(\"spinning\");\n\n // move once initially\n this.change( right ? -1 : 1 );\n\n // move after the interval\n this.autoInterval = setInterval(function() {\n this.change( right ? -1 : 1 );\n }.bind(this), this.autoRotateDelay() * this.stepSize);\n\n this.$element.trigger( \"tau.auto-rotate-start\" );\n };\n\n Tau.prototype.autoRotateDelay = function(){\n return (this.options.interval || Tau.autoRotateTraversalTime) / this.frames;\n };\n\n Tau.prototype.stopAutoRotate = function() {\n clearInterval( this.autoInterval );\n clearInterval( this.autostartTimeout );\n this.$element.removeClass(\"spinning\");\n this.autoInterval = undefined;\n this.$element.trigger( \"tau.auto-rotate-stop\" );\n };\n\n Tau.prototype.track = function( event ) {\n var point;\n\n // ignore tracking on control clicks\n if( $(event.target).closest(\".tau-controls\").length ){\n return;\n }\n\n // prevent dragging behavior for mousedown\n if( event.type === \"mousedown\" ){\n event.preventDefault();\n }\n\n if( event.type === \"touchstart\" ) {\n this.$element.trigger(\"tau.touch-tracking-start\");\n } else {\n this.$element.trigger(\"tau.mouse-tracking-start\");\n }\n\n if( this.tracking ) {\n return;\n }\n\n $doc.one( \"mouseup\", this.release.bind(this) );\n $doc.one( \"touchend\", this.release.bind(this) );\n\n this.tracking = true;\n\n // clean out the path since we'll need a new one for decel\n this.path.reset();\n\n // show the cursor as grabbing\n this.cursorGrab();\n\n // By default the number of pixels required to move the carousel by one\n // frame is the ratio of the tau element width to the number of frames. That\n // is, by default the user should be able to see the full rotation by moving\n // their input device from one side of the tau element to the other.\n var defaultThreshold = this.$element[0].clientWidth / this.frames ;\n\n // divide the default by the sensitivity. If the senstivity is greater than\n // 1 it will require less effort (smaller distance) to advance the rotation\n // by a single slide. If the sensitivity is less than 1 it will require more\n // effort\n this.rotateThreshold = defaultThreshold / (this.options.sensitivity || 1);\n\n // record the x for threshold calculations\n point = this.getPoint( event );\n this.downX = point.x;\n this.downY = point.y;\n this.downIndex = this.index;\n\n $doc.bind( \"mousemove\", this.mouseMoveBinding );\n $doc.bind( \"touchmove\", this.touchMoveBinding );\n };\n\n Tau.prototype.slow = function() {\n // if the path gets broken during the decel just stop\n if( !this.path.isSufficient() ) {\n this.clearSlowInterval();\n return;\n }\n\n this.rotate({\n x: this.path.last().x + this.velocity,\n y: this.path.last().y\n });\n\n if( this.velocity > 0 ){\n this.velocity = this.velocity - this.decelVal();\n\n if( this.velocity <= 0 ){\n this.clearSlowInterval();\n }\n } else {\n this.velocity = this.velocity + this.decelVal();\n\n if( this.velocity >= 0 ){\n this.clearSlowInterval();\n }\n }\n };\n\n Tau.prototype.decelVal = function(){\n return this.decelTimeStep() / 8;\n };\n\n Tau.prototype.clearSlowInterval = function() {\n clearInterval(this.slowInterval);\n this.velocity = 0;\n this.slowInterval = undefined;\n };\n\n Tau.prototype.decel = function() {\n var velocity, sign;\n\n // if we don't have two points of mouse or touch tracking this won't work\n if( !this.path.isSufficient() ) {\n return;\n }\n\n // determine the starting velocity based on the traced path\n velocity = this.path.velocity( this.decelTimeStep() );\n\n // borrowed from http://stackoverflow.com/questions/7624920/number-sign-in-javascript\n sign = velocity > 0 ? 1 : velocity < 0 ? -1 : 0;\n\n // keep a lid on how fast the rotation spins out\n if( Math.abs(velocity) > Tau.maxVelocity ){\n velocity = sign * Tau.maxVelocity;\n }\n\n this.velocity = velocity;\n this.slowInterval = setInterval(this.slow.bind(this), this.decelTimeStep());\n };\n\n Tau.prototype.decelTimeStep = function(){\n return this.autoRotateDelay() / 2;\n };\n\n Tau.prototype.release = function( event ) {\n if( $(event.target).closest(\".tau-controls\").length ){\n return;\n }\n\n if( !this.tracking ){\n return;\n }\n\n if( event.type === \"touchend\" ) {\n this.$element.trigger(\"tau.touch-tracking-stop\");\n } else {\n this.$element.trigger(\"tau.mouse-tracking-stop\");\n }\n\n this.decel();\n\n this.cursorRelease();\n\n // TODO sort out why shoestring borks when unbinding with a string split list\n $doc.unbind( \"mousemove\", this.mouseMoveBinding );\n $doc.unbind( \"touchmove\", this.touchMoveBinding );\n\n this.tracking = false;\n };\n\n Tau.prototype.cursorGrab = function() {\n $doc.addClass( \"grabbing\" );\n };\n\n Tau.prototype.cursorRelease = function() {\n $doc.removeClass( \"grabbing\" );\n };\n\n Tau.prototype.showLoading = function() {\n this.$loading.attr( \"style\" , \"display: block\" );\n };\n\n Tau.prototype.hideLoading = function() {\n this.$loading.attr( \"style\" , \"display: none\" );\n };\n\n Tau.prototype.getPoint = function( event ) {\n var touch = event.touches || (event.originalEvent && event.originalEvent.touches);\n\n if( touch ){\n return {\n x: touch[0].pageX,\n y: touch[0].pageY\n };\n }\n\n return {\n x: event.pageX || event.clientX,\n y: event.pageY || event.clientY\n };\n };\n\n Tau.prototype.rotateEvent = function( event ) {\n // NOTE it might be better to prevent when the rotation returns anything BUT false\n // so that slow drags still get the scroll prevented\n if( this.rotate(this.getPoint(event)) ){\n event.preventDefault();\n }\n };\n\n Tau.prototype.rotate = function( point ) {\n var deltaX, deltaY;\n\n deltaX = point.x - this.downX;\n deltaY = point.y - this.downY;\n\n // if the movement on the Y dominates X then skip and allow scroll\n if( Math.abs(deltaY) / Math.abs(deltaX) >= Tau.verticalScrollRatio ) {\n return false;\n }\n\n // since we're rotating record the point for decel\n this.path.record( point );\n\n // NOTE to reverse the spin direction add the delta/thresh to the downIndex\n if( Math.abs(deltaX) >= this.rotateThreshold ) {\n // NOTE works better on mousedown, here allows autorotate to continue\n this.stopAutoRotate();\n\n var index;\n\n if( this.options.reverse ) {\n index = this.downIndex + Math.round(deltaX / this.rotateThreshold);\n } else {\n index = this.downIndex - Math.round(deltaX / this.rotateThreshold);\n }\n\n this.goto( index );\n\n return true;\n }\n };\n})(this, jQuery);\n\n(function( window, $ ) {\n var ns = window.componentNamespace = window.componentNamespace || \"FG\";\n window[ns] = window[ns] || {};\n\n // IE 8\n Date.now = Date.now || function now() {\n return new Date().getTime();\n };\n\n var Path = window[ns].Tau.Path = function() {\n this.reset();\n };\n\n Path.prototype.isSufficient = function() {\n return !!this.prevPoint && this.prevPrevPoint;\n };\n\n Path.prototype.distance = function() {\n return this.prevPoint.x - this.prevPrevPoint.x;\n };\n\n Path.prototype.duration = function() {\n return this.prevTime - this.prevPrevTime;\n };\n\n // TODO sort out variable names\n Path.prototype.record = function( point ) {\n this.prevPrevTime = this.prevTime;\n this.prevPrevPoint = this.prevPoint;\n\n // record the most recent drag point for decel on release\n this.prevTime = Date.now();\n this.prevPoint = point;\n };\n\n Path.prototype.velocity = function( timeStep ) {\n var distance, time;\n\n distance = this.distance();\n time = this.duration();\n\n return distance / ( time / timeStep );\n };\n\n Path.prototype.reset = function() {\n this.prevPoint = undefined;\n this.prevTime = undefined;\n\n this.prevPrevTime = undefined;\n this.prevPrevPoint = undefined;\n };\n\n Path.prototype.last = function() {\n return this.prevPoint;\n };\n})(this, jQuery);\n\n(function(exports, $){\n\texports.Akamai = exports.Akamai || {};\n\tvar Util = exports.Akamai.Util = {};\n\n\tUtil.preflight = function(context, element, options, name){\n\t\tname = name || context.constructor.name;\n\n\t\tcontext._$el = $(element);\n\n\t\tif( !context._$el.length ){\n\t\t\tthrow new Error( name + \" component requires element\" );\n\t\t}\n\n\t\t// If the element has already been instantiated with this component, skip\n\t\tif( context._$el.data(name) ) {\n\t\t\treturn context._$el.data(name);\n\t\t}\n\n\t\t// Store the instance for access and to prevent double init above\n\t\tcontext._$el.data( name, context );\n\n\t\t// make sure the element has the data attribute for CSS that keys off it\n\t\tcontext._$el.attr(context.constructor._dataAttr, true);\n\n\t\tcontext._options =\n\t\t\tUtil.options(context.constructor.defaultOptions || {}, options || {}, context._$el, name);\n\n\t\t// retain the options decided at instantiation for reseting the options\n\t\t// when the breakpoint options don't apply (are \"backed out\")\n\t\tcontext._originalOptions = Util.extend(true, {}, context._options);\n\n\t\t// map all the child component events to the wrapper component events\n\t\tUtil.mapEvents(context);\n\n\t\treturn false;\n\t};\n\n\tUtil.component = function(name, async, init){\n\t\tif( !init ){\n\t\t\tinit = async;\n\t\t\tasync = false;\n\t\t}\n\n\t\tvar constr = function(element, options){\n\t\t\t// do the preflight\n\t\t\tvar existing = Util.preflight(this, element, options, name);\n\n\t\t\t// return on double init\n\t\t\tif( existing ){\n\t\t\t\tthis._trigger(\"init\");\n\t\t\t\treturn existing;\n\t\t\t}\n\n\t\t\t// call the init code\n\t\t\tinit.apply(this, arguments);\n\n\t\t\t// trigger the init event\n\t\t\tif( !async ) { this._trigger(\"init\"); }\n\t\t};\n\n\t\tconstr.name = name;\n\n\t\tconstr._dataAttr = `data-akamai-${name.toLowerCase()}`;\n\n\t\t// static constructor for many possible objects\n\t\tconstr.createMany = Util.createMany(constr);\n\n\t\t// component prefixed event trigger\n\t\tconstr.prototype._trigger = Util.trigger(name);\n\n\t\treturn constr;\n\t};\n\n\tUtil.mapEvents = function(context){\n\t\tvar mapping = context.constructor._componentEventMapping || {};\n\n\t\tfor( var event in mapping) {\n\t\t\tif( mapping.hasOwnProperty(event) ){\n\t\t\t\tvar mappedTo = mapping[event].to || mapping[event];\n\t\t\t\tvar $el =\n\t\t\t\t\tmapping[event].selector ? context._$el.find(mapping[event].selector) : undefined;\n\n\t\t\t\tUtil.mapEvent(context, event, mappedTo, $el);\n\t\t\t}\n\t\t}\n\t};\n\n\tUtil.mapEvent = function(context, from, to, $el){\n\t\t($el || context._$el).bind(from, function(){\n\t\t\tcontext._trigger(to);\n\t\t}.bind(context));\n\t};\n\n\tUtil.createMany = function(constructor, selector){\n\t\tselector = selector || `[${constructor._dataAttr}]`;\n\n\t\treturn function(element, options){\n\t\t\tvar $comps = $(element).find( selector );\n\n\t\t\t// sigh, shoestring needs map\n\t\t\tvar objects = [];\n\n\t\t\t$comps.each(function(i, el){\n\t\t\t\tobjects.push(new constructor(el, options));\n\t\t\t});\n\n\t\t\treturn objects;\n\t\t};\n\t};\n\n\t/**\n\t * Helper for building assertions into methods based on property requirements\n\t * @private\n\t * @static\n\t * @param {prop} prop - the property required for the function to operate correctly\n\t * @param {fn} function - the function definition\n\t */\n\tUtil.propRequired = function(prop, msg, fn){\n\t\treturn function(){\n\t\t\tvar args = arguments;\n\n\t\t\tif( !this[prop] ){\n\t\t\t\tthrow new Error( msg );\n\t\t\t}\n\n\t\t\treturn fn.apply(this, args);\n\t\t};\n\t};\n\n\tUtil.trigger = function(componentName){\n\t\tcomponentName = componentName.toLowerCase();\n\t\treturn function(event) {\n\t\t\tvar fullEvent = \"akamai-\" + componentName + \"-\" + event;\n\n\t\t\t// NOTE we assume here that if jQuery is present we will only want to trigger\n\t\t\t// with jQuery and that we are using the same DOM lib across the library\n\t\t\tthis._$el.trigger(fullEvent);\n\t\t};\n\t};\n\n\n\tUtil.camelToKabob = function(string){\n\t\treturn string.replace(/([A-Z]+)/g, function(m){\n\t\t\treturn \"-\" + m.toLowerCase();\n\t\t});\n\t};\n\n\tUtil.options = function(defaults, options, $element, name){\n\t\tvar dataAttrOptions = {};\n\t\t// make sure to make a new copy of the defaults so we don't mess with the original\n\t\tvar cloneDefaults = Util.extend(true, {}, defaults);\n\n\t\tif( $element && name ) {\n\t\t\tdataAttrOptions =\n\t\t\t\tUtil.getDataAttrOptions($element, defaults, \"data-akamai-\" + name.toLowerCase());\n\t\t}\n\n\t\t// use the following precedence, prefering settings further to the right\n\t\t// defaults -> options -> datattributes\n\t\tvar finalOptions =\n\t\t\t\tUtil.extend(true, Util.extend(true, cloneDefaults, dataAttrOptions), options);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tif( $element && name ){\n\t\t\tUtil.setDataAttrOptions($element, defaults, finalOptions, name);\n\t\t}\n\n\t\treturn finalOptions;\n\t};\n\n\tUtil.getDataAttrOptions = function($element, defaults, prefix){\n\t\tvar dataAttrOptions = {};\n\n\t\tfor( var prop in defaults ) {\n\t\t\tif( defaults.hasOwnProperty(prop) ){\n\t\t\t\tvar dashedProp = Util.camelToKabob(prop);\n\t\t\t\tvar currentDataProp = prefix + \"-\" + dashedProp;\n\n\t\t\t\tif( /object/.test(typeof defaults[prop]) && !(defaults[prop] instanceof Array)){\n\t\t\t\t\tvar subDefaults = defaults[prop];\n\t\t\t\t\tvar newPrefix = currentDataProp;\n\n\t\t\t\t\tvar subConfig = Util.getDataAttrOptions($element, subDefaults, newPrefix);\n\t\t\t\t\tdataAttrOptions[prop] = subConfig;\n\t\t\t\t} else {\n\t\t\t\t\tvar value = $element.attr(currentDataProp);\n\t\t\t\t\tvalue = Util.coerceAttrVal(value);\n\n\t\t\t\t\tif( value !== undefined && value !== null ){\n\t\t\t\t\t\t// set the config\n\t\t\t\t\t\tdataAttrOptions[prop] = value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn dataAttrOptions;\n\t};\n\n\tUtil.setDataAttrOptions = function($element, defaults, options, name){\n\t\tUtil.setDataAttrOptionsPrefix($element, defaults, options, \"data-akamai-\" + name.toLowerCase());\n\t};\n\n\n\tUtil.setDataAttrOptionsPrefix = function($element, defaults, options, prefix){\n\t\tdefaults = defaults || {};\n\n\t\tfor( var prop in options ) {\n\t\t\tif( options.hasOwnProperty(prop) && prop !== \"breakpoints\" ){\n\t\t\t\tvar dashedProp = Util.camelToKabob(prop);\n\t\t\t\tvar currentDataProp = prefix + \"-\" + dashedProp;\n\n\t\t\t\tif( /object/.test(typeof options[prop]) ){\n\t\t\t\t\tvar subOptions = options[prop];\n\t\t\t\t\tvar newPrefix = currentDataProp;\n\n\t\t\t\t\tUtil.setDataAttrOptionsPrefix($element, defaults[prop], subOptions, newPrefix);\n\t\t\t\t} else {\n\n\t\t\t\t\t// if the $element had the attribute already or the property exists\n\t\t\t\t\t// as one of the defaults then we can write it to the element.\n\t\t\t\t\tif( $element.attr(currentDataProp) || defaults[prop] !== undefined ) {\n\t\t\t\t\t\t$element.attr(currentDataProp, options[prop]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tUtil.coerceAttrVal = function(value){\n\t\tif( value === \"true\" ) { return true; }\n\t\tif( value === \"false\" ) { return false; }\n\t\tif( /^[0-9]+$/.test(value) ) { return parseInt(value, 10); }\n\t\tif( /^[0-9]+.[0-9]+$/.test(value) ) { return parseFloat(value, 10); }\n\n\t\treturn value;\n\t};\n\n\t// Due to jQuery, but with less argument handling\n\tUtil.extend = function( deep, target, options ) {\n\t\tvar name, src, copy, copyIsArray, clone;\n\n\t\t// Only deal with non-null/undefined values\n\t\tif ( options\t!= null ) {\n\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && Util.isPlainObject( copy ) ) {\n\n\t\t\t\t\tclone = src && Util.isPlainObject( src ) ? src : {};\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = Util.extend( deep, clone, copy );\n\n\t\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Return the modified object\n\t\treturn target;\n\t};\n\n\t// Due to jQuery\n\tUtil.isPlainObject = function( obj ) {\n\t\tvar proto, Ctor;\n\n\t\t// taken from https://github.com/jupiter/simple-mock/pull/3/files\n\t\tif ( !obj || Object.prototype.toString.call(obj) !== \"[object Object]\" ) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t};\n\n\t// Due to jQuery\n\tvar rtrim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n\tUtil.trim = function( text ) {\n\t\treturn text == null ?\n\t\t\t\"\" :\n\t\t\t( text + \"\" ).replace( rtrim, \"\" );\n\t};\n\n\tUtil.map = function(array, fn){\n\t\treturn Util.reduce(array, function(acc, val, i){\n\t\t\tacc.push(fn(val, i));\n\t\t\treturn acc;\n\t\t}, []);\n\t};\n\n\tUtil.reduce = function(array, fn, acc){\n\t\tfor(var i = 0; i < array.length; i++){\n\t\t\tacc = fn(acc, array[i], i);\n\t\t}\n\n\t\treturn acc;\n\t};\n\n\tUtil.log = function(msg, type){\n\t\ttype = type || \"log\";\n\n\t\tif( console ){\n\t\t\tconsole[type](msg);\n\t\t}\n\t};\n\n\tUtil.mapJSONVals = function(obj, callback, key){\n\t\tvar result = {};\n\n\t\tif(obj.map){\n\t\t\treturn obj.map(function(o){\n\t\t\t\treturn Util.mapJSONVals(o, callback);\n\t\t\t});\n\t\t}\n\n\t\tif([\"string\", \"number\", \"boolean\"].indexOf( typeof obj ) >= 0 ){\n\t\t\treturn callback(obj, key);\n\t\t}\n\n\t\tfor( var prop in obj ){\n\t\t\tif(obj.hasOwnProperty(prop)){\n\t\t\t\tresult[prop] = Util.mapJSONVals(obj[prop], callback, prop);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tUtil.escapeJSONVals = function(obj){\n\t\treturn Util.mapJSONVals(obj, function(val){\n\t\t\tif(typeof val === \"string\" ) {\n\t\t\t\treturn escapeHTML(val);\n\t\t\t}\n\n\t\t\treturn val;\n\t\t});\n\t};\n\n\t// https://github.com/janl/mustache.js/blob/23beb3a8805c9a857e3ea777431481599fab503e/mustache.js#L60\n\tvar entityMap = {\n\t\t'&': '&',\n\t\t'<': '<',\n\t\t'>': '>',\n\t\t'\"': '"',\n\t\t\"'\": ''',\n\t\t'/': '/',\n\t\t'`': '`',\n\t\t'=': '='\n\t};\n\n\tfunction escapeHTML(string) {\n\t\treturn String(string).replace(/[&<>\"'`=\\/]/g, function fromEntityMap (s) {\n\t\t\treturn entityMap[s];\n\t\t});\n\t}\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\tvar Advanceable = {\n\t\textension: {\n\t\t\tnext: function(callback){\n\t\t\t\tthis.goto(this.getIndex() + 1, function(){\n\t\t\t\t\tif( callback ) { callback(); };\n\t\t\t\t\tthis._trigger(\"next\");\n\t\t\t\t}.bind(this));\n\t\t\t},\n\n\t\t\tprevious: function(callback){\n\t\t\t\tthis.goto(this.getIndex() - 1, function(){\n\t\t\t\t\tif( callback ) { callback(); };\n\t\t\t\t\tthis._trigger(\"previous\");\n\t\t\t\t}.bind(this));\n\t\t\t}\n\t\t},\n\n\t\textend: function(constr) {\n\t\t\tif(!constr.prototype.goto || !constr.prototype.getIndex) {\n\t\t\t\tthrow new Error(\"Advanceable mixin requires `goto` and `getIndex` methods\");\n\t\t\t}\n\n\t\t\t$.extend(constr.prototype, this.extension);\n\t\t}\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Advanceable = Advanceable;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n\n// TODO this should be handled in an image component\n(function(exports, $){\n\tvar Sourceable = {\n\t\textension: {\n\t\t\t_srcArray: function(url, options, includeW ){\n\t\t\t\tvar policy = options.policy || options.thumbnail && options.thumbnail.policy;\n\t\t\t\tvar widthParam = options.widthParam;\n\n\t\t\t\treturn options.widths.map(function(w){\n\t\t\t\t\tvar src = url + (url.indexOf(\"?\") === -1 ? \"?\" : \"&\") + widthParam + \"=\" + w;\n\n\t\t\t\t\tif( policy ){\n\t\t\t\t\t\tsrc += \"&impolicy=\" + policy;\n\t\t\t\t\t}\n\n\t\t\t\t\t//note src width for srcset (eg ' 500w')\n\t\t\t\t\tif( includeW ){\n\t\t\t\t\t\tsrc += \" \" + w + \"w\";\n\t\t\t\t\t}\n\n\t\t\t\t\treturn src;\n\t\t\t\t});\n\t\t\t},\n\n\t\t\t_srcset: function(url, options){\n\t\t\t\treturn this._srcArray( url, options, true ).join(\", \");\n\t\t\t},\n\n\t\t\t_fallbackSrc: function(url, options){\n\t\t\t\treturn this._srcArray( url, options )[ 0 ];\n\t\t\t},\n\n\t\t\t_largestSrc: function(url, options){\n\t\t\t\treturn this._srcArray( url, options ).pop();\n\t\t\t}\n\t\t},\n\n\t\textendStatic: function(constr) {\n\t\t\tAkamai.Util.extend(false, constr, this.extension);\n\t\t}\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Sourceable = Sourceable;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n\n(function( w, undefined ){\n\n\t// requestAnimationFrame pfill\n\tvar raf = (function(){\n\t\treturn window.requestAnimationFrame ||\n\t\t\twindow.webkitRequestAnimationFrame ||\n\t\t\twindow.mozRequestAnimationFrame ||\n\t\t\tfunction( callback ){\n\t\t\t\tw.setTimeout(callback, 1000 / 60);\n\t\t\t};\n\t})();\n\n\n\n\n\t/* toss scrolls and element with easing\n\n\t// elem is the element to scroll\n\t// options hash:\n\t\t* left is the desired horizontal scroll. Default is \"+0\". For relative distances, pass a string with \"+\" or \"-\" in front.\n\t\t* top is the desired vertical scroll. Default is \"+0\". For relative distances, pass a string with \"+\" or \"-\" in front.\n\t\t* duration is the number of milliseconds the throw will take. Default is 100.\n\t\t* easing is an optional custom easing function. Default is w.toss.easing. Must follow the easing function signature\n\n\t*/\n\tw.toss = function( elem, options ){\n\t\ttoss.tossing( elem, false );\n\t\tvar i = 0,\n\t\t\tsLeft = elem.scrollLeft,\n\t\t\tsTop = elem.scrollTop,\n\t\t\t// Toss defaults\n\t\t\top = {\n\t\t\t\ttop: \"+0\",\n\t\t\t\tleft: \"+0\",\n\t\t\t\tduration: 200,\n\t\t\t\teasing: toss.easing,\n\t\t\t\tfinished: function() {}\n\t\t\t},\n\t\t\tendLeft, endTop;\n\n\t\t// Mixin based on predefined defaults\n\t\tif( options ){\n\t\t\tfor( var j in op ){\n\t\t\t\tif( options[ j ] !== undefined ){\n\t\t\t\t\top[ j ] = options[ j ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Convert relative values to ints\n\t\t// First the left val\n\t\tif( typeof op.left === \"string\" ){\n\t\t\top.left = parseFloat( op.left );\n\t\t\tendLeft = op.left + sLeft;\n\t\t}\n\t\telse {\n\t\t\tendLeft = op.left;\n\t\t\top.left = op.left - sLeft;\n\t\t}\n\t\t// Then the top val\n\t\tif( typeof op.top === \"string\" ){\n\n\t\t\top.top = parseFloat( op.top );\n\t\t\tendTop = op.top + sTop;\n\t\t}\n\t\telse {\n\t\t\tendTop = op.top;\n\t\t\top.top = op.top - sTop;\n\t\t}\n\n\t\ttoss.tossing( elem, true );\n\t\tvar startTime = new Date().getTime();\n\t\tvar endTime = startTime + op.duration;\n\t\tvar run = function(){\n\t\t\tvar curTime = new Date().getTime();\n\t\t\t// if tossing is suddenly not true, return the callback\n\t\t\tif( !toss.tossing( elem ) ){\n\t\t\t\tif( op.finished ){\n\t\t\t\t\top.finished();\n\t\t\t\t}\n\t\t\t}\n\t\t\t// if the time is still less than the end of duration, keep scrolling\n\t\t\telse if( curTime < endTime ){\n\t\t\t\ti = ( ( curTime - startTime ) / op.duration ) * op.duration;\n\t\t\t\telem.scrollLeft = op.easing( i, sLeft, op.left, op.duration );\n\t\t\t\telem.scrollTop = op.easing( i, sTop, op.top, op.duration );\n\t\t\t\treturn raf( run );\n\t\t\t}\n\t\t\t// if time is up,\n\t\t\telse{\n\t\t\t\telem.scrollLeft = endLeft;\n\t\t\t\telem.scrollTop = endTop;\n\t\t\t\tif( op.finished ){\n\t\t\t\t\top.finished();\n\t\t\t\t}\n\t\t\t\ttoss.tossing( elem, false );\n\t\t\t}\n\t\t};\n\n\t\traf( run );\n\n\t\t// Return the values, post-mixin, with end values specified\n\t\treturn { top: endTop, left: endLeft, duration: op.duration, easing: op.easing };\n\t};\n\n // tossing object keeps track of currently tossing elements. true during a programatic scroll\n var tossingElems = {};\n toss.tossing = function( elem, state ){\n if( state !== undefined ){\n tossingElems[ elem ] = state;\n }\n return tossingElems[ elem ];\n };\n\n // Easing can use any of Robert Penner's equations (http://www.robertpenner.com/easing_terms_of_use.html). By default, toss includes ease-out-cubic\n // arguments: t = current iteration, b = initial value, c = end value, d = total iterations\n // use w.toss.easing to provide a custom function externally, or pass an easing function as a callback to the toss method\n toss.easing = function (t, b, c, d) {\n return c*((t=t/d-1)*t*t + 1) + b;\n };\n\n //retain old api\n toss.toss = toss;\n\n})( this );\n\n;(function( w, $ ){\n\tvar pluginName = \"snapper\";\n\t$.fn[ pluginName ] = function(optionsOrMethod){\n\t\tvar pluginArgs = arguments;\n\t\tvar scrollListening = true;\n\n\t\t// css snap points feature test.\n\t\t// even if this test passes, several behaviors will still be polyfilled, such as snapping after resize, and animated advancing of slides with anchor links or next/prev links\n\t\tvar testProp = \"scroll-snap-type\";\n\t\t// test latest spec first. then fallback older\n\t\tvar snapSupported = w.CSS && w.CSS.supports && (w.CSS.supports(testProp, \"x mandatory\") || w.CSS.supports(testProp, \"mandatory\") || w.CSS.supports(\"-webkit-\" + testProp, \"mandatory\") || w.CSS.supports(\"-ms-\" + testProp, \"mandatory\") );\n\n\t\t// get the snapper_item elements whose left offsets fall within the scroll pane. Returns a wrapped array.\n\t\tfunction itemsAtOffset( elem, offset ){\n\t\t\tvar $childNodes = $( elem ).find( \".\" + pluginName + \"_item\" );\n\t\t\tvar containWidth = $( elem ).width();\n\t\t\tvar activeItems = [];\n\n\t\t\t$childNodes.each(function( i ){\n\t\t\t\tif( this.offsetLeft >= offset - 5 && this.offsetLeft < offset + containWidth - 5 ){\n\t\t\t\t\tactiveItems.push( this );\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn $( activeItems );\n\t\t}\n\n\t\tfunction outerWidth( $elem ){\n\t\t\treturn $elem.width() + parseFloat( $elem.css( \"margin-left\" ) ) + parseFloat( $elem.css( \"margin-right\" ) );\n\t\t}\n\n\t\tfunction outerHeight( $elem ){\n\t\t\treturn $elem.height() + parseFloat( $elem.css( \"margin-bottom\" ) ) + parseFloat( $elem.css( \"margin-top\" ) );\n\t\t}\n\n\n\t\t// snapEvent dispatches the \"snapper.snap\" event.\n\t\t// The snapper_item elements with left offsets that are inside the scroll viewport are listed in an array in the second callback argument's activeSlides property.\n\t\t// use like this: $( \".snapper\" ).bind( \"snapper.snap\", function( event, data ){ console.log( data.activeSlides );\t} );\n\t\tfunction snapEvent( elem, x, prefix ){\n\t\t\tprefix = prefix ? prefix + \"-\" : \"\";\n\t\t\tvar activeSlides = itemsAtOffset( elem, x );\n\t\t\t$( elem ).trigger( pluginName + \".\" + prefix + \"snap\", { activeSlides: activeSlides } );\n\t\t}\n\n\t\t// optional: include toss() in your page to get a smooth scroll, otherwise it'll just jump to the slide\n\t\tfunction goto( elem, x, nothrow, callback ){\n\t\t\tscrollListening = false;\n\t\t\tsnapEvent( elem, x );\n\n\t\t\tvar after = function(){\n\t\t\t\telem.scrollLeft = x;\n\t\t\t\t$(elem).closest( \".\" + pluginName ).removeClass( pluginName + \"-looping\" );\n\t\t\t\t$( elem ).trigger( pluginName + \".after-goto\", {\n\t\t\t\t\tactiveSlides: itemsAtOffset( elem, x )\n\t\t\t\t});\n\n\t\t\t\tif( callback ){ callback(); };\n\t\t\t\tsnapEvent( elem, x, \"after\" );\n\t\t\t\tscrollListening = true;\n\t\t\t};\n\n\t\t\t// backport to old toss for compat\n\t\t\tif( !w.toss && w.overthrow ){\n\t\t\t\tw.toss = w.overthrow.toss;\n\t\t\t}\n\t\t\tif( typeof w.toss !== \"undefined\" && !nothrow ){\n\t\t\t\tw.toss( elem, { left: x, finished: after });\n\t\t\t}\n\t\t\telse {\n\t\t\t\telem.scrollLeft = x;\n\t\t\t\tafter();\n\t\t\t}\n\t\t}\n\n\n\t\tvar result, innerResult;\n\n\t\t// Loop through snapper elements and enhance/bind events\n\t\tresult = this.each(function(){\n\t\t\tif( innerResult !== undefined ){\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar self = this;\n\t\t\tvar $self = $( self );\n\t\t\tvar addNextPrev = $self.is( \"[data-\" + pluginName + \"-nextprev]\" );\n\t\t\tvar autoTimeout;\n\t\t\tvar $slider = $( \".\" + pluginName + \"_pane\", self );\n\t\t\tvar enhancedClass = pluginName + \"-enhanced\";\n\t\t\tvar $itemsContain = $slider.find( \".\" + pluginName + \"_items\" );\n\t\t\tvar $items = $itemsContain.children();\n\t\t\t$items.addClass( pluginName + \"_item\" );\n\t\t\tvar numItems = $items.length;\n\t\t\tvar $nav = $( \".\" + pluginName + \"_nav\", self );\n\t\t\tvar navSelectedClass = pluginName + \"_nav_item-selected\";\n\t\t\tvar useDeepLinking = $self.attr( \"data-snapper-deeplinking\" ) !== \"false\";\n\n\t\t\tif( typeof optionsOrMethod === \"string\" ){\n\t\t\t\tvar args = Array.prototype.slice.call(pluginArgs, 1);\n\t\t\t\tvar index;\n\t\t\t\tvar itemWidth = ( $itemsContain.width() / numItems );\n\n\t\t\t\tswitch(optionsOrMethod) {\n\t\t\t\tcase \"goto\":\n\t\t\t\t\tindex = args[0] % numItems;\n\n\t\t\t\t\t// width / items * index to make sure it goes\n\t\t\t\t\toffset = itemWidth * index;\n\t\t\t\t\tgoto( $slider[ 0 ], offset, false, function(){\n\t\t\t\t\t\t// snap the scroll to the right position\n\t\t\t\t\t\tsnapScroll();\n\n\t\t\t\t\t\t// invoke the callback if it was supplied\n\t\t\t\t\t\tif( typeof args[1] === \"function\" ){\n\t\t\t\t\t\t\targs[1]();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"getIndex\":\n\t\t\t\t\t// NOTE make the scroll left value large enough to overcome\n\t\t\t\t\t// subpixel widths\n\t\t\t\t\tinnerResult = Math.floor(($slider[ 0 ].scrollLeft + 1)/ itemWidth);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"updateWidths\":\n\t\t\t\t\tupdateWidths();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// avoid double enhance activities\n\t\t\tif( $self.attr(\"data-\" + pluginName + \"-enhanced\") ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// NOTE all state manipulation has to come after method invocation to\n\t\t\t// avoid monkeying with the DOM when it's unwarranted\n\t\t\tvar $navInner = $nav.find( \".\" + pluginName + \"_nav_inner\" );\n\t\t\tif( !$navInner.length ){\n\t\t\t\t$navInner = $( '
' ).append( $nav.children() ).appendTo( $nav );\n\t\t\t}\n\n\t\t\t// give the pane a tabindex for arrow key handling\n\t\t\t$slider.attr(\"tabindex\", \"0\");\n\n\t\t\tfunction getAutoplayInterval() {\n\t\t\t\tvar autoTiming = $self.attr( \"data-autoplay\" ) || $self.attr( \"data-snapper-autoplay\" );\n\t\t\t\tvar parseError = false;\n\n\t\t\t\tif( autoTiming ) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tautoTiming = parseInt(autoTiming, 10);\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\tparseError = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// if NaN or there was an error throw an exception\n\t\t\t\t\tif( !autoTiming || parseError ) {\n\t\t\t\t\t\tvar msg = \"Snapper: `data-autoplay` must have an natural number value.\";\n\t\t\t\t\t\tthrow new Error(msg);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn autoTiming;\n\t\t\t}\n\n\t\t\t// this function updates the widths of the items within the slider, and their container.\n\t\t\t// It factors in margins and converts those to values that make sense when all items are placed in a long row\n\t\t\tfunction updateWidths(){\n\t\t\t\tvar itemsContainStyle = $itemsContain.attr( \"style\" );\n\t\t\t\t$itemsContain.attr( \"style\", \"\" );\n\t\t\t\tvar itemStyle = $items.eq(0).attr( \"style\" );\n\t\t\t\t$items.eq(0).attr( \"style\", \"\" );\n\t\t\t\tvar sliderWidth = $slider.width();\n\t\t\t\tvar itemWidth = $items.eq(0).width();\n\t\t\t\tvar computed = w.getComputedStyle( $items[ 0 ], null );\n\t\t\t\tvar itemLeftMargin = parseFloat( computed.getPropertyValue( \"margin-left\" ) );\n\t\t\t\tvar itemRightMargin = parseFloat( computed.getPropertyValue( \"margin-right\" ) );\n\t\t\t\tvar outerItemWidth = itemWidth + itemLeftMargin + itemRightMargin;\n\t\t\t\t$items.eq(0).attr( \"style\", itemStyle );\n\t\t\t\t$itemsContain.attr( \"style\", itemsContainStyle );\n\t\t\t\tvar parentWidth = numItems / Math.round(sliderWidth / outerItemWidth) * 100;\n\t\t\t\tvar iPercentWidth = itemWidth / sliderWidth * 100;\n\t\t\t\tvar iPercentRightMargin = itemRightMargin / sliderWidth * 100;\n\t\t\t\tvar iPercentLeftMargin = itemLeftMargin / sliderWidth * 100;\n\t\t\t\tvar outerPercentWidth = iPercentWidth + iPercentLeftMargin + iPercentRightMargin;\n\t\t\t\tvar percentAsWidth = iPercentWidth / outerPercentWidth;\n\t\t\t\tvar percentAsRightMargin = iPercentRightMargin / outerPercentWidth;\n\t\t\t\tvar percentAsLeftMargin = iPercentLeftMargin / outerPercentWidth;\n\t\t\t\t$itemsContain.css( \"width\", parentWidth + \"%\");\n\t\t\t\t$items.css( \"width\", 100 / numItems * percentAsWidth + \"%\" );\n\t\t\t\t$items.css( \"margin-left\", 100 / numItems * percentAsLeftMargin + \"%\" );\n\t\t\t\t$items.css( \"margin-right\", 100 / numItems * percentAsRightMargin + \"%\" );\n\t\t\t}\n\n\t\t\tupdateWidths();\n\t\t\t$( self ).addClass( enhancedClass );\n\n\t\t\t// if the nextprev option is set, add the nextprev nav\n\t\t\tif( addNextPrev ){\n\t\t\t\tvar\t$nextprev = $( '' );\n\t\t\t\tvar $nextprevContain = $( \".snapper_nextprev_contain\", self );\n\t\t\t\tif( !$nextprevContain.length ){\n\t\t\t\t\t$nextprevContain = $( self );\n\t\t\t\t}\n\t\t\t\t$nextprev.appendTo( $nextprevContain );\n\t\t\t}\n\n\t\t\t// This click binding will allow deep-linking to slides without causing the page to scroll to the carousel container\n\t\t\t// this also supports click handling for generated next/prev links\n\t\t\t$( \"a\", this ).bind( \"click\", function( e ){\n\t\t\t\tclearTimeout(autoTimeout);\n\t\t\t\tvar slideID = $( this ).attr( \"href\" );\n\n\t\t\t\tif( $( this ).is( \".snapper_nextprev_next\" ) ){\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\treturn arrowNavigate( true );\n\t\t\t\t}\n\t\t\t\telse if( $( this ).is( \".snapper_nextprev_prev\" ) ){\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\treturn arrowNavigate( false );\n\t\t\t\t}\n\t\t\t\t// internal links to slides\n\t\t\t\telse if( slideID.indexOf( \"#\" ) === 0 && slideID.length > 1 ){\n\t\t\t\t\te.preventDefault();\n\n\t\t\t\t\tvar $slide = $( slideID, self );\n\t\t\t\t\tif( $slide.length ){\n\t\t\t\t\t\tgoto( $slider[ 0 ], $slide[ 0 ].offsetLeft );\n\t\t\t\t\t\tif( useDeepLinking && \"replaceState\" in w.history ){\n\t\t\t\t\t\t\tw.history.replaceState( {}, document.title, slideID );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// arrow key bindings for next/prev\n\t\t\t$( this )\n\t\t\t\t.bind( \"keydown\", function( e ){\n\t\t\t\t\tif( e.keyCode === 37 || e.keyCode === 38 ){\n\t\t\t\t\t\tclearTimeout(autoTimeout);\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\te.stopImmediatePropagation();\n\t\t\t\t\t\tarrowNavigate( false );\n\t\t\t\t\t}\n\t\t\t\t\tif( e.keyCode === 39 || e.keyCode === 40 ){\n\t\t\t\t\t\tclearTimeout(autoTimeout);\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\te.stopImmediatePropagation();\n\t\t\t\t\t\tarrowNavigate( true );\n\t\t\t\t\t}\n\t\t\t\t} );\n\n\t\t\tvar snapScrollCancelled = false;\n\n\t\t\t// snap to nearest slide. Useful after a scroll stops, for polyfilling snap points\n\t\t\tfunction snapScroll(){\n\t\t\t\tif(isTouched){\n\t\t\t\t\tsnapScrollCancelled = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar currScroll = $slider[ 0 ].scrollLeft;\n\t\t\t\tvar width = $itemsContain.width();\n\t\t\t\tvar itemWidth = $items[ 1 ] ? $items[ 1 ].offsetLeft : outerWidth( $items.eq( 0 ) );\n\t\t\t\tvar roundedScroll = Math.round(currScroll/itemWidth)*itemWidth;\n\t\t\t\tvar maxScroll = width - $slider.width();\n\t\t\t\tif( roundedScroll > maxScroll ){\n\t\t\t\t\troundedScroll = maxScroll;\n\t\t\t\t}\n\t\t\t\tif( currScroll !== roundedScroll ){\n\t\t\t\t\tif( snapSupported ){\n\t\t\t\t\t\tsnapEvent( $slider[ 0 ], roundedScroll );\n\t\t\t\t\t\tsnapEvent( $slider[ 0 ], roundedScroll, \"after\" );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tgoto( $slider[ 0 ], roundedScroll );\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tgoto( $slider[ 0 ], roundedScroll );\n\t\t\t\t}\n\n\t\t\t\tsnapScrollCancelled = false;\n\t\t\t}\n\n\t\t\t// retain snapping on resize (necessary even in scroll-snap supporting browsers currently, unfortunately)\n\t\t\tvar startSlide;\n\t\t\tvar afterResize;\n\t\t\tfunction snapStay(){\n\t\t\t\tvar currScroll = $slider[ 0 ].scrollLeft;\n\t\t\t\tvar numItems = $items.length;\n\t\t\t\tvar width = $itemsContain.width();\n\t\t\t\tif( startSlide === undefined ){\n\t\t\t\t\tstartSlide = Math.round( currScroll / width * numItems );\n\t\t\t\t}\n\t\t\t\tif( afterResize ){\n\t\t\t\t\tclearTimeout( afterResize );\n\t\t\t\t}\n\t\t\t\tafterResize = setTimeout( function(){\n\t\t\t\t\tupdateWidths();\n\t\t\t\t\tgoto( $slider[ 0 ], $items[ startSlide ].offsetLeft, true );\n\t\t\t\t\tstartSlide = afterResize = undefined;\n\t\t\t\t}, 50 );\n\t\t\t}\n\t\t\t$( w ).bind( \"resize\", snapStay );\n\n\t\t\t// next/prev links or arrows should loop back to the other end when an extreme is reached\n\t\t\tfunction arrowNavigate( forward ){\n\t\t\t\tvar currScroll = $slider[ 0 ].scrollLeft;\n\t\t\t\tvar width = $itemsContain.width();\n\t\t\t\tvar itemWidth = outerWidth( $slider );\n\t\t\t\tvar maxScroll = width - itemWidth - 5;\n\t\t\t\tif( forward ){\n\t\t\t\t\tif( currScroll >= maxScroll ){\n\t\t\t\t\t\t$self.addClass( pluginName + \"-looping\" );\n\t\t\t\t\t\treturn first();\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\treturn next();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif( currScroll === 0 ){\n\t\t\t\t\t\t$self.addClass( pluginName + \"-looping\" );\n\t\t\t\t\t\treturn last();\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\treturn prev();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// advance slide one full scrollpane's width forward\n\t\t\tfunction next(){\n\t\t\t\tgoto( $slider[ 0 ], $slider[ 0 ].scrollLeft + ( $itemsContain.width() / numItems ), false, function(){\n\t\t\t\t\t$slider.trigger( pluginName + \".after-next\" );\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// advance slide one full scrollpane's width backwards\n\t\t\tfunction prev(){\n\t\t\t\tgoto( $slider[ 0 ], $slider[ 0 ].scrollLeft - ( $itemsContain.width() / numItems ), false, function(){\n\t\t\t\t\t$slider.trigger( pluginName + \".after-prev\" );\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// go to first slide\n\t\t\tfunction first(){\n\t\t\t\tgoto( $slider[ 0 ], 0 );\n\t\t\t}\n\n\t\t\t// go to last slide\n\t\t\tfunction last(){\n\t\t\t\tgoto( $slider[ 0 ], $itemsContain.width() - $slider.width() );\n\t\t\t}\n\n\t\t\t// update thumbnail state on pane scroll\n\t\t\tif( $nav.length ){\n\t\t\t\t// function for scrolling to the xy of the active thumbnail\n\t\t\t\tfunction scrollNav(elem, x, y){\n\t\t\t\t\tif( typeof w.toss !== \"undefined\" ){\n\t\t\t\t\t\tw.toss( elem, { left: x, top:y });\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\telem.scrollLeft = x;\n\t\t\t\t\t\telem.scrollTop = y;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tvar lastActiveItem;\n\t\t\t\tfunction activeItem( force ){\n\t\t\t\t\tvar currTime = new Date().getTime();\n\t\t\t\t\tif( !force && lastActiveItem && currTime - lastActiveItem < 200 ){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlastActiveItem = currTime;\n\t\t\t\t\tvar currScroll = $slider[ 0 ].scrollLeft;\n\t\t\t\t\tvar width = outerWidth( $itemsContain );\n\t\t\t\t\tvar navWidth = outerWidth( $nav );\n\t\t\t\t\tvar navHeight = outerHeight( $nav );\n\t\t\t\t\tvar activeIndex = Math.round( currScroll / width * numItems ) || 0;\n\t\t\t\t\tvar childs = $nav.find( \"a\" ).removeClass( navSelectedClass );\n\t\t\t\t\tvar activeChild = childs.eq( activeIndex ).addClass( navSelectedClass );\n\n\t\t\t\t\tvar thumbX = activeChild[ 0 ].offsetLeft - (navWidth/2);\n\t\t\t\t\tvar thumbY = activeChild[ 0 ].offsetTop - (navHeight/2);\n\n\t\t\t\t\tscrollNav( $navInner[ 0 ], thumbX, thumbY );\n\t\t\t\t}\n\n\t\t\t\t// set active item on init\n\t\t\t\tactiveItem();\n\n\t\t\t\t$slider.bind( \"scroll\", activeItem );\n\t\t\t}\n\n\t\t\t// apply snapping after scroll, in browsers that don't support CSS scroll-snap\n\t\t\tvar scrollStop;\n\t\t\tvar scrolling;\n\t\t\tvar lastScroll = 0;\n\n\n\t\t\t$slider.bind( \"scroll\", function(e){\n\t\t\t\tlastScroll = new Date().getTime();\n\t\t\t\tscrolling = true;\n\t\t\t});\n\n\t\t\tsetInterval(function(){\n\t\t\t\tif( scrolling && lastScroll <= new Date().getTime() - 150) {\n\t\t\t\t\tsnapScroll();\n\t\t\t\t\tif( activeItem ){\n\t\t\t\t\t\tactiveItem( true );\n\t\t\t\t\t}\n\t\t\t\t\tscrolling = false;\n\t\t\t\t}\n\t\t\t}, 150);\n\n\t\t\tvar isTouched = false;\n\n\t\t\t// if a touch event is fired on the snapper we know the user is trying to\n\t\t\t// interact with it and we should disable the auto play\n\t\t\t$slider.bind(\"touchstart\", function(){\n\t\t\t\tclearTimeout(autoTimeout);\n\n\t\t\t\tisTouched = true;\n\t\t\t});\n\n\t\t\t$slider.bind(\"touchend\", function(){\n\t\t\t\tisTouched = false;\n\n\t\t\t\tif(snapScrollCancelled && !scrolling){\n\t\t\t\t\tsnapScroll();\n\t\t\t\t\tscrolling = false;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// if the `data-autoplay` attribute is assigned a natural number value\n\t\t\t// use it to make the slides cycle until there is a user interaction\n\t\t\tfunction autoplay( autoTiming ) {\n\t\t\t\tif( autoTiming ){\n\t\t\t\t\t// autoTimeout is cleared in each user interaction binding\n\t\t\t\t\tautoTimeout = setTimeout(function(){\n\t\t\t\t\t\tvar timeout = getAutoplayInterval();\n\t\t\t\t\t\tif( timeout ) {\n\t\t\t\t\t\t\tarrowNavigate(true);\n\t\t\t\t\t\t\tautoplay( timeout );\n\t\t\t\t\t\t}\n\t\t\t\t\t}, autoTiming);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tautoplay( getAutoplayInterval() );\n\t\t\t$self.attr(\"data-\" + pluginName + \"-enhanced\", true);\n\t\t});\n\n\t\treturn (innerResult !== undefined ? innerResult : result);\n\t};\n}( this, jQuery ));\n\n;(function( w ){\n\tvar enlarge = function(){\n\t\tvar $ = w.jQuery;\n\t\tvar pluginName = \"enlarge\";\n\t\t$.fn[ pluginName ] = function( options ){\n\t\t\tvar pluginArgs = arguments;\n\n\t\t\t// options\n\t\t\tvar o = $(this).data(\"options\") || {\n\t\t\t\tbutton: true,\n\t\t\t\thoverZoomWithoutClick: true,\n\t\t\t\tdelay: 300,\n\t\t\t\tflyout: {\n\t\t\t\t\twidth: 200,\n\t\t\t\t\theight: 200\n\t\t\t\t},\n\t\t\t\tplacement: \"inline\",\n\t\t\t\tmagnification: 3\n\t\t\t};\n\n\t\t\tif( typeof options !== \"string\" ) {\n\t\t\t\t// extend with passed options\n\t\t\t\to = $.extend( o, options );\n\t\t\t\t$(this).data(\"options\", o);\n\t\t\t}\n\n\t\t\tvar internalResult;\n\n\t\t\tvar result = this.each(function(){\n\t\t\t\tvar $element = $(this);\n\n\t\t\t\tvar self = this;\n\t\t\t\tvar testimg = w.document.createElement( \"img\" );\n\t\t\t\tvar srcsetSupported = \"srcset\" in testimg;\n\t\t\t\tvar srcsetSizesSupported = srcsetSupported && \"sizes\" in testimg;\n\t\t\t\tvar $anchor = $( this ).find( \"a\" );\n\n\t\t\t\tif( !$anchor.length ){\n\t\t\t\t\tthrow new Error(pluginName + \": requires an anchor element with `href` for the enlarged image source\");\n\t\t\t\t}\n\n\t\t\t\t// find image within container\n\t\t\t\tvar initialImg = $element.find( \"img\" )[ 0 ];\n\t\t\t\tvar targetImg = initialImg;\n\t\t\t\tvar imgOriginalSrc = targetImg.src;\n\t\t\t\tvar srcset = $( targetImg ).attr( \"srcset\" );\n\t\t\t\tvar imgOriginalSizes = $( targetImg ).attr( \"sizes\" );\n\t\t\t\tvar imgZoomSrc = $anchor[ 0 ].href;\n\t\t\t\tvar initialText = $anchor[ 0 ].innerText;\n\t\t\t\tvar zoomClass = pluginName + \"-zoomed\";\n\t\t\t\tvar delayClass = pluginName + \"-delay\";\n\t\t\t\tvar $contain = $( targetImg ).closest( \".enlarge_contain\" );\n\t\t\t\tvar $zoomContain = $contain;\n\t\t\t\tvar $parentPane = $( targetImg ).closest( \".enlarge_pane\" ) || $element;\n\n\t\t\t\tvar $zoomParent = $(this).data(\"zoomParent\") || $parentPane;\n\t\t\t\t$(this).data(\"zoomParent\", $zoomParent);\n\n\t\t\t\tvar zoomed = $element.data(\"zoomed\") || false;\n\t\t\t\t$element.data(\"zoomed\", zoomed);\n\n\t\t\t\t$element.data(\"lockedZoom\", $element.data(\"lockedZoom\") || false);\n\n\t\t\t\tvar lockZoomClass = pluginName + \"-locked\";\n\n\t\t\t\tif( !$contain.length ){\n\t\t\t\t\tthrow new Error(pluginName + \": requires an element above the image marked with the class `enlarge_contain`\");\n\t\t\t\t}\n\n\t\t\t\t// this allows for methods and changing options in subsequent calls to the plugin\n\t\t\t\tif( typeof options === \"string\" ) {\n\t\t\t\t\tvar args = Array.prototype.slice.call(pluginArgs, 1);\n\n\t\t\t\t\tswitch(options){\n\t\t\t\t\tcase \"in\":\n\t\t\t\t\t\tif( !$element.data(\"zoomed\") ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"out\":\n\t\t\t\t\t\tif( $element.data(\"zoomed\") ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"isZoomed\":\n\t\t\t\t\t\tinternalResult = $element.data(\"zoomed\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"updateOptions\":\n\t\t\t\t\t\t$element.data( \"updateOptions\" )( args[ 0 ] );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// to toggle between inline and flyout modes, we change the elements that\n\t\t\t\t// the targetImg, zoomParent, and zoomContain vars refer to\n\t\t\t\tfunction updatePlacement(){\n\t\t\t\t\tif( o.placement === \"inline\" ){\n\t\t\t\t\t\ttargetImg = initialImg;\n\t\t\t\t\t\t$zoomParent = $parentPane;\n\t\t\t\t\t\t$element.data(\"zoomParent\", $zoomParent);\n\t\t\t\t\t\t$zoomContain = $contain;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttargetImg = $flyout.find( \"img\" )[ 0 ];\n\t\t\t\t\t\t$zoomParent = $zoomContain = $flyout;\n\t\t\t\t\t\t$element.data(\"zoomParent\", $zoomParent);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// this positions the loupe or side flyout element either according to mouse/touch coordinates\n\t\t\t\t// or the sides of the viewer specified\n\t\t\t\tfunction positionFlyout(){\n\n\t\t\t\t\t// set flyout width and height\n\t\t\t\t\t$flyout.css({\n\t\t\t\t\t\t\"width\": o.flyout.width + \"px\",\n\t\t\t\t\t\t\"height\": o.flyout.height + \"px\",\n\t\t\t\t\t\ttop: \"\",\n\t\t\t\t\t\tleft: \"\",\n\t\t\t\t\t\t\"margin-left\": \"\",\n\t\t\t\t\t\t\"margin-top\": \"\"\n\t\t\t\t\t});\n\n\t\t\t\t\t// set negative left or right value to match width\n\t\t\t\t\tvar flyoutSide = o.placement.match( /left|right/ );\n\n\t\t\t\t\tif( flyoutSide ){\n\t\t\t\t\t\t$flyout.css( flyoutSide[ 0 ], -o.flyout.width + \"px\" );\n\t\t\t\t\t}\n\t\t\t\t\t// if loupe mode, center offset\n\t\t\t\t\tvar loupe = o.placement.match( /loupe/ );\n\n\t\t\t\t\tif( loupe ) {\n\t\t\t\t\t\t// loupe\n\t\t\t\t\t\t$flyout.css({\n\t\t\t\t\t\t\t\"margin-left\": ( -o.flyout.width / 2 ) + \"px\",\n\t\t\t\t\t\t\t\"margin-top\": ( -o.flyout.height / 2 ) + \"px\"\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// add class to specify positioning spot for static css to apply\n\t\t\t\t\t$flyout[ 0 ].className = $flyout[ 0 ].className.replace( /enlarge_flyout\\-[^$\\s]+/, ' ' );\n\t\t\t\t\t$flyout.addClass( \"enlarge_flyout-\" +\t o.placement );\n\t\t\t\t}\n\n\t\t\t\tfunction disable(){\n\t\t\t\t\tif(o.disabled){\n\t\t\t\t\t\t$element.addClass(\"enlarge_disabled\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$element.removeClass(\"enlarge_disabled\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tdisable();\n\n\t\t\t\t// this allows for subsequent calls to the plugin to pass an updateOptions method and object,\n\t\t\t\t// which will pass through to the existing viewer on that element\n\t\t\t\t$element.data( \"updateOptions\", function( opts ){\n\t\t\t\t\to = $.extend( o, opts );\n\t\t\t\t\t$(this).data(\"options\", o);\n\n\t\t\t\t\tupdatePlacement();\n\t\t\t\t\tpositionFlyout();\n\t\t\t\t\thoverEnabled = o.hoverZoomWithoutClick;\n\t\t\t\t\tif( o.image && o.image.sizes ){\n\t\t\t\t\t\timgOriginalSizes = o.image.sizes;\n\t\t\t\t\t\ttoggleImgSrc();\n\t\t\t\t\t}\n\n\t\t\t\t\tdisable();\n\n\t\t\t\t\tif( o.disabled && $element.data(\"zoomed\") ) {\n\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// loader div holds a new image while its new source is loading\n\t\t\t\t// we insert this into the dom so that srcset/sizes can calculate a best source\n\t\t\t\tfunction addLoader(){\n\t\t\t\t\t$contain.append( '' );\n\t\t\t\t}\n\n\t\t\t\t// zoom state toggle boolean\n\t\t\t\tfunction toggleZoomState(){\n\t\t\t\t\tzoomed = !$element.data(\"zoomed\");\n\t\t\t\t\t$element.data(\"zoomed\", zoomed);\n\t\t\t\t}\n\n\t\t\t\t// toggle the image source bigger or smaller\n\t\t\t\t// ideally, this toggles the sizes attribute and allows the browser to select a new source from srcset\n\t\t\t\t// if srcset isn't supported or sizes attribute is not provided, the link href is used for the larger source\n\t\t\t\tfunction toggleImgSrc(after){\n\t\t\t\t\tafter = after || function(){};\n\n\t\t\t\t\tif( !zoomed ){\n\t\t\t\t\t\ttargetImg.sizes = imgOriginalSizes;\n\t\t\t\t\t\tif( !srcsetSizesSupported ){\n\t\t\t\t\t\t\ttargetImg.src = imgOriginalSrc;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tafter();\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// if the zooming is disabled do not replace with the larger source\n\t\t\t\t\t\t// NOTE we don't prevent switching to the original source because we\n\t\t\t\t\t\t// always want to allow the plugin to back out of the zoomed state\n\t\t\t\t\t\t// when disabled\n\t\t\t\t\t\tif( o.disabled ) { after(); return false; }\n\n\t\t\t\t\t\tvar zoomimg = new Image();\n\t\t\t\t\t\tzoomimg.className = \"enlarge_img-loading\";\n\t\t\t\t\t\tzoomimg.onload = function(){\n\t\t\t\t\t\t\ttargetImg.sizes = zoomimg.sizes;\n\t\t\t\t\t\t\tif( !srcsetSizesSupported || !srcset ){\n\t\t\t\t\t\t\t\ttargetImg.src = imgZoomSrc;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t$( zoomimg ).remove();\n\n\t\t\t\t\t\t\tafter();\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tzoomimg.sizes = imgZoomWidth() + \"px\";\n\n\t\t\t\t\t\tif( !srcsetSizesSupported || !srcset ){\n\t\t\t\t\t\t\tzoomimg.src = imgZoomSrc;\n\t\t\t\t\t\t} else if (srcset) {\n\t\t\t\t\t\t\tzoomimg.srcset = srcset;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t$( zoomimg ).insertBefore( targetImg );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// scroll to the center of the zoomed image\n\t\t\t\tfunction scrollToCenter(){\n\t\t\t\t\tvar pw = $zoomContain.width();\n\t\t\t\t\tvar ph = $zoomContain.height();\n\t\t\t\t\tvar w = targetImg.offsetWidth;\n\t\t\t\t\tvar h = targetImg.offsetHeight;\n\t\t\t\t\t$zoomContain[ 0 ].scrollLeft = ( w / 2 ) - ( pw / 2 );\n\t\t\t\t\t$zoomContain[ 0 ].scrollTop = ( h / 2 ) - ( ph / 2 );\n\t\t\t\t}\n\n\t\t\t\t// lock zoom mode allows for scrolling around normally without a cursor-follow behavior\n\t\t\t\tfunction toggleLockZoom(){\n\t\t\t\t\tif( !$element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t// NOTE we allow the image to zoom out if functionality gets disabled\n\t\t\t\t\t\t// when it's in a zoomed state\n\t\t\t\t\t\tif(o.disabled) { return false; }\n\n\t\t\t\t\t\t$parentPane.add( $zoomParent ).addClass( lockZoomClass );\n\t\t\t\t\t\t$element.data(\"lockedZoom\", lockedZoom = true);\n\t\t\t\t\t\t$zoomContain.attr( \"tabindex\", \"0\" );\n\t\t\t\t\t\t$zoomContain[ 0 ].focus();\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\t$parentPane.add( $zoomParent ).removeClass( lockZoomClass );\n\t\t\t\t\t\t$element.data(\"lockedZoom\", lockedZoom = false);\n\t\t\t\t\t\t$zoomContain.removeAttr( \"tabindex\" );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfunction imgZoomWidth(){\n\t\t\t\t\treturn $parentPane[0].offsetWidth * o.magnification;\n\t\t\t\t}\n\n\t\t\t\t// toggle magnification of image\n\t\t\t\tfunction toggleImgZoom(){\n\t\t\t\t\tif( $element.data(\"zoomed\") ){\n\t\t\t\t\t\t// NOTE we allow the image to zoom out if functionality gets disabled\n\t\t\t\t\t\t// when it's in a zoomed state\n\t\t\t\t\t\tif(o.disabled) { return false; }\n\n\t\t\t\t\t\tif( o.placement === \"inline\" ){\n\t\t\t\t\t\t\t$contain.add( $parentPane ).css( { \"width\": $parentPane[0].offsetWidth + \"px\", \"height\": parseFloat( getComputedStyle( $parentPane[0] ).height ) + \"px\" } );\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$zoomParent.addClass( zoomClass );\n\t\t\t\t\t\t$( targetImg ).css( \"width\", imgZoomWidth() + \"px\" );\n\n\t\t\t\t\t\t$(self).trigger( pluginName + \".after-zoom-in\");\n\t\t\t\t\t}\n\t\t\t\t\telse{\n\t\t\t\t\t\t$zoomParent.removeClass( zoomClass );\n\t\t\t\t\t\tif( o.placement === \"inline\" ){\n\t\t\t\t\t\t\t$contain.add( $parentPane ).css( { \"width\": \"\", \"height\": \"\" } );\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$( targetImg ).css( \"width\", \"\" );\n\n\t\t\t\t\t\t$(self).trigger( pluginName + \".after-zoom-out\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfunction forceInlineMode(){\n\t\t\t\t\tvar oldO = o.placement;\n\t\t\t\t\tif( oldO !== \"inline\" ){\n\t\t\t\t\t\tfunction resetPlacement(){\n\t\t\t\t\t\t\to.placement = oldO;\n\t\t\t\t\t\t\tupdatePlacement();\n\t\t\t\t\t\t\t$(self).unbind( pluginName + \".after-zoom-out\", resetPlacement );\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$(self).bind( pluginName + \".after-zoom-out\", resetPlacement );\n\t\t\t\t\t\to.placement = \"inline\";\n\t\t\t\t\t\tupdatePlacement();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// lock zoom mode toggle\n\t\t\t\tfunction standardToggleZoom(){\n\t\t\t\t\t// NOTE if the current is zoomed out and it's disabled prevent toggling\n\t\t\t\t\tif(o.disabled && !$element.data(\"zoomed\")) { return false; }\n\t\t\t\t\ttoggleZoomState();\n\t\t\t\t\ttoggleImgSrc(function(){\n\t\t\t\t\t\ttoggleLockZoom();\n\t\t\t\t\t\ttoggleImgZoom();\n\t\t\t\t\t\tscrollToCenter();\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tvar trackingOn;\n\t\t\t\tvar trackingTimer;\n\t\t\t\tvar mouseEntered;\n\t\t\t\tvar touchStarted;\n\t\t\t\tvar hoverEnabled = o.hoverZoomWithoutClick;\n\t\t\t\tvar lastMouseMove;\n\n\t\t\t\t// mouseenter or touchstart handler for dragging image\n\t\t\t\tfunction startTrackingDelay(e){\n\t\t\t\t\tif( e.type === \"touchstart\" ){\n\t\t\t\t\t\ttouchStarted = true;\n\t\t\t\t\t}\n\t\t\t\t\tif( touchStarted && e.type === \"mouseenter\" ||\n\t\t\t\t\t\t\te.type === \"mouseenter\" && !touchStarted && !hoverEnabled ||\n\t\t\t\t\t\t\t$element.data(\"lockedZoom\") ||\n\t\t\t\t\t\t\tmouseEntered ){\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\tmouseEntered = true;\n\t\t\t\t\t$contain.addClass( delayClass );\n\t\t\t\t\ttrackingTimer = setTimeout( function(){\n\t\t\t\t\t\t$contain.removeClass( delayClass );\n\t\t\t\t\t\ttoggleZoomState();\n\t\t\t\t\t\ttoggleImgSrc(function(){\n\t\t\t\t\t\t\ttoggleImgZoom();\n\t\t\t\t\t\t\ttrackingOn = true;\n\t\t\t\t\t\t\tif (lastMouseMove) {\n\t\t\t\t\t\t\t\tscrollWithMouse(lastMouseMove);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tscrollWithMouse(e);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}, o.delay );\n\t\t\t\t}\n\n\t\t\t\t// mouseleave or touchend after a drag\n\t\t\t\tfunction stopTrackingDelay( e ){\n\t\t\t\t\t$contain.removeClass( delayClass );\n\t\t\t\t\tclearTimeout( trackingTimer );\n\t\t\t\t\ttrackingOn = false;\n\t\t\t\t\tif( o.hoverZoomWithoutClick === false && !touchStarted ){\n\t\t\t\t\t\thoverEnabled = false;\n\t\t\t\t\t}\n\t\t\t\t\tif( touchStarted && e.type === \"mouseleave\" ){\n\t\t\t\t\t\ttouchStarted = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// mousemove or touch-drag image placement\n\t\t\t\tfunction scrollWithMouse( e ){\n\t\t\t\t\t// if tracking's not on yet, ignore. This allows the delay to work\n\t\t\t\t\tif( trackingOn ){\n\t\t\t\t\t\t// if the move was touch-started, and the event is mousemove, it can be ignored\n\t\t\t\t\t\t// (mouse events fire along with touch events and we just want the touch)\n\t\t\t\t\t\tif( touchStarted && e.type === \"mousemove\" ){\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// normalize ev to touch or mouse\n\t\t\t\t\t\tvar ev = e.touches ? e.touches[ 0 ] : e;\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tvar x = ev.clientX - $contain[ 0 ].getBoundingClientRect().left;\n\t\t\t\t\t\tvar y = ev.clientY - $contain[ 0 ].getBoundingClientRect().top;\n\n\t\t\t\t\t\tif( o.placement.match( /loupe/ ) ) {\n\t\t\t\t\t\t\t// offset the loupe a little differently for touch so that it's not directly beneath a finger\n\t\t\t\t\t\t\tvar mLeft = ( e.touches ? -o.flyout.width / 1.3 : -o.flyout.width / 2 ) + \"px\";\n\t\t\t\t\t\t\tvar mTop = ( e.touches ? -o.flyout.height / 1.3 : -o.flyout.height / 2 ) + \"px\";\n\t\t\t\t\t\t\trequestAnimationFrame(function(){\n\t\t\t\t\t\t\t\t$flyout.css( {\n\t\t\t\t\t\t\t\t\ttop: y + \"px\",\n\t\t\t\t\t\t\t\t\tleft: x + \"px\",\n\t\t\t\t\t\t\t\t\t\"margin-left\": mLeft,\n\t\t\t\t\t\t\t\t\t\"margin-top\": mTop\n\t\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar containWidth = $contain[ 0 ].offsetWidth;\n\t\t\t\t\t\tvar containHeight = $contain[ 0 ].offsetHeight;\n\t\t\t\t\t\tvar containScrollWidth =\ttargetImg.offsetWidth;\n\t\t\t\t\t\tvar containScrollHeight = targetImg.offsetHeight;\n\t\t\t\t\t\tvar zoomContainWidth = $zoomContain[ 0 ].offsetWidth;\n\t\t\t\t\t\tvar zoomContainHeight = $zoomContain[ 0 ].offsetHeight;\n\t\t\t\t\t\tvar widthFactor = containWidth / zoomContainWidth;\n\t\t\t\t\t\tvar heightFactor = containHeight / zoomContainHeight;\n\n\t\t\t\t\t\t$zoomContain[ 0 ].scrollLeft = (( x / containWidth ) * ( containScrollWidth - zoomContainWidth ));\n\t\t\t\t\t\t$zoomContain[0].scrollLeft += ((x / containWidth) - 0.5) * zoomContainWidth;\n\t\t\t\t\t\t$zoomContain[ 0 ].scrollTop = (( y / containHeight ) * ( containScrollHeight - zoomContainHeight )) ;\n\t\t\t\t\t\t$zoomContain[0].scrollTop += ((y / containHeight) - 0.5) * zoomContainHeight;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlastMouseMove = e;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// add flyout for flyout and loupe modes\n\t\t\t\t// flyout is always present, yet hidden when inline mode is active\n\t\t\t\tvar $flyout = $( '
' ).append( $contain.clone() );\n\t\t\t\t$flyout.insertAfter( $parentPane );\n\n\t\t\t\t// add loader div\n\t\t\t\taddLoader();\n\t\t\t\tupdatePlacement();\n\t\t\t\tpositionFlyout();\n\n\t\t\t\t// clicking the magnify anchor toggles lock-zoom mode\n\t\t\t\t$anchor\n\t\t\t\t\t.bind( \"keydown\", function(e){\n\t\t\t\t\t\tif( e.keyCode === 13 || e.keyCode === 32 ){\n\t\t\t\t\t\t\tforceInlineMode();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// spacebar triggers click too\n\t\t\t\t\t\tif( e.keyCode === 32 ){\n\t\t\t\t\t\t\te.preventDefault(); // don't scroll the new focused area\n\t\t\t\t\t\t\t$( this ).trigger( \"click\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.bind( \"click\", function( e ){\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t});\n\n\t\t\t\t// on resize, if in lock zoom mode, un zoom\n\t\t\t\t$( w )\n\t\t\t\t\t.bind( \"resize\", function( e ){\n\t\t\t\t\t\tif( $element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t// on click-out on the page, if in locked zoom mode, zoom out\n\t\t\t\t$( w.document )\n\t\t\t\t\t.bind( \"mouseup\", function( e ){\n\t\t\t\t\t\tif( $element.data(\"lockedZoom\") && !$( e.target ).closest( $parentPane ).length ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t// mouse hover and touch-drag gestures for a cursor-tracked zoom behavior\n\t\t\t\t$( initialImg )\n\t\t\t\t\t.bind( \"mouseenter touchstart\", startTrackingDelay )\n\t\t\t\t\t.bind( \"mousemove touchmove\", scrollWithMouse )\n\t\t\t\t\t.bind( \"mouseleave touchend\", function( e ){\n\t\t\t\t\t\tmouseEntered = false;\n\t\t\t\t\t\tif( zoomed && !$element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t\ttoggleZoomState();\n\t\t\t\t\t\t\ttoggleImgSrc(function(){\n\t\t\t\t\t\t\t\ttoggleImgZoom();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstopTrackingDelay( e );\n\t\t\t\t\t})\n\t\t\t\t// tapping the image should trigger a lock zoom\n\t\t\t\t// click will not fire after a touch-drag so it works as a tap for our needs here\n\t\t\t\t\t.bind( \"click\", function( e ){\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t// if the click was started with a touchstart event,\n\t\t\t\t\t\t// and placement is inline\n\t\t\t\t\t\t// toggle the locked zoom mode\n\t\t\t\t\t\tif( touchStarted && o.placement === \"inline\" ){\n\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( o.hoverZoomWithoutClick === false && !touchStarted ){\n\t\t\t\t\t\t\thoverEnabled = !hoverEnabled;\n\t\t\t\t\t\t\tif( hoverEnabled ){\n\t\t\t\t\t\t\t\t$( this ).trigger( \"mouseenter\" );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t$( this ).trigger( \"mouseleave\" );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} );\n\n\t\t\t\t// keyboard handling for arrows in zoom mode\n\t\t\t\t$(this).bind( \"keydown keyup\", function( e ){\n\t\t\t\t\tif( e.keyCode === 37 ||\n\t\t\t\t\t\t\te.keyCode === 38 ||\n\t\t\t\t\t\t\te.keyCode === 39 ||\n\t\t\t\t\t\t\te.keyCode === 40 ){\n\t\t\t\t\t\t\t\te.stopImmediatePropagation();\n\t\t\t\t\t\t\t\tif( !$element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if( e.type === \"keyup\" && $(this).data(\"lockedZoom\") && e.keyCode === 27 ){ //esc or backspace closes zoom\n\t\t\t\t\t\t\t\tstandardToggleZoom();\n\t\t\t\t\t\t\t\t$anchor[0].focus();\n\t\t\t\t\t\t\t\te.stopImmediatePropagation();\n\t\t\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// on scroll, zoom out\n\t\t\t\t$parentPane.bind( \"scroll\", function(){\n\t\t\t\t\tif( $element.data(\"zoomed\") ){\n\t\t\t\t\t\ttoggleZoomState();\n\t\t\t\t\t\ttoggleImgSrc(function(){\n\t\t\t\t\t\t\tif( $element.data(\"lockedZoom\") ){\n\t\t\t\t\t\t\t\ttoggleLockZoom();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttoggleImgZoom();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\n\t\t\treturn internalResult !== undefined ? internalResult : result;\n\t\t};\n\t};\n\n\tif( typeof module !== \"undefined\" ){\n\t\tmodule.exports = enlarge;\n\t} else {\n\t\tenlarge();\n\t}\n}( typeof global !== \"undefined\" ? global : this ));\n\n(function(exports){\n\tvar Image = {\n\t\tdefaultConfig: {\n\t\t\twidths: [\n\t\t\t\t\"320\",\n\t\t\t\t\"640\",\n\t\t\t\t\"800\",\n\t\t\t\t\"1024\",\n\t\t\t\t\"2048\",\n\t\t\t\t\"5000\"\n\t\t\t],\n\n\t\t\tsizes: \"100vw\",\n\t\t\tpolicy: undefined,\n\t\t\twidthParam: \"imwidth\"\n\t\t}\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Image = Image;\n})(typeof exports === 'undefined'? window : exports);\n\n(function(exports, $){\n\t/**\n\t * 360 degree viewer\n\t * @class\n\t * @alias Akamai.Spin360\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\tvar Spin360 = Akamai.Util.component(\"Spin360\", function(element, options){\n\t\tthis._comp = new Akamai.Tau(element, this._options);\n\t});\n\n\t// srcset stuff\n\tAkamai.Sourceable.extendStatic(Spin360);\n\n\t// Used in preflight to \"rename\" events based on the child component events\n\tSpin360._componentEventMapping = {\n\t\t\"tau.auto-rotate-start\": \"play\",\n\t\t\"tau.auto-rotate-stop\": \"pause\"\n\t};\n\n\tSpin360.prototype._updateOptions = function(options){\n\t\tif( !options ) {\n\t\t\tthis._options = this._originalOptions;\n\t\t}\n\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Spin360.defaultOptions, this._options, \"Spin360\");\n\n\t\t// TODO actually update the options\n\t};\n\n\tSpin360._renderImg = function(url, options){\n\t\treturn `\n\t\t\t\n\t\t`;\n\t};\n\n\tSpin360.render = function(json, options){\n\t\tvar finalOptions = Akamai.Util.options(Spin360.defaultOptions, options);\n\n\t\t// TODO fix the string problem in shoestring\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t${Akamai.Util\n\t\t\t\t\t\t.map(json.urls, function(url) { return Spin360._renderImg(url, finalOptions); })\n\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t}\n\t\t\t
\n\t\t`);\n\t};\n\n\t/**\n\t * Defines the global default options for all Spin360s on the page\n\t * @static\n\t * @property {Boolean} autoplay.enabled - Enable autoplay (default: false)\n\t * @property {Integer} autoplay.delay - Delay in milliseconds after initialization before spinning begins (default: 1000)\n\t * @property {Boolean} controls.arrows - Render controls to spin left and right (default: false)\n\t * @property {Boolean} controls.play - Render controls to enable and disable automatic spinning (default: false)\n\t * @property {String} controls.text.left - Left rotation control title and text (default: \"Rotate Left\")\n\t * @property {String} controls.text.right - Right rotation control title and text (default: \"Rotate Right\")\n\t * @property {String} controls.text.play - Spin control title and text (default: \"Spin Object\")\n\t * @property {Array} images.widths - list of available widths for an image (to be combined with image.widthParam), Default: [\"320\",\"640\",\"800\",\"1024\",\"2048\",\"5000\"]\n\t * @property {String} images.sizes - value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw`, `200px`, `(min-width:1000px) 500px, 100vw`.\n\t * @property {String} images.policy - query param value for policy, appended to &policy= per image url when specified. Values: `foo`. Default: undefined.\n\t * @property {String} images.widthParam - query string name to use for setting each url width. Default urls will be ?imwidth=320 for example. Values: `imwidth` (default), `w`, `width`, etc.\n\t * @property {Integer} interval - The full rotation interval in milliseconds, determines physics, (default: 3000)\n\t * @property {Boolean} reverse - Reverse the direction of the spin (default: false)\n\t * @property {Float} sensitivity - The speed at which the object rotates relative to user input (default: 1). At the default value of `1` the object will complete a full 360 rotation when you drag across the entire width of the spin360 component. To require less effort to complete a rotation, change this value to a higher number. For example, setting `sensitivity: 2` would complete a full 360 rotation by dragging halfway (ex. from the center to the edge).\n\t */\n\tSpin360.defaultOptions = {\n\t\tautoplay: {\n\t\t\tenabled: false,\n\t\t\tdelay: 1000\n\t\t},\n\n\t\tcontrols: {\n\t\t\tarrows: false,\n\t\t\tplay: false,\n\t\t\ttext: {\n\t\t\t\tleft: \"Rotate Left\",\n\t\t\t\tright: \"Rotate Right\",\n\t\t\t\tplay: \"Spin Object\"\n\t\t\t}\n\t\t},\n\n\t\t// unsupported, the number of frame images to create using the configured\n\t\t// template\n\t\tframes: 72,\n\t\timages: Akamai.Image.defaultConfig,\n\t\tinterval: 3000,\n\t\treverse: false,\n\n\t\tsensitivity: 1,\n\n\t\t// unsupported, template used to generate urls when only one image is\n\t\t// present in the 360 viewer\n\t\ttemplate: undefined\n\t};\n\n\t/**\n\t * Goto to a particular frame of the spining image\n\t * @method\n\t * @param {Integer} index - the frame to advance to\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.goto = function(index, callback){\n\t\tthis._comp.goto(index);\n\t\t// NOTE goto must come before the callback because the callback will be\n\t\t// used to trigger the `next` and `previous` events. The order should be\n\t\t// maintained consistently as `goto` -> `next`/`previous`\n\t\tthis._trigger(\"goto\");\n\t\tif(callback){ callback(); }\n\t};\n\n\t/**\n\t * Return the current frame index\n\t * @method\n\t * @returns {Integer}\n\t */\n\tSpin360.prototype.getIndex = function(){\n\t\treturn this._comp.index;\n\t};\n\n\t// Extend Carousel with Advanceable interface\n\tAkamai.Advanceable.extend(Spin360);\n\n\t/**\n\t * Go to the next frame\n\t * @method\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.next;\n\t// required for JSDocs\n\n\t/**\n\t * Go to the previous frame\n\t * @method\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.previous;\n\t// required for JSDocs\n\n\t/**\n\t * Begin the automatic rotation of the images\n\t * @todo support passing in an interval or speed?\n\t * @method\n\t * @fires Akamai.Spin360#akamai-spin360-play\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.play = function(){\n\t\tthis._comp.autoRotate();\n\t};\n\n\t/**\n\t * Stop the automatic rotation of the images\n\t * @method\n\t * @fires Akamai.Spin360#akamai-spin360-pause\n\t * @returns {undefined}\n\t */\n\tSpin360.prototype.pause = function(){\n\t\tthis._comp.stopAutoRotate();\n\t};\n\n\tSpin360.prototype.getElement = function(){\n\t\treturn this._$el[0];\n\t};\n\n\tSpin360.States = {\n\t\tPlaying: 0,\n\t\tPaused: 1\n\t};\n\n\t/**\n\t * Return the current state of the spin360\n\t * @example Spin360 states\n\t * Akamai.Spin360.States = {\n\t *\tPlaying: 0,\n\t *\tPaused: 1\n\t * };\n\t *\n\t * @example Conditioning on states\n\t * if( spin360.getState() === Akamai.Spin360.States.Paused ) {\n\t *\t spin360.play()\n\t * }\n\t * @method\n\t * @returns {Akamai.Spin360.State}\n\t */\n\tSpin360.prototype.getState = function(){\n\t\t// TODO expose using method in Tau\n\t\treturn !!this._comp.autoInterval ? Spin360.States.Playing : Spin360.States.Paused;\n\t};\n\n\t/**\n\t * Triggered when the spin360 has stoped automatically spinning\n\t * {@link Akamai.Spin360#pause}.\n\t *\n\t * @event Akamai.Spin360#akamai-spin360-stop-spin\n\t */\n\n\t/**\n\t * Triggered when the spin360 has started automatically spinning\n\t * {@link Akamai.Spin360#play}.\n\t *\n\t * @event Akamai.Spin360#akamai-spin360-start-spin\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Spin360}.\n\t *\n\t * @event Akamai.Spin360#akamai-spin360-init\n\t */\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Spin360 = Spin360;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(){\n\tfunction MagnifierImpl(element, options){\n\t\tthrow new Error('Not allowed to instantiate MagnifierImpl');\n\t}\n\n\tMagnifierImpl.prototype.updateOptions = function(options){\n\t\tthrow new Error('magnifier updateOptions not implemented');\n\t};\n\n\tMagnifierImpl.prototype.zoomIn = function(){\n\t\tthrow new Error('magnifier zoomIn not implemented');\n\t};\n\n\tMagnifierImpl.prototype.zoomOut = function(){\n\t\tthrow new Error('magnifier zoomOut not implemented');\n\t};\n\n\tMagnifierImpl.prototype.isMagnified = function(){\n\t\tthrow new Error('magnifier isMagnified not implemented');\n\t};\n\n\tMagnifierImpl.prototype.toggleZoom = function(){\n\t\tthrow new Error('magnifier toggleZoom not implemented');\n\t};\n\n\tMagnifierImpl.prototype.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){\n\t\tthrow new Error('magnifier render not implemented');\n\t};\n\n\tvar ex = (typeof exports === 'undefined') ? window : exports;\n\tex.Akamai = ex.Akamai || {};\n\tex.Akamai.MagnifierImpl = MagnifierImpl;\n})();\n(function(){\n\tfunction MagnifierImplEnlarge(element, options){\n\t\tthis._$el = $(element);\n\t\tthis._$el.enlarge(options);\n\t}\n\n\tMagnifierImplEnlarge.prototype = Object.create(Akamai.MagnifierImpl.prototype);\n\tMagnifierImplEnlarge.prototype.constructor = MagnifierImplEnlarge;\n\n\tMagnifierImplEnlarge.prototype.updateOptions = function(options){\n\t\tthis._$el.enlarge( \"updateOptions\", options );\n\t};\n\n\tMagnifierImplEnlarge.prototype.zoomIn = function(){\n\t\tthis._$el.enlarge( \"in\" );\n\t};\n\n\tMagnifierImplEnlarge.prototype.zoomOut = function(){\n\t\tthis._$el.enlarge( \"out\" );\n\t};\n\n\tMagnifierImplEnlarge.prototype.isMagnified = function(){\n\t\treturn this._$el.enlarge( \"isZoomed\" );\n\t};\n\n\tMagnifierImplEnlarge.prototype.toggleZoom = function(){\n\t\tif(this.isMagnified()){\n\t\t\tthis.zoomOut();\n\t\t} else {\n\t\t\tthis.zoomIn();\n\t\t}\n\t};\n\n\tMagnifierImplEnlarge.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){\n\t\treturn `\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t${options.buttonText}\n\t\t\t\t\n\t\t\t
\n\t\t`;\n\t};\n\t\n\tvar ex = (typeof exports === 'undefined') ? window : exports;\n\tex.Akamai = ex.Akamai || {};\n\tex.Akamai.MagnifierImplEnlarge = MagnifierImplEnlarge;\n})();\n(function(){\n\tfunction ClickTracker(element, handler){\n\t\tvar self = this;\n\t\tself.element = element;\n\t\tself.handler = handler;\n\t\tthis.reset();\n\t}\n\n\tClickTracker.prototype.onDown = function(e){\n\t\tif (e.target == this.element) {\n\t\t\tif (this.isDown) {\n\t\t\t\tthis.reset();\n\t\t\t} else {\n\t\t\t\tthis.down.x = e.clientX;\n\t\t\t\tthis.down.y = e.clientY;\n\t\t\t\tthis.down.timestamp = new Date();\n\t\t\t\tthis.isDown = true;\n\t\t\t}\n\t\t}\n\t};\n\n\tClickTracker.prototype.onMove = function(){\n\t\tthis.isMoved = true;\n\t};\n\n\tClickTracker.prototype.onUp = function(e){\n\t\tif (this.isDown) {\n\t\t\tthis.up.x = e.clientX;\n\t\t\tthis.up.y = e.clientY;\n\t\t\tthis.up.timestamp = new Date();\n\n\t\t\tif (!this.isMoved && (this.down.x === this.up.x && this.down.y === this.up.y && (this.up.timestamp.getTime() - this.down.timestamp.getTime() <= 500))) {\n\t\t\t\tthis.handler();\n\t\t\t}\n\t\t}\n\n\t\tthis.reset();\n\t};\n\n\tClickTracker.prototype.reset = function(x, y){\n\t\tthis.down = {\n\t\t\tx: -Infinity,\n\t\t\ty: -Infinity,\n\t\t\ttimestamp: new Date(0)\n\t\t};\n\n\t\tthis.isDown = false;\n\t\tthis.isMoved = false;\n\n\t\tthis.up = {\n\t\t\tx: Infinity,\n\t\t\ty: Infinity,\n\t\t\ttimestamp: new Date()\n\t\t};\n\t};\n\n\tfunction MagnifierImplScroller(element, options){\n\t\tthis._$el = $(element);\n\t\tthis._options = options;\n\t\tthis._container = this._$el.get(0);\n\t\tthis._content = this._$el.children().get(0);\n\t\tthis._imgCast = $(this._content).find('div').get(0);\n\t\tthis._zoomInBtn = this._$el.find(\"a.scroller-zoom-in\").get(0);\n\t\tthis._zoomOutBtn = this._$el.find(\"a.scroller-zoom-out\").get(0);\n\t\tthis._magnification = 1;\n\n\t\tvar self = this;\n\n\t\t// Initialize Scroller\n\t\tthis.scroller = new Scroller(this._renderer(), {\n\t\t\tzooming: options.enabled,\n\t\t\tminZoom: 1,\n\t\t\tmaxZoom: options.magnification,\n\t\t\tanimationDuration: options.animationDuration\n\t\t});\n\n\t\tvar rect = this._container.getBoundingClientRect();\n\t\tthis.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop);\n\n\t\tthis._installEventHandlers();\n\t\tthis._updateButtonStates();\n\t\tsetTimeout(function(){\n\t\t\tself._onResize();\n\t\t});\n\t}\n\n\tMagnifierImplScroller.prototype = Object.create(Akamai.MagnifierImpl.prototype);\n\tMagnifierImplScroller.prototype.constructor = MagnifierImplScroller;\n\n\tMagnifierImplScroller.prototype.updateOptions = function(options){\n\t\tvar rect = this._container.getBoundingClientRect();\n\t\tthis.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop);\n\t\tthis._onResize();\n\t\tthis._zoomBy(1 / this._options.magnification, false);\n\t};\n\n\tMagnifierImplScroller.prototype.zoomIn = function(){\n\t\tthis._zoomBy(this._options.incrementalZoomFactor);\n\t};\n\n\tMagnifierImplScroller.prototype.cyclicZoom = function(){\n\t\tvar self = this;\n\t\tif (this._magnification >= this._options.magnification) {\n\t\t\tthis._zoomBy(1 / this._options.magnification);\n\t\t} else {\n\t\t\tthis.zoomIn();\n\t\t}\n\t};\n\n\tMagnifierImplScroller.prototype.zoomOut = function(){\n\t\tthis._zoomBy(1 / this._options.incrementalZoomFactor);\n\t};\n\n\tMagnifierImplScroller.prototype.isMagnified = function(){\n\t\treturn this._magnification > 1;\n\t};\n\n\tMagnifierImplScroller.prototype.toggleZoom = function(){\n\t\tthrow new Error('zoom toggle behavior is undefined in scroller mode');\n\t};\n\n\tMagnifierImplScroller.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){\n\t\treturn `\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t${options.zoomInBtnText}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t${options.zoomOutBtnText}\n\t\t\t\t\n\t\t\t
\n\t\t`;\n\t};\n\n\tMagnifierImplScroller.prototype._zoomBy = function(magnification, animate){\n\t\tvar self = this, magnification = Number(magnification);\n\n\t\tif (typeof(animate) === \"undefined\") {\n\t\t\tanimate = this._options.animateZoom;\n\t\t}\n\n\t\tif (magnification === 1 || \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// if we are magnifying by 1 OR\n\t\t\t(this._magnification === 1 && magnification < 1) || \t\t\t\t\t\t\t// fully zoomed out and still attempting a zoom out OR\n\t\t\t(this._magnification === this._options.magnification && magnification > 1)) {\t// fully zoomed in and still attempting a zoom in\n\t\t\treturn;\n\t\t}\n\n\t\tthis.scroller.zoomTo(this._magnification * magnification, this._options.animateZoom);\n\n\t\tsetTimeout(function(){\n\t\t\tself._onZoom();\n\t\t}, this._options.animationDuration);\n\t}\n\n\tMagnifierImplScroller.prototype._onZoom = function(){\n\t\tvar oldMagnification = this._magnification;\n\n\t\tthis._magnification = Number(this.scroller.getValues().zoom.toFixed(2));\n\t\tthis._updateButtonStates();\n\n\t\tif (oldMagnification > this._magnification) {\n\t\t\tthis._$el.trigger(\"scroller.after-zoom-out\");\n\t\t} else if (oldMagnification < this._magnification) {\n\t\t\tthis._$el.trigger(\"scroller.after-zoom-in\");\n\t\t}\n\t};\n\n\tMagnifierImplScroller.prototype._renderer = function(){\n\t\tvar docStyle = document.documentElement.style, self = this;\n\n\t\tvar engine;\n\t\tif (window.opera && Object.prototype.toString.call(opera) === '[object Opera]') {\n\t\t\tengine = 'presto';\n\t\t} else if ('MozAppearance' in docStyle) {\n\t\t\tengine = 'gecko';\n\t\t} else if ('WebkitAppearance' in docStyle) {\n\t\t\tengine = 'webkit';\n\t\t} else if (typeof navigator.cpuClass === 'string') {\n\t\t\tengine = 'trident';\n\t\t}\n\n\t\tvar vendorPrefix = {\n\t\t\ttrident: 'ms',\n\t\t\tgecko: 'Moz',\n\t\t\twebkit: 'Webkit',\n\t\t\tpresto: 'O'\n\t\t}[engine];\n\n\t\tvar helperElem = document.createElement(\"div\");\n\t\tvar undef;\n\n\t\tvar perspectiveProperty = vendorPrefix + \"Perspective\";\n\t\tvar transformProperty = vendorPrefix + \"Transform\";\n\n\t\tif (helperElem.style[perspectiveProperty] !== undef) {\n\t\t\treturn function(left, top, zoom) {\n\t\t\t\tself._content.style[transformProperty] = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')';\n\t\t\t};\n\n\t\t} else if (helperElem.style[transformProperty] !== undef) {\n\t\t\treturn function(left, top, zoom) {\n\t\t\t\tself._content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')';\n\t\t\t};\n\n\t\t} else {\n\t\t\treturn function(left, top, zoom) {\n\t\t\t\tself._content.style.marginLeft = left ? (-left/zoom) + 'px' : '';\n\t\t\t\tself._content.style.marginTop = top ? (-top/zoom) + 'px' : '';\n\t\t\t\tself._content.style.zoom = zoom || '';\n\t\t\t};\n\t\t}\n\t};\n\n\tMagnifierImplScroller.prototype._onResize = function(){\n\t\tthis.scroller.setDimensions(this._container.clientWidth, this._container.clientHeight, this._container.clientWidth, this._container.clientHeight);\n\t};\n\n\tMagnifierImplScroller.prototype._installEventHandlers = function(){\n\t\tvar self = this, clickTracker = new ClickTracker(this._imgCast, function(){\n\t\t\tself.cyclicZoom();\n\t\t});\n\n\t\twindow.addEventListener(\"resize\", function(e){\n\t\t\tself._onResize(e);\n\t\t}, false);\n\n\t\tthis._zoomInBtn.addEventListener(\"click\", function(e) {\n\t\t\te.preventDefault();\n\t\t\tself.zoomIn();\n\t\t}, false);\n\n\t\tthis._zoomOutBtn.addEventListener(\"click\", function(e) {\n\t\t\te.preventDefault();\n\t\t\tself.zoomOut();\n\t\t}, false);\n\n\t\tif ('ontouchstart' in window) {\n\t\t\tself._container.addEventListener(\"touchstart\", function(e) {\n\t\t\t\t// Don't react if initial down happens on one of the zoom buttons\n\t\t\t\tif (e.touches[0].target == self._$el.find('a.scroller-zoom-in').get(0) || e.touches[0].target == self._$el.find('a.scroller-zoom-out').get(0)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Don't react if initial down happens on a form element\n\t\t\t\tif (e.touches[0] && e.touches[0].target && e.touches[0].target.tagName.match(/input|textarea|select/i)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.scroller.doTouchStart(e.touches, e.timeStamp);\n\n\t\t\t\tfor (var i = 0; i < e.touches.length; i++) {\n\t\t\t\t\tclickTracker.onDown(e.touches[i]);\n\t\t\t\t}\n\n\t\t\t\te.preventDefault();\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"touchmove\", function(e) {\n\t\t\t\tself.scroller.doTouchMove(e.touches, e.timeStamp, e.scale);\n\t\t\t\tclickTracker.onMove();\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"touchend\", function(e) {\n\t\t\t\tself.scroller.doTouchEnd(e.timeStamp);\n\n\t\t\t\t// handle any zoom that may have occured\n\t\t\t\tself._onZoom();\n\n\t\t\t\tfor (var i = 0; i < e.changedTouches.length; i++) {\n\t\t\t\t\tclickTracker.onUp(e.changedTouches[i]);\n\t\t\t\t}\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"touchcancel\", function(e) {\n\t\t\t\tself.scroller.doTouchEnd(e.timeStamp);\n\t\t\t\t// handle any zoom that may have occured\n\t\t\t\tself._onZoom();\n\n\t\t\t\tfor (var i = 0; i < e.changedTouches.length; i++) {\n\t\t\t\t\tclickTracker.onUp(e.changedTouches[i]);\n\t\t\t\t}\n\t\t\t}, false);\n\t\t} else {\n\t\t\tself._container.addEventListener(\"mousedown\", function(e) {\n\t\t\t\tif (e.target.tagName.match(/input|textarea|select/i)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (self.scroller.__clientWidth === 0) {\n\t\t\t\t\tself._onResize();\n\t\t\t\t}\n\t\t\t\tself.scroller.doTouchStart([{\n\t\t\t\t\tpageX: e.pageX,\n\t\t\t\t\tpageY: e.pageY\n\t\t\t\t}], e.timeStamp);\n\n\t\t\t\tclickTracker.onDown(e);\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"mousemove\", function(e) {\n\t\t\t\tif (!clickTracker.isDown) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.scroller.doTouchMove([{\n\t\t\t\t\tpageX: e.pageX,\n\t\t\t\t\tpageY: e.pageY\n\t\t\t\t}], e.timeStamp);\n\n\t\t\t\tclickTracker.onMove();\n\t\t\t}, false);\n\n\t\t\tdocument.addEventListener(\"mouseup\", function(e) {\n\t\t\t\tif (!clickTracker.isDown) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.scroller.doTouchEnd(e.timeStamp);\n\t\t\t\tclickTracker.onUp(e);\n\t\t\t}, false);\n\n\t\t\t// self._container.addEventListener(navigator.userAgent.indexOf(\"Firefox\") > -1 ? \"DOMMouseScroll\" : \"mousewheel\", function(e) {\n\t\t\t// \te.preventDefault();\n\t\t\t// \tself.scroller.doMouseZoom(e.detail ? (e.detail * -120) : e.wheelDelta, e.timeStamp, e.pageX, e.pageY);\n\t\t\t// }, false);\n\t\t}\n\t};\n\n\tMagnifierImplScroller.prototype._updateButtonStates = function(){\n\t\tif (this._magnification === 1) {\n\t\t\t$(this._$el).addClass('scroller-zoom-out-max');\n\t\t\t$(this._$el).removeClass('scroller-zoom-in-max');\n\t\t} else if (this._magnification === this._options.magnification) {\n\t\t\t$(this._$el).addClass('scroller-zoom-in-max');\n\t\t\t$(this._$el).removeClass('scroller-zoom-out-max');\n\t\t} else {\n\t\t\t$(this._$el).removeClass('scroller-zoom-in-max');\n\t\t\t$(this._$el).removeClass('scroller-zoom-out-max');\n\t\t}\n\t};\n\n\tvar ex = (typeof exports === 'undefined') ? window : exports;\n\tex.Akamai = ex.Akamai || {};\n\tex.Akamai.MagnifierImplScroller = MagnifierImplScroller;\n})();\n(function(exports, $){\n\t/**\n\t * Image magnifier\n\t * @class\n\t * @alias Akamai.Magnifier\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\tvar Magnifier = Akamai.Util.component(\"Magnifier\", function(element, options){\n\t\t// compat with enlarge `disabled` option\n\t\tthis._options.disabled = !this._options.enabled;\n\t\tif (this._options.mode === Magnifier.MODE_ANIMATED_ZOOM) {\n\t\t\tthis._impl = new Akamai.MagnifierImplScroller(element, options);\n\t\t} else {\n\t\t\tthis._impl = new Akamai.MagnifierImplEnlarge(element, options);\n\t\t}\n\t});\n\n\tMagnifier.prototype._updateOptions = function(options){\n\t\tif( !options ) {\n\t\t\tthis._options = this._originalOptions;\n\t\t}\n\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// compat with enlarge `disabled` option\n\t\tthis._options.disabled = !this._options.enabled;\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Magnifier.defaultOptions, this._options, \"Magnifier\");\n\n\t\tthis._impl.updateOptions(this._options);\n\t};\n\n\t// Used in preflight to \"rename\" events based on the child component events\n\tMagnifier._componentEventMapping = {\n\t\t\"enlarge.after-zoom-in\": \"in\",\n\t\t\"enlarge.after-zoom-out\": \"out\",\n\t\t\"scroller.after-zoom-in\": \"in\",\n\t\t\"scroller.after-zoom-out\": \"out\"\n\t};\n\n\tMagnifier.MODE_HOVER_ZOOM = 'hoverzoom';\n\tMagnifier.MODE_ANIMATED_ZOOM = 'animatedzoom';\n\n\t/**\n\t * Defines the global default options for all Magnifiers on the page\n\t * @static\n\t * @property {String} mode - Set what mode to run the magnifier in. There are two modes available: `hoverzoom` and `animatedzoom`. The `hoverzoom` mode provides the ability to magnify and pan the image by just hovering your mouse over the image. It also provides a `flyout` option where a clip of the zoomed in image is displayed on a separate widget floating somewhere around (configurable) the image. The `animatedzoom` mode only does inline magnification but provides smooth animation between magnification levels, it also allows for incremental zooming.\n\t * @property {Boolean} button - Whether to show a button for toggling magnification (default: true)\n\t * @property {Boolean} enabled - Enabled/disable magnification (default: true, breakpoints supported)\n\t * @property {Integer} magnification - The scale factor to magnify the image: `2`, `3` (default), `4`, `4.5`, etc\n\n\t * @property {Array} image.widths - List of available widths for an image (to be combined with image.widthParam) - (default: [\"320\",\"640\",\"800\",\"1024\",\"2048\",\"5000\"])\n\t * @property {String} image.sizes - Value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw` (default), `200px`, `(min-width:1000px) 500px, 100vw` - (default: `100vw`, breakpoints supported)\n\t * @property {String} image.policy - Query param value for policy, appended to &policy= per image url when specified. Values: `foo`. (default: undefined)\n\n\t * @property {Integer} delay - Only applicable in `hoverzoom` mode. The time delay in milliseconds between mouse hover and magnification (default: 300, breakpoints supported)\n\t * @property {String} buttonText - Only applicable in `hoverzoom` mode. Text for the zoom button. Also used for its title attribute. (default: \"Toggle Image Magnification\")\n\t * @property {Integer} flyout.width - Only applicable in `hoverzoom` mode. Width of the flyout image (default: 200)\n\t * @property {Integer} flyout.height - Only applicable in `hoverzoom` mode. Height of the flyout image (default: 200)\n\t * @property {Boolean} hoverZoomWithoutClick - Only applicable in `hoverzoom` mode. Zoom starts on mouse hover with no click needed (default: true; false will require a click to hover-zoom)\n\t * @property {String} placement - Only applicable in `hoverzoom` mode. Placement of the magnified image: `inline` , `flyoutloupe`, `flyouttopleft`,`flyoutbottomleft` ,`flyouttopright` and `flyoutbottomright` - (default: inline, breakpoins supported)\n\n\t * @property {Float} incrementalZoomFactor - Only applicable in `animatedzoom` mode. A number by which to incrementally zoom up until the specified `magnification`, default `3`. For example a `magnification` of `4` and an `incrementalZoomFactor` of `2` will zoom the image in `2` steps\n\t * @property {Boolean} animateZoom - Only applicable in `animatedzoom` mode. Animates the magnification process. Default `true`\n\t * @property {Integer} animationDuration - Only applicable in `animatedzoom` mode. If `animateZoom` is true, this specifies the length of the animation in milliseconds. Default `250`\n\t * @property {String} zoomInBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom in button. Default `Zoom In`\n\t * @property {String} zoomOutBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom out button. Default `Zoom Out`\n\t */\n\tMagnifier.defaultOptions = {\n\t\t// general options\n\t\tmode: Magnifier.MODE_HOVER_ZOOM,\n\t\tbutton: true,\n\t\tenabled: true,\n\t\tmagnification: 3,\n\n\t\t// image options\n\t\timage: Akamai.Image.defaultConfig,\n\n\t\t// hoverzoom options\n\t\tdelay: 300,\n\t\tbuttonText: \"Toggle Image Magnification\",\n\t\tflyout: {\n\t\t\twidth: 200,\n\t\t\theight: 200\n\t\t},\n\t\thoverZoomWithoutClick: true,\n\t\tplacement: \"inline\",\n\n\t\t// animatedzoom options\n\t\tincrementalZoomFactor: 3,\n\t\tanimateZoom: true,\n\t\tanimationDuration: 250,\n\t\tzoomInBtnText: 'Zoom In',\n\t\tzoomOutBtnText: 'Zoom Out'\n\t};\n\n\t// srcset stuff\n\tAkamai.Sourceable.extendStatic(Magnifier);\n\n\tMagnifier.render = function(json, options){\n\t\tvar finalOptions = Akamai.Util.options(Magnifier.defaultOptions, options);\n\t\tvar dataAttr = Magnifier._dataAttr;\n\t\tvar fallbackSrc = Magnifier._fallbackSrc(json.url, finalOptions.image);\n\t\tvar largestSrc = Magnifier._largestSrc(json.url, finalOptions.image);\n\t\tvar srcSet = Magnifier._srcset(json.url, finalOptions.image)\n\t\tvar sizes = finalOptions.image.sizes;\n\t\tvar impl = (finalOptions.mode === Magnifier.MODE_ANIMATED_ZOOM) ? Akamai.MagnifierImplScroller : Akamai.MagnifierImplEnlarge;\n\t\treturn impl.render(finalOptions, dataAttr, fallbackSrc, largestSrc, srcSet, sizes);\n\t};\n\n\t/**\n\t * Enter magnifier mode\n\t * @method\n\t * @fires Akamai.Magnifier#akamai-magnifier-in\n\t * @returns {undefined}\n\t */\n\tMagnifier.prototype.in = function(){\n\t\tthis._impl.zoomIn();\n\t};\n\n\t/**\n\t * Exit magnifier mode\n\t * @method\n\t * @fires Akamai.Magnifier#akamai-magnifier-out\n\t * @returns {undefined}\n\t */\n\tMagnifier.prototype.out = function(){\n\t\tthis._impl.zoomOut();\n\t};\n\n\t/**\n\t * Return the current state of the magnifier\n\t * @method\n\t * @returns {Boolean}\n\t */\n\tMagnifier.prototype.isMagnified = function(){\n\t\treturn this._impl.isMagnified();\n\t};\n\n\t/**\n\t * Toggle the state of the magnifier\n\t * @method\n\t * @fires Akamai.Magnifier#akamai-magnifier-in\n\t * @fires Akamai.Magnifier#akamai-magnifier-out\n\t * @returns {undefined}\n\t */\n\tMagnifier.prototype.toggle = function(){\n\t\tthis._impl.toggleZoom();\n\t};\n\n\t/**\n\t * Triggered when the magnifier has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carouse#in}.\n\t *\n\t * @event Akamai.Magnifier#akamai-magnifier-in\n\t */\n\n\t/**\n\t * Triggered when the magnifier has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carouse#out}.\n\t *\n\t * @event Akamai.Magnifier#akamai-magnifier-out\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Magnifier}.\n\t *\n\t * @event Akamai.Magnifier#akamai-magnifier-init\n\t */\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Magnifier = Magnifier;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\tvar VideoImpl = Akamai.Util.component(\"VideoImpl\", function(element, options){\n\t\tthrow new Error('Not allowed to instantiate VideoImpl');\n\t});\n\n\tVideoImpl.render = function(json, options){\n\t\tthrow new Error('VideoImpl.render not implemented');\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.VideoImpl = VideoImpl;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\tvar VideoImplPristine = Akamai.Util.component(\"VideoImplPristine\", function(element, options){\n\t});\n\n\tVideoImplPristine.prototype = Object.create(Akamai.VideoImpl.prototype);\n\tVideoImplPristine.prototype.constructor = VideoImplPristine;\n\n\tVideoImplPristine.render = function(json, options){\n\t\tvar finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options);\n\n\t\tvar url = json.url;\n\t\tvar poster = json.poster || \"\";\n\t\tvar mime = (json.mime) ? 'type=\"' + json.mime + '\"' : '';\n\n\t\tvar attrs = [\n\t\t\tfinalOptions.loop ? \"loop\" : \"\",\n\t\t\tfinalOptions.autoplay ? \"autoplay\" : \"\",\n\t\t\tfinalOptions.controls ? \"controls\" : \"\",\n\t\t\tfinalOptions.muted ? \"muted\" : \"\",\n\t\t\t\"playsinline\"\n\t\t];\n\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t`);\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.VideoImplPristine = VideoImplPristine;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\tvar VideoImplIm = Akamai.Util.component(\"VideoImplIm\", function(element, options){\n\t\tvar finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options);\n\t\tVideoImplIm._validateOptions(finalOptions);\n\t});\n\n\tVideoImplIm.prototype = Object.create(Akamai.VideoImpl.prototype);\n\tVideoImplIm.prototype.constructor = VideoImplIm;\n\n\tVideoImplIm._validateOptions = function(options){\n\t\tif (!Array.isArray(options.sizes) || options.sizes.length !== 3) {\n\t\t\tthrow new Error('Akamai.Video sizes must have three entries');\n\t\t}\n\n\t\tif (options.sizes.some(function (size) {\n\t\t\treturn isNaN(size);\n\t\t})) {\n\t\t\tthrow new Error('Akamai.VideoImplIm all sizes must be numbers');\n\t\t}\n\t}\n\n\tVideoImplIm.render = function(json, options){\n\t\tvar finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options);\n\t\tVideoImplIm._validateOptions(finalOptions);\n\n\t\tvar url = json.url;\n\t\tvar poster = json.poster || \"\";\n\t\tvar width;\n\n\t\tvar viewPortWidth = window.innerWidth;\n\n\t\tif (viewPortWidth < 992) {\n\t\t\twidth = finalOptions.sizes[0];\n\t\t} else if (viewPortWidth < 1200) {\n\t\t\twidth = finalOptions.sizes[1];\n\t\t} else {\n\t\t\twidth = finalOptions.sizes[2];\n\t\t}\n\n\t\tvar attrs = [\n\t\t\tfinalOptions.loop ? \"loop\" : \"\",\n\t\t\tfinalOptions.autoplay ? \"autoplay\" : \"\",\n\t\t\tfinalOptions.controls ? \"controls\" : \"\",\n\t\t\tfinalOptions.muted ? \"muted\" : \"\",\n\t\t\t\"playsinline\"\n\t\t];\n\n\t\tvar joiner = url.indexOf('?') === -1 ? '?' : '&';\n\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t`);\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.VideoImplIm = VideoImplIm;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t/**\n\t * Video component\n\t * @class\n\t * @alias Akamai.Video\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\tvar Video = Akamai.Util.component(\"Video\", function(element, options){\n\t\t// TODO\n\t\tthis._comp = this._$el.length && undefined;\n\t\tthis._$videoElement = this._$el.find(\"video\");\n\t\tthis._videoElement = this._$videoElement[0];\n\n\t\tif( !this._videoElement ){\n\t\t\tthrow new Error( \"Akamai.Video requires a child HTML Video element\" );\n\t\t}\n\n\t\tif( !this._videoElement.play || !this._videoElement.pause ){\n\t\t\tthis._unsupportedAPI = true;\n\t\t\tthis._unsupported();\n\t\t\treturn;\n\t\t}\n\n\t\t// get the initial state (could be autoplaying on render)\n\t\tthis._setState(this._videoElement.paused ? Video.States.Paused : Video.States.Playing);\n\n\t\t// state bindings\n\t\tthis._$el\n\t\t\t.bind(\"akamai-video-play\", function(){\n\t\t\t\tthis._setState(Video.States.Playing);\n\t\t\t}.bind(this))\n\t\t\t.bind(\"akamai-video-pause\", function(){\n\t\t\t\tthis._setState(Video.States.Paused);\n\t\t\t}.bind(this));\n\n\t\tthis._createPlayButton();\n\t});\n\n\tVideo.MODE_IM = 'im';\n\tVideo.MODE_PRISTINE = 'pristine';\n\n\tVideo._componentEventMapping = {\n\t\t\"play\": {\n\t\t\tto: \"play\",\n\t\t\tselector: \"video\"\n\t\t},\n\n\t\t\"pause\": {\n\t\t\tto: \"pause\",\n\t\t\tselector: \"video\"\n\t\t},\n\n\t\t\"seeked\": {\n\t\t\tto: \"seek\",\n\t\t\tselector: \"video\"\n\t\t }\n\t};\n\n\tVideo.prototype._updateOptions = function(options){\n\t\tif( !options ) {\n\t\t\tthis._options = this._originalOptions;\n\t\t}\n\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Video.defaultOptions, this._options, \"Video\");\n\n\t\t// TODO see Magnifier for example\n\t};\n\n\t/**\n\t * Defines the global default options for all Spin360s on the page\n\t * @static\n\t * @property {Boolean} autoplay - Automatically play the video on load (default: false)\n\t * @property {Boolean} controls - Display the video controls (default: true)\n\t * @property {Boolean} loop - Restart the video when it reaches the end (default: false)\n\t * @property {Boolean} muted - Mute the video (default: true)\n\t * @property {String} mode - What video component to use. `im` will let you use any size pristine video and will automatically request & generate the right size when the page is loaded. `pristine` just passes through the original video into the video player and this is what will always play on the users device. Default: `pristine`\n\t * @property {Array} sizes - Video sizes (widths) to use for different screen widths. Defaults: [1920 (large screens), 1280 (tablet), 854 (mobile), ]\n\t */\n\tVideo.defaultOptions = {\n\t\tautoplay: false,\n\t\tcontrols: true,\n\t\tloop: false,\n\t\tmuted: true,\n\t\tmode: Video.MODE_PRISTINE,\n\t\tsizes: [854, 1280, 1920]\n\t};\n\n\tVideo.render = function(json, options){\n\t\tvar impl = (options && options.mode === Video.MODE_IM) ? Akamai.VideoImplIm : Akamai.VideoImplPristine;\n\t\treturn impl.render(json, options);\n\t};\n\n\tVideo.States = {\n\t\tPlaying: 0,\n\t\tPaused: 1\n\t};\n\n\tVideo.prototype._unsupported = function(){\n\t\tif( this._unsupportedAPI ){\n\t\t\tAkamai.Util.log(\"Video: video API not supported\", 'error');\n\t\t}\n\n\t\treturn this._unsupportedAPI;\n\t};\n\n\t/**\n\t * Play the video, idempotent\n\t * @method\n\t * @fires Akamai.Video#akamai-video-play\n\t * @returns {undefined}\n\t */\n\tVideo.prototype.play = function(){\n\t\tif( this._unsupported() ) { return; }\n\t\tthis._videoElement.play();\n\t};\n\n\t/**\n\t * Pause the video\n\t * @method\n\t * @fires Akamai.Video#akamai-video-pause\n\t * @returns {undefined}\n\t */\n\tVideo.prototype.pause = function(){\n\t\tif( this._unsupported() ) { return; }\n\t\tthis._videoElement.pause();\n\t};\n\n\t/**\n\t * Return the current state of the video\n\t * @example Video states\n\t * Akamai.Video.States = {\n\t *\tPlaying: 0,\n\t *\tPaused: 1\n\t * };\n\t *\n\t * @example Conditioning on states\n\t * if( video.getState() === Akamai.Video.States.Paused ) {\n\t *\t video.play()\n\t * }\n\t * @method\n\t * @returns {Akamai.Video.State}\n\t */\n\tVideo.prototype.getState = function(){\n\t\treturn this._state;\n\t};\n\n\tVideo.prototype._setState = function(value){\n\t\tthis._state = value;\n\t};\n\n\t/**\n\t * Seek to the input percentage.\n\t * @method\n\t * @fires Akamai.Video#akamai-video-seek\n\t * @param {Integer} percent - value between 0 and 100 percent for seeking\n\t * @returns {undefined}\n\t */\n\tVideo.prototype.seek = function(percent){\n\t\tif( percent < 0 || 100 < percent ) {\n\t\t\tthrow new Error( \"seek takes a an integer between 0 and 100\" );\n\t\t}\n\n\t\tvar newTime = (percent / 100) * (this._videoElement.duration || 1);\n\t\tthis._videoElement.currentTime = newTime;\n\t};\n\n\t/**\n\t * Set whether the video should loop or not\n\t * @method\n\t * @param {Boolean} value - The value true or false\n\t * @returns {undefined}\n\t */\n\tVideo.prototype.setLoop = function(value){\n\t\tthis._videoElement.loop = value;\n\t};\n\n\tVideo.prototype.getElement = function(){\n\t\treturn this._$el[0];\n\t};\n\n\t// Borrowed from https://codepen.io/chrisnager/pen/jPrJgQ\n\tVideo.prototype._createPlayButton = function(){\n\t\tvar videoPlayButton;\n\t\tvar videoWrapper = this._$el[0];\n\t\tvar video = this._videoElement;\n\n\t\tif(this._options.autoplay){\n\t\t\treturn;\n\t\t}\n\n\t\t// TODO move to render?\n\t\tvideoWrapper.insertAdjacentHTML('beforeend', `\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t`);\n\n\t\tvideo.removeAttribute('controls');\n\t\tvideoPlayButton = this._$el.find('svg')[0];\n\n\t\tvideoPlayButton.addEventListener('click', function(){\n\t\t\tvideo.play();\n\t\t\tvideoPlayButton.classList.add('is-hidden');\n\t\t\tif( this._options.controls ) {\n\t\t\t\tvideo.setAttribute('controls', 'controls');\n\t\t\t}\n\t\t}.bind(this));\n\t};\n\n\t/**\n\t * Triggered when the video has been played. See {@link Akamai.Video#play}.\n\t *\n\t * @event Akamai.Video#akamai-video-play\n\t */\n\n\t/**\n\t * Triggered when the video has been paused. See {@link Akamai.Video#pause}.\n\t *\n\t * @event Akamai.Video#akamai-video-pause\n\t */\n\n\t/**\n\t * Triggered when the video has seeked to a position. See {@link Akamai.Video#seek}.\n\t *\n\t * @event Akamai.Video#akamai-video-seek\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Video}.\n\t *\n\t * @event Akamai.Video#akamai-video-init\n\t */\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Video = Video;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t/**\n\t * Image carousel\n\t * @class\n\t * @alias Akamai.Carousel\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\tvar Carousel = Akamai.Util.component(\"Carousel\", function(element, options){\n\t\tthis._spins = Akamai.Spin360.createMany(this._$el[0], this._options.spin360);\n\t\tthis._magnifiers = Akamai.Magnifier.createMany(this._$el[0], this._options.magnifier);\n\t\tthis._videos = Akamai.Video.createMany(this._$el[0], this._options.video);\n\n\t\tthis._$el.snapper(this._options);\n\n\t\tthis._setAspectRatio();\n\t\tthis._bindAspectAttributes();\n\n\t\tif( this._options.slideshow.autostart ){\n\t\t\tsetTimeout(function(){\n\t\t\t\tthis.startSlideshow();\n\t\t\t}.bind(this), this._options.slideshow.interval);\n\t\t}\n\n\t\t// NOTE !! the following two bindings must happen in order, the\n\t\t// autoplayables binding relies on the attribut\n\n\t\t// add item attrs and maintain the active item attributes to determine the\n\t\t// active item state\n\t\tthis._activeItemAttributes();\n\t\tthis._$el.bind( \"akamai-carousel-goto\", this._activeItemAttributes.bind(this));\n\n\t\t// TODO pause autoplay videos that are not visible\n\t\tthis._handleAutoplayables();\n\t\tthis._$el.bind( \"akamai-carousel-goto\", this._handleAutoplayables.bind(this));\n\n\t\tthis._$el\n\t\t\t.bind( \"tau.touch-tracking-start\", function(){\n\t\t\t\tthis._$el.find(\".snapper_pane\").addClass(\"no-scroll\");\n\t\t\t}.bind(this))\n\t\t\t.bind( \"tau.touch-tracking-stop\", function(){\n\t\t\t\tthis._$el.find(\".snapper_pane\").removeClass(\"no-scroll\");\n\t\t\t}.bind(this));\n\n\t\t// when child components are doing things, stop the carousel from\n\t\t// automatically advancing\n\t\tthis._$el.bind( this.constructor._stopSlideshowEvents.join(\" \"), this.stopSlideshow.bind(this));\n\t});\n\n\tCarousel._stopSlideshowEvents = [\n\t\t\"akamai-magnifier-in\",\n\t\t\"akamai-magnifier-out\",\n\t\t\"akamai-spin360-goto\",\n\t\t\"akamai-video-play\",\n\t\t\"akamai-video-seek\"\n\t];\n\n\t// clearly there should be an autoplayable interface\n\tCarousel.prototype._handleAutoplayables = function(){\n\t\tvar isParentActive = function(comp){\n\t\t\treturn !!$(comp.getElement())\n\t\t\t\t.closest(\"[\" + this.constructor.activeItemAttr + \"]\")\n\t\t\t\t.length;\n\t\t}.bind(this);\n\n\t\tthis._videos.concat(this._spins).forEach(function(comp){\n\t\t\tif( isParentActive(comp) && comp._carouselWasPlaying ) {\n\t\t\t\tcomp.play();\n\t\t\t} else {\n\t\t\t\tif( comp.getState() == comp.constructor.States.Playing ){\n\t\t\t\t\tcomp._carouselWasPlaying = true;\n\t\t\t\t\tcomp.pause();\n\t\t\t\t} else {\n\t\t\t\t\tcomp._carouselWasPlaying = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}.bind(this));\n\t};\n\n\t// Unique counter for IDs\n\tCarousel.counter = 0;\n\n\tCarousel.prototype._updateOptions = function(options){\n\t\tif( !options ) {\n\t\t\tthis._options = this._originalOptions;\n\t\t}\n\n\t\t// update the current options\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// update the options for each of the subcomponents\n\t\tvar update = function(comp, name){\n\t\t\tcomp._updateOptions(this._options[name]);\n\t\t}.bind(this);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Carousel.defaultOptions, this._options, \"Carousel\");\n\t\tthis._$el.snapper( \"updateOptions\", options );\n\n\t\tthis._spins.forEach(function(c){ update(c, \"spin360\"); });\n\t\tthis._magnifiers.forEach(function(c){ update(c, \"magnifier\"); });\n\t\tthis._videos.forEach(function(c){ update(c, \"video\"); });\n\n\t\tthis._setAspectRatio();\n\t};\n\n\t// Used in preflight to \"rename\" events based on the child component events\n\tCarousel._componentEventMapping = {\n\t\t\"snapper.after-snap\": \"goto\",\n\t\t\"snapper.snap\": \"snap\",\n\t\t\"snapper.after-next\": \"next\",\n\t\t\"snapper.after-prev\": \"previous\"\n\t};\n\n\t/**\n\t * Defines the global default options for all Carousels on the page\n\t * @static\n\t * @property {Boolean} arrows - Show carousel controls - (default: true)\n\t * @property {Number} aspectratio - Specify a percentage-based height for the carousel, relative to the width. Values: `false`, `100`, `45.6`, `78` - (default `false`, breakpoints supported)\n\t * @property {Integer} slideshow.interval - Time in milliseconds between slide advances - (default: 4000, breakpoints supported)\n\t * @property {Integer} slideshow.autostart - Start the slideshow on instantiation - (default: false)\n\t * @property {String} thumbnail.placement - Placement relative to the slide container: `left`, `right`, `bottom` (default: `bottom`, breakpoints supported)\n\t * @property {String} thumbnail.type - Type of thumbnail: `dots`, `none`, `images` - (default: `images`, breakpoints supported)\n\t * @property {String} thumbnail.policy - String to use for query parameter, ex: \"&policy=\" (default: undefined)\n\t * @property {String} thumbnail.sizes - Sizes attribute value to use if thumbnail policy is set. Values: `300px`, `200px`, `(min-width:1000px) 300px, 100px` - (default: `300px`)\n\t * @property {Object} images - Defaults to the Magnifier image option's settings\n\t */\n\tCarousel.defaultOptions = {\n\t\tarrows: true,\n\n\t\taspectratio: false,\n\n\t\tslideshow: {\n\t\t\tinterval: 4000,\n\t\t\tautostart: false\n\t\t},\n\n\t\tthumbnail: {\n\t\t\tplacement: \"bottom\",\n\t\t\ttype: \"images\",\n\t\t\tpolicy: undefined,\n\t\t\tsizes: \"300px\"\n\t\t},\n\n\t\timages: Akamai.Image.defaultConfig\n\t};\n\n\tCarousel.renderMapping = {\n\t\timage: Akamai.Magnifier,\n\t\tspin360: Akamai.Spin360,\n\t\tvideo: Akamai.Video\n\t};\n\n\tCarousel._typeMapping = {\n\t\timage: \"magnifier\"\n\t};\n\n\tCarousel._uniqueItemId = function(item, i) {\n\t\treturn \"akamai-carousel-\" + Carousel.counter + \"-\" + item.type + \"-\" + i;\n\t};\n\n\t// srcset stuff\n\tAkamai.Sourceable.extendStatic(Carousel);\n\n\tCarousel._renderItem = function(item, i, options) {\n\t\tvar mappedType = Carousel._typeMapping[item.type] || item.type;\n\n\t\tif( !Carousel.renderMapping[item.type] ){\n\t\t\tthrow new Error(`\n\t\t\t\titem type ${item.type} at index ${i} must be 'image', 'spin360', or 'video'\n\t\t\t`);\n\t\t}\n\n\t\treturn `\n\t\t\t
\n\t\t\t\t${Carousel.renderMapping[item.type].render(item, options[mappedType])}\n\t\t\t
\n\t\t`;\n\t};\n\n\tCarousel._renderThumbnailAnchor = function(item, data, i, options) {\n\t\tvar thumbUrl = \"\";\n\t\tvar altText = item.alt || \"\";\n\n\t\t// use the video poster, the canonical url, or the first in a sequence\n\t\tif( item.type === 'video' ){\n\t\t\tthumbUrl = item.poster || data.reduce(function(acc, i){\n\t\t\t\treturn acc || Carousel._thumbUrl(i);\n\t\t\t}, \"\");\n\t\t} else {\n\t\t\tthumbUrl = Carousel._thumbUrl(item);\n\t\t}\n\n\t\t// TODO this sucks\n\t\toptions.images.policy = options.thumbnail.policy;\n\n\t\tvar src = Carousel._fallbackSrc( thumbUrl, options.images );\n\t\tvar srcset = Carousel._srcset( thumbUrl, options.images );\n\t\tvar sizes = options.images.sizes;\n\n\t\t// if there's a thumbnail policy, the thumbnails will be fresh image requests,\n\t\t// so they should have better sizes attribute values\n\t\tif( options.thumbnail.policy ){\n\t\t\tsizes = options.thumbnail.sizes;\n\t\t}\n\n\t\tvar attrs = [\n\t\t\t`${Carousel._dataAttr}-thumb-type=\"${item.type}\"`,\n\t\t\titem.type == \"video\" && !item.poster ? \"${Carousel._dataAttr}-thumb-noposter\" : \"\"\n\t\t];\n\n\t\treturn `\n\t\t\t\n\t\t\t\t\"${altText}\"\n\t\t\t\n\t\t`;\n\t};\n\n\tCarousel._thumbUrl = function(item){\n\t\treturn item.type === 'video' ? item.poster : item.url || (item.urls && item.urls[0]);\n\t};\n\n\tCarousel._renderThumbnails = function(data, options) {\n\t\tif(data.length <= 1 ){\n\t\t\treturn \"\";\n\t\t};\n\n\t\treturn `\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t${Akamai.Util.map(data, function(item, i){\n\t\t\t\t\t\treturn Carousel._renderThumbnailAnchor(item, data, i, options);\n\t\t\t\t\t}).join(\"\\n\")}\n\t\t\t\t
\n\t\t\t
\n\t\t`;\n\t};\n\n\tCarousel.prototype._setAspectAttributes = function( $el ){\n\t\tvar orientationAttr = `${Carousel._dataAttr}-aspectratio-orientation`;\n\t\tif( $el.height() > $el.width() ) {\n\t\t\t$el.attr( orientationAttr, \"portrait\" );\n\t\t} else if( $el.height() < $el.width() ) {\n\t\t\t$el.attr( orientationAttr, \"landscape\" );\n\t\t} else {\n\t\t\tif ($el.parent().height() >= $el.parent().width()) {\n\t\t\t\t$el.attr( orientationAttr, \"landscape\" );\n\t\t\t} else {\n\t\t\t\t$el.attr( orientationAttr, \"portrait\" );\n\t\t\t}\n\t\t}\n\t};\n\n\tCarousel.prototype._setAspectRatio = function(){\n\t\tvar value = this._options.aspectratio || 0;\n\t\tvar $items = this._$el.find( \".snapper_item\" );\n\t\t$items.css( \"padding-top\", (value / $items.length) + \"%\" );\n\t};\n\n\tCarousel.prototype._bindAspectAttributes = function(){\n\t\tif( this._options.aspectratio === false ){\n\t\t\treturn;\n\t\t}\n\n\t\tvar value = this._options.aspectratio || 0;\n\t\tvar self = this;\n\t\tvar $items = this._$el.find( \".snapper_item\" );\n\n\t\t$items.each(function(){\n\t\t\tvar loadBound;\n\t\t\tvar $item = $(this);\n\n\t\t\tvar $loadable = $item.find( \"img, video\" ).eq(0);\n\n\t\t\t$loadable.bind(\"load loadedmetadata\", loadBinding = function(){\n\t\t\t\tclearTimeout(loadBound);\n\n\t\t\t\tif( $item.is(`[${Akamai.Spin360._dataAttr}]`) ){\n\t\t\t\t\t// TODO it's not always a canvas, sometimes it's a collection of\n\t\t\t\t\t// images based on settings\n\t\t\t\t\tself._setAspectAttributes( $item.find( \"canvas\" ) );\n\t\t\t\t}\telse{\n\t\t\t\t\tself._setAspectAttributes( $loadable );\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tloadBound = setTimeout(loadBinding, 5000);\n\t\t});\n\n\t\t$items.eq(0).find( \"img,video\" ).eq(0).bind( \"load loadedmetadata\", function(){\n\t\t\tself._trigger( \"first-media-load\" );\n\t\t});\n\t};\n\n\tCarousel.render = function(data, options){\n\t\toptions = options || {};\n\n\t\t// establish the extended default options for the carousel\n\t\tvar carouselOptions = Akamai.Util.options(Carousel.defaultOptions, options);\n\n\t\tvar templateAttrs = [\n\t\t\t`${Carousel._dataAttr}`,\n\t\t\t`${Carousel._dataAttr}-item-count=\"${(data || []).length}\"`,\n\t\t\t`data-snapper-deeplinking=\"false\"`,\n\t\t\tcarouselOptions.arrows ? \"data-snapper-nextprev\" : \"\"\n\t\t];\n\n\t\tCarousel.counter++;\n\n\t\t// TODO can we do something about the `enlarge_pane` class here, seems it\n\t\t// could be conditioned on at least one regular image type in the json\n\t\t// TODO options should dictate `data-snapper-nextprev`\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t${Akamai.Util.map(data, function(item, i){\n\t\t\t\t\t\t\t\t\treturn Carousel._renderItem(item, i, carouselOptions);\n\t\t\t\t\t\t\t\t}).join(\"\\n\")}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t${Carousel._renderThumbnails(data, carouselOptions)}\n\t\t\t
\n\t\t`);\n\t};\n\n\t/**\n\t * Instantiate Carousels by looking for children matching\n\t * [data-akamai-carousel] in `element` param\n\t * @method\n\t * @static\n\t * @param {HTMLElement} element - the element to search in for\n\t * @returns {Akamai.Carousel[]}\n\t */\n\n\n\t/**\n\t * @method\n\t * @returns {Akamai.Spin360[]}\n\t */\n\tCarousel.prototype.getSpin360s = function(){\n\t\treturn this._spins;\n\t};\n\n\t/**\n\t * @method\n\t * @returns {Akamai.Magnifier[]}\n\t */\n\tCarousel.prototype.getMagnifiers = function(){\n\t\treturn this._magnifiers;\n\t};\n\n\t/**\n\t * @method\n\t * @returns {Akamai.Video[]}\n\t */\n\tCarousel.prototype.getVideos = function(){\n\t\treturn this._videos;\n\t};\n\n\t/**\n\t * Go to a particular slide.\n\t *\n\t * Note that the state of the DOM on the page and thus the index of the\n\t * carousel will not be up-to-date until the {@link\n\t * Akamai.Carousel#akamai-carousel-goto} event has been fired. That is,\n\t * calling this method and the calling `getIndex` will not necessarily result\n\t * in an updated index value. Either bind to the event or supply a callback.\n\t *\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-goto\n\t * @param {Integer} index - The zero-based slide index to go to\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.goto = function(index, callback){\n\t\tthis._$el.snapper( \"goto\", index, callback );\n\t};\n\n\tCarousel._itemAttr = `${Carousel._dataAttr}-item`;\n\tCarousel._activeItemAttr = `${Carousel._itemAttr}-active`;\n\n\t/**\n\t * Add an attribute to all carousel items,\n\t * and additionally maintain an active attribute on the active carousel item\n\t *\n\t * @method\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype._activeItemAttributes = function(){\n\t\tvar attrAllItems = this.constructor._itemAttr;\n\t\tvar attrActiveItem = this.constructor._activeItemAttr;\n\n\t\tthis._$el\n\t\t\t.find( \".snapper_item\" )\n\t\t\t.attr( attrAllItems, true )\n\t\t\t.removeAttr( attrActiveItem )\n\t\t\t.eq( this.getIndex() ).attr( attrActiveItem, true );\n\t};\n\n\t/**\n\t * Return the current slide index\n\t * @method\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.getIndex = function(){\n\t\treturn this._$el.snapper( \"getIndex\" );\n\t};\n\n\t// Extend Carousel with Advanceabl interface\n\tAkamai.Advanceable.extend(Carousel);\n\n\t/**\n\t * Advance to the next item\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-next\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.next;\n\t// required for JSDocs, implementation in Advanceable\n\n\t/**\n\t * Retreat to the previous item\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-previous\n\t * @param {Function?} callback - callback invoked after the action has completed in the DOM\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.previous;\n\t// required for JSDocs, implementation in Advanceable\n\n\t/**\n\t * Start automatic advancement of the carousel items\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-start-slideshow\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.startSlideshow\t= function(){\n\t\tthis._$el.one(\"mousedown touchstart\", this.stopSlideshow.bind(this));\n\n\t\tthis.next(function(){\n\t\t\tthis._trigger(\"start-slideshow\");\n\t\t\tthis._slideshowTimer = setTimeout(function(){\n\t\t\t\tthis.startSlideshow();\n\n\t\t\t\t// TODO remove || when default options are added\n\t\t\t}.bind(this), this._options.slideshow.interval );\n\t\t}.bind(this));\n\t};\n\n\t/**\n\t * Start automatic advancement of the carousel items\n\t * @method\n\t * @fires Akamai.Carousel#akamai-carousel-stop-slideshow\n\t * @returns {undefined}\n\t */\n\tCarousel.prototype.stopSlideshow = function(){\n\t\tclearTimeout(this._slideshowTimer);\n\t\tthis._slideshowTimer = undefined;\n\t\tthis._trigger(\"stop-slideshow\");\n\t};\n\n\t/**\n\t * Triggered when the carousel has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carousel#goto}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-goto\n\t */\n\n\t/**\n\t * Triggered when the carousel has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carousel#next}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-next\n\t */\n\n\t/**\n\t * Triggered when the carousel has completed it transition to\n\t * a new index due to user interaction or a call to {@link Akamai.Carousel#previous}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-previous\n\t */\n\n\t/**\n\t * Triggered when the carousel has started the slide show due to a call to\n\t * {@link Akamai.Carousel#startSlideshow}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-start-slideshow\n\t */\n\n\t/**\n\t * Triggered when the carousel has stoped the slide show due to a call to\n\t * {@link Akamai.Carousel#stopSlideshow}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-stop-slideshow\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Carousel}.\n\t *\n\t * @event Akamai.Carousel#akamai-carousel-init\n\t */\n\n\tCarousel.prototype.refresh = function(){\n\t\tthis._$el.snapper(\"updateWidths\");\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Carousel = Carousel;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t/**\n\t * Image Fullscreen\n\t * @class\n\t * @alias Akamai.Fullscreen\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t */\n\n\t// TODO JSDocs\n\t// TODO Tests\n\tvar Fullscreen = Akamai.Util.component(\"Fullscreen\", function(element, options){\n\t\tif( this._options.enabled ) {\n\t\t\tthis._init();\n\t\t}\n\t});\n\n\tFullscreen.prototype._init = function(){\n\t\tthis._$fullscreen = this._$el;\n\n\t\t// parent container does not allow widths to be set (Firefox, fullscreen), so we’ll adjust the children\n\t\tthis.addButton();\n\n\t\tthis._fullscreenApiKeys = Fullscreen._keys();\n\n\t\tif( this._options.native ) {\n\t\t\tthis._useNativeApi = !!this._fullscreenApiKeys;\n\t\t} else {\n\t\t\tthis._useNativeApi = false;\n\t\t}\n\n\t\t// Fullscreen API is disabled and the fallback behavior is active\n\t\tthis._fullscreenFallbackEnabled = false;\n\t\tthis._$placeholder = $( \"
\" );\n\n\t\tthis._isFullscreen = false;\n\t\tthis.addEvents();\n\t};\n\n\tFullscreen.classes = {\n\t\tbtn: \"akamai-fullscreen-btn\",\n\t\tbtnContainer: \"akamai-fullscreen-btncontainer\",\n\t\tenterBtn: \"akamai-fullscreen-btn-enter\",\n\t\texitBtn: \"akamai-fullscreen-btn-exit\",\n\t\tactive: \"akamai-fullscreen-active\",\n\t\tfallback: \"akamai-fullscreen-fallback\"\n\t};\n\n\tFullscreen.attr = {\n\t\twidth: \"data-akamai-fullscreen-width\",\n\t\tenterBtn: \"data-akamai-fullscreen-btn\",\n\t\texitBtn: \"data-akamai-fullscreen-exit-btn\"\n\t};\n\n\tFullscreen.defaultOptions = {\n\t\tenabled: false,\n\t\tnative: false\n\t};\n\n\tFullscreen.prototype.addButton = function(){\n\t\tthis._$widthAdjust = this._$fullscreen.children().filter(\".focused\");\n\n\t\tif( !this._$widthAdjust.length ) {\n\t\t\tthis._$widthAdjust = this._$fullscreen\n\t\t\t\t.children()\n\t\t\t\t.eq(0);\n\t\t}\n\n\t\tif( this._$widthAdjust.attr(Fullscreen.attr.width) === null ){\n\t\t\tthis._$widthAdjust.attr( Fullscreen.attr.width, \"\" );\n\t\t}\n\n\t\tif( this._$widthAdjust.find(\"[\" + Fullscreen.attr.enterBtn + \"]\").length ){\n\t\t\treturn;\n\t\t}\n\n\t\t// buttons parent\n\t\tthis._$buttonContainer = this._$widthAdjust;\n\t\tthis._$buttonContainer.addClass( Fullscreen.classes.btnContainer );\n\n\t\tthis._$buttonContainer.append(this.render());\n\t};\n\n\tFullscreen.prototype.addEvents = function() {\n\t\tthis._$fullscreen.on( \"click\", function( e ) {\n\t\t\tvar $target = $( e.target );\n\t\t\tif( !$target.is(\"[\" + Fullscreen.attr.enterBtn + \"]\") ){\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.enter();\n\t\t\te.preventDefault();\n\t\t}.bind( this ));\n\n\t\tthis._$fullscreen.on( \"click\", function( e ) {\n\t\t\tvar $target = $( e.target );\n\n\t\t\tif( !$target.is( \"[\" + Fullscreen.attr.exitBtn + \"]\" ) ){\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.exit();\n\t\t\te.preventDefault();\n\t\t}.bind( this ));\n\n\t\tif( this._useNativeApi ) {\n\t\t\tdocument.addEventListener( this._fullscreenApiKeys.onchange, function() {\n\t\t\t\t// exiting fullscreen using native method (ESC or menu option)\n\t\t\t\tif( !document[ this._fullscreenApiKeys.element ] ) {\n\t\t\t\t\tthis._exit();\n\t\t\t\t}\n\t\t\t}.bind( this ), false );\n\t\t}\n\n\t\t// ESC to close\n\t\t$( document ).on( \"keydown\", function( e ) {\n\t\t\tvar code = e.keyCode || e.which;\n\t\t\tif( code === 27 ) {\n\t\t\t\tthis._exit();\n\t\t\t}\n\t\t}.bind( this ));\n\t};\n\n\tFullscreen.prototype.render = function() {\n\t\treturn Akamai.Util.trim(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t};\n\n\tFullscreen._keyLookup = [\n\t\t{\n\t\t\tenter: \"requestFullscreen\",\n\t\t\texit: \"exitFullscreen\",\n\t\t\telement: \"fullscreenElement\",\n\t\t\tonchange: \"fullscreenchange\"\n\t\t},\n\t\t{\n\t\t\tenter: \"webkitRequestFullscreen\",\n\t\t\texit: \"webkitExitFullscreen\",\n\t\t\telement: \"webkitFullscreenElement\",\n\t\t\tonchange: \"webkitfullscreenchange\"\n\t\t},\n\t\t{\n\t\t\tenter: \"webkitRequestFullScreen\",\n\t\t\texit: \"webkitCancelFullScreen\",\n\t\t\telement: \"webkitCurrentFullScreenElement\",\n\t\t\tonchange: \"webkitfullscreenchange\"\n\t\t},\n\t\t{\n\t\t\tenter: \"mozRequestFullScreen\",\n\t\t\texit: \"mozCancelFullScreen\",\n\t\t\telement: \"mozFullScreenElement\",\n\t\t\tonchange: \"mozfullscreenchange\"\n\t\t},\n\t\t{\n\t\t\tenter: \"msRequestFullscreen\",\n\t\t\texit: \"msExitFullscreen\",\n\t\t\telement: \"msFullscreenElement\",\n\t\t\tonchange: \"MSFullscreenChange\"\n\t\t}\n\t];\n\n\tFullscreen._keys = function( el ) {\n\t\tvar keys = Fullscreen._keyLookup;\n\n\t\tvar el = document.body;\n\t\tfor( var j = 0, k = keys.length; j < k; j++ ) {\n\t\t\tif( keys[ j ].enter in el ) {\n\t\t\t\treturn keys[ j ];\n\t\t\t}\n\t\t}\n\t};\n\n\tFullscreen.prototype._maximizePlacement = function(){\n\t\tthis._fullscreenFallbackEnabled = true;\n\t\tthis._$fullscreen.addClass( Fullscreen.classes.fallback );\n\t\tthis._$placeholder.insertAfter( this._$fullscreen );\n\t\tthis._$fullscreen.appendTo( document.body );\n\t};\n\n\tFullscreen.prototype._restorePlacement = function(){\n\t\tif( !this._fullscreenFallbackEnabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._fullscreenFallbackEnabled = false;\n\t\tthis._$fullscreen.removeClass( Fullscreen.classes.fallback );\n\t\tthis._$fullscreen.insertAfter( this._$placeholder );\n\t\tthis._$placeholder.remove();\n\t};\n\n\tFullscreen.prototype._adjustWidth = function() {\n\t\tthis._$fullscreen.css( \"width\", \"auto !important\" );\n\t\t// wish this could go into the change event above, but alas the dimensions don’t measure right\n\t\tvar widthSmall = this._$fullscreen.width();\n\t\tvar heightSmall = this._$fullscreen.height();\n\n\t\tvar viewportWidth = this._useNativeApi ? window.screen.width : Math.max( document.documentElement.clientWidth, window.innerWidth || 0 );\n\t\tvar viewportHeight = this._useNativeApi ? window.screen.height : Math.max( document.documentElement.clientHeight, window.innerHeight || 0 );\n\n\t\t// calculate the maximum width we can use to fill the viewportHeight\n\t\tvar widthBig = widthSmall * viewportHeight / heightSmall;\n\n\t\t// if the width is bigger than the maximum, just set to 100% (won’t fill the entire height, but that’s ok)\n\t\tif( widthBig > viewportWidth ) {\n\t\t\twidthBig = \"100%\";\n\t\t}\n\n\t\tthis._$widthAdjust.width( widthBig );\n\t};\n\n\tFullscreen.prototype._revertWidth = function() {\n\t\tthis._$widthAdjust.css( \"width\", \"\" );\n\t};\n\n\t/**\n\t * Enter full screen mode\n\t * @method\n\t * @returns {undefined}\n\t */\n\tFullscreen.prototype.enter = function(){\n\t\tvar activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex();\n\t\tthis._$fullscreen.addClass( Fullscreen.classes.active );\n\n\n\t\tif( this._useNativeApi ) {\n\t\t\tthis._$fullscreen[ 0 ][ this._fullscreenApiKeys.enter ]();\n\t\t\tthis._adjustWidth();\n\t\t} else {\n\t\t\tthis._adjustWidth();\n\t\t\tthis._maximizePlacement();\n\t\t}\n\t\tthis._isFullscreen = true;\n\t\tthis._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex);\n\t\tthis._trigger(\"enter\");\n\t};\n\n\tFullscreen.prototype._exit = function(){\n\t\tvar activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex();\n\t\tthis._$fullscreen.removeClass( Fullscreen.classes.active );\n\n\t\tif( !this._useNativeApi ) {\n\t\t\tthis._restorePlacement();\n\t\t}\n\n\t\tthis._revertWidth();\n\t\tthis._isFullscreen = false;\n\t\tthis._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex);\n\t\tthis._trigger(\"exit\");\n\t};\n\n\t/**\n\t * Exits full screen mode\n\t * @method\n\t * @returns {undefined}\n\t */\n\tFullscreen.prototype.exit = function(){\n\t\tif( this._useNativeApi ) {\n\t\t\t// note some close behavior happens in the onchange event handler above\n\t\t\tdocument[ this._fullscreenApiKeys.exit ]();\n\t\t}\n\n\t\tthis._exit();\n\t};\n\n\tFullscreen.prototype.isFullscreen = function(){\n\t\treturn this._isFullscreen;\n\t};\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Fullscreen = Fullscreen;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t/**\n\t * Akamai Viewer omni-component\n\t * @class\n\t * @alias Akamai.Viewer\n\t * @param {HTMLElement} element - the DOM element representing the component markup\n\t * @param {Object} options - configuration options\n\t *\n\t * @example Instatiation\n\t * var element = document.querySelector( \"[data-akamai-viewer]\" );\n\t * var viewer = Akamai.Viewer( element );\n\t */\n\tvar Viewer = Akamai.Util.component(\"Viewer\", true, function(element, options){\n\t\t// try to update the carousel and magnifier sizes options to the width of\n\t\t// the viewer for more accurate srcset selection\n\t\tthis._updateSizesOptions(true);\n\t\tthis._onResizeComplete(this._updateSizesOptions.bind(this));\n\n\t\t// move all the flattened component options (spin, zoom, video) onto the carousel\n\t\tthis._options.carousel =\n\t\t\tViewer._extendCarouselOptions(this._options);\n\n\t\tthis._withData(function(data){\n\t\t\tif( data ) {\n\t\t\t\t// check all of the urls agains the hostname and whitelist\n\t\t\t\tthis._checkJSONUrls(data);\n\n\t\t\t\t// prevent XSS / injection attacks by escaping string values\n\t\t\t\tdata = Akamai.Util.escapeJSONVals(data);\n\n\t\t\t\t// store escaped data for later reference\n\t\t\t\tthis._options.items.data = data;\n\t\t\t}\n\n\t\t\tthis._tagMapping = {};\n\n\t\t\t// TODO if the element is empty and/or options has `items` render into element\n\t\t\tif( this._$el.children().length == 0 && data ) {\n\t\t\t\tthis._tagMapping = this.constructor._tagSplit(data, this._options.items);\n\t\t\t\tthis._$el.append(this.constructor.render(data, this._options, true));\n\t\t\t}\n\n\t\t\t// intantiate all child carousels\n\t\t\tthis._carousels = Akamai.Carousel.createMany(element, this._options.carousel);\n\t\t\tthis._fullscreen = new Akamai.Fullscreen(element, this._options.fullscreen);\n\n\t\t\t// on enter and exit of fullscreen resolve the breakpoints and update options\n\t\t\tthis._$el.bind( \"akamai-fullscreen-enter akamai-fullscreen-exit\", function(){\n\t\t\t\tthis._updateOptions(this._resolveBreakpointOptions());\n\t\t\t}.bind(this));\n\n\t\t\t// use the breakpoints to set match media listeners\n\t\t\tthis._setBreakpoints();\n\n\t\t\t// have to trigger init explicitly for async constructors\n\t\t\tthis._trigger(\"init\");\n\t\t}.bind(this), function(msg){\n\t\t\tthrow new Error(msg);\n\t\t});\n\t});\n\n\tViewer.prototype._updateSizesOptions = function(localUpdateOnly){\n\t\t// update sizes option to something more specific if possible\n\t\tvar elWidth = this._$el.width();\n\t\tif( elWidth && elWidth > 0 ){\n\t\t\telWidth += \"px\";\n\t\t\tthis._options = Akamai.Util.extend(true, this._options, {\n\t\t\t\tcarousel: {\n\t\t\t\t\timages: {\n\t\t\t\t\t\tsizes: elWidth\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif( !localUpdateOnly ){\n\t\t\tthis._updateOptions(this._options);\n\t\t}\n\t};\n\n\tViewer.prototype._withData = function(after, fail){\n\t\tif(!this._options.items.uri){\n\t\t\tif( this._exceedsLimit(this._options.items.data, fail)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tafter(this._options.items.data);\n\t\t}\n\n\t\telse if (typeof(this._options.items.uri) !== 'undefined' && this._options.items.uri.length > 0)\n\t\t{\n\t\t\t$.get(this._options.items.uri, function(data){\n\t\t\t\t// make sure the data gets sorted out regardless of `get` impl\n\t\t\t\tdata = (typeof data === \"string\" ? JSON.parse(data) : data);\n\n\t\t\t\tif( this._exceedsLimit(data.items, fail) ){\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tafter(data.items);\n\t\t\t}.bind(this));\n\t\t}\n\t};\n\n\t// value in kibibytes\n\tViewer.prototype._exceedsLimit = function(data, fail){\n\t\t// TODO some tests don't provide data\n\t\tif( !data ) { return false; }\n\n\t\t// 1 unicode character = 4 bytes\n\t\t// length = # of chars\n\t\t// kibibyte = 1024 bytes\n\t\t// # of chars * 4 / 1024\n\t\tvar kb = JSON.stringify(data).length * 4 / 1024;\n\n\t\tif( kb > this._options.items.limit ) {\n\t\t\tfail = fail || function(){};\n\t\t\tfail(`JSON data size exceeds ${this._options.items.limit} KiB. Limit can be configured with items.limit Akamai.Viewer options.`);\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t};\n\n\tViewer.prototype._setBreakpoints = function(){\n\t\tthis._breakpoints = this._reduceBreakpoints(this._options.breakpoints || {});\n\t\tthis._fullscreenBreakpoints =\n\t\t\tthis._reduceBreakpoints(this._options.fullscreenBreakpoints || {});\n\n\t\tthis._bindBreakpoints();\n\t};\n\n\tViewer.prototype._reduceBreakpoints = function(breakpoints){\n\t\tvar bps = [];\n\n\t\tfor( var bp in breakpoints ){\n\t\t\tif(breakpoints.hasOwnProperty(bp)){\n\t\t\t\ttry {\n\t\t\t\t\tbps.push(parseInt(bp, 10));\n\t\t\t\t} catch(e){\n\t\t\t\t\tAkamai.Util.log(e, 'error');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tbps.sort(function(a, b){ return b < a;});\n\n\t\treturn bps;\n\t};\n\n\tViewer.prototype._bindBreakpoints = function(){\n\t\t[this._breakpoints, this._fullscreenBreakpoints].forEach(function(bps){\n\t\t\t// bind using the minwidth and the next breakpoint as the maxwidth\n\t\t\t// adds a range from 0 to the first breakpoint and from the last\n\t\t\t// breakpoint to a very large number\n\t\t\tif(!bps.length) { return; }\n\n\t\t\tfor( var i = -1; i < bps.length; i++ ){\n\t\t\t\tthis._bindMatchMedia(bps[i], bps[i+1]);\n\t\t\t}\n\t\t}.bind(this));\n\t};\n\n\t// bind a callback to run after resize completes\n\tViewer.prototype._onResizeComplete = function( callback ){\n\t\tvar cbtimer;\n\t\tvar self = this;\n\t\twindow.addEventListener( \"resize\", function(){\n\t\t\tclearTimeout( cbtimer );\n\t\t\tcbtimer = setTimeout( callback, 500 );\n\t\t});\n\t};\n\n\tViewer.prototype._bindMatchMedia = function(minWidth, maxWidth){\n\t\tminWidth = minWidth || 0;\n\n\t\t// large number so we can use one media query template\n\t\tmaxWidth = maxWidth || 1000000000;\n\n\t\tif( window.matchMedia ){\n\t\t\t// bind using the minwidth and maxwidth so we get the events at the boundaries\n\t\t\t// so we can asses which set of options applies.\n\t\t\tvar query = `(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`;\n\n\t\t\t// create a media list to bind to\n\t\t\tvar initList = window.matchMedia(query);\n\n\t\t\tif(initList && initList.addListener) {\n\t\t\t\t// handle a match on instantiation\n\t\t\t\tthis._mediaMatch(initList, minWidth, true);\n\n\t\t\t\t// bind for later changes in whether the media query matches\n\t\t\t\tinitList.addListener(function(list){\n\t\t\t\t\tthis._mediaMatch(list, minWidth);\n\t\t\t\t}.bind(this));\n\t\t\t}\n\t\t}\n\t};\n\n\tViewer.prototype._mediaMatch = function(list, minWidth, ignoreDefault){\n\t\t// If there is a match for the breakpoint (we're in the bp range)\n\t\t// Then set the options based on that breakpoint\n\t\t// Else if there is not a match and the client width is below the\n\t\t// breakpoint that's being disabled, then use the original options\n\t\tif(list.matches){\n\t\t\tvar resolvedOptions = this._resolveBreakpointOptions(minWidth);\n\t\t\tthis._updateOptions(resolvedOptions);\n\t\t}\n\t};\n\n\t// TODO a ton of duplication with bindMatchmedia\n\tViewer.prototype._getCurrentMinWidth = function(bps){\n\t\t// bind using the minwidth and the next breakpoint as the maxwidth\n\t\t// adds a range from 0 to the first breakpoint and from the last\n\t\t// breakpoint to a very large number\n\t\tfor( var i = -1; i < bps.length; i++ ){\n\t\t\tminWidth = bps[i] || 0;\n\t\t\tmaxWidth = bps[i+1] || 100000000;\n\n\t\t\tif( window.matchMedia ){\n\t\t\t\t// bind using the minwidth and maxwidth so we get the events at the boundaries\n\t\t\t\t// so we can asses which set of options applies.\n\t\t\t\tvar query = `(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`;\n\n\t\t\t\t// create a media list to bind to\n\t\t\t\tvar initList = window.matchMedia(query);\n\n\t\t\t\tif(initList && initList.matches) {\n\t\t\t\t\treturn minWidth;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t};\n\n\tViewer.prototype._resolveBreakpointOptions = function(minWidth){\n\t\t// TODO bind on the reduce callback was not working\n\t\tvar self = this;\n\t\tvar bps, bpConfig;\n\n\t\tif( this._fullscreen.isFullscreen() ) {\n\t\t\tbps = this._fullscreenBreakpoints;\n\t\t\tbpConfig = self._options.fullscreenBreakpoints;\n\t\t} else {\n\t\t\tbps = this._breakpoints;\n\t\t\tbpConfig = self._options.breakpoints;\n\t\t}\n\n\t\tif(!minWidth){\n\t\t\tminWidth = self._getCurrentMinWidth(bps);\n\t\t}\n\n\t\t// There may be no matching breakpoints when this method is called to\n\t\t// resolve the current options. If that's the case then we need to default\n\t\t// to the top level options\n\t\tif(!bps.length){ return self._originalOptions; }\n\n\t\treturn bps.reduce(function(acc, bp){\n\t\t\tvar cloned = Akamai.Util.extend(true, {}, acc);\n\t\t\tvar ptions;\n\n\n\t\t\tif(bp <= minWidth){\n\t\t\t\tcloned = Akamai.Util.extend(true, cloned, bpConfig[bp]);\n\t\t\t}\n\n\t\t\treturn cloned;\n\t\t}, Akamai.Util.extend(true, {}, this._originalOptions));\n\t};\n\n\tViewer._extendCarouselOptions = function(options){\n\t\t// clone the carousel options for the given breakpoint\n\t\tvar clonedCarousel = Akamai.Util.extend(true, {}, options.carousel || {});\n\n\t\t// TODO shared code with constructor\n\t\t// extend the cloned options with the breakpoint options so that all the\n\t\t// child component config is attached to the carousel config (also happens\n\t\t// in the constructor )\n\t\treturn Akamai.Util.extend(true, clonedCarousel, {\n\t\t\tmagnifier: options.magnifier,\n\t\t\tspin360: options.spin360,\n\t\t\tvideo: options.video,\n\t\t\tfullscreen: options.fullscreen\n\t\t});\n\t};\n\n\tViewer.prototype._updateOptions = function(options){\n\t\tthis._options = Akamai.Util.extend(true, this._options, options);\n\n\t\t// extend the carousel options for the given breakpoint\n\t\tvar carouselOptions = Viewer._extendCarouselOptions(options);\n\n\t\t// push the final options down to the dom element so that CSS that keys off\n\t\t// of the attributes can apply when JS config is used\n\t\tAkamai.Util.setDataAttrOptions(this._$el, Viewer.defaultOptions, options, \"Viewer\");\n\n\t\t// pass the new options down to all child carousels\n\t\tthis._carousels.forEach(function(comp){\n\t\t\tcomp._updateOptions(carouselOptions);\n\t\t}.bind(this));\n\t};\n\n\tViewer.prototype._checkJSONUrls = function(obj){\n\t\tvar hostnames = this._options.items.hostnames;\n\n\t\treturn Akamai.Util.mapJSONVals(obj,function(val, key){\n\t\t\tif(key === \"url\" && !this._urlHostnameMatch(val)){\n\t\t\t\tthrow new Error(`The URL \\`${val}\\` does not match this page's hostname or the whitelist defined in Akamai.Viewer option \\`hostnames\\` which is:\n\n${hostnames.length ? hostnames.join(\"\\n\") : \"No hostnames\"}\n`);\n\t\t\t}\n\n\t\t\treturn val;\n\t\t}.bind(this));\n\t};\n\n\tViewer.prototype._urlHostnameMatch = function(url){\n\t\tvar parser = document.createElement('a');\n\t\tparser.href = url;\n\n\t\treturn parser.hostname === \"\" || parser.hostname === window.location.hostname ||\n\t\t\tthis._options.items.hostnames.indexOf(parser.hostname) >= 0;\n\t};\n\n\t/**\n\t * Defines the global default options for all Viewers on the page\n\t * @static\n\t * @property {Object} breakpoints - configuration changes for child components at breakpoints (no default)\n\t * @property {Object} fullscreenBreakpoints - configuration changes for child components at breakpoints when in fullscreen mode (no default)\n\t * @property {Object[]} items.data - array of items from the Akamai JSON (default: undefined)\n\t * @property {String} items.defaultTag - (default: \"akamai-untagged\")\n\t * @property {String[]} items.hostnames - whitelist of URL hostnames to check for in JSON, (default: empty array)\n\t * @property {Integer} items.limit - size limit of JSON data in kibibytes (default: 100)\n\t * @property {String} items.renderAll - (default: false)\n\t * @property {String[]} items.tags - Set of tags to match against the Akamai JSON data (default: undefined)\n\t * @property {String} items.uri - URI at which to retrieve the Akamai JSON (default: undefined)\n\t * @property {Object} carousel - child {@link Akamai.Carousel} options\n\t * @property {Object} magnifier - child {@link Akamai.Magnifier} options\n\t * @property {Object} spin360 - child {@link Akamai.Spin360} options\n\t * @property {Object} video - child {@link Akamai.Video} options\n\t * @property {Object} fullscreen - child {@link Akamai.Fullscreen} options\n\t */\n\tViewer.defaultOptions = {\n\t\tbreakpoints: {},\n\t\titems: {\n\t\t\tdata: undefined,\n\t\t\tdefaultTag: \"akamai-untagged\",\n\t\t\thostnames: [],\n\t\t\tlimit: 100,\n\t\t\trenderAll: false,\n\t\t\ttags: undefined,\n\t\t\turi: undefined\n\t\t},\n\t\tcarousel: Akamai.Carousel.defaultOptions,\n\t\tmagnifier: Akamai.Magnifier.defaultOptions,\n\t\tspin360: Akamai.Spin360.defaultOptions,\n\t\tvideo: Akamai.Video.defaultOptions,\n\t\tfullscreen: Akamai.Fullscreen.defaultOptions\n\t};\n\n\tViewer._tagSplit = function(data, options){\n\t\tvar items = options;\n\n\t\t// set all items without a tag to the default\n\t\tdata = data.map(function(item){\n\t\t\titem.tags = (item.tags && item.tags.length) ? item.tags : [items.defaultTag];\n\t\t\treturn item;\n\t\t});\n\n\t\t// if the tags option was set, filter items out that don't match\n\t\tif( items.tags ) {\n\t\t\tdata = items.data.filter(function(item){\n\t\t\t\treturn item.tags.reduce(function(acc, tag){\n\t\t\t\t\treturn acc || items.tags.indexOf(tag) >= 0;\n\t\t\t\t}, false);\n\t\t\t});\n\t\t}\n\n\t\t// from the filtered get the first (may be default tag)\n\t\tvar def = data[0].tags[0];\n\n\t\t// create a mapping from tags to items in the set\n\t\tvar tagMapping = data.reduce(function(acc, item){\n\t\t\t(item.tags || []).forEach(function(tag){\n\t\t\t\tacc[tag] = acc[tag] || [];\n\t\t\t\tacc[tag].push(item);\n\t\t\t});\n\n\t\t\treturn acc;\n\t\t}, {});\n\n\t\t// set the first (default) object to the first tag\n\t\ttagMapping[Viewer._firstRenderTag] = tagMapping[def];\n\n\t\treturn tagMapping;\n\t};\n\n\tViewer._firstRenderTag = \"akamai-first-render\";\n\tViewer._tagAttr = `${Viewer._dataAttr}-tag`;\n\n\tViewer._renderTag = function(data, options){\n\t\t// get the first item and it's tag\n\t\t// NOTE this assumes that the data has been normalized to have a default tag\n\t\tvar tag = data[0].tags[0];\n\n\t\t// TODO shoestring doesn't treat html correctly unless the leading `<` has\n\t\t// no whitepsace in front of it:\n\t\t// shoestring/issues/94\n\t\treturn Akamai.Util.trim(`\n\t\t\t
\n\t\t\t\t${Akamai.Carousel.render(data, options)}\n\t\t\t
\n\t\t`);\n\t};\n\n\t/**\n\t * Takes standard options including an `items` attribute and renders HTML\n\t * that conforms to the component expectations\n\t * @method\n\t * @static\n\t * @params {Object[]} json - Akamai JSON data\n\t * @params {Object} options - options object with an `items` attribute, see default options\n\t * @params {Boolean} options.items - configuration for how the items should be\n\t * rendered, see {@link Akamai.Viewer.defaultOptions} default options items sub-configuration\n\t * @returns { String }\n\t */\n\tViewer.render = function(json, options, unwrapped){\n\t\t// NOTE we do not extend the default options here because it has no bearing\n\t\t// on the rendering of the Viewer markup or child markup. This is contrast\n\t\t// to the carousel which does extend the passed options with defaults so the\n\t\t// markup can make use of them\n\t\tvar data = Viewer._tagSplit(json, options.items);\n\t\tvar carouselOptions = Viewer._extendCarouselOptions(options);\n\n\t\tvar childMarkup;\n\n\t\tif( options.items.renderAll ) {\n\t\t\tchildMarkup = data.map(function(acc, datum){\n\t\t\t\treturn Viewer._renderTag(datum, carouselOptions);\n\t\t\t}).join(\" \");\n\t\t} else {\n\t\t\tchildMarkup = Viewer._renderTag(data[Viewer._firstRenderTag], carouselOptions);\n\t\t}\n\n\t\tvar wrapped = `\n\t\t\t
\n\t\t\t\t${childMarkup}\n\t\t\t
\n\t\t`;\n\n\t\treturn unwrapped ? childMarkup : wrapped ;\n\t};\n\n\t/**\n\t * Accessor for child carousel components.\n\t * @method\n\t * @returns { Akamai.Carousel[] }\n\t */\n\tViewer.prototype.getCarousels = function(){\n\t\treturn this._carousels;\n\t};\n\n\t/**\n\t * Set the visible carousel based on the tag. Note the method activity does\n\t * not complete until all the images from the relevant viewer have loaded, use\n\t * the `akamai-viewer-switch-tag` event\n\t * @method\n\t * @fires Akamai.Viewer#akamai-viewer-switch-tag\n\t * @params {String} tag - The tag corresponding to the desired carousel\n\t * @returns {undefined}\n\t */\n\tViewer.prototype.switchTag = function(tag){\n\t\tvar tags = this.getTags();\n\n\t\t// if the passed tag isn't in the data set return early and log an error in\n\t\t// the console\n\t\tif( tags.indexOf(tag) == -1){\n\t\t\tAkamai.Util.log(`tag: ${tag} is not present in the data for this viewer`, 'error');\n\t\t\treturn;\n\t\t}\n\n\t\tvar selector = `[${this.constructor._tagAttr}=\"${tag}\"]`;\n\n\t\t// try to find an existing element with the tag\n\t\tvar $taggedViewer = this._$el.find(selector);\n\n\t\t// if there's no element append the newly rendered tag markup\n\t\tif( !$taggedViewer.length ){\n\t\t\t// make sure the config takes into account the current breakpoint\n\t\t\tvar options = this._resolveBreakpointOptions();\n\t\t\tvar carouselOptions = Viewer._extendCarouselOptions(options);\n\n\t\t\t// create the markup that will be inserted\n\t\t\tvar $markup = $(this.constructor._renderTag(this._tagMapping[tag], carouselOptions, true));\n\t\t\tvar $imgs = $markup.find(\"img\");\n\t\t\tvar imgCount = $imgs.length;\n\t\t\tvar loaded = 0;\n\n\t\t\t// hide the new carousel initially\n\t\t\t$markup.css(\"display\", \"none\");\n\n\t\t\t$imgs.bind(\"load\", function(){\n\t\t\t\tif( ++loaded !== imgCount ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// TODO namespace?\n\t\t\t\t$imgs.unbind(\"load\");\n\n\t\t\t\tvar carousels = Akamai.Carousel.createMany($markup[0], carouselOptions);\n\n\t\t\t\tcarousels.forEach(function(c){\n\t\t\t\t\t// TODO figure out why the carousel constructor doesn't apply the\n\t\t\t\t\t// active index class we want on instantiation, likely due to the fact\n\t\t\t\t\t// that the carousel is hidden so the \"getIndex\" calc is broken\n\t\t\t\t\t// mark the carousel item as active\n\t\t\t\t\tc.goto(c.getIndex());\n\t\t\t\t});\n\n\t\t\t\t// create and store the new carousels (should be one)\n\t\t\t\tthis._carousels = this._carousels.concat(carousels);\n\t\t\t\tthis._showViewer($markup, carousels);\n\t\t\t}.bind(this));\n\n\t\t\t// append the new markup to the existing viewer\n\t\t\tthis._$el.append($markup);\n\t\t} else {\n\t\t\tthis._showViewer($taggedViewer);\n\t\t}\n\t};\n\n\t// TODO should be handled in CSS\n\tViewer.prototype._showViewer = function($viewer, carousels){\n\t\tthis._$el\n\t\t\t.find(`[${this.constructor._tagAttr}]`)\n\t\t\t.css(\"display\", \"none\")\n\t\t\t.removeClass(\"focused\");\n\n\t\t$viewer\n\t\t\t.css(\"display\", \"block\")\n\t\t\t.addClass(\"focused\");\n\n\t\tif( carousels ){\n\t\t\tcarousels.map(function(c) { c.refresh(); });\n\t\t}\n\n\t\t// TODO the placement here seems arbitrary, probably belongs in `_showViewer`\n\t\t// TODO also sucks to be so tightly coupled\n\t\tthis._fullscreen.addButton();\n\n\t\tthis._trigger( \"switch-tag\" );\n\t};\n\n\t/**\n\t * Accessor for tags derived from Akamai JSON data\n\t * @method\n\t * @returns { String[] }\n\t */\n\tViewer.prototype.getTags = function(){\n\t\tif( this._tags ) {\n\t\t\treturn this._tags;\n\t\t};\n\n\t\tvar tags = [];\n\n\t\t// map and store all the carousel tags\n\t\tfor(var tag in this._tagMapping){\n\t\t\tif(this._tagMapping.hasOwnProperty(tag) && tag !== Viewer._firstRenderTag ) {\n\t\t\t\t// otherwise grab the list of tags\n\t\t\t\ttags.push(tag);\n\t\t\t}\n\t\t}\n\n\t\treturn this._tags = tags;\n\t};\n\n\t/**\n\t * Triggered when the viewer switches tag views. This includes waiting for\n\t * images to load for carousels dedicated to previously unviewed tags.\n\t * {@link Akamai.Viewer#switchTag}.\n\t *\n\t * @event Akamai.Viewer#akamai-viewer-switch-tag\n\t */\n\n\t/**\n\t * Triggered when initialization finishes\n\t * {@link Akamai.Viewer}.\n\t *\n\t * @event Akamai.Viewer#akamai-viewer-init\n\t */\n\n\texports.Akamai = exports.Akamai || {};\n\texports.Akamai.Viewer = Viewer;\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n\n(function(exports, $){\n\t$.fn.akamaiViewer = function(options){\n\t\tthis.each(function(i, element){\n\t\t\tnew Akamai.Viewer(element, options);\n\t\t});\n\t};\n})(typeof exports === 'undefined'? window : exports, this.jQuery);\n"]} \ No newline at end of file diff --git a/app/html/js/docs.es2015 b/app/html/js/docs.es2015 new file mode 100644 index 0000000..b7c2162 --- /dev/null +++ b/app/html/js/docs.es2015 @@ -0,0 +1,9630 @@ +/*! Copyright 2019, Akamai Technologies, Inc. All Rights Reserved. akamai-viewer-0.7.2 *//** + * Generic animation class with support for dropped frames both optional easing and duration. + * + * Optional duration is useful when the lifetime is defined by another condition than time + * e.g. speed of an animating object, etc. + * + * Dropped frame logic allows to keep using the same updater logic independent from the actual + * rendering. This eases a lot of cases where it might be pretty complex to break down a state + * based on the pure time difference. + */ +(function(global) { + var time = Date.now || function() { + return +new Date(); + }; + var desiredFrames = 60; + var millisecondsPerSecond = 1000; + var running = {}; + var counter = 1; + + // Create namespaces + if (!global.core) { + global.core = { effect : {} }; + + } else if (!core.effect) { + core.effect = {}; + } + + core.effect.Animate = { + + /** + * A requestAnimationFrame wrapper / polyfill. + * + * @param callback {Function} The callback to be invoked before the next repaint. + * @param root {HTMLElement} The root element for the repaint + */ + requestAnimationFrame: (function() { + + // Check for request animation Frame support + var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame; + var isNative = !!requestFrame; + + if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) { + isNative = false; + } + + if (isNative) { + return function(callback, root) { + requestFrame(callback, root) + }; + } + + var TARGET_FPS = 60; + var requests = {}; + var requestCount = 0; + var rafHandle = 1; + var intervalHandle = null; + var lastActive = +new Date(); + + return function(callback, root) { + var callbackHandle = rafHandle++; + + // Store callback + requests[callbackHandle] = callback; + requestCount++; + + // Create timeout at first request + if (intervalHandle === null) { + + intervalHandle = setInterval(function() { + + var time = +new Date(); + var currentRequests = requests; + + // Reset data structure before executing callbacks + requests = {}; + requestCount = 0; + + for(var key in currentRequests) { + if (currentRequests.hasOwnProperty(key)) { + currentRequests[key](time); + lastActive = time; + } + } + + // Disable the timeout when nothing happens for a certain + // period of time + if (time - lastActive > 2500) { + clearInterval(intervalHandle); + intervalHandle = null; + } + + }, 1000 / TARGET_FPS); + } + + return callbackHandle; + }; + + })(), + + + /** + * Stops the given animation. + * + * @param id {Integer} Unique animation ID + * @return {Boolean} Whether the animation was stopped (aka, was running before) + */ + stop: function(id) { + var cleared = running[id] != null; + if (cleared) { + running[id] = null; + } + + return cleared; + }, + + + /** + * Whether the given animation is still running. + * + * @param id {Integer} Unique animation ID + * @return {Boolean} Whether the animation is still running + */ + isRunning: function(id) { + return running[id] != null; + }, + + + /** + * Start the animation. + * + * @param stepCallback {Function} Pointer to function which is executed on every step. + * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }` + * @param verifyCallback {Function} Executed before every animation step. + * Signature of the method should be `function() { return continueWithAnimation; }` + * @param completedCallback {Function} + * Signature of the method should be `function(droppedFrames, finishedAnimation) {}` + * @param duration {Integer} Milliseconds to run the animation + * @param easingMethod {Function} Pointer to easing function + * Signature of the method should be `function(percent) { return modifiedValue; }` + * @param root {Element ? document.body} Render root, when available. Used for internal + * usage of requestAnimationFrame. + * @return {Integer} Identifier of animation. Can be used to stop it any time. + */ + start: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) { + + var start = time(); + var lastFrame = start; + var percent = 0; + var dropCounter = 0; + var id = counter++; + + if (!root) { + root = document.body; + } + + // Compacting running db automatically every few new animations + if (id % 20 === 0) { + var newRunning = {}; + for (var usedId in running) { + newRunning[usedId] = true; + } + running = newRunning; + } + + // This is the internal step method which is called every few milliseconds + var step = function(virtual) { + + // Normalize virtual value + var render = virtual !== true; + + // Get current time + var now = time(); + + // Verification is executed before next animation step + if (!running[id] || (verifyCallback && !verifyCallback(id))) { + + running[id] = null; + completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false); + return; + + } + + // For the current rendering to apply let's update omitted steps in memory. + // This is important to bring internal state variables up-to-date with progress in time. + if (render) { + + var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1; + for (var j = 0; j < Math.min(droppedFrames, 4); j++) { + step(true); + dropCounter++; + } + + } + + // Compute percent value + if (duration) { + percent = (now - start) / duration; + if (percent > 1) { + percent = 1; + } + } + + // Execute step callback, then... + var value = easingMethod ? easingMethod(percent) : percent; + if ((stepCallback(value, now, render) === false || percent === 1) && render) { + running[id] = null; + completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null); + } else if (render) { + lastFrame = now; + core.effect.Animate.requestAnimationFrame(step, root); + } + }; + + // Mark as running + running[id] = true; + + // Init first step + core.effect.Animate.requestAnimationFrame(step, root); + + // Return unique animation ID + return id; + } + }; +})(this); + + +var Scroller; + +(function() { + + /** + * A pure logic 'component' for 'virtual' scrolling/zooming. + */ + Scroller = function(callback, options) { + + this.__callback = callback; + + this.options = { + + /** Enable scrolling on x-axis */ + scrollingX: true, + + /** Enable scrolling on y-axis */ + scrollingY: true, + + /** Enable animations for deceleration, snap back, zooming and scrolling */ + animating: true, + + /** duration for animations triggered by scrollTo/zoomTo */ + animationDuration: 250, + + /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */ + bouncing: true, + + /** Enable locking to the main axis if user moves only slightly on one of them at start */ + locking: true, + + /** Enable pagination mode (switching between full page content panes) */ + paging: false, + + /** Enable snapping of content to a configured pixel grid */ + snapping: false, + + /** Enable zooming of content via API, fingers and mouse wheel */ + zooming: false, + + /** Minimum zoom level */ + minZoom: 0.5, + + /** Maximum zoom level */ + maxZoom: 3 + + }; + + for (var key in options) { + this.options[key] = options[key]; + } + + }; + + + // Easing Equations (c) 2003 Robert Penner, all rights reserved. + // Open source under the BSD License. + + /** + * @param pos {Number} position between 0 (start of effect) and 1 (end of effect) + **/ + var easeOutCubic = function(pos) { + return (Math.pow((pos - 1), 3) + 1); + }; + + /** + * @param pos {Number} position between 0 (start of effect) and 1 (end of effect) + **/ + var easeInOutCubic = function(pos) { + if ((pos /= 0.5) < 1) { + return 0.5 * Math.pow(pos, 3); + } + + return 0.5 * (Math.pow((pos - 2), 3) + 2); + }; + + + var members = { + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: STATUS + --------------------------------------------------------------------------- + */ + + /** {Boolean} Whether only a single finger is used in touch handling */ + __isSingleTouch: false, + + /** {Boolean} Whether a touch event sequence is in progress */ + __isTracking: false, + + /** + * {Boolean} Whether a gesture zoom/rotate event is in progress. Activates when + * a gesturestart event happens. This has higher priority than dragging. + */ + __isGesturing: false, + + /** + * {Boolean} Whether the user has moved by such a distance that we have enabled + * dragging mode. Hint: It's only enabled after some pixels of movement to + * not interrupt with clicks etc. + */ + __isDragging: false, + + /** + * {Boolean} Not touching and dragging anymore, and smoothly animating the + * touch sequence using deceleration. + */ + __isDecelerating: false, + + /** + * {Boolean} Smoothly animating the currently configured change + */ + __isAnimating: false, + + + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: DIMENSIONS + --------------------------------------------------------------------------- + */ + + /** {Integer} Available outer left position (from document perspective) */ + __clientLeft: 0, + + /** {Integer} Available outer top position (from document perspective) */ + __clientTop: 0, + + /** {Integer} Available outer width */ + __clientWidth: 0, + + /** {Integer} Available outer height */ + __clientHeight: 0, + + /** {Integer} Outer width of content */ + __contentWidth: 0, + + /** {Integer} Outer height of content */ + __contentHeight: 0, + + /** {Integer} Snapping width for content */ + __snapWidth: 100, + + /** {Integer} Snapping height for content */ + __snapHeight: 100, + + /** {Integer} Height to assign to refresh area */ + __refreshHeight: null, + + /** {Boolean} Whether the refresh process is enabled when the event is released now */ + __refreshActive: false, + + /** {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */ + __refreshActivate: null, + + /** {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */ + __refreshDeactivate: null, + + /** {Function} Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */ + __refreshStart: null, + + /** {Number} Zoom level */ + __zoomLevel: 1, + + /** {Number} Scroll position on x-axis */ + __scrollLeft: 0, + + /** {Number} Scroll position on y-axis */ + __scrollTop: 0, + + /** {Integer} Maximum allowed scroll position on x-axis */ + __maxScrollLeft: 0, + + /** {Integer} Maximum allowed scroll position on y-axis */ + __maxScrollTop: 0, + + /* {Number} Scheduled left position (final position when animating) */ + __scheduledLeft: 0, + + /* {Number} Scheduled top position (final position when animating) */ + __scheduledTop: 0, + + /* {Number} Scheduled zoom level (final scale when animating) */ + __scheduledZoom: 0, + + + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: LAST POSITIONS + --------------------------------------------------------------------------- + */ + + /** {Number} Left position of finger at start */ + __lastTouchLeft: null, + + /** {Number} Top position of finger at start */ + __lastTouchTop: null, + + /** {Date} Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */ + __lastTouchMove: null, + + /** {Array} List of positions, uses three indexes for each state: left, top, timestamp */ + __positions: null, + + + + /* + --------------------------------------------------------------------------- + INTERNAL FIELDS :: DECELERATION SUPPORT + --------------------------------------------------------------------------- + */ + + /** {Integer} Minimum left scroll position during deceleration */ + __minDecelerationScrollLeft: null, + + /** {Integer} Minimum top scroll position during deceleration */ + __minDecelerationScrollTop: null, + + /** {Integer} Maximum left scroll position during deceleration */ + __maxDecelerationScrollLeft: null, + + /** {Integer} Maximum top scroll position during deceleration */ + __maxDecelerationScrollTop: null, + + /** {Number} Current factor to modify horizontal scroll position with on every step */ + __decelerationVelocityX: null, + + /** {Number} Current factor to modify vertical scroll position with on every step */ + __decelerationVelocityY: null, + + + + /* + --------------------------------------------------------------------------- + PUBLIC API + --------------------------------------------------------------------------- + */ + + /** + * Configures the dimensions of the client (outer) and content (inner) elements. + * Requires the available space for the outer element and the outer size of the inner element. + * All values which are falsy (null or zero etc.) are ignored and the old value is kept. + * + * @param clientWidth {Integer ? null} Inner width of outer element + * @param clientHeight {Integer ? null} Inner height of outer element + * @param contentWidth {Integer ? null} Outer width of inner element + * @param contentHeight {Integer ? null} Outer height of inner element + */ + setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) { + + var self = this; + + // Only update values which are defined + if (clientWidth) { + self.__clientWidth = clientWidth; + } + + if (clientHeight) { + self.__clientHeight = clientHeight; + } + + if (contentWidth) { + self.__contentWidth = contentWidth; + } + + if (contentHeight) { + self.__contentHeight = contentHeight; + } + + // Refresh maximums + self.__computeScrollMax(); + + // Refresh scroll position + self.scrollTo(self.__scrollLeft, self.__scrollTop, true); + + }, + + + /** + * Sets the client coordinates in relation to the document. + * + * @param left {Integer ? 0} Left position of outer element + * @param top {Integer ? 0} Top position of outer element + */ + setPosition: function(left, top) { + + var self = this; + + self.__clientLeft = left || 0; + self.__clientTop = top || 0; + + }, + + + /** + * Configures the snapping (when snapping is active) + * + * @param width {Integer} Snapping width + * @param height {Integer} Snapping height + */ + setSnapSize: function(width, height) { + + var self = this; + + self.__snapWidth = width; + self.__snapHeight = height; + + }, + + + /** + * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever + * the user event is released during visibility of this zone. This was introduced by some apps on iOS like + * the official Twitter client. + * + * @param height {Integer} Height of pull-to-refresh zone on top of rendered list + * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release. + * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled. + * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh. + */ + activatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback) { + + var self = this; + + self.__refreshHeight = height; + self.__refreshActivate = activateCallback; + self.__refreshDeactivate = deactivateCallback; + self.__refreshStart = startCallback; + + }, + + + /** + * Signalizes that pull-to-refresh is finished. + */ + finishPullToRefresh: function() { + + var self = this; + + self.__refreshActive = false; + if (self.__refreshDeactivate) { + self.__refreshDeactivate(); + } + + self.scrollTo(self.__scrollLeft, self.__scrollTop, true); + + }, + + + /** + * Returns the scroll position and zooming values + * + * @return {Map} `left` and `top` scroll position and `zoom` level + */ + getValues: function() { + + var self = this; + + return { + left: self.__scrollLeft, + top: self.__scrollTop, + zoom: self.__zoomLevel + }; + + }, + + + /** + * Returns the maximum scroll values + * + * @return {Map} `left` and `top` maximum scroll values + */ + getScrollMax: function() { + + var self = this; + + return { + left: self.__maxScrollLeft, + top: self.__maxScrollTop + }; + + }, + + + /** + * Zooms to the given level. Supports optional animation. Zooms + * the center when no coordinates are given. + * + * @param level {Number} Level to zoom to + * @param animate {Boolean ? false} Whether to use animation + * @param originLeft {Number ? null} Zoom in at given left coordinate + * @param originTop {Number ? null} Zoom in at given top coordinate + */ + zoomTo: function(level, animate, originLeft, originTop) { + + var self = this; + + if (!self.options.zooming) { + throw new Error("Zooming is not enabled!"); + } + + // Stop deceleration + if (self.__isDecelerating) { + core.effect.Animate.stop(self.__isDecelerating); + self.__isDecelerating = false; + } + + var oldLevel = self.__zoomLevel; + + // Normalize input origin to center of viewport if not defined + if (originLeft == null) { + originLeft = self.__clientWidth / 2; + } + + if (originTop == null) { + originTop = self.__clientHeight / 2; + } + + // Limit level according to configuration + level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom); + + // Recompute maximum values while temporary tweaking maximum scroll ranges + self.__computeScrollMax(level); + + // Recompute left and top coordinates based on new zoom level + var left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft; + var top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop; + + // Limit x-axis + if (left > self.__maxScrollLeft) { + left = self.__maxScrollLeft; + } else if (left < 0) { + left = 0; + } + + // Limit y-axis + if (top > self.__maxScrollTop) { + top = self.__maxScrollTop; + } else if (top < 0) { + top = 0; + } + + // Push values out + self.__publish(left, top, level, animate); + + }, + + + /** + * Zooms the content by the given factor. + * + * @param factor {Number} Zoom by given factor + * @param animate {Boolean ? false} Whether to use animation + * @param originLeft {Number ? 0} Zoom in at given left coordinate + * @param originTop {Number ? 0} Zoom in at given top coordinate + */ + zoomBy: function(factor, animate, originLeft, originTop) { + + var self = this; + + self.zoomTo(self.__zoomLevel * factor, animate, originLeft, originTop); + + }, + + + /** + * Scrolls to the given position. Respect limitations and snapping automatically. + * + * @param left {Number?null} Horizontal scroll position, keeps current if value is null + * @param top {Number?null} Vertical scroll position, keeps current if value is null + * @param animate {Boolean?false} Whether the scrolling should happen using an animation + * @param zoom {Number?null} Zoom level to go to + */ + scrollTo: function(left, top, animate, zoom) { + + var self = this; + + // Stop deceleration + if (self.__isDecelerating) { + core.effect.Animate.stop(self.__isDecelerating); + self.__isDecelerating = false; + } + + // Correct coordinates based on new zoom level + if (zoom != null && zoom !== self.__zoomLevel) { + + if (!self.options.zooming) { + throw new Error("Zooming is not enabled!"); + } + + left *= zoom; + top *= zoom; + + // Recompute maximum values while temporary tweaking maximum scroll ranges + self.__computeScrollMax(zoom); + + } else { + + // Keep zoom when not defined + zoom = self.__zoomLevel; + + } + + if (!self.options.scrollingX) { + + left = self.__scrollLeft; + + } else { + + if (self.options.paging) { + left = Math.round(left / self.__clientWidth) * self.__clientWidth; + } else if (self.options.snapping) { + left = Math.round(left / self.__snapWidth) * self.__snapWidth; + } + + } + + if (!self.options.scrollingY) { + + top = self.__scrollTop; + + } else { + + if (self.options.paging) { + top = Math.round(top / self.__clientHeight) * self.__clientHeight; + } else if (self.options.snapping) { + top = Math.round(top / self.__snapHeight) * self.__snapHeight; + } + + } + + // Limit for allowed ranges + left = Math.max(Math.min(self.__maxScrollLeft, left), 0); + top = Math.max(Math.min(self.__maxScrollTop, top), 0); + + // Don't animate when no change detected, still call publish to make sure + // that rendered position is really in-sync with internal data + if (left === self.__scrollLeft && top === self.__scrollTop) { + animate = false; + } + + // Publish new values + self.__publish(left, top, zoom, animate); + + }, + + + /** + * Scroll by the given offset + * + * @param left {Number ? 0} Scroll x-axis by given offset + * @param top {Number ? 0} Scroll x-axis by given offset + * @param animate {Boolean ? false} Whether to animate the given change + */ + scrollBy: function(left, top, animate) { + + var self = this; + + var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft; + var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop; + + self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate); + + }, + + + + /* + --------------------------------------------------------------------------- + EVENT CALLBACKS + --------------------------------------------------------------------------- + */ + + /** + * Mouse wheel handler for zooming support + */ + doMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) { + + var self = this; + var change = wheelDelta > 0 ? 0.97 : 1.03; + + return self.zoomTo(self.__zoomLevel * change, false, pageX - self.__clientLeft, pageY - self.__clientTop); + + }, + + + /** + * Touch start handler for scrolling support + */ + doTouchStart: function(touches, timeStamp) { + + // Array-like check is enough here + if (touches.length == null) { + throw new Error("Invalid touch list: " + touches); + } + + if (timeStamp instanceof Date) { + timeStamp = timeStamp.valueOf(); + } + if (typeof timeStamp !== "number") { + throw new Error("Invalid timestamp value: " + timeStamp); + } + + var self = this; + + // Stop deceleration + if (self.__isDecelerating) { + core.effect.Animate.stop(self.__isDecelerating); + self.__isDecelerating = false; + } + + // Stop animation + if (self.__isAnimating) { + core.effect.Animate.stop(self.__isAnimating); + self.__isAnimating = false; + } + + // Use center point when dealing with two fingers + var currentTouchLeft, currentTouchTop; + var isSingleTouch = touches.length === 1; + if (isSingleTouch) { + currentTouchLeft = touches[0].pageX; + currentTouchTop = touches[0].pageY; + } else { + currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; + currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; + } + + // Store initial positions + self.__initialTouchLeft = currentTouchLeft; + self.__initialTouchTop = currentTouchTop; + + // Store current zoom level + self.__zoomLevelStart = self.__zoomLevel; + + // Store initial touch positions + self.__lastTouchLeft = currentTouchLeft; + self.__lastTouchTop = currentTouchTop; + + // Store initial move time stamp + self.__lastTouchMove = timeStamp; + + // Reset initial scale + self.__lastScale = 1; + + // Reset locking flags + self.__enableScrollX = !isSingleTouch && self.options.scrollingX; + self.__enableScrollY = !isSingleTouch && self.options.scrollingY; + + // Reset tracking flag + self.__isTracking = true; + + // Dragging starts directly with two fingers, otherwise lazy with an offset + self.__isDragging = !isSingleTouch; + + // Some features are disabled in multi touch scenarios + self.__isSingleTouch = isSingleTouch; + + // Clearing data structure + self.__positions = []; + + }, + + + /** + * Touch move handler for scrolling support + */ + doTouchMove: function(touches, timeStamp, scale) { + + // Array-like check is enough here + if (touches.length == null) { + throw new Error("Invalid touch list: " + touches); + } + + if (timeStamp instanceof Date) { + timeStamp = timeStamp.valueOf(); + } + if (typeof timeStamp !== "number") { + throw new Error("Invalid timestamp value: " + timeStamp); + } + + var self = this; + + // Ignore event when tracking is not enabled (event might be outside of element) + if (!self.__isTracking) { + return; + } + + + var currentTouchLeft, currentTouchTop; + + // Compute move based around of center of fingers + if (touches.length === 2) { + currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; + currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; + } else { + currentTouchLeft = touches[0].pageX; + currentTouchTop = touches[0].pageY; + } + + var positions = self.__positions; + + // Are we already is dragging mode? + if (self.__isDragging) { + + // Compute move distance + var moveX = currentTouchLeft - self.__lastTouchLeft; + var moveY = currentTouchTop - self.__lastTouchTop; + + // Read previous scroll position and zooming + var scrollLeft = self.__scrollLeft; + var scrollTop = self.__scrollTop; + var level = self.__zoomLevel; + + // Work with scaling + if (scale != null && self.options.zooming) { + + var oldLevel = level; + + // Recompute level based on previous scale and new scale + level = level / self.__lastScale * scale; + + // Limit level according to configuration + level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom); + + // Only do further compution when change happened + if (oldLevel !== level) { + + // Compute relative event position to container + var currentTouchLeftRel = currentTouchLeft - self.__clientLeft; + var currentTouchTopRel = currentTouchTop - self.__clientTop; + + // Recompute left and top coordinates based on new zoom level + scrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel; + scrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel; + + // Recompute max scroll values + self.__computeScrollMax(level); + + } + } + + if (self.__enableScrollX) { + + scrollLeft -= moveX; + var maxScrollLeft = self.__maxScrollLeft; + + if (scrollLeft > maxScrollLeft || scrollLeft < 0) { + + // Slow down on the edges + if (self.options.bouncing) { + + scrollLeft += (moveX / 2); + + } else if (scrollLeft > maxScrollLeft) { + + scrollLeft = maxScrollLeft; + + } else { + + scrollLeft = 0; + + } + } + } + + // Compute new vertical scroll position + if (self.__enableScrollY) { + + scrollTop -= moveY; + var maxScrollTop = self.__maxScrollTop; + + if (scrollTop > maxScrollTop || scrollTop < 0) { + + // Slow down on the edges + if (self.options.bouncing) { + + scrollTop += (moveY / 2); + + // Support pull-to-refresh (only when only y is scrollable) + if (!self.__enableScrollX && self.__refreshHeight != null) { + + if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) { + + self.__refreshActive = true; + if (self.__refreshActivate) { + self.__refreshActivate(); + } + + } else if (self.__refreshActive && scrollTop > -self.__refreshHeight) { + + self.__refreshActive = false; + if (self.__refreshDeactivate) { + self.__refreshDeactivate(); + } + + } + } + + } else if (scrollTop > maxScrollTop) { + + scrollTop = maxScrollTop; + + } else { + + scrollTop = 0; + + } + } + } + + // Keep list from growing infinitely (holding min 10, max 20 measure points) + if (positions.length > 60) { + positions.splice(0, 30); + } + + // Track scroll movement for decleration + positions.push(scrollLeft, scrollTop, timeStamp); + + // Sync scroll position + self.__publish(scrollLeft, scrollTop, level); + + // Otherwise figure out whether we are switching into dragging mode now. + } else { + + var minimumTrackingForScroll = self.options.locking ? 3 : 0; + var minimumTrackingForDrag = 5; + + var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft); + var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop); + + self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll; + self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll; + + positions.push(self.__scrollLeft, self.__scrollTop, timeStamp); + + self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag); + + } + + // Update last touch positions and time stamp for next event + self.__lastTouchLeft = currentTouchLeft; + self.__lastTouchTop = currentTouchTop; + self.__lastTouchMove = timeStamp; + self.__lastScale = scale; + + }, + + + /** + * Touch end handler for scrolling support + */ + doTouchEnd: function(timeStamp) { + + if (timeStamp instanceof Date) { + timeStamp = timeStamp.valueOf(); + } + if (typeof timeStamp !== "number") { + throw new Error("Invalid timestamp value: " + timeStamp); + } + + var self = this; + + // Ignore event when tracking is not enabled (no touchstart event on element) + // This is required as this listener ('touchmove') sits on the document and not on the element itself. + if (!self.__isTracking) { + return; + } + + // Not touching anymore (when two finger hit the screen there are two touch end events) + self.__isTracking = false; + + // Be sure to reset the dragging flag now. Here we also detect whether + // the finger has moved fast enough to switch into a deceleration animation. + if (self.__isDragging) { + + // Reset dragging flag + self.__isDragging = false; + + // Start deceleration + // Verify that the last move detected was in some relevant time frame + if (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) { + + // Then figure out what the scroll position was about 100ms ago + var positions = self.__positions; + var endPos = positions.length - 1; + var startPos = endPos; + + // Move pointer to position measured 100ms ago + for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) { + startPos = i; + } + + // If start and stop position is identical in a 100ms timeframe, + // we cannot compute any useful deceleration. + if (startPos !== endPos) { + + // Compute relative movement between these two points + var timeOffset = positions[endPos] - positions[startPos]; + var movedLeft = self.__scrollLeft - positions[startPos - 2]; + var movedTop = self.__scrollTop - positions[startPos - 1]; + + // Based on 50ms compute the movement to apply for each render step + self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60); + self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60); + + // How much velocity is required to start the deceleration + var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? 4 : 1; + + // Verify that we have enough velocity to start deceleration + if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) { + + // Deactivate pull-to-refresh when decelerating + if (!self.__refreshActive) { + + self.__startDeceleration(timeStamp); + + } + } + } + } + } + + // If this was a slower move it is per default non decelerated, but this + // still means that we want snap back to the bounds which is done here. + // This is placed outside the condition above to improve edge case stability + // e.g. touchend fired without enabled dragging. This should normally do not + // have modified the scroll positions or even showed the scrollbars though. + if (!self.__isDecelerating) { + + if (self.__refreshActive && self.__refreshStart) { + + // Use publish instead of scrollTo to allow scrolling to out of boundary position + // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled + self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true); + + if (self.__refreshStart) { + self.__refreshStart(); + } + + } else { + + self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel); + + // Directly signalize deactivation (nothing todo on refresh?) + if (self.__refreshActive) { + + self.__refreshActive = false; + if (self.__refreshDeactivate) { + self.__refreshDeactivate(); + } + + } + } + } + + // Fully cleanup list + self.__positions.length = 0; + + }, + + + + /* + --------------------------------------------------------------------------- + PRIVATE API + --------------------------------------------------------------------------- + */ + + /** + * Applies the scroll position to the content element + * + * @param left {Number} Left scroll position + * @param top {Number} Top scroll position + * @param animate {Boolean?false} Whether animation should be used to move to the new coordinates + */ + __publish: function(left, top, zoom, animate) { + + var self = this; + + // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation + var wasAnimating = self.__isAnimating; + if (wasAnimating) { + core.effect.Animate.stop(wasAnimating); + self.__isAnimating = false; + } + + if (animate && self.options.animating) { + + // Keep scheduled positions for scrollBy/zoomBy functionality + self.__scheduledLeft = left; + self.__scheduledTop = top; + self.__scheduledZoom = zoom; + + var oldLeft = self.__scrollLeft; + var oldTop = self.__scrollTop; + var oldZoom = self.__zoomLevel; + + var diffLeft = left - oldLeft; + var diffTop = top - oldTop; + var diffZoom = zoom - oldZoom; + + var step = function(percent, now, render) { + + if (render) { + + self.__scrollLeft = oldLeft + (diffLeft * percent); + self.__scrollTop = oldTop + (diffTop * percent); + self.__zoomLevel = oldZoom + (diffZoom * percent); + + // Push values out + if (self.__callback) { + self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel); + } + + } + }; + + var verify = function(id) { + return self.__isAnimating === id; + }; + + var completed = function(renderedFramesPerSecond, animationId, wasFinished) { + if (animationId === self.__isAnimating) { + self.__isAnimating = false; + } + + if (self.options.zooming) { + self.__computeScrollMax(); + } + }; + + // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out + self.__isAnimating = core.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic); + + } else { + + self.__scheduledLeft = self.__scrollLeft = left; + self.__scheduledTop = self.__scrollTop = top; + self.__scheduledZoom = self.__zoomLevel = zoom; + + // Push values out + if (self.__callback) { + self.__callback(left, top, zoom); + } + + // Fix max scroll ranges + if (self.options.zooming) { + self.__computeScrollMax(); + } + } + }, + + + /** + * Recomputes scroll minimum values based on client dimensions and content dimensions. + */ + __computeScrollMax: function(zoomLevel) { + + var self = this; + + if (zoomLevel == null) { + zoomLevel = self.__zoomLevel; + } + + self.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0); + self.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0); + + }, + + + + /* + --------------------------------------------------------------------------- + ANIMATION (DECELERATION) SUPPORT + --------------------------------------------------------------------------- + */ + + /** + * Called when a touch sequence end and the speed of the finger was high enough + * to switch into deceleration mode. + */ + __startDeceleration: function(timeStamp) { + + var self = this; + + if (self.options.paging) { + + var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0); + var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0); + var clientWidth = self.__clientWidth; + var clientHeight = self.__clientHeight; + + // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area. + // Each page should have exactly the size of the client area. + self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth; + self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight; + self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth; + self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight; + + } else { + + self.__minDecelerationScrollLeft = 0; + self.__minDecelerationScrollTop = 0; + self.__maxDecelerationScrollLeft = self.__maxScrollLeft; + self.__maxDecelerationScrollTop = self.__maxScrollTop; + + } + + // Wrap class method + var step = function(percent, now, render) { + self.__stepThroughDeceleration(render); + }; + + // How much velocity is required to keep the deceleration running + var minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1; + + // Detect whether it's still worth to continue animating steps + // If we are already slow enough to not being user perceivable anymore, we stop the whole process here. + var verify = function() { + return Math.abs(self.__decelerationVelocityX) >= minVelocityToKeepDecelerating || Math.abs(self.__decelerationVelocityY) >= minVelocityToKeepDecelerating; + }; + + var completed = function(renderedFramesPerSecond, animationId, wasFinished) { + self.__isDecelerating = false; + + // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions + self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping); + }; + + // Start animation and switch on flag + self.__isDecelerating = core.effect.Animate.start(step, verify, completed); + + }, + + + /** + * Called on every step of the animation + * + * @param inMemory {Boolean?false} Whether to not render the current step, but keep it in memory only. Used internally only! + */ + __stepThroughDeceleration: function(render) { + + var self = this; + + + // + // COMPUTE NEXT SCROLL POSITION + // + + // Add deceleration to scroll position + var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX; + var scrollTop = self.__scrollTop + self.__decelerationVelocityY; + + + // + // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE + // + + if (!self.options.bouncing) { + + var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft); + if (scrollLeftFixed !== scrollLeft) { + scrollLeft = scrollLeftFixed; + self.__decelerationVelocityX = 0; + } + + var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop); + if (scrollTopFixed !== scrollTop) { + scrollTop = scrollTopFixed; + self.__decelerationVelocityY = 0; + } + + } + + + // + // UPDATE SCROLL POSITION + // + + if (render) { + + self.__publish(scrollLeft, scrollTop, self.__zoomLevel); + + } else { + + self.__scrollLeft = scrollLeft; + self.__scrollTop = scrollTop; + + } + + + // + // SLOW DOWN + // + + // Slow down velocity on every iteration + if (!self.options.paging) { + + // This is the factor applied to every iteration of the animation + // to slow down the process. This should emulate natural behavior where + // objects slow down when the initiator of the movement is removed + var frictionFactor = 0.95; + + self.__decelerationVelocityX *= frictionFactor; + self.__decelerationVelocityY *= frictionFactor; + + } + + + // + // BOUNCING SUPPORT + // + + if (self.options.bouncing) { + + var scrollOutsideX = 0; + var scrollOutsideY = 0; + + // This configures the amount of change applied to deceleration/acceleration when reaching boundaries + var penetrationDeceleration = 0.03; + var penetrationAcceleration = 0.08; + + // Check limits + if (scrollLeft < self.__minDecelerationScrollLeft) { + scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft; + } else if (scrollLeft > self.__maxDecelerationScrollLeft) { + scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft; + } + + if (scrollTop < self.__minDecelerationScrollTop) { + scrollOutsideY = self.__minDecelerationScrollTop - scrollTop; + } else if (scrollTop > self.__maxDecelerationScrollTop) { + scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop; + } + + // Slow down until slow enough, then flip back to snap position + if (scrollOutsideX !== 0) { + if (scrollOutsideX * self.__decelerationVelocityX <= 0) { + self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration; + } else { + self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration; + } + } + + if (scrollOutsideY !== 0) { + if (scrollOutsideY * self.__decelerationVelocityY <= 0) { + self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration; + } else { + self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration; + } + } + } + } + }; + + // Copy over members to prototype + for (var key in members) { + Scroller.prototype[key] = members[key]; + } + +})(); + +(function( factory ) { + if( typeof define === 'function' && define.amd ) { + // AMD. Register as an anonymous module. + define( [ 'shoestring' ], factory ); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = factory(); + } else { + // Browser globals + factory(); + } +}(function () { + var win = typeof window !== "undefined" ? window : this; + var doc = win.document; + + + /** + * The shoestring object constructor. + * + * @param {string,object} prim The selector to find or element to wrap. + * @param {object} sec The context in which to match the `prim` selector. + * @returns shoestring + * @this window + */ + function shoestring( prim, sec ){ + var pType = typeof( prim ), + ret = [], + sel; + + // return an empty shoestring object + if( !prim ){ + return new Shoestring( ret ); + } + + // ready calls + if( prim.call ){ + return shoestring.ready( prim ); + } + + // handle re-wrapping shoestring objects + if( prim.constructor === Shoestring && !sec ){ + return prim; + } + + // if string starting with <, make html + if( pType === "string" && prim.indexOf( "<" ) === 0 ){ + var dfrag = doc.createElement( "div" ); + + dfrag.innerHTML = prim; + + // TODO depends on children (circular) + return shoestring( dfrag ).children().each(function(){ + dfrag.removeChild( this ); + }); + } + + // if string, it's a selector, use qsa + if( pType === "string" ){ + if( sec ){ + return shoestring( sec ).find( prim ); + } + + sel = doc.querySelectorAll( prim ); + + return new Shoestring( sel, prim ); + } + + // array like objects or node lists + if( Object.prototype.toString.call( pType ) === '[object Array]' || + (win.NodeList && prim instanceof win.NodeList) ){ + + return new Shoestring( prim, prim ); + } + + // if it's an array, use all the elements + if( prim.constructor === Array ){ + return new Shoestring( prim, prim ); + } + + // otherwise assume it's an object the we want at an index + return new Shoestring( [prim], prim ); + } + + var Shoestring = function( ret, prim ) { + this.length = 0; + this.selector = prim; + shoestring.merge(this, ret); + }; + + // TODO only required for tests + Shoestring.prototype.reverse = [].reverse; + + // For adding element set methods + shoestring.fn = Shoestring.prototype; + + shoestring.Shoestring = Shoestring; + + // For extending objects + // TODO move to separate module when we use prototypes + shoestring.extend = function( first, second ){ + for( var i in second ){ + if( second.hasOwnProperty( i ) ){ + first[ i ] = second[ i ]; + } + } + + return first; + }; + + // taken directly from jQuery + shoestring.merge = function( first, second ) { + var len, j, i; + + len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }; + + // expose + win.shoestring = shoestring; + + + + /** + * Make an HTTP request to a url. + * + * **NOTE** the following options are supported: + * + * - *method* - The HTTP method used with the request. Default: `GET`. + * - *data* - Raw object with keys and values to pass with request as query params. Default `null`. + * - *headers* - Set of request headers to add. Default `{}`. + * - *async* - Whether the opened request is asynchronouse. Default `true`. + * - *success* - Callback for successful request and response. Passed the response data. + * - *error* - Callback for failed request and response. + * - *cancel* - Callback for cancelled request and response. + * + * @param {string} url The url to request. + * @param {object} options The options object, see Notes. + * @return shoestring + * @this shoestring + */ + + shoestring.ajax = function( url, options ) { + var params = "", req = new XMLHttpRequest(), settings, key; + + settings = shoestring.extend( {}, shoestring.ajax.settings ); + + if( options ){ + shoestring.extend( settings, options ); + } + + if( !url ){ + url = settings.url; + } + + if( !req || !url ){ + return; + } + + // create parameter string from data object + if( settings.data ){ + for( key in settings.data ){ + if( settings.data.hasOwnProperty( key ) ){ + if( params !== "" ){ + params += "&"; + } + params += encodeURIComponent( key ) + "=" + + encodeURIComponent( settings.data[key] ); + } + } + } + + // append params to url for GET requests + if( settings.method === "GET" && params ){ + + url += "?" + params; + } + + req.open( settings.method, url, settings.async ); + + if( req.setRequestHeader ){ + req.setRequestHeader( "X-Requested-With", "XMLHttpRequest" ); + + // Set 'Content-type' header for POST requests + if( settings.method === "POST" && params ){ + req.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" ); + } + + for( key in settings.headers ){ + if( settings.headers.hasOwnProperty( key ) ){ + req.setRequestHeader(key, settings.headers[ key ]); + } + } + } + + req.onreadystatechange = function () { + if( req.readyState === 4 ){ + // Trim the whitespace so shoestring('
') works + var res = (req.responseText || '').replace(/^\s+|\s+$/g, ''); + if( req.status.toString().indexOf( "0" ) === 0 ){ + return settings.cancel( res, req.status, req ); + } + else if ( req.status.toString().match( /^(4|5)/ ) && RegExp.$1 ){ + return settings.error( res, req.status, req ); + } + else if (settings.success) { + return settings.success( res, req.status, req ); + } + } + }; + + if( req.readyState === 4 ){ + return req; + } + + // Send request + if( settings.method === "POST" && params ){ + req.send( params ); + } else { + req.send(); + } + + return req; + }; + + shoestring.ajax.settings = { + success: function(){}, + error: function(){}, + cancel: function(){}, + method: "GET", + async: true, + data: null, + headers: {} + }; + + + + /** + * Helper function wrapping a call to [ajax](ajax.js.html) using the `GET` method. + * + * @param {string} url The url to GET from. + * @param {function} callback Callback to invoke on success. + * @return shoestring + * @this shoestring + */ + shoestring.get = function( url, callback ){ + return shoestring.ajax( url, { success: callback } ); + }; + + + + /** + * Load the HTML response from `url` into the current set of elements. + * + * @param {string} url The url to GET from. + * @param {function} callback Callback to invoke after HTML is inserted. + * @return shoestring + * @this shoestring + */ + shoestring.fn.load = function( url, callback ){ + var self = this, + args = arguments, + intCB = function( data ){ + self.each(function(){ + shoestring( this ).html( data ); + }); + + if( callback ){ + callback.apply( self, args ); + } + }; + + shoestring.ajax( url, { success: intCB } ); + return this; + }; + + + + /** + * Helper function wrapping a call to [ajax](ajax.js.html) using the `POST` method. + * + * @param {string} url The url to POST to. + * @param {object} data The data to send. + * @param {function} callback Callback to invoke on success. + * @return shoestring + * @this shoestring + */ + shoestring.post = function( url, data, callback ){ + return shoestring.ajax( url, { data: data, method: "POST", success: callback } ); + }; + + + + /** + * Iterates over `shoestring` collections. + * + * @param {function} callback The callback to be invoked on each element and index + * @return shoestring + * @this shoestring + */ + shoestring.fn.each = function( callback ){ + return shoestring.each( this, callback ); + }; + + shoestring.each = function( collection, callback ) { + var val; + for( var i = 0, il = collection.length; i < il; i++ ){ + val = callback.call( collection[i], i, collection[i] ); + if( val === false ){ + break; + } + } + + return collection; + }; + + + + /** + * Check for array membership. + * + * @param {object} needle The thing to find. + * @param {object} haystack The thing to find the needle in. + * @return {boolean} + * @this window + */ + shoestring.inArray = function( needle, haystack ){ + var isin = -1; + for( var i = 0, il = haystack.length; i < il; i++ ){ + if( haystack.hasOwnProperty( i ) && haystack[ i ] === needle ){ + isin = i; + } + } + return isin; + }; + + + + /** + * Bind callbacks to be run when the DOM is "ready". + * + * @param {function} fn The callback to be run + * @return shoestring + * @this shoestring + */ + shoestring.ready = function( fn ){ + if( ready && fn ){ + fn.call( doc ); + } + else if( fn ){ + readyQueue.push( fn ); + } + else { + runReady(); + } + + return [doc]; + }; + + // TODO necessary? + shoestring.fn.ready = function( fn ){ + shoestring.ready( fn ); + return this; + }; + + // Empty and exec the ready queue + var ready = false, + readyQueue = [], + runReady = function(){ + if( !ready ){ + while( readyQueue.length ){ + readyQueue.shift().call( doc ); + } + ready = true; + } + }; + + // If DOM is already ready at exec time, depends on the browser. + // From: https://github.com/mobify/mobifyjs/blob/526841be5509e28fc949038021799e4223479f8d/src/capture.js#L128 + if (doc.attachEvent ? doc.readyState === "complete" : doc.readyState !== "loading") { + runReady(); + } else { + doc.addEventListener( "DOMContentLoaded", runReady, false ); + doc.addEventListener( "readystatechange", runReady, false ); + win.addEventListener( "load", runReady, false ); + } + + + + /** + * Checks the current set of elements against the selector, if one matches return `true`. + * + * @param {string} selector The selector to check. + * @return {boolean} + * @this {shoestring} + */ + shoestring.fn.is = function( selector ){ + var ret = false, self = this, parents, check; + + // assume a dom element + if( typeof selector !== "string" ){ + // array-like, ie shoestring objects or element arrays + if( selector.length && selector[0] ){ + check = selector; + } else { + check = [selector]; + } + + return _checkElements(this, check); + } + + parents = this.parent(); + + if( !parents.length ){ + parents = shoestring( doc ); + } + + parents.each(function( i, e ) { + var children; + + children = e.querySelectorAll( selector ); + + ret = _checkElements( self, children ); + }); + + return ret; + }; + + function _checkElements(needles, haystack){ + var ret = false; + + needles.each(function() { + var j = 0; + + while( j < haystack.length ){ + if( this === haystack[j] ){ + ret = true; + } + + j++; + } + }); + + return ret; + } + + + + /** + * Get data attached to the first element or set data values on all elements in the current set. + * + * @param {string} name The data attribute name. + * @param {any} value The value assigned to the data attribute. + * @return {any|shoestring} + * @this shoestring + */ + shoestring.fn.data = function( name, value ){ + if( name !== undefined ){ + if( value !== undefined ){ + return this.each(function(){ + if( !this.shoestringData ){ + this.shoestringData = {}; + } + + this.shoestringData[ name ] = value; + }); + } + else { + if( this[ 0 ] ) { + if( this[ 0 ].shoestringData ) { + return this[ 0 ].shoestringData[ name ]; + } + } + } + } + else { + return this[ 0 ] ? this[ 0 ].shoestringData || {} : undefined; + } + }; + + + /** + * Remove data associated with `name` or all the data, for each element in the current set. + * + * @param {string} name The data attribute name. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeData = function( name ){ + return this.each(function(){ + if( name !== undefined && this.shoestringData ){ + this.shoestringData[ name ] = undefined; + delete this.shoestringData[ name ]; + } else { + this[ 0 ].shoestringData = {}; + } + }); + }; + + + + /** + * An alias for the `shoestring` constructor. + */ + if (typeof(win.$) === "undefined") { win.$ = shoestring; } + + + + /** + * Add a class to each DOM element in the set of elements. + * + * @param {string} className The name of the class to be added. + * @return shoestring + * @this shoestring + */ + shoestring.fn.addClass = function( className ){ + var classes = className.replace(/^\s+|\s+$/g, '').split( " " ); + + return this.each(function(){ + for( var i = 0, il = classes.length; i < il; i++ ){ + if( this.className !== undefined && + (this.className === "" || + !this.className.match( new RegExp( "(^|\\s)" + classes[ i ] + "($|\\s)"))) ){ + this.className += " " + classes[ i ]; + } + } + }); + }; + + + + /** + * Add elements matching the selector to the current set. + * + * @param {string} selector The selector for the elements to add from the DOM + * @return shoestring + * @this shoestring + */ + shoestring.fn.add = function( selector ){ + var ret = []; + this.each(function(){ + ret.push( this ); + }); + + shoestring( selector ).each(function(){ + ret.push( this ); + }); + + return shoestring( ret ); + }; + + + + /** + * Insert an element or HTML string after each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.after = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + if( fragment.length > 1 ){ + fragment = fragment.reverse(); + } + return this.each(function( i ){ + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + var insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ]; + this.parentNode.insertBefore( insertEl, this.nextSibling ); + } + }); + }; + + + + /** + * Insert an element or HTML string as the last child of each element in the set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.append = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + return this.each(function( i ){ + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + this.appendChild( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ] ); + } + }); + }; + + + + /** + * Insert the current set as the last child of the elements matching the selector. + * + * @param {string} selector The selector after which to append the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.appendTo = function( selector ){ + return this.each(function(){ + shoestring( selector ).append( this ); + }); + }; + + + + /** + * Get the value of the first element of the set or set the value of all the elements in the set. + * + * @param {string} name The attribute name. + * @param {string} value The new value for the attribute. + * @return {shoestring|string|undefined} + * @this {shoestring} + */ + shoestring.fn.attr = function( name, value ){ + var nameStr = typeof( name ) === "string"; + + if( value !== undefined || !nameStr ){ + return this.each(function(){ + if( nameStr ){ + this.setAttribute( name, value ); + } else { + for( var i in name ){ + if( name.hasOwnProperty( i ) ){ + this.setAttribute( i, name[ i ] ); + } + } + } + }); + } else { + return this[ 0 ] ? this[ 0 ].getAttribute( name ) : undefined; + } + }; + + + + /** + * Insert an element or HTML string before each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.before = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + return this.each(function( i ){ + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + this.parentNode.insertBefore( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ], this ); + } + }); + }; + + + + /** + * Get the children of the current collection. + * @return shoestring + * @this shoestring + */ + shoestring.fn.children = function(){ + var ret = [], + childs, + j; + this.each(function(){ + childs = this.children; + j = -1; + + while( j++ < childs.length-1 ){ + if( shoestring.inArray( childs[ j ], ret ) === -1 ){ + ret.push( childs[ j ] ); + } + } + }); + return shoestring(ret); + }; + + + + /** + * Clone and return the current set of nodes into a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.clone = function() { + var ret = []; + + this.each(function() { + ret.push( this.cloneNode( true ) ); + }); + + return shoestring( ret ); + }; + + + + /** + * Find an element matching the selector in the set of the current element and its parents. + * + * @param {string} selector The selector used to identify the target element. + * @return shoestring + * @this shoestring + */ + shoestring.fn.closest = function( selector ){ + var ret = []; + + if( !selector ){ + return shoestring( ret ); + } + + this.each(function(){ + var element, $self = shoestring( element = this ); + + if( $self.is(selector) ){ + ret.push( this ); + return; + } + + while( element.parentElement ) { + if( shoestring(element.parentElement).is(selector) ){ + ret.push( element.parentElement ); + break; + } + + element = element.parentElement; + } + }); + + return shoestring( ret ); + }; + + + + shoestring.cssExceptions = { + 'float': [ 'cssFloat' ] + }; + + + + (function() { + var cssExceptions = shoestring.cssExceptions; + + // marginRight instead of margin-right + function convertPropertyName( str ) { + return str.replace( /\-([A-Za-z])/g, function ( match, character ) { + return character.toUpperCase(); + }); + } + + function _getStyle( element, property ) { + return win.getComputedStyle( element, null ).getPropertyValue( property ); + } + + var vendorPrefixes = [ '', '-webkit-', '-ms-', '-moz-', '-o-', '-khtml-' ]; + + /** + * Private function for getting the computed style of an element. + * + * **NOTE** Please use the [css](../css.js.html) method instead. + * + * @method _getStyle + * @param {HTMLElement} element The element we want the style property for. + * @param {string} property The css property we want the style for. + */ + shoestring._getStyle = function( element, property ) { + var convert, value, j, k; + + if( cssExceptions[ property ] ) { + for( j = 0, k = cssExceptions[ property ].length; j < k; j++ ) { + value = _getStyle( element, cssExceptions[ property ][ j ] ); + + if( value ) { + return value; + } + } + } + + for( j = 0, k = vendorPrefixes.length; j < k; j++ ) { + convert = convertPropertyName( vendorPrefixes[ j ] + property ); + + // VendorprefixKeyName || key-name + value = _getStyle( element, convert ); + + if( convert !== property ) { + value = value || _getStyle( element, property ); + } + + if( vendorPrefixes[ j ] ) { + // -vendorprefix-key-name + value = value || _getStyle( element, vendorPrefixes[ j ] + property ); + } + + if( value ) { + return value; + } + } + + return undefined; + }; + })(); + + + + (function() { + var cssExceptions = shoestring.cssExceptions; + + // marginRight instead of margin-right + function convertPropertyName( str ) { + return str.replace( /\-([A-Za-z])/g, function ( match, character ) { + return character.toUpperCase(); + }); + } + + /** + * Private function for setting the style of an element. + * + * **NOTE** Please use the [css](../css.js.html) method instead. + * + * @method _setStyle + * @param {HTMLElement} element The element we want to style. + * @param {string} property The property being used to style the element. + * @param {string} value The css value for the style property. + */ + shoestring._setStyle = function( element, property, value ) { + var convertedProperty = convertPropertyName(property); + + element.style[ property ] = value; + + if( convertedProperty !== property ) { + element.style[ convertedProperty ] = value; + } + + if( cssExceptions[ property ] ) { + for( var j = 0, k = cssExceptions[ property ].length; j -1 ){ + ret.push( this ); + } + } + }); + + return shoestring( ret ); + }; + + + + /** + * Find descendant elements of the current collection. + * + * @param {string} selector The selector used to find the children + * @return shoestring + * @this shoestring + */ + shoestring.fn.find = function( selector ){ + var ret = [], + finds; + this.each(function(){ + finds = this.querySelectorAll( selector ); + + for( var i = 0, il = finds.length; i < il; i++ ){ + ret = ret.concat( finds[i] ); + } + }); + return shoestring( ret ); + }; + + + + /** + * Returns the first element of the set wrapped in a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.first = function(){ + return this.eq( 0 ); + }; + + + + /** + * Returns the raw DOM node at the passed index. + * + * @param {integer} index The index of the element to wrap and return. + * @return {HTMLElement|undefined|array} + * @this shoestring + */ + shoestring.fn.get = function( index ){ + + // return an array of elements if index is undefined + if( index === undefined ){ + var elements = []; + + for( var i = 0; i < this.length; i++ ){ + elements.push( this[ i ] ); + } + + return elements; + } else { + return this[ index ]; + } + }; + + + + /** + * Private function for setting/getting the offset property for height/width. + * + * **NOTE** Please use the [width](width.js.html) or [height](height.js.html) methods instead. + * + * @param {shoestring} set The set of elements. + * @param {string} name The string "height" or "width". + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this window + */ + shoestring._dimension = function( set, name, value ){ + var offsetName; + + if( value === undefined ){ + offsetName = name.replace(/^[a-z]/, function( letter ) { + return letter.toUpperCase(); + }); + + return set[ 0 ][ "offset" + offsetName ]; + } else { + // support integer values as pixels + value = typeof value === "string" ? value : value + "px"; + + return set.each(function(){ + this.style[ name ] = value; + }); + } + }; + + + + /** + * Gets the height value of the first element or sets the height for the whole set. + * + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.height = function( value ){ + return shoestring._dimension( this, "height", value ); + }; + + + + var set = function( html ){ + if( typeof html === "string" || typeof html === "number" ){ + return this.each(function(){ + this.innerHTML = "" + html; + }); + } else { + var h = ""; + if( typeof html.length !== "undefined" ){ + for( var i = 0, l = html.length; i < l; i++ ){ + h += html[i].outerHTML; + } + } else { + h = html.outerHTML; + } + return this.each(function(){ + this.innerHTML = h; + }); + } + }; + /** + * Gets or sets the `innerHTML` from all the elements in the set. + * + * @param {string|undefined} html The html to assign + * @return {string|shoestring} + * @this shoestring + */ + shoestring.fn.html = function( html ){ + if( typeof html !== "undefined" ){ + return set.call( this, html ); + } else { // get + var pile = ""; + + this.each(function(){ + pile += this.innerHTML; + }); + + return pile; + } + }; + + + + (function() { + function _getIndex( set, test ) { + var i, result, element; + + for( i = result = 0; i < set.length; i++ ) { + element = set.item ? set.item(i) : set[i]; + + if( test(element) ){ + return result; + } + + // ignore text nodes, etc + // NOTE may need to be more permissive + if( element.nodeType === 1 ){ + result++; + } + } + + return -1; + } + + /** + * Find the index in the current set for the passed selector. + * Without a selector it returns the index of the first node within the array of its siblings. + * + * @param {string|undefined} selector The selector used to search for the index. + * @return {integer} + * @this {shoestring} + */ + shoestring.fn.index = function( selector ){ + var self, children; + + self = this; + + // no arg? check the children, otherwise check each element that matches + if( selector === undefined ){ + children = ( ( this[ 0 ] && this[0].parentNode ) || doc.documentElement).childNodes; + + // check if the element matches the first of the set + return _getIndex(children, function( element ) { + return self[0] === element; + }); + } else { + + // check if the element matches the first selected node from the parent + return _getIndex(self, function( element ) { + return element === (shoestring( selector, element.parentNode )[ 0 ]); + }); + } + }; + })(); + + + + /** + * Insert the current set after the elements matching the selector. + * + * @param {string} selector The selector after which to insert the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.insertAfter = function( selector ){ + return this.each(function(){ + shoestring( selector ).after( this ); + }); + }; + + + + /** + * Insert the current set before the elements matching the selector. + * + * @param {string} selector The selector before which to insert the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.insertBefore = function( selector ){ + return this.each(function(){ + shoestring( selector ).before( this ); + }); + }; + + + + /** + * Returns the last element of the set wrapped in a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.last = function(){ + return this.eq( this.length - 1 ); + }; + + + + /** + * Returns a `shoestring` object with the set of siblings of each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.next = function(){ + + var result = []; + + // TODO need to implement map + this.each(function() { + var children, item, found; + + // get the child nodes for this member of the set + children = shoestring( this.parentNode )[0].childNodes; + + for( var i = 0; i < children.length; i++ ){ + item = children.item( i ); + + // found the item we needed (found) which means current item value is + // the next node in the list, as long as it's viable grab it + // NOTE may need to be more permissive + if( found && item.nodeType === 1 ){ + result.push( item ); + break; + } + + // find the current item and mark it as found + if( item === this ){ + found = true; + } + } + }); + + return shoestring( result ); + }; + + + + /** + * Removes elements from the current set. + * + * @param {string} selector The selector to use when removing the elements. + * @return shoestring + * @this shoestring + */ + shoestring.fn.not = function( selector ){ + var ret = []; + + this.each(function(){ + var found = shoestring( selector, this.parentNode ); + + if( shoestring.inArray(this, found) === -1 ){ + ret.push( this ); + } + }); + + return shoestring( ret ); + }; + + + + /** + * Returns an object with the `top` and `left` properties corresponging to the first elements offsets. + * + * @return object + * @this shoestring + */ + shoestring.fn.offset = function(){ + return { + top: this[ 0 ].offsetTop, + left: this[ 0 ].offsetLeft + }; + }; + + + + /** + * Returns the set of first parents for each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.parent = function(){ + var ret = [], + parent; + + this.each(function(){ + // no parent node, assume top level + // jQuery parent: return the document object for or the parent node if it exists + parent = (this === doc.documentElement ? doc : this.parentNode); + + // if there is a parent and it's not a document fragment + if( parent && parent.nodeType !== 11 ){ + ret.push( parent ); + } + }); + + return shoestring(ret); + }; + + + + /** + * Returns the set of all parents matching the selector if provided for each element in the current set. + * + * @param {string} selector The selector to check the parents with. + * @return shoestring + * @this shoestring + */ + shoestring.fn.parents = function( selector ){ + var ret = []; + + this.each(function(){ + var curr = this, match; + + while( curr.parentElement && !match ){ + curr = curr.parentElement; + + if( selector ){ + if( curr === shoestring( selector )[0] ){ + match = true; + + if( shoestring.inArray( curr, ret ) === -1 ){ + ret.push( curr ); + } + } + } else { + if( shoestring.inArray( curr, ret ) === -1 ){ + ret.push( curr ); + } + } + } + }); + + return shoestring(ret); + }; + + + + /** + * Add an HTML string or element before the children of each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML string or element to add. + * @return shoestring + * @this shoestring + */ + shoestring.fn.prepend = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + return this.each(function( i ){ + + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + var insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ]; + if ( this.firstChild ){ + this.insertBefore( insertEl, this.firstChild ); + } else { + this.appendChild( insertEl ); + } + } + }); + }; + + + + /** + * Add each element of the current set before the children of the selected elements. + * + * @param {string} selector The selector for the elements to add the current set to.. + * @return shoestring + * @this shoestring + */ + shoestring.fn.prependTo = function( selector ){ + return this.each(function(){ + shoestring( selector ).prepend( this ); + }); + }; + + + + /** + * Returns a `shoestring` object with the set of *one* siblingx before each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.prev = function(){ + + var result = []; + + // TODO need to implement map + this.each(function() { + var children, item, found; + + // get the child nodes for this member of the set + children = shoestring( this.parentNode )[0].childNodes; + + for( var i = children.length -1; i >= 0; i-- ){ + item = children.item( i ); + + // found the item we needed (found) which means current item value is + // the next node in the list, as long as it's viable grab it + // NOTE may need to be more permissive + if( found && item.nodeType === 1 ){ + result.push( item ); + break; + } + + // find the current item and mark it as found + if( item === this ){ + found = true; + } + } + }); + + return shoestring( result ); + }; + + + + /** + * Returns a `shoestring` object with the set of *all* siblings before each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.prevAll = function(){ + + var result = []; + + this.each(function() { + var $previous = shoestring( this ).prev(); + + while( $previous.length ){ + result.push( $previous[0] ); + $previous = $previous.prev(); + } + }); + + return shoestring( result ); + }; + + + + // Property normalization, a subset taken from jQuery src + shoestring.propFix = { + "class": "className", + contenteditable: "contentEditable", + "for": "htmlFor", + readonly: "readOnly", + tabindex: "tabIndex" + }; + + + + /** + * Gets the property value from the first element or sets the property value on all elements of the currrent set. + * + * @param {string} name The property name. + * @param {any} value The property value. + * @return {any|shoestring} + * @this shoestring + */ + shoestring.fn.prop = function( name, value ){ + if( !this[0] ){ + return; + } + + name = shoestring.propFix[ name ] || name; + + if( value !== undefined ){ + return this.each(function(){ + this[ name ] = value; + }); + } else { + return this[ 0 ][ name ]; + } + }; + + + + /** + * Remove an attribute from each element in the current set. + * + * @param {string} name The name of the attribute. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeAttr = function( name ){ + return this.each(function(){ + this.removeAttribute( name ); + }); + }; + + + + /** + * Remove a class from each DOM element in the set of elements. + * + * @param {string} className The name of the class to be removed. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeClass = function( cname ){ + var classes = cname.replace(/^\s+|\s+$/g, '').split( " " ); + + return this.each(function(){ + var newClassName, regex; + + for( var i = 0, il = classes.length; i < il; i++ ){ + if( this.className !== undefined ){ + regex = new RegExp( "(^|\\s)" + classes[ i ] + "($|\\s)", "gmi" ); + newClassName = this.className.replace( regex, " " ); + + this.className = newClassName.replace(/^\s+|\s+$/g, ''); + } + } + }); + }; + + + + /** + * Remove the current set of elements from the DOM. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.remove = function(){ + return this.each(function(){ + if( this.parentNode ) { + this.parentNode.removeChild( this ); + } + }); + }; + + + + /** + * Remove a proprety from each element in the current set. + * + * @param {string} name The name of the property. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeProp = function( property ){ + var name = shoestring.propFix[ property ] || property; + + return this.each(function(){ + this[ name ] = undefined; + delete this[ name ]; + }); + }; + + + + /** + * Replace each element in the current set with that argument HTML string or HTMLElement. + * + * @param {string|HTMLElement} fragment The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.replaceWith = function( fragment ){ + if( typeof( fragment ) === "string" ){ + fragment = shoestring( fragment ); + } + + var ret = []; + + if( fragment.length > 1 ){ + fragment = fragment.reverse(); + } + this.each(function( i ){ + var clone = this.cloneNode( true ), + insertEl; + ret.push( clone ); + + // If there is no parentNode, this is pointless, drop it. + if( !this.parentNode ){ return; } + + if( fragment.length === 1 ){ + insertEl = i > 0 ? fragment[ 0 ].cloneNode( true ) : fragment[ 0 ]; + this.parentNode.replaceChild( insertEl, this ); + } else { + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ]; + this.parentNode.insertBefore( insertEl, this.nextSibling ); + } + this.parentNode.removeChild( this ); + } + }); + + return shoestring( ret ); + }; + + + + shoestring.inputTypes = [ + "text", + "hidden", + "password", + "color", + "date", + "datetime", + // "datetime\-local" matched by datetime + "email", + "month", + "number", + "range", + "search", + "tel", + "time", + "url", + "week" + ]; + + shoestring.inputTypeTest = new RegExp( shoestring.inputTypes.join( "|" ) ); + + + /** + * Serialize child input element values into an object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.serialize = function(){ + var data = {}; + + shoestring( "input, select", this ).each(function(){ + var type = this.type, name = this.name, value = this.value; + + if( shoestring.inputTypeTest.test( type ) || + ( type === "checkbox" || type === "radio" ) && + this.checked ){ + + data[ name ] = value; + } else if( this.nodeName === "SELECT" ){ + data[ name ] = this.options[ this.selectedIndex ].nodeValue; + } + }); + + return data; + }; + + + + /** + * Get all of the sibling elements for each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.siblings = function(){ + + if( !this.length ) { + return shoestring( [] ); + } + + var sibs = [], el = this[ 0 ].parentNode.firstChild; + + do { + if( el.nodeType === 1 && el !== this[ 0 ] ) { + sibs.push( el ); + } + + el = el.nextSibling; + } while( el ); + + return shoestring( sibs ); + }; + + + + var getText = function( elem ){ + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; + }; + + /** + * Recursively retrieve the text content of the each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.text = function() { + + return getText( this ); + }; + + + + + /** + * Get the value of the first element or set the value of all elements in the current set. + * + * @param {string} value The value to set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.val = function( value ){ + var el; + if( value !== undefined ){ + return this.each(function(){ + if( this.tagName === "SELECT" ){ + var optionSet, option, + options = this.options, + values = [], + i = options.length, + newIndex; + + values[0] = value; + while ( i-- ) { + option = options[ i ]; + if ( (option.selected = shoestring.inArray( option.value, values ) >= 0) ) { + optionSet = true; + newIndex = i; + } + } + // force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + this.selectedIndex = -1; + } else { + this.selectedIndex = newIndex; + } + } else { + this.value = value; + } + }); + } else { + el = this[0]; + + if( el.tagName === "SELECT" ){ + if( el.selectedIndex < 0 ){ return ""; } + return el.options[ el.selectedIndex ].value; + } else { + return el.value; + } + } + }; + + + + /** + * Gets the width value of the first element or sets the width for the whole set. + * + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.width = function( value ){ + return shoestring._dimension( this, "width", value ); + }; + + + + /** + * Wraps the child elements in the provided HTML. + * + * @param {string} html The wrapping HTML. + * @return shoestring + * @this shoestring + */ + shoestring.fn.wrapInner = function( html ){ + return this.each(function(){ + var inH = this.innerHTML; + + this.innerHTML = ""; + shoestring( this ).append( shoestring( html ).html( inH ) ); + }); + }; + + + + function initEventCache( el, evt ) { + if ( !el.shoestringData ) { + el.shoestringData = {}; + } + if ( !el.shoestringData.events ) { + el.shoestringData.events = {}; + } + if ( !el.shoestringData.loop ) { + el.shoestringData.loop = {}; + } + if ( !el.shoestringData.events[ evt ] ) { + el.shoestringData.events[ evt ] = []; + } + } + + function addToEventCache( el, evt, eventInfo ) { + var obj = {}; + obj.isCustomEvent = eventInfo.isCustomEvent; + obj.callback = eventInfo.callfunc; + obj.originalCallback = eventInfo.originalCallback; + obj.namespace = eventInfo.namespace; + + el.shoestringData.events[ evt ].push( obj ); + + if( eventInfo.customEventLoop ) { + el.shoestringData.loop[ evt ] = eventInfo.customEventLoop; + } + } + + /** + * Bind a callback to an event for the currrent set of elements. + * + * @param {string} evt The event(s) to watch for. + * @param {object,function} data Data to be included with each event or the callback. + * @param {function} originalCallback Callback to be invoked when data is define.d. + * @return shoestring + * @this shoestring + */ + shoestring.fn.bind = function( evt, data, originalCallback ){ + + if( typeof data === "function" ){ + originalCallback = data; + data = null; + } + + var evts = evt.split( " " ); + + // NOTE the `triggeredElement` is purely for custom events from IE + function encasedCallback( e, namespace, triggeredElement ){ + var result; + + if( e._namespace && e._namespace !== namespace ) { + return; + } + + e.data = data; + e.namespace = e._namespace; + + var returnTrue = function(){ + return true; + }; + + e.isDefaultPrevented = function(){ + return false; + }; + + var originalPreventDefault = e.preventDefault; + var preventDefaultConstructor = function(){ + if( originalPreventDefault ) { + return function(){ + e.isDefaultPrevented = returnTrue; + originalPreventDefault.call(e); + }; + } else { + return function(){ + e.isDefaultPrevented = returnTrue; + e.returnValue = false; + }; + } + }; + + // thanks https://github.com/jonathantneal/EventListener + e.target = triggeredElement || e.target || e.srcElement; + e.preventDefault = preventDefaultConstructor(); + e.stopPropagation = e.stopPropagation || function () { + e.cancelBubble = true; + }; + + result = originalCallback.apply(this, [ e ].concat( e._args ) ); + + if( result === false ){ + e.preventDefault(); + e.stopPropagation(); + } + + return result; + } + + return this.each(function(){ + var domEventCallback, + customEventCallback, + customEventLoop, + oEl = this; + + for( var i = 0, il = evts.length; i < il; i++ ){ + var split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + domEventCallback = function( originalEvent ) { + if( oEl.ssEventTrigger ) { + originalEvent._namespace = oEl.ssEventTrigger._namespace; + originalEvent._args = oEl.ssEventTrigger._args; + + oEl.ssEventTrigger = null; + } + return encasedCallback.call( oEl, originalEvent, namespace ); + }; + customEventCallback = null; + customEventLoop = null; + + initEventCache( this, evt ); + + this.addEventListener( evt, domEventCallback, false ); + + addToEventCache( this, evt, { + callfunc: customEventCallback || domEventCallback, + isCustomEvent: !!customEventCallback, + customEventLoop: customEventLoop, + originalCallback: originalCallback, + namespace: namespace + }); + } + }); + }; + + shoestring.fn.on = shoestring.fn.bind; + + + + + /** + * Unbind a previous bound callback for an event. + * + * @param {string} event The event(s) the callback was bound to.. + * @param {function} callback Callback to unbind. + * @return shoestring + * @this shoestring + */ + shoestring.fn.unbind = function( event, callback ){ + + + var evts = event ? event.split( " " ) : []; + + return this.each(function(){ + if( !this.shoestringData || !this.shoestringData.events ) { + return; + } + + if( !evts.length ) { + unbindAll.call( this ); + } else { + var split, evt, namespace; + for( var i = 0, il = evts.length; i < il; i++ ){ + split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + if( evt ) { + unbind.call( this, evt, namespace, callback ); + } else { + unbindAll.call( this, namespace, callback ); + } + } + } + }); + }; + + function unbind( evt, namespace, callback ) { + var bound = this.shoestringData.events[ evt ]; + if( !(bound && bound.length) ) { + return; + } + + var matched = [], j, jl; + for( j = 0, jl = bound.length; j < jl; j++ ) { + if( !namespace || namespace === bound[ j ].namespace ) { + if( callback === undefined || callback === bound[ j ].originalCallback ) { + this.removeEventListener( evt, bound[ j ].callback, false ); + matched.push( j ); + } + } + } + + for( j = 0, jl = matched.length; j < jl; j++ ) { + this.shoestringData.events[ evt ].splice( j, 1 ); + } + } + + function unbindAll( namespace, callback ) { + for( var evtKey in this.shoestringData.events ) { + unbind.call( this, evtKey, namespace, callback ); + } + } + + shoestring.fn.off = shoestring.fn.unbind; + + + /** + * Bind a callback to an event for the currrent set of elements, unbind after one occurence. + * + * @param {string} event The event(s) to watch for. + * @param {function} callback Callback to invoke on the event. + * @return shoestring + * @this shoestring + */ + shoestring.fn.one = function( event, callback ){ + var evts = event.split( " " ); + + return this.each(function(){ + var thisevt, cbs = {}, $t = shoestring( this ); + + for( var i = 0, il = evts.length; i < il; i++ ){ + thisevt = evts[ i ]; + + cbs[ thisevt ] = function( e ){ + var $t = shoestring( this ); + + for( var j in cbs ) { + $t.unbind( j, cbs[ j ] ); + } + + return callback.apply( this, [ e ].concat( e._args ) ); + }; + + $t.bind( thisevt, cbs[ thisevt ] ); + } + }); + }; + + + + /** + * Trigger an event on the first element in the set, no bubbling, no defaults. + * + * @param {string} event The event(s) to trigger. + * @param {object} args Arguments to append to callback invocations. + * @return shoestring + * @this shoestring + */ + shoestring.fn.triggerHandler = function( event, args ){ + var e = event.split( " " )[ 0 ], + el = this[ 0 ], + ret; + + // See this.fireEvent( 'on' + evts[ i ], document.createEventObject() ); instead of click() etc in trigger. + if( doc.createEvent && el.shoestringData && el.shoestringData.events && el.shoestringData.events[ e ] ){ + var bindings = el.shoestringData.events[ e ]; + for (var i in bindings ){ + if( bindings.hasOwnProperty( i ) ){ + event = doc.createEvent( "Event" ); + event.initEvent( e, true, true ); + event._args = args; + args.unshift( event ); + + ret = bindings[ i ].originalCallback.apply( event.target, args ); + } + } + } + + return ret; + }; + + + + /** + * Trigger an event on each of the DOM elements in the current set. + * + * @param {string} event The event(s) to trigger. + * @param {object} args Arguments to append to callback invocations. + * @return shoestring + * @this shoestring + */ + shoestring.fn.trigger = function( event, args ){ + var evts = event.split( " " ); + + return this.each(function(){ + var split, evt, namespace; + for( var i = 0, il = evts.length; i < il; i++ ){ + split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + if( evt === "click" ){ + if( this.tagName === "INPUT" && this.type === "checkbox" && this.click ){ + this.click(); + return false; + } + } + + if( doc.createEvent ){ + var event = doc.createEvent( "Event" ); + event.initEvent( evt, true, true ); + event._args = args; + event._namespace = namespace; + + this.dispatchEvent( event ); + } + } + }); + }; + + + + return shoestring; +})); + +if( !window.jQuery ){ + window.jQuery = window.jQuery || window.shoestring; +} + +(function(exports){ + /** + * The Akamai component namespace contains the {@link Akamai.Viewer}, {@link + * Akamai.Spin360}, {@link Akamai.Carousel}, and {@link Akamai.Zoom} components. + * The {@link Akamai.Viewer} component manages the other components depending on + * the state of the markup it is provided, start there for more. + * + * @example On-ready automatic initialization with jQuery + * $(function(){ + * $( "[data-akamai-viewer]" ).each(function(i, element){ + * var viewer = Akamai.Viewer( element ); + * ... + * // example API use, stoping autoration of first {@link Akamai.Spin360} + * viewer.getCarousels()[0].getSpin360s()[0].stopSpin(); + * }) + * }) + * + * @namespace Akamai + */ + exports.Akamai = exports.Akamai || {}; + window.componentNamespace = "Akamai"; +})(typeof exports === 'undefined'? window : exports); + +(function( window, $ ) { + var $window, $doc; + + $window = $(window); + $doc = $( document.documentElement ); + + var ns = window.componentNamespace = window.componentNamespace || "FG"; + window[ns] = window[ns] || {}; + + Function.prototype.bind = Function.prototype.bind || function( context ) { + var self = this; + + return function() { + self.apply( context, arguments ); + }; + }; + + var Tau = window[ns].Tau = function( element, options ) { + var startIndex, reducedStepSize; + + this.element = element; + this.options = options || {}; + this.$element = $( element ); + this.$initial = this.$element.find( "img" ); + this.$loading = this.$element.find( ".loading" ); + this.index = 0; + + // frame count by order of precendence + // 1. initial frames when they are specified explicitly + // 2. the data attribute on the initial image + // 3. the configured number of frames + this.frames = + this.$initial.length > 1 ? this.$initial.length : + parseInt( this.$initial.attr("data-frames"), 10 ) || + this.options.frames; + + // grab the user specified step size for when the browser is less-abled + reducedStepSize = parseInt( this.$initial.attr("data-reduced-step-size"), 10 ) || 4; + + // TODO sort out a better qualification for the full set of images? + this.stepSize = window.requestAnimationFrame ? 1 : reducedStepSize; + + // grab the user specified auto start delay + this.autoRotateStartDelay = + (this.options.autoplay || {}).delay || + parseInt( this.$initial.attr("data-auto-rotate-delay"), 10 ) || + Tau.autoRotateStartDelay; + + this.mouseMoveBinding = this.rotateEvent.bind(this); + this.touchMoveBinding = this.rotateEvent.bind(this); + + this.path = new Tau.Path(); + + // make sure the initial image stays visible after enhance + this.$initial.first().addClass( "focused" ); + + // hide all other images + this.$element.addClass( "tau-enhanced" ); + + // create a rendering spot to force decoding in IE and prevent blinking + this.$render = $( "
" ) + .css( "position", "absolute" ) + .css( "left", "0" ) + .css( "top", "0" ) + .prependTo( this.element ); + + if( this.options.canvas !== false ){ + this.canvas = $( "").prependTo( this.element )[0]; + + if(this.canvas.getContext ){ + this.canvasCtx = this.canvas.getContext("2d"); + this.$element.addClass( "tau-canvas" ); + + $(window).bind("resize", function(){ + clearTimeout(this.canvasResizeTimeout); + this.canvasResizeTimeout = setTimeout(this.renderCanvas.bind(this), 100); + }.bind(this)); + } + } + + if( this.options.controls ){ + this.options.controls.text = this.options.controls.text || { + play: "Spin Object", + left: "Rotate Left", + right: "Rotate Right" + }; + + this.createControls(); + } + + // create the rest of the images + this.createImages(); + + // set the initial index and image + if( this.options.autoplay && this.options.autoplay.enabled ){ + // start the automatic rotation + this.autostartTimeout = setTimeout(this.autoRotate.bind(this), this.autoRotateStartDelay); + } + + // setup the event bindings for touch drag and mouse drag rotation + this.bind(); + }; + + Tau.autoRotateTraversalTime = 4500; + Tau.autoRotateStartDelay = 100; + Tau.verticalScrollRatio = 4; + // Tau.decelTimeStep = Tau.autoRotateDelay / 2; + // Tau.decel = Tau.decelTimeStep / 8; + Tau.maxVelocity = 60; + + Tau.prototype.createControls = function(){ + this.$controls = $("
"); + + if(this.options.controls.play){ + this.$controls.append(this.controlAnchorMarkup("play")); + } + + if(this.options.controls.arrows){ + this.$controls + .prepend(this.controlAnchorMarkup("left")) + .append(this.controlAnchorMarkup("right")); + } + + this.$controls.bind("mousedown touchstart", this.onControlDown.bind(this)); + this.$controls.bind("mouseup", this.onControlUp.bind(this)); + + // prevent link clicks from bubbling + this.$controls.bind("click", function(event){ + if( $(event.target).is("a") ){ + event.preventDefault(); + } + }); + + this.$element.append(this.$controls); + }; + + Tau.prototype.controlAnchorMarkup = function(name){ + var text = this.options.controls.text[name]; + + return "" + text + + ""; + }; + + Tau.prototype.onControlDown = function(event){ + var $link = $(event.target).closest("a"); + + switch($link.attr("data-tau-controls")){ + case "left": + this.$element.addClass("control-left-down"); + this.stopAutoRotate(); + this.autoRotate(); + break; + case "right": + this.$element.addClass("control-right-down"); + this.stopAutoRotate(); + this.autoRotate(true); + break; + } + }; + + Tau.prototype.onControlUp = function(event){ + var $link = $(event.target).closest("a"); + + switch($link.attr("data-tau-controls")){ + case "left": + case "right": + this.$element.removeClass("control-left-down"); + this.$element.removeClass("control-right-down"); + this.stopAutoRotate(); + break; + case "play": + if( this.autoInterval ){ + this.stopAutoRotate(); + } else { + this.autoRotate(); + } + break; + } + }; + + Tau.prototype.change = function( delta ) { + this.goto( this.options.reverse ? this.index - delta : this.index + delta ); + }; + + Tau.prototype.goto = function( index ) { + var $next, normalizedIndex, imageCount = this.$images.length; + + index = index % imageCount; + + // stay within the bounds of the array + normalizedIndex = (imageCount + index) % imageCount; + + // set the next image that's going to be shown/focused + $next = this.$images.eq( normalizedIndex ); + + // skip this action if the desired image isn't loaded yet + // TODO do something fancier here instead of just throwing up hands + if( !$next[0].tauImageLoaded ) { + this.showLoading(); + return false; + } + + // hide any image that happens to be visible (initial image when canvas) + if( this.$current ) { + this.$current.removeClass( "focused" ); + } else { + this.$images.removeClass( "focused" ); + } + + // record the current focused image and make it visible + this.$current = $next; + + // record the updated index only after advancing is possible + this.index = normalizedIndex; + + if( this.canvasCtx ) { + return this.renderCanvas(); + } else { + // show the new focused image + this.$current.addClass( "focused" ); + return true; + } + }; + + Tau.prototype.renderCanvas = function() { + var $img = this.$current; + var img = $img[0]; + + var width = img.naturalWidth; + var height = img.naturalHeight; + + var parentWidth = this.element.clientWidth; + var calcHeight = (parentWidth/width) * height; + + if(!width || !height || !img.complete){ + return false; + } + + if( this.canvas.width !== parentWidth || + this.canvas.height !== calcHeight || (parentWidth && calcHeight) ) { + this.canvas.width = parentWidth; + this.canvas.height = calcHeight; + } + + this.canvasCtx.drawImage(img, 0, 0, parentWidth, calcHeight); + + return true; + }; + + // TODO transplant the attributes from the initial image + Tau.prototype.createImages = function() { + var src, frames, html, $new, boundImageLoaded; + + // if there are no image elements, raise an exception + if( this.$initial.length < 1 ){ + throw new Error( "At least one image required" ); + } + + this.loadedCount = 0; + + // if there is only one image element, assume it's a template + if( this.$initial.length == 1 ) { + this.markImageLoaded( this.$initial[0] ); + + src = + this.options.template || + this.$initial.attr( "data-src-template" ); + + var imgs = []; + for( var i = this.stepSize + 1; i <= this.frames; i+= this.stepSize ) { + html = ""; + $new = $( html ); + imgs.push($new); + } + + $.each(imgs, function(i, e){ + var $img = $(e); + + $img.bind("load error", function(e){ this.imageLoaded(i, e.target, e); }.bind(this)); + + this.$element.append( $img ); + this.$render.append( $img.html() ); + }.bind(this)); + + // take all the child images and use them as frames of the rotation + this.$images = this.$element.children().filter( "img" ); + this.$current = this.$images; + this.goto(0); + } else { + // take all the child images and use them as frames of the rotation + this.$images = this.$element.children().filter( "img" ); + + this.$images.each(function(i, e){ + // if the image height is greater than zero we assume the image is loaded + // otherwise we bind to onload and pray that we win the race + if( $(e).height() > 0 ){ + this.imageLoaded( i, e ); + } else { + $(e).bind("load error", function(event){ + this.imageLoaded( i, event.target, event ); + }.bind(this)); + } + }.bind(this)); + } + }; + + + Tau.prototype.imageLoaded = function( index, element, event ) { + var initTriggered = false; + this.markImageLoaded( element ); + + // if the isn't going to play automatically and the first image is + // loaded make sure to render it + if( this.$element.find("img")[0] == element && + (!event || event.type !== "error") && + (!this.options.autoplay || !this.options.autoplay.enabled) ){ + this.goto(0); + this.$element.trigger("tau.init"); + initTriggered = true; + } + + this.loadedCount++; + + if( this.loadedCount >= this.frames - 1) { + this.hideLoading(); + + if(!initTriggered) { + this.$element.trigger("tau.init"); + this.initialized = true; + } + } + }; + + Tau.prototype.markImageLoaded = function( element ) { + element.tauImageLoaded = true; + }; + + Tau.prototype.bind = function() { + this.$element.bind( "mousedown touchstart", this.track.bind(this) ); + }; + + Tau.prototype.autoRotate = function( right ) { + // already rotating + if( this.autoInterval ) { + return; + } + + this.$element.addClass("spinning"); + + // move once initially + this.change( right ? -1 : 1 ); + + // move after the interval + this.autoInterval = setInterval(function() { + this.change( right ? -1 : 1 ); + }.bind(this), this.autoRotateDelay() * this.stepSize); + + this.$element.trigger( "tau.auto-rotate-start" ); + }; + + Tau.prototype.autoRotateDelay = function(){ + return (this.options.interval || Tau.autoRotateTraversalTime) / this.frames; + }; + + Tau.prototype.stopAutoRotate = function() { + clearInterval( this.autoInterval ); + clearInterval( this.autostartTimeout ); + this.$element.removeClass("spinning"); + this.autoInterval = undefined; + this.$element.trigger( "tau.auto-rotate-stop" ); + }; + + Tau.prototype.track = function( event ) { + var point; + + // ignore tracking on control clicks + if( $(event.target).closest(".tau-controls").length ){ + return; + } + + // prevent dragging behavior for mousedown + if( event.type === "mousedown" ){ + event.preventDefault(); + } + + if( event.type === "touchstart" ) { + this.$element.trigger("tau.touch-tracking-start"); + } else { + this.$element.trigger("tau.mouse-tracking-start"); + } + + if( this.tracking ) { + return; + } + + $doc.one( "mouseup", this.release.bind(this) ); + $doc.one( "touchend", this.release.bind(this) ); + + this.tracking = true; + + // clean out the path since we'll need a new one for decel + this.path.reset(); + + // show the cursor as grabbing + this.cursorGrab(); + + // By default the number of pixels required to move the carousel by one + // frame is the ratio of the tau element width to the number of frames. That + // is, by default the user should be able to see the full rotation by moving + // their input device from one side of the tau element to the other. + var defaultThreshold = this.$element[0].clientWidth / this.frames ; + + // divide the default by the sensitivity. If the senstivity is greater than + // 1 it will require less effort (smaller distance) to advance the rotation + // by a single slide. If the sensitivity is less than 1 it will require more + // effort + this.rotateThreshold = defaultThreshold / (this.options.sensitivity || 1); + + // record the x for threshold calculations + point = this.getPoint( event ); + this.downX = point.x; + this.downY = point.y; + this.downIndex = this.index; + + $doc.bind( "mousemove", this.mouseMoveBinding ); + $doc.bind( "touchmove", this.touchMoveBinding ); + }; + + Tau.prototype.slow = function() { + // if the path gets broken during the decel just stop + if( !this.path.isSufficient() ) { + this.clearSlowInterval(); + return; + } + + this.rotate({ + x: this.path.last().x + this.velocity, + y: this.path.last().y + }); + + if( this.velocity > 0 ){ + this.velocity = this.velocity - this.decelVal(); + + if( this.velocity <= 0 ){ + this.clearSlowInterval(); + } + } else { + this.velocity = this.velocity + this.decelVal(); + + if( this.velocity >= 0 ){ + this.clearSlowInterval(); + } + } + }; + + Tau.prototype.decelVal = function(){ + return this.decelTimeStep() / 8; + }; + + Tau.prototype.clearSlowInterval = function() { + clearInterval(this.slowInterval); + this.velocity = 0; + this.slowInterval = undefined; + }; + + Tau.prototype.decel = function() { + var velocity, sign; + + // if we don't have two points of mouse or touch tracking this won't work + if( !this.path.isSufficient() ) { + return; + } + + // determine the starting velocity based on the traced path + velocity = this.path.velocity( this.decelTimeStep() ); + + // borrowed from http://stackoverflow.com/questions/7624920/number-sign-in-javascript + sign = velocity > 0 ? 1 : velocity < 0 ? -1 : 0; + + // keep a lid on how fast the rotation spins out + if( Math.abs(velocity) > Tau.maxVelocity ){ + velocity = sign * Tau.maxVelocity; + } + + this.velocity = velocity; + this.slowInterval = setInterval(this.slow.bind(this), this.decelTimeStep()); + }; + + Tau.prototype.decelTimeStep = function(){ + return this.autoRotateDelay() / 2; + }; + + Tau.prototype.release = function( event ) { + if( $(event.target).closest(".tau-controls").length ){ + return; + } + + if( !this.tracking ){ + return; + } + + if( event.type === "touchend" ) { + this.$element.trigger("tau.touch-tracking-stop"); + } else { + this.$element.trigger("tau.mouse-tracking-stop"); + } + + this.decel(); + + this.cursorRelease(); + + // TODO sort out why shoestring borks when unbinding with a string split list + $doc.unbind( "mousemove", this.mouseMoveBinding ); + $doc.unbind( "touchmove", this.touchMoveBinding ); + + this.tracking = false; + }; + + Tau.prototype.cursorGrab = function() { + $doc.addClass( "grabbing" ); + }; + + Tau.prototype.cursorRelease = function() { + $doc.removeClass( "grabbing" ); + }; + + Tau.prototype.showLoading = function() { + this.$loading.attr( "style" , "display: block" ); + }; + + Tau.prototype.hideLoading = function() { + this.$loading.attr( "style" , "display: none" ); + }; + + Tau.prototype.getPoint = function( event ) { + var touch = event.touches || (event.originalEvent && event.originalEvent.touches); + + if( touch ){ + return { + x: touch[0].pageX, + y: touch[0].pageY + }; + } + + return { + x: event.pageX || event.clientX, + y: event.pageY || event.clientY + }; + }; + + Tau.prototype.rotateEvent = function( event ) { + // NOTE it might be better to prevent when the rotation returns anything BUT false + // so that slow drags still get the scroll prevented + if( this.rotate(this.getPoint(event)) ){ + event.preventDefault(); + } + }; + + Tau.prototype.rotate = function( point ) { + var deltaX, deltaY; + + deltaX = point.x - this.downX; + deltaY = point.y - this.downY; + + // if the movement on the Y dominates X then skip and allow scroll + if( Math.abs(deltaY) / Math.abs(deltaX) >= Tau.verticalScrollRatio ) { + return false; + } + + // since we're rotating record the point for decel + this.path.record( point ); + + // NOTE to reverse the spin direction add the delta/thresh to the downIndex + if( Math.abs(deltaX) >= this.rotateThreshold ) { + // NOTE works better on mousedown, here allows autorotate to continue + this.stopAutoRotate(); + + var index; + + if( this.options.reverse ) { + index = this.downIndex + Math.round(deltaX / this.rotateThreshold); + } else { + index = this.downIndex - Math.round(deltaX / this.rotateThreshold); + } + + this.goto( index ); + + return true; + } + }; +})(this, jQuery); + +(function( window, $ ) { + var ns = window.componentNamespace = window.componentNamespace || "FG"; + window[ns] = window[ns] || {}; + + // IE 8 + Date.now = Date.now || function now() { + return new Date().getTime(); + }; + + var Path = window[ns].Tau.Path = function() { + this.reset(); + }; + + Path.prototype.isSufficient = function() { + return !!this.prevPoint && this.prevPrevPoint; + }; + + Path.prototype.distance = function() { + return this.prevPoint.x - this.prevPrevPoint.x; + }; + + Path.prototype.duration = function() { + return this.prevTime - this.prevPrevTime; + }; + + // TODO sort out variable names + Path.prototype.record = function( point ) { + this.prevPrevTime = this.prevTime; + this.prevPrevPoint = this.prevPoint; + + // record the most recent drag point for decel on release + this.prevTime = Date.now(); + this.prevPoint = point; + }; + + Path.prototype.velocity = function( timeStep ) { + var distance, time; + + distance = this.distance(); + time = this.duration(); + + return distance / ( time / timeStep ); + }; + + Path.prototype.reset = function() { + this.prevPoint = undefined; + this.prevTime = undefined; + + this.prevPrevTime = undefined; + this.prevPrevPoint = undefined; + }; + + Path.prototype.last = function() { + return this.prevPoint; + }; +})(this, jQuery); + +(function(exports, $){ + exports.Akamai = exports.Akamai || {}; + var Util = exports.Akamai.Util = {}; + + Util.preflight = function(context, element, options, name){ + name = name || context.constructor.name; + + context._$el = $(element); + + if( !context._$el.length ){ + throw new Error( name + " component requires element" ); + } + + // If the element has already been instantiated with this component, skip + if( context._$el.data(name) ) { + return context._$el.data(name); + } + + // Store the instance for access and to prevent double init above + context._$el.data( name, context ); + + // make sure the element has the data attribute for CSS that keys off it + context._$el.attr(context.constructor._dataAttr, true); + + context._options = + Util.options(context.constructor.defaultOptions || {}, options || {}, context._$el, name); + + // retain the options decided at instantiation for reseting the options + // when the breakpoint options don't apply (are "backed out") + context._originalOptions = Util.extend(true, {}, context._options); + + // map all the child component events to the wrapper component events + Util.mapEvents(context); + + return false; + }; + + Util.component = function(name, async, init){ + if( !init ){ + init = async; + async = false; + } + + var constr = function(element, options){ + // do the preflight + var existing = Util.preflight(this, element, options, name); + + // return on double init + if( existing ){ + this._trigger("init"); + return existing; + } + + // call the init code + init.apply(this, arguments); + + // trigger the init event + if( !async ) { this._trigger("init"); } + }; + + constr.name = name; + + constr._dataAttr = `data-akamai-${name.toLowerCase()}`; + + // static constructor for many possible objects + constr.createMany = Util.createMany(constr); + + // component prefixed event trigger + constr.prototype._trigger = Util.trigger(name); + + return constr; + }; + + Util.mapEvents = function(context){ + var mapping = context.constructor._componentEventMapping || {}; + + for( var event in mapping) { + if( mapping.hasOwnProperty(event) ){ + var mappedTo = mapping[event].to || mapping[event]; + var $el = + mapping[event].selector ? context._$el.find(mapping[event].selector) : undefined; + + Util.mapEvent(context, event, mappedTo, $el); + } + } + }; + + Util.mapEvent = function(context, from, to, $el){ + ($el || context._$el).bind(from, function(){ + context._trigger(to); + }.bind(context)); + }; + + Util.createMany = function(constructor, selector){ + selector = selector || `[${constructor._dataAttr}]`; + + return function(element, options){ + var $comps = $(element).find( selector ); + + // sigh, shoestring needs map + var objects = []; + + $comps.each(function(i, el){ + objects.push(new constructor(el, options)); + }); + + return objects; + }; + }; + + /** + * Helper for building assertions into methods based on property requirements + * @private + * @static + * @param {prop} prop - the property required for the function to operate correctly + * @param {fn} function - the function definition + */ + Util.propRequired = function(prop, msg, fn){ + return function(){ + var args = arguments; + + if( !this[prop] ){ + throw new Error( msg ); + } + + return fn.apply(this, args); + }; + }; + + Util.trigger = function(componentName){ + componentName = componentName.toLowerCase(); + return function(event) { + var fullEvent = "akamai-" + componentName + "-" + event; + + // NOTE we assume here that if jQuery is present we will only want to trigger + // with jQuery and that we are using the same DOM lib across the library + this._$el.trigger(fullEvent); + }; + }; + + + Util.camelToKabob = function(string){ + return string.replace(/([A-Z]+)/g, function(m){ + return "-" + m.toLowerCase(); + }); + }; + + Util.options = function(defaults, options, $element, name){ + var dataAttrOptions = {}; + // make sure to make a new copy of the defaults so we don't mess with the original + var cloneDefaults = Util.extend(true, {}, defaults); + + if( $element && name ) { + dataAttrOptions = + Util.getDataAttrOptions($element, defaults, "data-akamai-" + name.toLowerCase()); + } + + // use the following precedence, prefering settings further to the right + // defaults -> options -> datattributes + var finalOptions = + Util.extend(true, Util.extend(true, cloneDefaults, dataAttrOptions), options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + if( $element && name ){ + Util.setDataAttrOptions($element, defaults, finalOptions, name); + } + + return finalOptions; + }; + + Util.getDataAttrOptions = function($element, defaults, prefix){ + var dataAttrOptions = {}; + + for( var prop in defaults ) { + if( defaults.hasOwnProperty(prop) ){ + var dashedProp = Util.camelToKabob(prop); + var currentDataProp = prefix + "-" + dashedProp; + + if( /object/.test(typeof defaults[prop]) && !(defaults[prop] instanceof Array)){ + var subDefaults = defaults[prop]; + var newPrefix = currentDataProp; + + var subConfig = Util.getDataAttrOptions($element, subDefaults, newPrefix); + dataAttrOptions[prop] = subConfig; + } else { + var value = $element.attr(currentDataProp); + value = Util.coerceAttrVal(value); + + if( value !== undefined && value !== null ){ + // set the config + dataAttrOptions[prop] = value; + } + } + } + } + + return dataAttrOptions; + }; + + Util.setDataAttrOptions = function($element, defaults, options, name){ + Util.setDataAttrOptionsPrefix($element, defaults, options, "data-akamai-" + name.toLowerCase()); + }; + + + Util.setDataAttrOptionsPrefix = function($element, defaults, options, prefix){ + defaults = defaults || {}; + + for( var prop in options ) { + if( options.hasOwnProperty(prop) && prop !== "breakpoints" ){ + var dashedProp = Util.camelToKabob(prop); + var currentDataProp = prefix + "-" + dashedProp; + + if( /object/.test(typeof options[prop]) ){ + var subOptions = options[prop]; + var newPrefix = currentDataProp; + + Util.setDataAttrOptionsPrefix($element, defaults[prop], subOptions, newPrefix); + } else { + + // if the $element had the attribute already or the property exists + // as one of the defaults then we can write it to the element. + if( $element.attr(currentDataProp) || defaults[prop] !== undefined ) { + $element.attr(currentDataProp, options[prop]); + } + } + } + } + }; + + Util.coerceAttrVal = function(value){ + if( value === "true" ) { return true; } + if( value === "false" ) { return false; } + if( /^[0-9]+$/.test(value) ) { return parseInt(value, 10); } + if( /^[0-9]+.[0-9]+$/.test(value) ) { return parseFloat(value, 10); } + + return value; + }; + + // Due to jQuery, but with less argument handling + Util.extend = function( deep, target, options ) { + var name, src, copy, copyIsArray, clone; + + // Only deal with non-null/undefined values + if ( options != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && Util.isPlainObject( copy ) ) { + + clone = src && Util.isPlainObject( src ) ? src : {}; + + // Never move original objects, clone them + target[ name ] = Util.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + + // Return the modified object + return target; + }; + + // Due to jQuery + Util.isPlainObject = function( obj ) { + var proto, Ctor; + + // taken from https://github.com/jupiter/simple-mock/pull/3/files + if ( !obj || Object.prototype.toString.call(obj) !== "[object Object]" ) { + return false; + } + + return true; + }; + + // Due to jQuery + var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + Util.trim = function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }; + + Util.map = function(array, fn){ + return Util.reduce(array, function(acc, val, i){ + acc.push(fn(val, i)); + return acc; + }, []); + }; + + Util.reduce = function(array, fn, acc){ + for(var i = 0; i < array.length; i++){ + acc = fn(acc, array[i], i); + } + + return acc; + }; + + Util.log = function(msg, type){ + type = type || "log"; + + if( console ){ + console[type](msg); + } + }; + + Util.mapJSONVals = function(obj, callback, key){ + var result = {}; + + if(obj.map){ + return obj.map(function(o){ + return Util.mapJSONVals(o, callback); + }); + } + + if(["string", "number", "boolean"].indexOf( typeof obj ) >= 0 ){ + return callback(obj, key); + } + + for( var prop in obj ){ + if(obj.hasOwnProperty(prop)){ + result[prop] = Util.mapJSONVals(obj[prop], callback, prop); + } + } + + return result; + }; + + Util.escapeJSONVals = function(obj){ + return Util.mapJSONVals(obj, function(val){ + if(typeof val === "string" ) { + return escapeHTML(val); + } + + return val; + }); + }; + + // https://github.com/janl/mustache.js/blob/23beb3a8805c9a857e3ea777431481599fab503e/mustache.js#L60 + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + function escapeHTML(string) { + return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) { + return entityMap[s]; + }); + } +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + var Advanceable = { + extension: { + next: function(callback){ + this.goto(this.getIndex() + 1, function(){ + if( callback ) { callback(); }; + this._trigger("next"); + }.bind(this)); + }, + + previous: function(callback){ + this.goto(this.getIndex() - 1, function(){ + if( callback ) { callback(); }; + this._trigger("previous"); + }.bind(this)); + } + }, + + extend: function(constr) { + if(!constr.prototype.goto || !constr.prototype.getIndex) { + throw new Error("Advanceable mixin requires `goto` and `getIndex` methods"); + } + + $.extend(constr.prototype, this.extension); + } + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Advanceable = Advanceable; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + + +// TODO this should be handled in an image component +(function(exports, $){ + var Sourceable = { + extension: { + _srcArray: function(url, options, includeW ){ + var policy = options.policy || options.thumbnail && options.thumbnail.policy; + var widthParam = options.widthParam; + + return options.widths.map(function(w){ + var src = url + (url.indexOf("?") === -1 ? "?" : "&") + widthParam + "=" + w; + + if( policy ){ + src += "&impolicy=" + policy; + } + + //note src width for srcset (eg ' 500w') + if( includeW ){ + src += " " + w + "w"; + } + + return src; + }); + }, + + _srcset: function(url, options){ + return this._srcArray( url, options, true ).join(", "); + }, + + _fallbackSrc: function(url, options){ + return this._srcArray( url, options )[ 0 ]; + }, + + _largestSrc: function(url, options){ + return this._srcArray( url, options ).pop(); + } + }, + + extendStatic: function(constr) { + Akamai.Util.extend(false, constr, this.extension); + } + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Sourceable = Sourceable; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + + +(function( w, undefined ){ + + // requestAnimationFrame pfill + var raf = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + function( callback ){ + w.setTimeout(callback, 1000 / 60); + }; + })(); + + + + + /* toss scrolls and element with easing + + // elem is the element to scroll + // options hash: + * left is the desired horizontal scroll. Default is "+0". For relative distances, pass a string with "+" or "-" in front. + * top is the desired vertical scroll. Default is "+0". For relative distances, pass a string with "+" or "-" in front. + * duration is the number of milliseconds the throw will take. Default is 100. + * easing is an optional custom easing function. Default is w.toss.easing. Must follow the easing function signature + + */ + w.toss = function( elem, options ){ + toss.tossing( elem, false ); + var i = 0, + sLeft = elem.scrollLeft, + sTop = elem.scrollTop, + // Toss defaults + op = { + top: "+0", + left: "+0", + duration: 200, + easing: toss.easing, + finished: function() {} + }, + endLeft, endTop; + + // Mixin based on predefined defaults + if( options ){ + for( var j in op ){ + if( options[ j ] !== undefined ){ + op[ j ] = options[ j ]; + } + } + } + + // Convert relative values to ints + // First the left val + if( typeof op.left === "string" ){ + op.left = parseFloat( op.left ); + endLeft = op.left + sLeft; + } + else { + endLeft = op.left; + op.left = op.left - sLeft; + } + // Then the top val + if( typeof op.top === "string" ){ + + op.top = parseFloat( op.top ); + endTop = op.top + sTop; + } + else { + endTop = op.top; + op.top = op.top - sTop; + } + + toss.tossing( elem, true ); + var startTime = new Date().getTime(); + var endTime = startTime + op.duration; + var run = function(){ + var curTime = new Date().getTime(); + // if tossing is suddenly not true, return the callback + if( !toss.tossing( elem ) ){ + if( op.finished ){ + op.finished(); + } + } + // if the time is still less than the end of duration, keep scrolling + else if( curTime < endTime ){ + i = ( ( curTime - startTime ) / op.duration ) * op.duration; + elem.scrollLeft = op.easing( i, sLeft, op.left, op.duration ); + elem.scrollTop = op.easing( i, sTop, op.top, op.duration ); + return raf( run ); + } + // if time is up, + else{ + elem.scrollLeft = endLeft; + elem.scrollTop = endTop; + if( op.finished ){ + op.finished(); + } + toss.tossing( elem, false ); + } + }; + + raf( run ); + + // Return the values, post-mixin, with end values specified + return { top: endTop, left: endLeft, duration: op.duration, easing: op.easing }; + }; + + // tossing object keeps track of currently tossing elements. true during a programatic scroll + var tossingElems = {}; + toss.tossing = function( elem, state ){ + if( state !== undefined ){ + tossingElems[ elem ] = state; + } + return tossingElems[ elem ]; + }; + + // Easing can use any of Robert Penner's equations (http://www.robertpenner.com/easing_terms_of_use.html). By default, toss includes ease-out-cubic + // arguments: t = current iteration, b = initial value, c = end value, d = total iterations + // use w.toss.easing to provide a custom function externally, or pass an easing function as a callback to the toss method + toss.easing = function (t, b, c, d) { + return c*((t=t/d-1)*t*t + 1) + b; + }; + + //retain old api + toss.toss = toss; + +})( this ); + +;(function( w, $ ){ + var pluginName = "snapper"; + $.fn[ pluginName ] = function(optionsOrMethod){ + var pluginArgs = arguments; + var scrollListening = true; + + // css snap points feature test. + // even if this test passes, several behaviors will still be polyfilled, such as snapping after resize, and animated advancing of slides with anchor links or next/prev links + var testProp = "scroll-snap-type"; + // test latest spec first. then fallback older + var snapSupported = w.CSS && w.CSS.supports && (w.CSS.supports(testProp, "x mandatory") || w.CSS.supports(testProp, "mandatory") || w.CSS.supports("-webkit-" + testProp, "mandatory") || w.CSS.supports("-ms-" + testProp, "mandatory") ); + + // get the snapper_item elements whose left offsets fall within the scroll pane. Returns a wrapped array. + function itemsAtOffset( elem, offset ){ + var $childNodes = $( elem ).find( "." + pluginName + "_item" ); + var containWidth = $( elem ).width(); + var activeItems = []; + + $childNodes.each(function( i ){ + if( this.offsetLeft >= offset - 5 && this.offsetLeft < offset + containWidth - 5 ){ + activeItems.push( this ); + } + }); + return $( activeItems ); + } + + function outerWidth( $elem ){ + return $elem.width() + parseFloat( $elem.css( "margin-left" ) ) + parseFloat( $elem.css( "margin-right" ) ); + } + + function outerHeight( $elem ){ + return $elem.height() + parseFloat( $elem.css( "margin-bottom" ) ) + parseFloat( $elem.css( "margin-top" ) ); + } + + + // snapEvent dispatches the "snapper.snap" event. + // The snapper_item elements with left offsets that are inside the scroll viewport are listed in an array in the second callback argument's activeSlides property. + // use like this: $( ".snapper" ).bind( "snapper.snap", function( event, data ){ console.log( data.activeSlides ); } ); + function snapEvent( elem, x, prefix ){ + prefix = prefix ? prefix + "-" : ""; + var activeSlides = itemsAtOffset( elem, x ); + $( elem ).trigger( pluginName + "." + prefix + "snap", { activeSlides: activeSlides } ); + } + + // optional: include toss() in your page to get a smooth scroll, otherwise it'll just jump to the slide + function goto( elem, x, nothrow, callback ){ + scrollListening = false; + snapEvent( elem, x ); + + var after = function(){ + elem.scrollLeft = x; + $(elem).closest( "." + pluginName ).removeClass( pluginName + "-looping" ); + $( elem ).trigger( pluginName + ".after-goto", { + activeSlides: itemsAtOffset( elem, x ) + }); + + if( callback ){ callback(); }; + snapEvent( elem, x, "after" ); + scrollListening = true; + }; + + // backport to old toss for compat + if( !w.toss && w.overthrow ){ + w.toss = w.overthrow.toss; + } + if( typeof w.toss !== "undefined" && !nothrow ){ + w.toss( elem, { left: x, finished: after }); + } + else { + elem.scrollLeft = x; + after(); + } + } + + + var result, innerResult; + + // Loop through snapper elements and enhance/bind events + result = this.each(function(){ + if( innerResult !== undefined ){ + return; + } + + var self = this; + var $self = $( self ); + var addNextPrev = $self.is( "[data-" + pluginName + "-nextprev]" ); + var autoTimeout; + var $slider = $( "." + pluginName + "_pane", self ); + var enhancedClass = pluginName + "-enhanced"; + var $itemsContain = $slider.find( "." + pluginName + "_items" ); + var $items = $itemsContain.children(); + $items.addClass( pluginName + "_item" ); + var numItems = $items.length; + var $nav = $( "." + pluginName + "_nav", self ); + var navSelectedClass = pluginName + "_nav_item-selected"; + var useDeepLinking = $self.attr( "data-snapper-deeplinking" ) !== "false"; + + if( typeof optionsOrMethod === "string" ){ + var args = Array.prototype.slice.call(pluginArgs, 1); + var index; + var itemWidth = ( $itemsContain.width() / numItems ); + + switch(optionsOrMethod) { + case "goto": + index = args[0] % numItems; + + // width / items * index to make sure it goes + offset = itemWidth * index; + goto( $slider[ 0 ], offset, false, function(){ + // snap the scroll to the right position + snapScroll(); + + // invoke the callback if it was supplied + if( typeof args[1] === "function" ){ + args[1](); + } + }); + break; + case "getIndex": + // NOTE make the scroll left value large enough to overcome + // subpixel widths + innerResult = Math.floor(($slider[ 0 ].scrollLeft + 1)/ itemWidth); + break; + case "updateWidths": + updateWidths(); + break; + } + + return; + } + + // avoid double enhance activities + if( $self.attr("data-" + pluginName + "-enhanced") ) { + return; + } + + // NOTE all state manipulation has to come after method invocation to + // avoid monkeying with the DOM when it's unwarranted + var $navInner = $nav.find( "." + pluginName + "_nav_inner" ); + if( !$navInner.length ){ + $navInner = $( '
' ).append( $nav.children() ).appendTo( $nav ); + } + + // give the pane a tabindex for arrow key handling + $slider.attr("tabindex", "0"); + + function getAutoplayInterval() { + var autoTiming = $self.attr( "data-autoplay" ) || $self.attr( "data-snapper-autoplay" ); + var parseError = false; + + if( autoTiming ) { + try { + autoTiming = parseInt(autoTiming, 10); + } catch(e) { + parseError = true; + } + + // if NaN or there was an error throw an exception + if( !autoTiming || parseError ) { + var msg = "Snapper: `data-autoplay` must have an natural number value."; + throw new Error(msg); + } + } + + return autoTiming; + } + + // this function updates the widths of the items within the slider, and their container. + // It factors in margins and converts those to values that make sense when all items are placed in a long row + function updateWidths(){ + var itemsContainStyle = $itemsContain.attr( "style" ); + $itemsContain.attr( "style", "" ); + var itemStyle = $items.eq(0).attr( "style" ); + $items.eq(0).attr( "style", "" ); + var sliderWidth = $slider.width(); + var itemWidth = $items.eq(0).width(); + var computed = w.getComputedStyle( $items[ 0 ], null ); + var itemLeftMargin = parseFloat( computed.getPropertyValue( "margin-left" ) ); + var itemRightMargin = parseFloat( computed.getPropertyValue( "margin-right" ) ); + var outerItemWidth = itemWidth + itemLeftMargin + itemRightMargin; + $items.eq(0).attr( "style", itemStyle ); + $itemsContain.attr( "style", itemsContainStyle ); + var parentWidth = numItems / Math.round(sliderWidth / outerItemWidth) * 100; + var iPercentWidth = itemWidth / sliderWidth * 100; + var iPercentRightMargin = itemRightMargin / sliderWidth * 100; + var iPercentLeftMargin = itemLeftMargin / sliderWidth * 100; + var outerPercentWidth = iPercentWidth + iPercentLeftMargin + iPercentRightMargin; + var percentAsWidth = iPercentWidth / outerPercentWidth; + var percentAsRightMargin = iPercentRightMargin / outerPercentWidth; + var percentAsLeftMargin = iPercentLeftMargin / outerPercentWidth; + $itemsContain.css( "width", parentWidth + "%"); + $items.css( "width", 100 / numItems * percentAsWidth + "%" ); + $items.css( "margin-left", 100 / numItems * percentAsLeftMargin + "%" ); + $items.css( "margin-right", 100 / numItems * percentAsRightMargin + "%" ); + } + + updateWidths(); + $( self ).addClass( enhancedClass ); + + // if the nextprev option is set, add the nextprev nav + if( addNextPrev ){ + var $nextprev = $( '' ); + var $nextprevContain = $( ".snapper_nextprev_contain", self ); + if( !$nextprevContain.length ){ + $nextprevContain = $( self ); + } + $nextprev.appendTo( $nextprevContain ); + } + + // This click binding will allow deep-linking to slides without causing the page to scroll to the carousel container + // this also supports click handling for generated next/prev links + $( "a", this ).bind( "click", function( e ){ + clearTimeout(autoTimeout); + var slideID = $( this ).attr( "href" ); + + if( $( this ).is( ".snapper_nextprev_next" ) ){ + e.preventDefault(); + return arrowNavigate( true ); + } + else if( $( this ).is( ".snapper_nextprev_prev" ) ){ + e.preventDefault(); + return arrowNavigate( false ); + } + // internal links to slides + else if( slideID.indexOf( "#" ) === 0 && slideID.length > 1 ){ + e.preventDefault(); + + var $slide = $( slideID, self ); + if( $slide.length ){ + goto( $slider[ 0 ], $slide[ 0 ].offsetLeft ); + if( useDeepLinking && "replaceState" in w.history ){ + w.history.replaceState( {}, document.title, slideID ); + } + } + } + }); + + // arrow key bindings for next/prev + $( this ) + .bind( "keydown", function( e ){ + if( e.keyCode === 37 || e.keyCode === 38 ){ + clearTimeout(autoTimeout); + e.preventDefault(); + e.stopImmediatePropagation(); + arrowNavigate( false ); + } + if( e.keyCode === 39 || e.keyCode === 40 ){ + clearTimeout(autoTimeout); + e.preventDefault(); + e.stopImmediatePropagation(); + arrowNavigate( true ); + } + } ); + + var snapScrollCancelled = false; + + // snap to nearest slide. Useful after a scroll stops, for polyfilling snap points + function snapScroll(){ + if(isTouched){ + snapScrollCancelled = true; + return; + } + + var currScroll = $slider[ 0 ].scrollLeft; + var width = $itemsContain.width(); + var itemWidth = $items[ 1 ] ? $items[ 1 ].offsetLeft : outerWidth( $items.eq( 0 ) ); + var roundedScroll = Math.round(currScroll/itemWidth)*itemWidth; + var maxScroll = width - $slider.width(); + if( roundedScroll > maxScroll ){ + roundedScroll = maxScroll; + } + if( currScroll !== roundedScroll ){ + if( snapSupported ){ + snapEvent( $slider[ 0 ], roundedScroll ); + snapEvent( $slider[ 0 ], roundedScroll, "after" ); + } + else { + goto( $slider[ 0 ], roundedScroll ); + } + } else { + goto( $slider[ 0 ], roundedScroll ); + } + + snapScrollCancelled = false; + } + + // retain snapping on resize (necessary even in scroll-snap supporting browsers currently, unfortunately) + var startSlide; + var afterResize; + function snapStay(){ + var currScroll = $slider[ 0 ].scrollLeft; + var numItems = $items.length; + var width = $itemsContain.width(); + if( startSlide === undefined ){ + startSlide = Math.round( currScroll / width * numItems ); + } + if( afterResize ){ + clearTimeout( afterResize ); + } + afterResize = setTimeout( function(){ + updateWidths(); + goto( $slider[ 0 ], $items[ startSlide ].offsetLeft, true ); + startSlide = afterResize = undefined; + }, 50 ); + } + $( w ).bind( "resize", snapStay ); + + // next/prev links or arrows should loop back to the other end when an extreme is reached + function arrowNavigate( forward ){ + var currScroll = $slider[ 0 ].scrollLeft; + var width = $itemsContain.width(); + var itemWidth = outerWidth( $slider ); + var maxScroll = width - itemWidth - 5; + if( forward ){ + if( currScroll >= maxScroll ){ + $self.addClass( pluginName + "-looping" ); + return first(); + } + else { + return next(); + } + } + else { + if( currScroll === 0 ){ + $self.addClass( pluginName + "-looping" ); + return last(); + } + else { + return prev(); + } + } + } + + // advance slide one full scrollpane's width forward + function next(){ + goto( $slider[ 0 ], $slider[ 0 ].scrollLeft + ( $itemsContain.width() / numItems ), false, function(){ + $slider.trigger( pluginName + ".after-next" ); + }); + } + + // advance slide one full scrollpane's width backwards + function prev(){ + goto( $slider[ 0 ], $slider[ 0 ].scrollLeft - ( $itemsContain.width() / numItems ), false, function(){ + $slider.trigger( pluginName + ".after-prev" ); + }); + } + + // go to first slide + function first(){ + goto( $slider[ 0 ], 0 ); + } + + // go to last slide + function last(){ + goto( $slider[ 0 ], $itemsContain.width() - $slider.width() ); + } + + // update thumbnail state on pane scroll + if( $nav.length ){ + // function for scrolling to the xy of the active thumbnail + function scrollNav(elem, x, y){ + if( typeof w.toss !== "undefined" ){ + w.toss( elem, { left: x, top:y }); + } + else { + elem.scrollLeft = x; + elem.scrollTop = y; + } + } + var lastActiveItem; + function activeItem( force ){ + var currTime = new Date().getTime(); + if( !force && lastActiveItem && currTime - lastActiveItem < 200 ){ + return; + } + lastActiveItem = currTime; + var currScroll = $slider[ 0 ].scrollLeft; + var width = outerWidth( $itemsContain ); + var navWidth = outerWidth( $nav ); + var navHeight = outerHeight( $nav ); + var activeIndex = Math.round( currScroll / width * numItems ) || 0; + var childs = $nav.find( "a" ).removeClass( navSelectedClass ); + var activeChild = childs.eq( activeIndex ).addClass( navSelectedClass ); + + var thumbX = activeChild[ 0 ].offsetLeft - (navWidth/2); + var thumbY = activeChild[ 0 ].offsetTop - (navHeight/2); + + scrollNav( $navInner[ 0 ], thumbX, thumbY ); + } + + // set active item on init + activeItem(); + + $slider.bind( "scroll", activeItem ); + } + + // apply snapping after scroll, in browsers that don't support CSS scroll-snap + var scrollStop; + var scrolling; + var lastScroll = 0; + + + $slider.bind( "scroll", function(e){ + lastScroll = new Date().getTime(); + scrolling = true; + }); + + setInterval(function(){ + if( scrolling && lastScroll <= new Date().getTime() - 150) { + snapScroll(); + if( activeItem ){ + activeItem( true ); + } + scrolling = false; + } + }, 150); + + var isTouched = false; + + // if a touch event is fired on the snapper we know the user is trying to + // interact with it and we should disable the auto play + $slider.bind("touchstart", function(){ + clearTimeout(autoTimeout); + + isTouched = true; + }); + + $slider.bind("touchend", function(){ + isTouched = false; + + if(snapScrollCancelled && !scrolling){ + snapScroll(); + scrolling = false; + } + }); + + // if the `data-autoplay` attribute is assigned a natural number value + // use it to make the slides cycle until there is a user interaction + function autoplay( autoTiming ) { + if( autoTiming ){ + // autoTimeout is cleared in each user interaction binding + autoTimeout = setTimeout(function(){ + var timeout = getAutoplayInterval(); + if( timeout ) { + arrowNavigate(true); + autoplay( timeout ); + } + }, autoTiming); + } + } + + autoplay( getAutoplayInterval() ); + $self.attr("data-" + pluginName + "-enhanced", true); + }); + + return (innerResult !== undefined ? innerResult : result); + }; +}( this, jQuery )); + +;(function( w ){ + var enlarge = function(){ + var $ = w.jQuery; + var pluginName = "enlarge"; + $.fn[ pluginName ] = function( options ){ + var pluginArgs = arguments; + + // options + var o = $(this).data("options") || { + button: true, + hoverZoomWithoutClick: true, + delay: 300, + flyout: { + width: 200, + height: 200 + }, + placement: "inline", + magnification: 3 + }; + + if( typeof options !== "string" ) { + // extend with passed options + o = $.extend( o, options ); + $(this).data("options", o); + } + + var internalResult; + + var result = this.each(function(){ + var $element = $(this); + + var self = this; + var testimg = w.document.createElement( "img" ); + var srcsetSupported = "srcset" in testimg; + var srcsetSizesSupported = srcsetSupported && "sizes" in testimg; + var $anchor = $( this ).find( "a" ); + + if( !$anchor.length ){ + throw new Error(pluginName + ": requires an anchor element with `href` for the enlarged image source"); + } + + // find image within container + var initialImg = $element.find( "img" )[ 0 ]; + var targetImg = initialImg; + var imgOriginalSrc = targetImg.src; + var srcset = $( targetImg ).attr( "srcset" ); + var imgOriginalSizes = $( targetImg ).attr( "sizes" ); + var imgZoomSrc = $anchor[ 0 ].href; + var initialText = $anchor[ 0 ].innerText; + var zoomClass = pluginName + "-zoomed"; + var delayClass = pluginName + "-delay"; + var $contain = $( targetImg ).closest( ".enlarge_contain" ); + var $zoomContain = $contain; + var $parentPane = $( targetImg ).closest( ".enlarge_pane" ) || $element; + + var $zoomParent = $(this).data("zoomParent") || $parentPane; + $(this).data("zoomParent", $zoomParent); + + var zoomed = $element.data("zoomed") || false; + $element.data("zoomed", zoomed); + + $element.data("lockedZoom", $element.data("lockedZoom") || false); + + var lockZoomClass = pluginName + "-locked"; + + if( !$contain.length ){ + throw new Error(pluginName + ": requires an element above the image marked with the class `enlarge_contain`"); + } + + // this allows for methods and changing options in subsequent calls to the plugin + if( typeof options === "string" ) { + var args = Array.prototype.slice.call(pluginArgs, 1); + + switch(options){ + case "in": + if( !$element.data("zoomed") ){ + standardToggleZoom(); + } + break; + case "out": + if( $element.data("zoomed") ){ + standardToggleZoom(); + } + break; + case "isZoomed": + internalResult = $element.data("zoomed"); + break; + case "updateOptions": + $element.data( "updateOptions" )( args[ 0 ] ); + break; + } + return; + } + + // to toggle between inline and flyout modes, we change the elements that + // the targetImg, zoomParent, and zoomContain vars refer to + function updatePlacement(){ + if( o.placement === "inline" ){ + targetImg = initialImg; + $zoomParent = $parentPane; + $element.data("zoomParent", $zoomParent); + $zoomContain = $contain; + } else { + targetImg = $flyout.find( "img" )[ 0 ]; + $zoomParent = $zoomContain = $flyout; + $element.data("zoomParent", $zoomParent); + } + } + + // this positions the loupe or side flyout element either according to mouse/touch coordinates + // or the sides of the viewer specified + function positionFlyout(){ + + // set flyout width and height + $flyout.css({ + "width": o.flyout.width + "px", + "height": o.flyout.height + "px", + top: "", + left: "", + "margin-left": "", + "margin-top": "" + }); + + // set negative left or right value to match width + var flyoutSide = o.placement.match( /left|right/ ); + + if( flyoutSide ){ + $flyout.css( flyoutSide[ 0 ], -o.flyout.width + "px" ); + } + // if loupe mode, center offset + var loupe = o.placement.match( /loupe/ ); + + if( loupe ) { + // loupe + $flyout.css({ + "margin-left": ( -o.flyout.width / 2 ) + "px", + "margin-top": ( -o.flyout.height / 2 ) + "px" + }); + } + + // add class to specify positioning spot for static css to apply + $flyout[ 0 ].className = $flyout[ 0 ].className.replace( /enlarge_flyout\-[^$\s]+/, ' ' ); + $flyout.addClass( "enlarge_flyout-" + o.placement ); + } + + function disable(){ + if(o.disabled){ + $element.addClass("enlarge_disabled"); + } else { + $element.removeClass("enlarge_disabled"); + } + } + + disable(); + + // this allows for subsequent calls to the plugin to pass an updateOptions method and object, + // which will pass through to the existing viewer on that element + $element.data( "updateOptions", function( opts ){ + o = $.extend( o, opts ); + $(this).data("options", o); + + updatePlacement(); + positionFlyout(); + hoverEnabled = o.hoverZoomWithoutClick; + if( o.image && o.image.sizes ){ + imgOriginalSizes = o.image.sizes; + toggleImgSrc(); + } + + disable(); + + if( o.disabled && $element.data("zoomed") ) { + standardToggleZoom(); + } + }); + + // loader div holds a new image while its new source is loading + // we insert this into the dom so that srcset/sizes can calculate a best source + function addLoader(){ + $contain.append( '' ); + } + + // zoom state toggle boolean + function toggleZoomState(){ + zoomed = !$element.data("zoomed"); + $element.data("zoomed", zoomed); + } + + // toggle the image source bigger or smaller + // ideally, this toggles the sizes attribute and allows the browser to select a new source from srcset + // if srcset isn't supported or sizes attribute is not provided, the link href is used for the larger source + function toggleImgSrc(after){ + after = after || function(){}; + + if( !zoomed ){ + targetImg.sizes = imgOriginalSizes; + if( !srcsetSizesSupported ){ + targetImg.src = imgOriginalSrc; + } + after(); + } else { + // if the zooming is disabled do not replace with the larger source + // NOTE we don't prevent switching to the original source because we + // always want to allow the plugin to back out of the zoomed state + // when disabled + if( o.disabled ) { after(); return false; } + + var zoomimg = new Image(); + zoomimg.className = "enlarge_img-loading"; + zoomimg.onload = function(){ + targetImg.sizes = zoomimg.sizes; + if( !srcsetSizesSupported || !srcset ){ + targetImg.src = imgZoomSrc; + } + $( zoomimg ).remove(); + + after(); + }; + + zoomimg.sizes = imgZoomWidth() + "px"; + + if( !srcsetSizesSupported || !srcset ){ + zoomimg.src = imgZoomSrc; + } else if (srcset) { + zoomimg.srcset = srcset; + } + + $( zoomimg ).insertBefore( targetImg ); + } + } + + // scroll to the center of the zoomed image + function scrollToCenter(){ + var pw = $zoomContain.width(); + var ph = $zoomContain.height(); + var w = targetImg.offsetWidth; + var h = targetImg.offsetHeight; + $zoomContain[ 0 ].scrollLeft = ( w / 2 ) - ( pw / 2 ); + $zoomContain[ 0 ].scrollTop = ( h / 2 ) - ( ph / 2 ); + } + + // lock zoom mode allows for scrolling around normally without a cursor-follow behavior + function toggleLockZoom(){ + if( !$element.data("lockedZoom") ){ + // NOTE we allow the image to zoom out if functionality gets disabled + // when it's in a zoomed state + if(o.disabled) { return false; } + + $parentPane.add( $zoomParent ).addClass( lockZoomClass ); + $element.data("lockedZoom", lockedZoom = true); + $zoomContain.attr( "tabindex", "0" ); + $zoomContain[ 0 ].focus(); + } + else { + $parentPane.add( $zoomParent ).removeClass( lockZoomClass ); + $element.data("lockedZoom", lockedZoom = false); + $zoomContain.removeAttr( "tabindex" ); + } + } + + function imgZoomWidth(){ + return $parentPane[0].offsetWidth * o.magnification; + } + + // toggle magnification of image + function toggleImgZoom(){ + if( $element.data("zoomed") ){ + // NOTE we allow the image to zoom out if functionality gets disabled + // when it's in a zoomed state + if(o.disabled) { return false; } + + if( o.placement === "inline" ){ + $contain.add( $parentPane ).css( { "width": $parentPane[0].offsetWidth + "px", "height": parseFloat( getComputedStyle( $parentPane[0] ).height ) + "px" } ); + } + $zoomParent.addClass( zoomClass ); + $( targetImg ).css( "width", imgZoomWidth() + "px" ); + + $(self).trigger( pluginName + ".after-zoom-in"); + } + else{ + $zoomParent.removeClass( zoomClass ); + if( o.placement === "inline" ){ + $contain.add( $parentPane ).css( { "width": "", "height": "" } ); + } + $( targetImg ).css( "width", "" ); + + $(self).trigger( pluginName + ".after-zoom-out"); + } + } + + function forceInlineMode(){ + var oldO = o.placement; + if( oldO !== "inline" ){ + function resetPlacement(){ + o.placement = oldO; + updatePlacement(); + $(self).unbind( pluginName + ".after-zoom-out", resetPlacement ); + } + $(self).bind( pluginName + ".after-zoom-out", resetPlacement ); + o.placement = "inline"; + updatePlacement(); + } + } + + // lock zoom mode toggle + function standardToggleZoom(){ + // NOTE if the current is zoomed out and it's disabled prevent toggling + if(o.disabled && !$element.data("zoomed")) { return false; } + toggleZoomState(); + toggleImgSrc(function(){ + toggleLockZoom(); + toggleImgZoom(); + scrollToCenter(); + }); + } + + var trackingOn; + var trackingTimer; + var mouseEntered; + var touchStarted; + var hoverEnabled = o.hoverZoomWithoutClick; + var lastMouseMove; + + // mouseenter or touchstart handler for dragging image + function startTrackingDelay(e){ + if( e.type === "touchstart" ){ + touchStarted = true; + } + if( touchStarted && e.type === "mouseenter" || + e.type === "mouseenter" && !touchStarted && !hoverEnabled || + $element.data("lockedZoom") || + mouseEntered ){ + return; + } + mouseEntered = true; + $contain.addClass( delayClass ); + trackingTimer = setTimeout( function(){ + $contain.removeClass( delayClass ); + toggleZoomState(); + toggleImgSrc(function(){ + toggleImgZoom(); + trackingOn = true; + if (lastMouseMove) { + scrollWithMouse(lastMouseMove); + } else { + scrollWithMouse(e); + } + }); + }, o.delay ); + } + + // mouseleave or touchend after a drag + function stopTrackingDelay( e ){ + $contain.removeClass( delayClass ); + clearTimeout( trackingTimer ); + trackingOn = false; + if( o.hoverZoomWithoutClick === false && !touchStarted ){ + hoverEnabled = false; + } + if( touchStarted && e.type === "mouseleave" ){ + touchStarted = false; + } + } + + // mousemove or touch-drag image placement + function scrollWithMouse( e ){ + // if tracking's not on yet, ignore. This allows the delay to work + if( trackingOn ){ + // if the move was touch-started, and the event is mousemove, it can be ignored + // (mouse events fire along with touch events and we just want the touch) + if( touchStarted && e.type === "mousemove" ){ + return; + } + // normalize ev to touch or mouse + var ev = e.touches ? e.touches[ 0 ] : e; + e.preventDefault(); + var x = ev.clientX - $contain[ 0 ].getBoundingClientRect().left; + var y = ev.clientY - $contain[ 0 ].getBoundingClientRect().top; + + if( o.placement.match( /loupe/ ) ) { + // offset the loupe a little differently for touch so that it's not directly beneath a finger + var mLeft = ( e.touches ? -o.flyout.width / 1.3 : -o.flyout.width / 2 ) + "px"; + var mTop = ( e.touches ? -o.flyout.height / 1.3 : -o.flyout.height / 2 ) + "px"; + requestAnimationFrame(function(){ + $flyout.css( { + top: y + "px", + left: x + "px", + "margin-left": mLeft, + "margin-top": mTop + } ); + }); + } + + var containWidth = $contain[ 0 ].offsetWidth; + var containHeight = $contain[ 0 ].offsetHeight; + var containScrollWidth = targetImg.offsetWidth; + var containScrollHeight = targetImg.offsetHeight; + var zoomContainWidth = $zoomContain[ 0 ].offsetWidth; + var zoomContainHeight = $zoomContain[ 0 ].offsetHeight; + var widthFactor = containWidth / zoomContainWidth; + var heightFactor = containHeight / zoomContainHeight; + + $zoomContain[ 0 ].scrollLeft = (( x / containWidth ) * ( containScrollWidth - zoomContainWidth )); + $zoomContain[0].scrollLeft += ((x / containWidth) - 0.5) * zoomContainWidth; + $zoomContain[ 0 ].scrollTop = (( y / containHeight ) * ( containScrollHeight - zoomContainHeight )) ; + $zoomContain[0].scrollTop += ((y / containHeight) - 0.5) * zoomContainHeight; + } else { + lastMouseMove = e; + } + } + + // add flyout for flyout and loupe modes + // flyout is always present, yet hidden when inline mode is active + var $flyout = $( '
' ).append( $contain.clone() ); + $flyout.insertAfter( $parentPane ); + + // add loader div + addLoader(); + updatePlacement(); + positionFlyout(); + + // clicking the magnify anchor toggles lock-zoom mode + $anchor + .bind( "keydown", function(e){ + if( e.keyCode === 13 || e.keyCode === 32 ){ + forceInlineMode(); + } + // spacebar triggers click too + if( e.keyCode === 32 ){ + e.preventDefault(); // don't scroll the new focused area + $( this ).trigger( "click" ); + } + }) + .bind( "click", function( e ){ + e.preventDefault(); + standardToggleZoom(); + }); + + // on resize, if in lock zoom mode, un zoom + $( w ) + .bind( "resize", function( e ){ + if( $element.data("lockedZoom") ){ + standardToggleZoom(); + } + }); + + // on click-out on the page, if in locked zoom mode, zoom out + $( w.document ) + .bind( "mouseup", function( e ){ + if( $element.data("lockedZoom") && !$( e.target ).closest( $parentPane ).length ){ + standardToggleZoom(); + } + }); + + // mouse hover and touch-drag gestures for a cursor-tracked zoom behavior + $( initialImg ) + .bind( "mouseenter touchstart", startTrackingDelay ) + .bind( "mousemove touchmove", scrollWithMouse ) + .bind( "mouseleave touchend", function( e ){ + mouseEntered = false; + if( zoomed && !$element.data("lockedZoom") ){ + toggleZoomState(); + toggleImgSrc(function(){ + toggleImgZoom(); + }); + } + stopTrackingDelay( e ); + }) + // tapping the image should trigger a lock zoom + // click will not fire after a touch-drag so it works as a tap for our needs here + .bind( "click", function( e ){ + e.preventDefault(); + // if the click was started with a touchstart event, + // and placement is inline + // toggle the locked zoom mode + if( touchStarted && o.placement === "inline" ){ + standardToggleZoom(); + } + + if( o.hoverZoomWithoutClick === false && !touchStarted ){ + hoverEnabled = !hoverEnabled; + if( hoverEnabled ){ + $( this ).trigger( "mouseenter" ); + } + else { + $( this ).trigger( "mouseleave" ); + } + } + } ); + + // keyboard handling for arrows in zoom mode + $(this).bind( "keydown keyup", function( e ){ + if( e.keyCode === 37 || + e.keyCode === 38 || + e.keyCode === 39 || + e.keyCode === 40 ){ + e.stopImmediatePropagation(); + if( !$element.data("lockedZoom") ){ + e.preventDefault(); + } + } else if( e.type === "keyup" && $(this).data("lockedZoom") && e.keyCode === 27 ){ //esc or backspace closes zoom + standardToggleZoom(); + $anchor[0].focus(); + e.stopImmediatePropagation(); + } + }); + + // on scroll, zoom out + $parentPane.bind( "scroll", function(){ + if( $element.data("zoomed") ){ + toggleZoomState(); + toggleImgSrc(function(){ + if( $element.data("lockedZoom") ){ + toggleLockZoom(); + } + toggleImgZoom(); + }); + } + }); + }); + + return internalResult !== undefined ? internalResult : result; + }; + }; + + if( typeof module !== "undefined" ){ + module.exports = enlarge; + } else { + enlarge(); + } +}( typeof global !== "undefined" ? global : this )); + +(function(exports){ + var Image = { + defaultConfig: { + widths: [ + "320", + "640", + "800", + "1024", + "2048", + "5000" + ], + + sizes: "100vw", + policy: undefined, + widthParam: "imwidth" + } + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Image = Image; +})(typeof exports === 'undefined'? window : exports); + +(function(exports, $){ + /** + * 360 degree viewer + * @class + * @alias Akamai.Spin360 + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Spin360 = Akamai.Util.component("Spin360", function(element, options){ + this._comp = new Akamai.Tau(element, this._options); + }); + + // srcset stuff + Akamai.Sourceable.extendStatic(Spin360); + + // Used in preflight to "rename" events based on the child component events + Spin360._componentEventMapping = { + "tau.auto-rotate-start": "play", + "tau.auto-rotate-stop": "pause" + }; + + Spin360.prototype._updateOptions = function(options){ + if( !options ) { + this._options = this._originalOptions; + } + + this._options = Akamai.Util.extend(true, this._options, options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Spin360.defaultOptions, this._options, "Spin360"); + + // TODO actually update the options + }; + + Spin360._renderImg = function(url, options){ + return ` + + `; + }; + + Spin360.render = function(json, options){ + var finalOptions = Akamai.Util.options(Spin360.defaultOptions, options); + + // TODO fix the string problem in shoestring + return Akamai.Util.trim(` +
+ ${Akamai.Util + .map(json.urls, function(url) { return Spin360._renderImg(url, finalOptions); }) + .join("\n") + } +
+ `); + }; + + /** + * Defines the global default options for all Spin360s on the page + * @static + * @property {Boolean} autoplay.enabled - Enable autoplay (default: false) + * @property {Integer} autoplay.delay - Delay in milliseconds after initialization before spinning begins (default: 1000) + * @property {Boolean} controls.arrows - Render controls to spin left and right (default: false) + * @property {Boolean} controls.play - Render controls to enable and disable automatic spinning (default: false) + * @property {String} controls.text.left - Left rotation control title and text (default: "Rotate Left") + * @property {String} controls.text.right - Right rotation control title and text (default: "Rotate Right") + * @property {String} controls.text.play - Spin control title and text (default: "Spin Object") + * @property {Array} images.widths - list of available widths for an image (to be combined with image.widthParam), Default: ["320","640","800","1024","2048","5000"] + * @property {String} images.sizes - value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw`, `200px`, `(min-width:1000px) 500px, 100vw`. + * @property {String} images.policy - query param value for policy, appended to &policy= per image url when specified. Values: `foo`. Default: undefined. + * @property {String} images.widthParam - query string name to use for setting each url width. Default urls will be ?imwidth=320 for example. Values: `imwidth` (default), `w`, `width`, etc. + * @property {Integer} interval - The full rotation interval in milliseconds, determines physics, (default: 3000) + * @property {Boolean} reverse - Reverse the direction of the spin (default: false) + * @property {Float} sensitivity - The speed at which the object rotates relative to user input (default: 1). At the default value of `1` the object will complete a full 360 rotation when you drag across the entire width of the spin360 component. To require less effort to complete a rotation, change this value to a higher number. For example, setting `sensitivity: 2` would complete a full 360 rotation by dragging halfway (ex. from the center to the edge). + */ + Spin360.defaultOptions = { + autoplay: { + enabled: false, + delay: 1000 + }, + + controls: { + arrows: false, + play: false, + text: { + left: "Rotate Left", + right: "Rotate Right", + play: "Spin Object" + } + }, + + // unsupported, the number of frame images to create using the configured + // template + frames: 72, + images: Akamai.Image.defaultConfig, + interval: 3000, + reverse: false, + + sensitivity: 1, + + // unsupported, template used to generate urls when only one image is + // present in the 360 viewer + template: undefined + }; + + /** + * Goto to a particular frame of the spining image + * @method + * @param {Integer} index - the frame to advance to + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Spin360.prototype.goto = function(index, callback){ + this._comp.goto(index); + // NOTE goto must come before the callback because the callback will be + // used to trigger the `next` and `previous` events. The order should be + // maintained consistently as `goto` -> `next`/`previous` + this._trigger("goto"); + if(callback){ callback(); } + }; + + /** + * Return the current frame index + * @method + * @returns {Integer} + */ + Spin360.prototype.getIndex = function(){ + return this._comp.index; + }; + + // Extend Carousel with Advanceable interface + Akamai.Advanceable.extend(Spin360); + + /** + * Go to the next frame + * @method + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Spin360.prototype.next; + // required for JSDocs + + /** + * Go to the previous frame + * @method + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Spin360.prototype.previous; + // required for JSDocs + + /** + * Begin the automatic rotation of the images + * @todo support passing in an interval or speed? + * @method + * @fires Akamai.Spin360#akamai-spin360-play + * @returns {undefined} + */ + Spin360.prototype.play = function(){ + this._comp.autoRotate(); + }; + + /** + * Stop the automatic rotation of the images + * @method + * @fires Akamai.Spin360#akamai-spin360-pause + * @returns {undefined} + */ + Spin360.prototype.pause = function(){ + this._comp.stopAutoRotate(); + }; + + Spin360.prototype.getElement = function(){ + return this._$el[0]; + }; + + Spin360.States = { + Playing: 0, + Paused: 1 + }; + + /** + * Return the current state of the spin360 + * @example Spin360 states + * Akamai.Spin360.States = { + * Playing: 0, + * Paused: 1 + * }; + * + * @example Conditioning on states + * if( spin360.getState() === Akamai.Spin360.States.Paused ) { + * spin360.play() + * } + * @method + * @returns {Akamai.Spin360.State} + */ + Spin360.prototype.getState = function(){ + // TODO expose using method in Tau + return !!this._comp.autoInterval ? Spin360.States.Playing : Spin360.States.Paused; + }; + + /** + * Triggered when the spin360 has stoped automatically spinning + * {@link Akamai.Spin360#pause}. + * + * @event Akamai.Spin360#akamai-spin360-stop-spin + */ + + /** + * Triggered when the spin360 has started automatically spinning + * {@link Akamai.Spin360#play}. + * + * @event Akamai.Spin360#akamai-spin360-start-spin + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Spin360}. + * + * @event Akamai.Spin360#akamai-spin360-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Spin360 = Spin360; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(){ + function MagnifierImpl(element, options){ + throw new Error('Not allowed to instantiate MagnifierImpl'); + } + + MagnifierImpl.prototype.updateOptions = function(options){ + throw new Error('magnifier updateOptions not implemented'); + }; + + MagnifierImpl.prototype.zoomIn = function(){ + throw new Error('magnifier zoomIn not implemented'); + }; + + MagnifierImpl.prototype.zoomOut = function(){ + throw new Error('magnifier zoomOut not implemented'); + }; + + MagnifierImpl.prototype.isMagnified = function(){ + throw new Error('magnifier isMagnified not implemented'); + }; + + MagnifierImpl.prototype.toggleZoom = function(){ + throw new Error('magnifier toggleZoom not implemented'); + }; + + MagnifierImpl.prototype.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){ + throw new Error('magnifier render not implemented'); + }; + + var ex = (typeof exports === 'undefined') ? window : exports; + ex.Akamai = ex.Akamai || {}; + ex.Akamai.MagnifierImpl = MagnifierImpl; +})(); +(function(){ + function MagnifierImplEnlarge(element, options){ + this._$el = $(element); + this._$el.enlarge(options); + } + + MagnifierImplEnlarge.prototype = Object.create(Akamai.MagnifierImpl.prototype); + MagnifierImplEnlarge.prototype.constructor = MagnifierImplEnlarge; + + MagnifierImplEnlarge.prototype.updateOptions = function(options){ + this._$el.enlarge( "updateOptions", options ); + }; + + MagnifierImplEnlarge.prototype.zoomIn = function(){ + this._$el.enlarge( "in" ); + }; + + MagnifierImplEnlarge.prototype.zoomOut = function(){ + this._$el.enlarge( "out" ); + }; + + MagnifierImplEnlarge.prototype.isMagnified = function(){ + return this._$el.enlarge( "isZoomed" ); + }; + + MagnifierImplEnlarge.prototype.toggleZoom = function(){ + if(this.isMagnified()){ + this.zoomOut(); + } else { + this.zoomIn(); + } + }; + + MagnifierImplEnlarge.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){ + return ` + + `; + }; + + var ex = (typeof exports === 'undefined') ? window : exports; + ex.Akamai = ex.Akamai || {}; + ex.Akamai.MagnifierImplEnlarge = MagnifierImplEnlarge; +})(); +(function(){ + function ClickTracker(element, handler){ + var self = this; + self.element = element; + self.handler = handler; + this.reset(); + } + + ClickTracker.prototype.onDown = function(e){ + if (e.target == this.element) { + if (this.isDown) { + this.reset(); + } else { + this.down.x = e.clientX; + this.down.y = e.clientY; + this.down.timestamp = new Date(); + this.isDown = true; + } + } + }; + + ClickTracker.prototype.onMove = function(){ + this.isMoved = true; + }; + + ClickTracker.prototype.onUp = function(e){ + if (this.isDown) { + this.up.x = e.clientX; + this.up.y = e.clientY; + this.up.timestamp = new Date(); + + if (!this.isMoved && (this.down.x === this.up.x && this.down.y === this.up.y && (this.up.timestamp.getTime() - this.down.timestamp.getTime() <= 500))) { + this.handler(); + } + } + + this.reset(); + }; + + ClickTracker.prototype.reset = function(x, y){ + this.down = { + x: -Infinity, + y: -Infinity, + timestamp: new Date(0) + }; + + this.isDown = false; + this.isMoved = false; + + this.up = { + x: Infinity, + y: Infinity, + timestamp: new Date() + }; + }; + + function MagnifierImplScroller(element, options){ + this._$el = $(element); + this._options = options; + this._container = this._$el.get(0); + this._content = this._$el.children().get(0); + this._imgCast = $(this._content).find('div').get(0); + this._zoomInBtn = this._$el.find("a.scroller-zoom-in").get(0); + this._zoomOutBtn = this._$el.find("a.scroller-zoom-out").get(0); + this._magnification = 1; + + var self = this; + + // Initialize Scroller + this.scroller = new Scroller(this._renderer(), { + zooming: options.enabled, + minZoom: 1, + maxZoom: options.magnification, + animationDuration: options.animationDuration + }); + + var rect = this._container.getBoundingClientRect(); + this.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop); + + this._installEventHandlers(); + this._updateButtonStates(); + setTimeout(function(){ + self._onResize(); + }); + } + + MagnifierImplScroller.prototype = Object.create(Akamai.MagnifierImpl.prototype); + MagnifierImplScroller.prototype.constructor = MagnifierImplScroller; + + MagnifierImplScroller.prototype.updateOptions = function(options){ + var rect = this._container.getBoundingClientRect(); + this.scroller.setPosition(rect.left + this._container.clientLeft, rect.top + this._container.clientTop); + this._onResize(); + this._zoomBy(1 / this._options.magnification, false); + }; + + MagnifierImplScroller.prototype.zoomIn = function(){ + this._zoomBy(this._options.incrementalZoomFactor); + }; + + MagnifierImplScroller.prototype.cyclicZoom = function(){ + var self = this; + if (this._magnification >= this._options.magnification) { + this._zoomBy(1 / this._options.magnification); + } else { + this.zoomIn(); + } + }; + + MagnifierImplScroller.prototype.zoomOut = function(){ + this._zoomBy(1 / this._options.incrementalZoomFactor); + }; + + MagnifierImplScroller.prototype.isMagnified = function(){ + return this._magnification > 1; + }; + + MagnifierImplScroller.prototype.toggleZoom = function(){ + throw new Error('zoom toggle behavior is undefined in scroller mode'); + }; + + MagnifierImplScroller.render = function(options, dataAttr, fallbackSrc, largestSrc, srcSet){ + return ` + + `; + }; + + MagnifierImplScroller.prototype._zoomBy = function(magnification, animate){ + var self = this, magnification = Number(magnification); + + if (typeof(animate) === "undefined") { + animate = this._options.animateZoom; + } + + if (magnification === 1 || // if we are magnifying by 1 OR + (this._magnification === 1 && magnification < 1) || // fully zoomed out and still attempting a zoom out OR + (this._magnification === this._options.magnification && magnification > 1)) { // fully zoomed in and still attempting a zoom in + return; + } + + this.scroller.zoomTo(this._magnification * magnification, this._options.animateZoom); + + setTimeout(function(){ + self._onZoom(); + }, this._options.animationDuration); + } + + MagnifierImplScroller.prototype._onZoom = function(){ + var oldMagnification = this._magnification; + + this._magnification = Number(this.scroller.getValues().zoom.toFixed(2)); + this._updateButtonStates(); + + if (oldMagnification > this._magnification) { + this._$el.trigger("scroller.after-zoom-out"); + } else if (oldMagnification < this._magnification) { + this._$el.trigger("scroller.after-zoom-in"); + } + }; + + MagnifierImplScroller.prototype._renderer = function(){ + var docStyle = document.documentElement.style, self = this; + + var engine; + if (window.opera && Object.prototype.toString.call(opera) === '[object Opera]') { + engine = 'presto'; + } else if ('MozAppearance' in docStyle) { + engine = 'gecko'; + } else if ('WebkitAppearance' in docStyle) { + engine = 'webkit'; + } else if (typeof navigator.cpuClass === 'string') { + engine = 'trident'; + } + + var vendorPrefix = { + trident: 'ms', + gecko: 'Moz', + webkit: 'Webkit', + presto: 'O' + }[engine]; + + var helperElem = document.createElement("div"); + var undef; + + var perspectiveProperty = vendorPrefix + "Perspective"; + var transformProperty = vendorPrefix + "Transform"; + + if (helperElem.style[perspectiveProperty] !== undef) { + return function(left, top, zoom) { + self._content.style[transformProperty] = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')'; + }; + + } else if (helperElem.style[transformProperty] !== undef) { + return function(left, top, zoom) { + self._content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')'; + }; + + } else { + return function(left, top, zoom) { + self._content.style.marginLeft = left ? (-left/zoom) + 'px' : ''; + self._content.style.marginTop = top ? (-top/zoom) + 'px' : ''; + self._content.style.zoom = zoom || ''; + }; + } + }; + + MagnifierImplScroller.prototype._onResize = function(){ + this.scroller.setDimensions(this._container.clientWidth, this._container.clientHeight, this._container.clientWidth, this._container.clientHeight); + }; + + MagnifierImplScroller.prototype._installEventHandlers = function(){ + var self = this, clickTracker = new ClickTracker(this._imgCast, function(){ + self.cyclicZoom(); + }); + + window.addEventListener("resize", function(e){ + self._onResize(e); + }, false); + + this._zoomInBtn.addEventListener("click", function(e) { + e.preventDefault(); + self.zoomIn(); + }, false); + + this._zoomOutBtn.addEventListener("click", function(e) { + e.preventDefault(); + self.zoomOut(); + }, false); + + if ('ontouchstart' in window) { + self._container.addEventListener("touchstart", function(e) { + // Don't react if initial down happens on one of the zoom buttons + if (e.touches[0].target == self._$el.find('a.scroller-zoom-in').get(0) || e.touches[0].target == self._$el.find('a.scroller-zoom-out').get(0)) { + return; + } + + // Don't react if initial down happens on a form element + if (e.touches[0] && e.touches[0].target && e.touches[0].target.tagName.match(/input|textarea|select/i)) { + return; + } + + self.scroller.doTouchStart(e.touches, e.timeStamp); + + for (var i = 0; i < e.touches.length; i++) { + clickTracker.onDown(e.touches[i]); + } + + e.preventDefault(); + }, false); + + document.addEventListener("touchmove", function(e) { + self.scroller.doTouchMove(e.touches, e.timeStamp, e.scale); + clickTracker.onMove(); + }, false); + + document.addEventListener("touchend", function(e) { + self.scroller.doTouchEnd(e.timeStamp); + + // handle any zoom that may have occured + self._onZoom(); + + for (var i = 0; i < e.changedTouches.length; i++) { + clickTracker.onUp(e.changedTouches[i]); + } + }, false); + + document.addEventListener("touchcancel", function(e) { + self.scroller.doTouchEnd(e.timeStamp); + // handle any zoom that may have occured + self._onZoom(); + + for (var i = 0; i < e.changedTouches.length; i++) { + clickTracker.onUp(e.changedTouches[i]); + } + }, false); + } else { + self._container.addEventListener("mousedown", function(e) { + if (e.target.tagName.match(/input|textarea|select/i)) { + return; + } + + if (self.scroller.__clientWidth === 0) { + self._onResize(); + } + self.scroller.doTouchStart([{ + pageX: e.pageX, + pageY: e.pageY + }], e.timeStamp); + + clickTracker.onDown(e); + }, false); + + document.addEventListener("mousemove", function(e) { + if (!clickTracker.isDown) { + return; + } + + self.scroller.doTouchMove([{ + pageX: e.pageX, + pageY: e.pageY + }], e.timeStamp); + + clickTracker.onMove(); + }, false); + + document.addEventListener("mouseup", function(e) { + if (!clickTracker.isDown) { + return; + } + + self.scroller.doTouchEnd(e.timeStamp); + clickTracker.onUp(e); + }, false); + + // self._container.addEventListener(navigator.userAgent.indexOf("Firefox") > -1 ? "DOMMouseScroll" : "mousewheel", function(e) { + // e.preventDefault(); + // self.scroller.doMouseZoom(e.detail ? (e.detail * -120) : e.wheelDelta, e.timeStamp, e.pageX, e.pageY); + // }, false); + } + }; + + MagnifierImplScroller.prototype._updateButtonStates = function(){ + if (this._magnification === 1) { + $(this._$el).addClass('scroller-zoom-out-max'); + $(this._$el).removeClass('scroller-zoom-in-max'); + } else if (this._magnification === this._options.magnification) { + $(this._$el).addClass('scroller-zoom-in-max'); + $(this._$el).removeClass('scroller-zoom-out-max'); + } else { + $(this._$el).removeClass('scroller-zoom-in-max'); + $(this._$el).removeClass('scroller-zoom-out-max'); + } + }; + + var ex = (typeof exports === 'undefined') ? window : exports; + ex.Akamai = ex.Akamai || {}; + ex.Akamai.MagnifierImplScroller = MagnifierImplScroller; +})(); +(function(exports, $){ + /** + * Image magnifier + * @class + * @alias Akamai.Magnifier + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Magnifier = Akamai.Util.component("Magnifier", function(element, options){ + // compat with enlarge `disabled` option + this._options.disabled = !this._options.enabled; + if (this._options.mode === Magnifier.MODE_ANIMATED_ZOOM) { + this._impl = new Akamai.MagnifierImplScroller(element, options); + } else { + this._impl = new Akamai.MagnifierImplEnlarge(element, options); + } + }); + + Magnifier.prototype._updateOptions = function(options){ + if( !options ) { + this._options = this._originalOptions; + } + + this._options = Akamai.Util.extend(true, this._options, options); + + // compat with enlarge `disabled` option + this._options.disabled = !this._options.enabled; + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Magnifier.defaultOptions, this._options, "Magnifier"); + + this._impl.updateOptions(this._options); + }; + + // Used in preflight to "rename" events based on the child component events + Magnifier._componentEventMapping = { + "enlarge.after-zoom-in": "in", + "enlarge.after-zoom-out": "out", + "scroller.after-zoom-in": "in", + "scroller.after-zoom-out": "out" + }; + + Magnifier.MODE_HOVER_ZOOM = 'hoverzoom'; + Magnifier.MODE_ANIMATED_ZOOM = 'animatedzoom'; + + /** + * Defines the global default options for all Magnifiers on the page + * @static + * @property {String} mode - Set what mode to run the magnifier in. There are two modes available: `hoverzoom` and `animatedzoom`. The `hoverzoom` mode provides the ability to magnify and pan the image by just hovering your mouse over the image. It also provides a `flyout` option where a clip of the zoomed in image is displayed on a separate widget floating somewhere around (configurable) the image. The `animatedzoom` mode only does inline magnification but provides smooth animation between magnification levels, it also allows for incremental zooming. + * @property {Boolean} button - Whether to show a button for toggling magnification (default: true) + * @property {Boolean} enabled - Enabled/disable magnification (default: true, breakpoints supported) + * @property {Integer} magnification - The scale factor to magnify the image: `2`, `3` (default), `4`, `4.5`, etc + + * @property {Array} image.widths - List of available widths for an image (to be combined with image.widthParam) - (default: ["320","640","800","1024","2048","5000"]) + * @property {String} image.sizes - Value for image sizes attribute. Default is set dynamically to viewer width when rendered with JS, and updated dynamically. Values: `100vw` (default), `200px`, `(min-width:1000px) 500px, 100vw` - (default: `100vw`, breakpoints supported) + * @property {String} image.policy - Query param value for policy, appended to &policy= per image url when specified. Values: `foo`. (default: undefined) + + * @property {Integer} delay - Only applicable in `hoverzoom` mode. The time delay in milliseconds between mouse hover and magnification (default: 300, breakpoints supported) + * @property {String} buttonText - Only applicable in `hoverzoom` mode. Text for the zoom button. Also used for its title attribute. (default: "Toggle Image Magnification") + * @property {Integer} flyout.width - Only applicable in `hoverzoom` mode. Width of the flyout image (default: 200) + * @property {Integer} flyout.height - Only applicable in `hoverzoom` mode. Height of the flyout image (default: 200) + * @property {Boolean} hoverZoomWithoutClick - Only applicable in `hoverzoom` mode. Zoom starts on mouse hover with no click needed (default: true; false will require a click to hover-zoom) + * @property {String} placement - Only applicable in `hoverzoom` mode. Placement of the magnified image: `inline` , `flyoutloupe`, `flyouttopleft`,`flyoutbottomleft` ,`flyouttopright` and `flyoutbottomright` - (default: inline, breakpoins supported) + + * @property {Float} incrementalZoomFactor - Only applicable in `animatedzoom` mode. A number by which to incrementally zoom up until the specified `magnification`, default `3`. For example a `magnification` of `4` and an `incrementalZoomFactor` of `2` will zoom the image in `2` steps + * @property {Boolean} animateZoom - Only applicable in `animatedzoom` mode. Animates the magnification process. Default `true` + * @property {Integer} animationDuration - Only applicable in `animatedzoom` mode. If `animateZoom` is true, this specifies the length of the animation in milliseconds. Default `250` + * @property {String} zoomInBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom in button. Default `Zoom In` + * @property {String} zoomOutBtnText - Only applicable in `animatedzoom` mode. Hover text to display on the zoom out button. Default `Zoom Out` + */ + Magnifier.defaultOptions = { + // general options + mode: Magnifier.MODE_HOVER_ZOOM, + button: true, + enabled: true, + magnification: 3, + + // image options + image: Akamai.Image.defaultConfig, + + // hoverzoom options + delay: 300, + buttonText: "Toggle Image Magnification", + flyout: { + width: 200, + height: 200 + }, + hoverZoomWithoutClick: true, + placement: "inline", + + // animatedzoom options + incrementalZoomFactor: 3, + animateZoom: true, + animationDuration: 250, + zoomInBtnText: 'Zoom In', + zoomOutBtnText: 'Zoom Out' + }; + + // srcset stuff + Akamai.Sourceable.extendStatic(Magnifier); + + Magnifier.render = function(json, options){ + var finalOptions = Akamai.Util.options(Magnifier.defaultOptions, options); + var dataAttr = Magnifier._dataAttr; + var fallbackSrc = Magnifier._fallbackSrc(json.url, finalOptions.image); + var largestSrc = Magnifier._largestSrc(json.url, finalOptions.image); + var srcSet = Magnifier._srcset(json.url, finalOptions.image) + var sizes = finalOptions.image.sizes; + var impl = (finalOptions.mode === Magnifier.MODE_ANIMATED_ZOOM) ? Akamai.MagnifierImplScroller : Akamai.MagnifierImplEnlarge; + return impl.render(finalOptions, dataAttr, fallbackSrc, largestSrc, srcSet, sizes); + }; + + /** + * Enter magnifier mode + * @method + * @fires Akamai.Magnifier#akamai-magnifier-in + * @returns {undefined} + */ + Magnifier.prototype.in = function(){ + this._impl.zoomIn(); + }; + + /** + * Exit magnifier mode + * @method + * @fires Akamai.Magnifier#akamai-magnifier-out + * @returns {undefined} + */ + Magnifier.prototype.out = function(){ + this._impl.zoomOut(); + }; + + /** + * Return the current state of the magnifier + * @method + * @returns {Boolean} + */ + Magnifier.prototype.isMagnified = function(){ + return this._impl.isMagnified(); + }; + + /** + * Toggle the state of the magnifier + * @method + * @fires Akamai.Magnifier#akamai-magnifier-in + * @fires Akamai.Magnifier#akamai-magnifier-out + * @returns {undefined} + */ + Magnifier.prototype.toggle = function(){ + this._impl.toggleZoom(); + }; + + /** + * Triggered when the magnifier has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carouse#in}. + * + * @event Akamai.Magnifier#akamai-magnifier-in + */ + + /** + * Triggered when the magnifier has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carouse#out}. + * + * @event Akamai.Magnifier#akamai-magnifier-out + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Magnifier}. + * + * @event Akamai.Magnifier#akamai-magnifier-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Magnifier = Magnifier; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + var VideoImpl = Akamai.Util.component("VideoImpl", function(element, options){ + throw new Error('Not allowed to instantiate VideoImpl'); + }); + + VideoImpl.render = function(json, options){ + throw new Error('VideoImpl.render not implemented'); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.VideoImpl = VideoImpl; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + var VideoImplPristine = Akamai.Util.component("VideoImplPristine", function(element, options){ + }); + + VideoImplPristine.prototype = Object.create(Akamai.VideoImpl.prototype); + VideoImplPristine.prototype.constructor = VideoImplPristine; + + VideoImplPristine.render = function(json, options){ + var finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options); + + var url = json.url; + var poster = json.poster || ""; + var mime = (json.mime) ? 'type="' + json.mime + '"' : ''; + + var attrs = [ + finalOptions.loop ? "loop" : "", + finalOptions.autoplay ? "autoplay" : "", + finalOptions.controls ? "controls" : "", + finalOptions.muted ? "muted" : "", + "playsinline" + ]; + + return Akamai.Util.trim(` +
+ +
+ `); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.VideoImplPristine = VideoImplPristine; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + var VideoImplIm = Akamai.Util.component("VideoImplIm", function(element, options){ + var finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options); + VideoImplIm._validateOptions(finalOptions); + }); + + VideoImplIm.prototype = Object.create(Akamai.VideoImpl.prototype); + VideoImplIm.prototype.constructor = VideoImplIm; + + VideoImplIm._validateOptions = function(options){ + if (!Array.isArray(options.sizes) || options.sizes.length !== 3) { + throw new Error('Akamai.Video sizes must have three entries'); + } + + if (options.sizes.some(function (size) { + return isNaN(size); + })) { + throw new Error('Akamai.VideoImplIm all sizes must be numbers'); + } + } + + VideoImplIm.render = function(json, options){ + var finalOptions = Akamai.Util.options(Akamai.Video.defaultOptions, options); + VideoImplIm._validateOptions(finalOptions); + + var url = json.url; + var poster = json.poster || ""; + var width; + + var viewPortWidth = window.innerWidth; + + if (viewPortWidth < 992) { + width = finalOptions.sizes[0]; + } else if (viewPortWidth < 1200) { + width = finalOptions.sizes[1]; + } else { + width = finalOptions.sizes[2]; + } + + var attrs = [ + finalOptions.loop ? "loop" : "", + finalOptions.autoplay ? "autoplay" : "", + finalOptions.controls ? "controls" : "", + finalOptions.muted ? "muted" : "", + "playsinline" + ]; + + var joiner = url.indexOf('?') === -1 ? '?' : '&'; + + return Akamai.Util.trim(` +
+ +
+ `); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.VideoImplIm = VideoImplIm; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + /** + * Video component + * @class + * @alias Akamai.Video + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Video = Akamai.Util.component("Video", function(element, options){ + // TODO + this._comp = this._$el.length && undefined; + this._$videoElement = this._$el.find("video"); + this._videoElement = this._$videoElement[0]; + + if( !this._videoElement ){ + throw new Error( "Akamai.Video requires a child HTML Video element" ); + } + + if( !this._videoElement.play || !this._videoElement.pause ){ + this._unsupportedAPI = true; + this._unsupported(); + return; + } + + // get the initial state (could be autoplaying on render) + this._setState(this._videoElement.paused ? Video.States.Paused : Video.States.Playing); + + // state bindings + this._$el + .bind("akamai-video-play", function(){ + this._setState(Video.States.Playing); + }.bind(this)) + .bind("akamai-video-pause", function(){ + this._setState(Video.States.Paused); + }.bind(this)); + + this._createPlayButton(); + }); + + Video.MODE_IM = 'im'; + Video.MODE_PRISTINE = 'pristine'; + + Video._componentEventMapping = { + "play": { + to: "play", + selector: "video" + }, + + "pause": { + to: "pause", + selector: "video" + }, + + "seeked": { + to: "seek", + selector: "video" + } + }; + + Video.prototype._updateOptions = function(options){ + if( !options ) { + this._options = this._originalOptions; + } + + this._options = Akamai.Util.extend(true, this._options, options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Video.defaultOptions, this._options, "Video"); + + // TODO see Magnifier for example + }; + + /** + * Defines the global default options for all Spin360s on the page + * @static + * @property {Boolean} autoplay - Automatically play the video on load (default: false) + * @property {Boolean} controls - Display the video controls (default: true) + * @property {Boolean} loop - Restart the video when it reaches the end (default: false) + * @property {Boolean} muted - Mute the video (default: true) + * @property {String} mode - What video component to use. `im` will let you use any size pristine video and will automatically request & generate the right size when the page is loaded. `pristine` just passes through the original video into the video player and this is what will always play on the users device. Default: `pristine` + * @property {Array} sizes - Video sizes (widths) to use for different screen widths. Defaults: [1920 (large screens), 1280 (tablet), 854 (mobile), ] + */ + Video.defaultOptions = { + autoplay: false, + controls: true, + loop: false, + muted: true, + mode: Video.MODE_PRISTINE, + sizes: [854, 1280, 1920] + }; + + Video.render = function(json, options){ + var impl = (options && options.mode === Video.MODE_IM) ? Akamai.VideoImplIm : Akamai.VideoImplPristine; + return impl.render(json, options); + }; + + Video.States = { + Playing: 0, + Paused: 1 + }; + + Video.prototype._unsupported = function(){ + if( this._unsupportedAPI ){ + Akamai.Util.log("Video: video API not supported", 'error'); + } + + return this._unsupportedAPI; + }; + + /** + * Play the video, idempotent + * @method + * @fires Akamai.Video#akamai-video-play + * @returns {undefined} + */ + Video.prototype.play = function(){ + if( this._unsupported() ) { return; } + this._videoElement.play(); + }; + + /** + * Pause the video + * @method + * @fires Akamai.Video#akamai-video-pause + * @returns {undefined} + */ + Video.prototype.pause = function(){ + if( this._unsupported() ) { return; } + this._videoElement.pause(); + }; + + /** + * Return the current state of the video + * @example Video states + * Akamai.Video.States = { + * Playing: 0, + * Paused: 1 + * }; + * + * @example Conditioning on states + * if( video.getState() === Akamai.Video.States.Paused ) { + * video.play() + * } + * @method + * @returns {Akamai.Video.State} + */ + Video.prototype.getState = function(){ + return this._state; + }; + + Video.prototype._setState = function(value){ + this._state = value; + }; + + /** + * Seek to the input percentage. + * @method + * @fires Akamai.Video#akamai-video-seek + * @param {Integer} percent - value between 0 and 100 percent for seeking + * @returns {undefined} + */ + Video.prototype.seek = function(percent){ + if( percent < 0 || 100 < percent ) { + throw new Error( "seek takes a an integer between 0 and 100" ); + } + + var newTime = (percent / 100) * (this._videoElement.duration || 1); + this._videoElement.currentTime = newTime; + }; + + /** + * Set whether the video should loop or not + * @method + * @param {Boolean} value - The value true or false + * @returns {undefined} + */ + Video.prototype.setLoop = function(value){ + this._videoElement.loop = value; + }; + + Video.prototype.getElement = function(){ + return this._$el[0]; + }; + + // Borrowed from https://codepen.io/chrisnager/pen/jPrJgQ + Video.prototype._createPlayButton = function(){ + var videoPlayButton; + var videoWrapper = this._$el[0]; + var video = this._videoElement; + + if(this._options.autoplay){ + return; + } + + // TODO move to render? + videoWrapper.insertAdjacentHTML('beforeend', ` + + + + + `); + + video.removeAttribute('controls'); + videoPlayButton = this._$el.find('svg')[0]; + + videoPlayButton.addEventListener('click', function(){ + video.play(); + videoPlayButton.classList.add('is-hidden'); + if( this._options.controls ) { + video.setAttribute('controls', 'controls'); + } + }.bind(this)); + }; + + /** + * Triggered when the video has been played. See {@link Akamai.Video#play}. + * + * @event Akamai.Video#akamai-video-play + */ + + /** + * Triggered when the video has been paused. See {@link Akamai.Video#pause}. + * + * @event Akamai.Video#akamai-video-pause + */ + + /** + * Triggered when the video has seeked to a position. See {@link Akamai.Video#seek}. + * + * @event Akamai.Video#akamai-video-seek + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Video}. + * + * @event Akamai.Video#akamai-video-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Video = Video; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + /** + * Image carousel + * @class + * @alias Akamai.Carousel + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + var Carousel = Akamai.Util.component("Carousel", function(element, options){ + this._spins = Akamai.Spin360.createMany(this._$el[0], this._options.spin360); + this._magnifiers = Akamai.Magnifier.createMany(this._$el[0], this._options.magnifier); + this._videos = Akamai.Video.createMany(this._$el[0], this._options.video); + + this._$el.snapper(this._options); + + this._setAspectRatio(); + this._bindAspectAttributes(); + + if( this._options.slideshow.autostart ){ + setTimeout(function(){ + this.startSlideshow(); + }.bind(this), this._options.slideshow.interval); + } + + // NOTE !! the following two bindings must happen in order, the + // autoplayables binding relies on the attribut + + // add item attrs and maintain the active item attributes to determine the + // active item state + this._activeItemAttributes(); + this._$el.bind( "akamai-carousel-goto", this._activeItemAttributes.bind(this)); + + // TODO pause autoplay videos that are not visible + this._handleAutoplayables(); + this._$el.bind( "akamai-carousel-goto", this._handleAutoplayables.bind(this)); + + this._$el + .bind( "tau.touch-tracking-start", function(){ + this._$el.find(".snapper_pane").addClass("no-scroll"); + }.bind(this)) + .bind( "tau.touch-tracking-stop", function(){ + this._$el.find(".snapper_pane").removeClass("no-scroll"); + }.bind(this)); + + // when child components are doing things, stop the carousel from + // automatically advancing + this._$el.bind( this.constructor._stopSlideshowEvents.join(" "), this.stopSlideshow.bind(this)); + }); + + Carousel._stopSlideshowEvents = [ + "akamai-magnifier-in", + "akamai-magnifier-out", + "akamai-spin360-goto", + "akamai-video-play", + "akamai-video-seek" + ]; + + // clearly there should be an autoplayable interface + Carousel.prototype._handleAutoplayables = function(){ + var isParentActive = function(comp){ + return !!$(comp.getElement()) + .closest("[" + this.constructor.activeItemAttr + "]") + .length; + }.bind(this); + + this._videos.concat(this._spins).forEach(function(comp){ + if( isParentActive(comp) && comp._carouselWasPlaying ) { + comp.play(); + } else { + if( comp.getState() == comp.constructor.States.Playing ){ + comp._carouselWasPlaying = true; + comp.pause(); + } else { + comp._carouselWasPlaying = false; + } + } + }.bind(this)); + }; + + // Unique counter for IDs + Carousel.counter = 0; + + Carousel.prototype._updateOptions = function(options){ + if( !options ) { + this._options = this._originalOptions; + } + + // update the current options + this._options = Akamai.Util.extend(true, this._options, options); + + // update the options for each of the subcomponents + var update = function(comp, name){ + comp._updateOptions(this._options[name]); + }.bind(this); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Carousel.defaultOptions, this._options, "Carousel"); + this._$el.snapper( "updateOptions", options ); + + this._spins.forEach(function(c){ update(c, "spin360"); }); + this._magnifiers.forEach(function(c){ update(c, "magnifier"); }); + this._videos.forEach(function(c){ update(c, "video"); }); + + this._setAspectRatio(); + }; + + // Used in preflight to "rename" events based on the child component events + Carousel._componentEventMapping = { + "snapper.after-snap": "goto", + "snapper.snap": "snap", + "snapper.after-next": "next", + "snapper.after-prev": "previous" + }; + + /** + * Defines the global default options for all Carousels on the page + * @static + * @property {Boolean} arrows - Show carousel controls - (default: true) + * @property {Number} aspectratio - Specify a percentage-based height for the carousel, relative to the width. Values: `false`, `100`, `45.6`, `78` - (default `false`, breakpoints supported) + * @property {Integer} slideshow.interval - Time in milliseconds between slide advances - (default: 4000, breakpoints supported) + * @property {Integer} slideshow.autostart - Start the slideshow on instantiation - (default: false) + * @property {String} thumbnail.placement - Placement relative to the slide container: `left`, `right`, `bottom` (default: `bottom`, breakpoints supported) + * @property {String} thumbnail.type - Type of thumbnail: `dots`, `none`, `images` - (default: `images`, breakpoints supported) + * @property {String} thumbnail.policy - String to use for query parameter, ex: "&policy=" (default: undefined) + * @property {String} thumbnail.sizes - Sizes attribute value to use if thumbnail policy is set. Values: `300px`, `200px`, `(min-width:1000px) 300px, 100px` - (default: `300px`) + * @property {Object} images - Defaults to the Magnifier image option's settings + */ + Carousel.defaultOptions = { + arrows: true, + + aspectratio: false, + + slideshow: { + interval: 4000, + autostart: false + }, + + thumbnail: { + placement: "bottom", + type: "images", + policy: undefined, + sizes: "300px" + }, + + images: Akamai.Image.defaultConfig + }; + + Carousel.renderMapping = { + image: Akamai.Magnifier, + spin360: Akamai.Spin360, + video: Akamai.Video + }; + + Carousel._typeMapping = { + image: "magnifier" + }; + + Carousel._uniqueItemId = function(item, i) { + return "akamai-carousel-" + Carousel.counter + "-" + item.type + "-" + i; + }; + + // srcset stuff + Akamai.Sourceable.extendStatic(Carousel); + + Carousel._renderItem = function(item, i, options) { + var mappedType = Carousel._typeMapping[item.type] || item.type; + + if( !Carousel.renderMapping[item.type] ){ + throw new Error(` + item type ${item.type} at index ${i} must be 'image', 'spin360', or 'video' + `); + } + + return ` +
+ ${Carousel.renderMapping[item.type].render(item, options[mappedType])} +
+ `; + }; + + Carousel._renderThumbnailAnchor = function(item, data, i, options) { + var thumbUrl = ""; + var altText = item.alt || ""; + + // use the video poster, the canonical url, or the first in a sequence + if( item.type === 'video' ){ + thumbUrl = item.poster || data.reduce(function(acc, i){ + return acc || Carousel._thumbUrl(i); + }, ""); + } else { + thumbUrl = Carousel._thumbUrl(item); + } + + // TODO this sucks + options.images.policy = options.thumbnail.policy; + + var src = Carousel._fallbackSrc( thumbUrl, options.images ); + var srcset = Carousel._srcset( thumbUrl, options.images ); + var sizes = options.images.sizes; + + // if there's a thumbnail policy, the thumbnails will be fresh image requests, + // so they should have better sizes attribute values + if( options.thumbnail.policy ){ + sizes = options.thumbnail.sizes; + } + + var attrs = [ + `${Carousel._dataAttr}-thumb-type="${item.type}"`, + item.type == "video" && !item.poster ? "${Carousel._dataAttr}-thumb-noposter" : "" + ]; + + return ` + + ${altText} + + `; + }; + + Carousel._thumbUrl = function(item){ + return item.type === 'video' ? item.poster : item.url || (item.urls && item.urls[0]); + }; + + Carousel._renderThumbnails = function(data, options) { + if(data.length <= 1 ){ + return ""; + }; + + return ` +
+
+ ${Akamai.Util.map(data, function(item, i){ + return Carousel._renderThumbnailAnchor(item, data, i, options); + }).join("\n")} +
+
+ `; + }; + + Carousel.prototype._setAspectAttributes = function( $el ){ + var orientationAttr = `${Carousel._dataAttr}-aspectratio-orientation`; + if( $el.height() > $el.width() ) { + $el.attr( orientationAttr, "portrait" ); + } else if( $el.height() < $el.width() ) { + $el.attr( orientationAttr, "landscape" ); + } else { + if ($el.parent().height() >= $el.parent().width()) { + $el.attr( orientationAttr, "landscape" ); + } else { + $el.attr( orientationAttr, "portrait" ); + } + } + }; + + Carousel.prototype._setAspectRatio = function(){ + var value = this._options.aspectratio || 0; + var $items = this._$el.find( ".snapper_item" ); + $items.css( "padding-top", (value / $items.length) + "%" ); + }; + + Carousel.prototype._bindAspectAttributes = function(){ + if( this._options.aspectratio === false ){ + return; + } + + var value = this._options.aspectratio || 0; + var self = this; + var $items = this._$el.find( ".snapper_item" ); + + $items.each(function(){ + var loadBound; + var $item = $(this); + + var $loadable = $item.find( "img, video" ).eq(0); + + $loadable.bind("load loadedmetadata", loadBinding = function(){ + clearTimeout(loadBound); + + if( $item.is(`[${Akamai.Spin360._dataAttr}]`) ){ + // TODO it's not always a canvas, sometimes it's a collection of + // images based on settings + self._setAspectAttributes( $item.find( "canvas" ) ); + } else{ + self._setAspectAttributes( $loadable ); + } + }); + + loadBound = setTimeout(loadBinding, 5000); + }); + + $items.eq(0).find( "img,video" ).eq(0).bind( "load loadedmetadata", function(){ + self._trigger( "first-media-load" ); + }); + }; + + Carousel.render = function(data, options){ + options = options || {}; + + // establish the extended default options for the carousel + var carouselOptions = Akamai.Util.options(Carousel.defaultOptions, options); + + var templateAttrs = [ + `${Carousel._dataAttr}`, + `${Carousel._dataAttr}-item-count="${(data || []).length}"`, + `data-snapper-deeplinking="false"`, + carouselOptions.arrows ? "data-snapper-nextprev" : "" + ]; + + Carousel.counter++; + + // TODO can we do something about the `enlarge_pane` class here, seems it + // could be conditioned on at least one regular image type in the json + // TODO options should dictate `data-snapper-nextprev` + return Akamai.Util.trim(` +
+
+
+
+
+ ${Akamai.Util.map(data, function(item, i){ + return Carousel._renderItem(item, i, carouselOptions); + }).join("\n")} +
+
+
+
+ + ${Carousel._renderThumbnails(data, carouselOptions)} +
+ `); + }; + + /** + * Instantiate Carousels by looking for children matching + * [data-akamai-carousel] in `element` param + * @method + * @static + * @param {HTMLElement} element - the element to search in for + * @returns {Akamai.Carousel[]} + */ + + + /** + * @method + * @returns {Akamai.Spin360[]} + */ + Carousel.prototype.getSpin360s = function(){ + return this._spins; + }; + + /** + * @method + * @returns {Akamai.Magnifier[]} + */ + Carousel.prototype.getMagnifiers = function(){ + return this._magnifiers; + }; + + /** + * @method + * @returns {Akamai.Video[]} + */ + Carousel.prototype.getVideos = function(){ + return this._videos; + }; + + /** + * Go to a particular slide. + * + * Note that the state of the DOM on the page and thus the index of the + * carousel will not be up-to-date until the {@link + * Akamai.Carousel#akamai-carousel-goto} event has been fired. That is, + * calling this method and the calling `getIndex` will not necessarily result + * in an updated index value. Either bind to the event or supply a callback. + * + * @method + * @fires Akamai.Carousel#akamai-carousel-goto + * @param {Integer} index - The zero-based slide index to go to + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Carousel.prototype.goto = function(index, callback){ + this._$el.snapper( "goto", index, callback ); + }; + + Carousel._itemAttr = `${Carousel._dataAttr}-item`; + Carousel._activeItemAttr = `${Carousel._itemAttr}-active`; + + /** + * Add an attribute to all carousel items, + * and additionally maintain an active attribute on the active carousel item + * + * @method + * @returns {undefined} + */ + Carousel.prototype._activeItemAttributes = function(){ + var attrAllItems = this.constructor._itemAttr; + var attrActiveItem = this.constructor._activeItemAttr; + + this._$el + .find( ".snapper_item" ) + .attr( attrAllItems, true ) + .removeAttr( attrActiveItem ) + .eq( this.getIndex() ).attr( attrActiveItem, true ); + }; + + /** + * Return the current slide index + * @method + * @returns {undefined} + */ + Carousel.prototype.getIndex = function(){ + return this._$el.snapper( "getIndex" ); + }; + + // Extend Carousel with Advanceabl interface + Akamai.Advanceable.extend(Carousel); + + /** + * Advance to the next item + * @method + * @fires Akamai.Carousel#akamai-carousel-next + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Carousel.prototype.next; + // required for JSDocs, implementation in Advanceable + + /** + * Retreat to the previous item + * @method + * @fires Akamai.Carousel#akamai-carousel-previous + * @param {Function?} callback - callback invoked after the action has completed in the DOM + * @returns {undefined} + */ + Carousel.prototype.previous; + // required for JSDocs, implementation in Advanceable + + /** + * Start automatic advancement of the carousel items + * @method + * @fires Akamai.Carousel#akamai-carousel-start-slideshow + * @returns {undefined} + */ + Carousel.prototype.startSlideshow = function(){ + this._$el.one("mousedown touchstart", this.stopSlideshow.bind(this)); + + this.next(function(){ + this._trigger("start-slideshow"); + this._slideshowTimer = setTimeout(function(){ + this.startSlideshow(); + + // TODO remove || when default options are added + }.bind(this), this._options.slideshow.interval ); + }.bind(this)); + }; + + /** + * Start automatic advancement of the carousel items + * @method + * @fires Akamai.Carousel#akamai-carousel-stop-slideshow + * @returns {undefined} + */ + Carousel.prototype.stopSlideshow = function(){ + clearTimeout(this._slideshowTimer); + this._slideshowTimer = undefined; + this._trigger("stop-slideshow"); + }; + + /** + * Triggered when the carousel has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carousel#goto}. + * + * @event Akamai.Carousel#akamai-carousel-goto + */ + + /** + * Triggered when the carousel has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carousel#next}. + * + * @event Akamai.Carousel#akamai-carousel-next + */ + + /** + * Triggered when the carousel has completed it transition to + * a new index due to user interaction or a call to {@link Akamai.Carousel#previous}. + * + * @event Akamai.Carousel#akamai-carousel-previous + */ + + /** + * Triggered when the carousel has started the slide show due to a call to + * {@link Akamai.Carousel#startSlideshow}. + * + * @event Akamai.Carousel#akamai-carousel-start-slideshow + */ + + /** + * Triggered when the carousel has stoped the slide show due to a call to + * {@link Akamai.Carousel#stopSlideshow}. + * + * @event Akamai.Carousel#akamai-carousel-stop-slideshow + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Carousel}. + * + * @event Akamai.Carousel#akamai-carousel-init + */ + + Carousel.prototype.refresh = function(){ + this._$el.snapper("updateWidths"); + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Carousel = Carousel; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + /** + * Image Fullscreen + * @class + * @alias Akamai.Fullscreen + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + */ + + // TODO JSDocs + // TODO Tests + var Fullscreen = Akamai.Util.component("Fullscreen", function(element, options){ + if( this._options.enabled ) { + this._init(); + } + }); + + Fullscreen.prototype._init = function(){ + this._$fullscreen = this._$el; + + // parent container does not allow widths to be set (Firefox, fullscreen), so we’ll adjust the children + this.addButton(); + + this._fullscreenApiKeys = Fullscreen._keys(); + + if( this._options.native ) { + this._useNativeApi = !!this._fullscreenApiKeys; + } else { + this._useNativeApi = false; + } + + // Fullscreen API is disabled and the fallback behavior is active + this._fullscreenFallbackEnabled = false; + this._$placeholder = $( "
" ); + + this._isFullscreen = false; + this.addEvents(); + }; + + Fullscreen.classes = { + btn: "akamai-fullscreen-btn", + btnContainer: "akamai-fullscreen-btncontainer", + enterBtn: "akamai-fullscreen-btn-enter", + exitBtn: "akamai-fullscreen-btn-exit", + active: "akamai-fullscreen-active", + fallback: "akamai-fullscreen-fallback" + }; + + Fullscreen.attr = { + width: "data-akamai-fullscreen-width", + enterBtn: "data-akamai-fullscreen-btn", + exitBtn: "data-akamai-fullscreen-exit-btn" + }; + + Fullscreen.defaultOptions = { + enabled: false, + native: false + }; + + Fullscreen.prototype.addButton = function(){ + this._$widthAdjust = this._$fullscreen.children().filter(".focused"); + + if( !this._$widthAdjust.length ) { + this._$widthAdjust = this._$fullscreen + .children() + .eq(0); + } + + if( this._$widthAdjust.attr(Fullscreen.attr.width) === null ){ + this._$widthAdjust.attr( Fullscreen.attr.width, "" ); + } + + if( this._$widthAdjust.find("[" + Fullscreen.attr.enterBtn + "]").length ){ + return; + } + + // buttons parent + this._$buttonContainer = this._$widthAdjust; + this._$buttonContainer.addClass( Fullscreen.classes.btnContainer ); + + this._$buttonContainer.append(this.render()); + }; + + Fullscreen.prototype.addEvents = function() { + this._$fullscreen.on( "click", function( e ) { + var $target = $( e.target ); + if( !$target.is("[" + Fullscreen.attr.enterBtn + "]") ){ + return; + } + + this.enter(); + e.preventDefault(); + }.bind( this )); + + this._$fullscreen.on( "click", function( e ) { + var $target = $( e.target ); + + if( !$target.is( "[" + Fullscreen.attr.exitBtn + "]" ) ){ + return; + } + + this.exit(); + e.preventDefault(); + }.bind( this )); + + if( this._useNativeApi ) { + document.addEventListener( this._fullscreenApiKeys.onchange, function() { + // exiting fullscreen using native method (ESC or menu option) + if( !document[ this._fullscreenApiKeys.element ] ) { + this._exit(); + } + }.bind( this ), false ); + } + + // ESC to close + $( document ).on( "keydown", function( e ) { + var code = e.keyCode || e.which; + if( code === 27 ) { + this._exit(); + } + }.bind( this )); + }; + + Fullscreen.prototype.render = function() { + return Akamai.Util.trim(` + + + `); + }; + + Fullscreen._keyLookup = [ + { + enter: "requestFullscreen", + exit: "exitFullscreen", + element: "fullscreenElement", + onchange: "fullscreenchange" + }, + { + enter: "webkitRequestFullscreen", + exit: "webkitExitFullscreen", + element: "webkitFullscreenElement", + onchange: "webkitfullscreenchange" + }, + { + enter: "webkitRequestFullScreen", + exit: "webkitCancelFullScreen", + element: "webkitCurrentFullScreenElement", + onchange: "webkitfullscreenchange" + }, + { + enter: "mozRequestFullScreen", + exit: "mozCancelFullScreen", + element: "mozFullScreenElement", + onchange: "mozfullscreenchange" + }, + { + enter: "msRequestFullscreen", + exit: "msExitFullscreen", + element: "msFullscreenElement", + onchange: "MSFullscreenChange" + } + ]; + + Fullscreen._keys = function( el ) { + var keys = Fullscreen._keyLookup; + + var el = document.body; + for( var j = 0, k = keys.length; j < k; j++ ) { + if( keys[ j ].enter in el ) { + return keys[ j ]; + } + } + }; + + Fullscreen.prototype._maximizePlacement = function(){ + this._fullscreenFallbackEnabled = true; + this._$fullscreen.addClass( Fullscreen.classes.fallback ); + this._$placeholder.insertAfter( this._$fullscreen ); + this._$fullscreen.appendTo( document.body ); + }; + + Fullscreen.prototype._restorePlacement = function(){ + if( !this._fullscreenFallbackEnabled ) { + return; + } + + this._fullscreenFallbackEnabled = false; + this._$fullscreen.removeClass( Fullscreen.classes.fallback ); + this._$fullscreen.insertAfter( this._$placeholder ); + this._$placeholder.remove(); + }; + + Fullscreen.prototype._adjustWidth = function() { + this._$fullscreen.css( "width", "auto !important" ); + // wish this could go into the change event above, but alas the dimensions don’t measure right + var widthSmall = this._$fullscreen.width(); + var heightSmall = this._$fullscreen.height(); + + var viewportWidth = this._useNativeApi ? window.screen.width : Math.max( document.documentElement.clientWidth, window.innerWidth || 0 ); + var viewportHeight = this._useNativeApi ? window.screen.height : Math.max( document.documentElement.clientHeight, window.innerHeight || 0 ); + + // calculate the maximum width we can use to fill the viewportHeight + var widthBig = widthSmall * viewportHeight / heightSmall; + + // if the width is bigger than the maximum, just set to 100% (won’t fill the entire height, but that’s ok) + if( widthBig > viewportWidth ) { + widthBig = "100%"; + } + + this._$widthAdjust.width( widthBig ); + }; + + Fullscreen.prototype._revertWidth = function() { + this._$widthAdjust.css( "width", "" ); + }; + + /** + * Enter full screen mode + * @method + * @returns {undefined} + */ + Fullscreen.prototype.enter = function(){ + var activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex(); + this._$fullscreen.addClass( Fullscreen.classes.active ); + + + if( this._useNativeApi ) { + this._$fullscreen[ 0 ][ this._fullscreenApiKeys.enter ](); + this._adjustWidth(); + } else { + this._adjustWidth(); + this._maximizePlacement(); + } + this._isFullscreen = true; + this._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex); + this._trigger("enter"); + }; + + Fullscreen.prototype._exit = function(){ + var activeIndex = this._$fullscreen[0].shoestringData.Viewer._carousels[0].getIndex(); + this._$fullscreen.removeClass( Fullscreen.classes.active ); + + if( !this._useNativeApi ) { + this._restorePlacement(); + } + + this._revertWidth(); + this._isFullscreen = false; + this._$fullscreen[0].shoestringData.Viewer._carousels[0].goto(activeIndex); + this._trigger("exit"); + }; + + /** + * Exits full screen mode + * @method + * @returns {undefined} + */ + Fullscreen.prototype.exit = function(){ + if( this._useNativeApi ) { + // note some close behavior happens in the onchange event handler above + document[ this._fullscreenApiKeys.exit ](); + } + + this._exit(); + }; + + Fullscreen.prototype.isFullscreen = function(){ + return this._isFullscreen; + }; + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Fullscreen = Fullscreen; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + /** + * Akamai Viewer omni-component + * @class + * @alias Akamai.Viewer + * @param {HTMLElement} element - the DOM element representing the component markup + * @param {Object} options - configuration options + * + * @example Instatiation + * var element = document.querySelector( "[data-akamai-viewer]" ); + * var viewer = Akamai.Viewer( element ); + */ + var Viewer = Akamai.Util.component("Viewer", true, function(element, options){ + // try to update the carousel and magnifier sizes options to the width of + // the viewer for more accurate srcset selection + this._updateSizesOptions(true); + this._onResizeComplete(this._updateSizesOptions.bind(this)); + + // move all the flattened component options (spin, zoom, video) onto the carousel + this._options.carousel = + Viewer._extendCarouselOptions(this._options); + + this._withData(function(data){ + if( data ) { + // check all of the urls agains the hostname and whitelist + this._checkJSONUrls(data); + + // prevent XSS / injection attacks by escaping string values + data = Akamai.Util.escapeJSONVals(data); + + // store escaped data for later reference + this._options.items.data = data; + } + + this._tagMapping = {}; + + // TODO if the element is empty and/or options has `items` render into element + if( this._$el.children().length == 0 && data ) { + this._tagMapping = this.constructor._tagSplit(data, this._options.items); + this._$el.append(this.constructor.render(data, this._options, true)); + } + + // intantiate all child carousels + this._carousels = Akamai.Carousel.createMany(element, this._options.carousel); + this._fullscreen = new Akamai.Fullscreen(element, this._options.fullscreen); + + // on enter and exit of fullscreen resolve the breakpoints and update options + this._$el.bind( "akamai-fullscreen-enter akamai-fullscreen-exit", function(){ + this._updateOptions(this._resolveBreakpointOptions()); + }.bind(this)); + + // use the breakpoints to set match media listeners + this._setBreakpoints(); + + // have to trigger init explicitly for async constructors + this._trigger("init"); + }.bind(this), function(msg){ + throw new Error(msg); + }); + }); + + Viewer.prototype._updateSizesOptions = function(localUpdateOnly){ + // update sizes option to something more specific if possible + var elWidth = this._$el.width(); + if( elWidth && elWidth > 0 ){ + elWidth += "px"; + this._options = Akamai.Util.extend(true, this._options, { + carousel: { + images: { + sizes: elWidth + } + } + }); + } + + if( !localUpdateOnly ){ + this._updateOptions(this._options); + } + }; + + Viewer.prototype._withData = function(after, fail){ + if(!this._options.items.uri){ + if( this._exceedsLimit(this._options.items.data, fail)) { + return; + } + + after(this._options.items.data); + } + + else if (typeof(this._options.items.uri) !== 'undefined' && this._options.items.uri.length > 0) + { + $.get(this._options.items.uri, function(data){ + // make sure the data gets sorted out regardless of `get` impl + data = (typeof data === "string" ? JSON.parse(data) : data); + + if( this._exceedsLimit(data.items, fail) ){ + return; + } + + after(data.items); + }.bind(this)); + } + }; + + // value in kibibytes + Viewer.prototype._exceedsLimit = function(data, fail){ + // TODO some tests don't provide data + if( !data ) { return false; } + + // 1 unicode character = 4 bytes + // length = # of chars + // kibibyte = 1024 bytes + // # of chars * 4 / 1024 + var kb = JSON.stringify(data).length * 4 / 1024; + + if( kb > this._options.items.limit ) { + fail = fail || function(){}; + fail(`JSON data size exceeds ${this._options.items.limit} KiB. Limit can be configured with items.limit Akamai.Viewer options.`); + return true; + } + + return false; + }; + + Viewer.prototype._setBreakpoints = function(){ + this._breakpoints = this._reduceBreakpoints(this._options.breakpoints || {}); + this._fullscreenBreakpoints = + this._reduceBreakpoints(this._options.fullscreenBreakpoints || {}); + + this._bindBreakpoints(); + }; + + Viewer.prototype._reduceBreakpoints = function(breakpoints){ + var bps = []; + + for( var bp in breakpoints ){ + if(breakpoints.hasOwnProperty(bp)){ + try { + bps.push(parseInt(bp, 10)); + } catch(e){ + Akamai.Util.log(e, 'error'); + } + } + } + + bps.sort(function(a, b){ return b < a;}); + + return bps; + }; + + Viewer.prototype._bindBreakpoints = function(){ + [this._breakpoints, this._fullscreenBreakpoints].forEach(function(bps){ + // bind using the minwidth and the next breakpoint as the maxwidth + // adds a range from 0 to the first breakpoint and from the last + // breakpoint to a very large number + if(!bps.length) { return; } + + for( var i = -1; i < bps.length; i++ ){ + this._bindMatchMedia(bps[i], bps[i+1]); + } + }.bind(this)); + }; + + // bind a callback to run after resize completes + Viewer.prototype._onResizeComplete = function( callback ){ + var cbtimer; + var self = this; + window.addEventListener( "resize", function(){ + clearTimeout( cbtimer ); + cbtimer = setTimeout( callback, 500 ); + }); + }; + + Viewer.prototype._bindMatchMedia = function(minWidth, maxWidth){ + minWidth = minWidth || 0; + + // large number so we can use one media query template + maxWidth = maxWidth || 1000000000; + + if( window.matchMedia ){ + // bind using the minwidth and maxwidth so we get the events at the boundaries + // so we can asses which set of options applies. + var query = `(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`; + + // create a media list to bind to + var initList = window.matchMedia(query); + + if(initList && initList.addListener) { + // handle a match on instantiation + this._mediaMatch(initList, minWidth, true); + + // bind for later changes in whether the media query matches + initList.addListener(function(list){ + this._mediaMatch(list, minWidth); + }.bind(this)); + } + } + }; + + Viewer.prototype._mediaMatch = function(list, minWidth, ignoreDefault){ + // If there is a match for the breakpoint (we're in the bp range) + // Then set the options based on that breakpoint + // Else if there is not a match and the client width is below the + // breakpoint that's being disabled, then use the original options + if(list.matches){ + var resolvedOptions = this._resolveBreakpointOptions(minWidth); + this._updateOptions(resolvedOptions); + } + }; + + // TODO a ton of duplication with bindMatchmedia + Viewer.prototype._getCurrentMinWidth = function(bps){ + // bind using the minwidth and the next breakpoint as the maxwidth + // adds a range from 0 to the first breakpoint and from the last + // breakpoint to a very large number + for( var i = -1; i < bps.length; i++ ){ + minWidth = bps[i] || 0; + maxWidth = bps[i+1] || 100000000; + + if( window.matchMedia ){ + // bind using the minwidth and maxwidth so we get the events at the boundaries + // so we can asses which set of options applies. + var query = `(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`; + + // create a media list to bind to + var initList = window.matchMedia(query); + + if(initList && initList.matches) { + return minWidth; + } + } + } + + return false; + }; + + Viewer.prototype._resolveBreakpointOptions = function(minWidth){ + // TODO bind on the reduce callback was not working + var self = this; + var bps, bpConfig; + + if( this._fullscreen.isFullscreen() ) { + bps = this._fullscreenBreakpoints; + bpConfig = self._options.fullscreenBreakpoints; + } else { + bps = this._breakpoints; + bpConfig = self._options.breakpoints; + } + + if(!minWidth){ + minWidth = self._getCurrentMinWidth(bps); + } + + // There may be no matching breakpoints when this method is called to + // resolve the current options. If that's the case then we need to default + // to the top level options + if(!bps.length){ return self._originalOptions; } + + return bps.reduce(function(acc, bp){ + var cloned = Akamai.Util.extend(true, {}, acc); + var ptions; + + + if(bp <= minWidth){ + cloned = Akamai.Util.extend(true, cloned, bpConfig[bp]); + } + + return cloned; + }, Akamai.Util.extend(true, {}, this._originalOptions)); + }; + + Viewer._extendCarouselOptions = function(options){ + // clone the carousel options for the given breakpoint + var clonedCarousel = Akamai.Util.extend(true, {}, options.carousel || {}); + + // TODO shared code with constructor + // extend the cloned options with the breakpoint options so that all the + // child component config is attached to the carousel config (also happens + // in the constructor ) + return Akamai.Util.extend(true, clonedCarousel, { + magnifier: options.magnifier, + spin360: options.spin360, + video: options.video, + fullscreen: options.fullscreen + }); + }; + + Viewer.prototype._updateOptions = function(options){ + this._options = Akamai.Util.extend(true, this._options, options); + + // extend the carousel options for the given breakpoint + var carouselOptions = Viewer._extendCarouselOptions(options); + + // push the final options down to the dom element so that CSS that keys off + // of the attributes can apply when JS config is used + Akamai.Util.setDataAttrOptions(this._$el, Viewer.defaultOptions, options, "Viewer"); + + // pass the new options down to all child carousels + this._carousels.forEach(function(comp){ + comp._updateOptions(carouselOptions); + }.bind(this)); + }; + + Viewer.prototype._checkJSONUrls = function(obj){ + var hostnames = this._options.items.hostnames; + + return Akamai.Util.mapJSONVals(obj,function(val, key){ + if(key === "url" && !this._urlHostnameMatch(val)){ + throw new Error(`The URL \`${val}\` does not match this page's hostname or the whitelist defined in Akamai.Viewer option \`hostnames\` which is: + +${hostnames.length ? hostnames.join("\n") : "No hostnames"} +`); + } + + return val; + }.bind(this)); + }; + + Viewer.prototype._urlHostnameMatch = function(url){ + var parser = document.createElement('a'); + parser.href = url; + + return parser.hostname === "" || parser.hostname === window.location.hostname || + this._options.items.hostnames.indexOf(parser.hostname) >= 0; + }; + + /** + * Defines the global default options for all Viewers on the page + * @static + * @property {Object} breakpoints - configuration changes for child components at breakpoints (no default) + * @property {Object} fullscreenBreakpoints - configuration changes for child components at breakpoints when in fullscreen mode (no default) + * @property {Object[]} items.data - array of items from the Akamai JSON (default: undefined) + * @property {String} items.defaultTag - (default: "akamai-untagged") + * @property {String[]} items.hostnames - whitelist of URL hostnames to check for in JSON, (default: empty array) + * @property {Integer} items.limit - size limit of JSON data in kibibytes (default: 100) + * @property {String} items.renderAll - (default: false) + * @property {String[]} items.tags - Set of tags to match against the Akamai JSON data (default: undefined) + * @property {String} items.uri - URI at which to retrieve the Akamai JSON (default: undefined) + * @property {Object} carousel - child {@link Akamai.Carousel} options + * @property {Object} magnifier - child {@link Akamai.Magnifier} options + * @property {Object} spin360 - child {@link Akamai.Spin360} options + * @property {Object} video - child {@link Akamai.Video} options + * @property {Object} fullscreen - child {@link Akamai.Fullscreen} options + */ + Viewer.defaultOptions = { + breakpoints: {}, + items: { + data: undefined, + defaultTag: "akamai-untagged", + hostnames: [], + limit: 100, + renderAll: false, + tags: undefined, + uri: undefined + }, + carousel: Akamai.Carousel.defaultOptions, + magnifier: Akamai.Magnifier.defaultOptions, + spin360: Akamai.Spin360.defaultOptions, + video: Akamai.Video.defaultOptions, + fullscreen: Akamai.Fullscreen.defaultOptions + }; + + Viewer._tagSplit = function(data, options){ + var items = options; + + // set all items without a tag to the default + data = data.map(function(item){ + item.tags = (item.tags && item.tags.length) ? item.tags : [items.defaultTag]; + return item; + }); + + // if the tags option was set, filter items out that don't match + if( items.tags ) { + data = items.data.filter(function(item){ + return item.tags.reduce(function(acc, tag){ + return acc || items.tags.indexOf(tag) >= 0; + }, false); + }); + } + + // from the filtered get the first (may be default tag) + var def = data[0].tags[0]; + + // create a mapping from tags to items in the set + var tagMapping = data.reduce(function(acc, item){ + (item.tags || []).forEach(function(tag){ + acc[tag] = acc[tag] || []; + acc[tag].push(item); + }); + + return acc; + }, {}); + + // set the first (default) object to the first tag + tagMapping[Viewer._firstRenderTag] = tagMapping[def]; + + return tagMapping; + }; + + Viewer._firstRenderTag = "akamai-first-render"; + Viewer._tagAttr = `${Viewer._dataAttr}-tag`; + + Viewer._renderTag = function(data, options){ + // get the first item and it's tag + // NOTE this assumes that the data has been normalized to have a default tag + var tag = data[0].tags[0]; + + // TODO shoestring doesn't treat html correctly unless the leading `<` has + // no whitepsace in front of it: + // shoestring/issues/94 + return Akamai.Util.trim(` +
+ ${Akamai.Carousel.render(data, options)} +
+ `); + }; + + /** + * Takes standard options including an `items` attribute and renders HTML + * that conforms to the component expectations + * @method + * @static + * @params {Object[]} json - Akamai JSON data + * @params {Object} options - options object with an `items` attribute, see default options + * @params {Boolean} options.items - configuration for how the items should be + * rendered, see {@link Akamai.Viewer.defaultOptions} default options items sub-configuration + * @returns { String } + */ + Viewer.render = function(json, options, unwrapped){ + // NOTE we do not extend the default options here because it has no bearing + // on the rendering of the Viewer markup or child markup. This is contrast + // to the carousel which does extend the passed options with defaults so the + // markup can make use of them + var data = Viewer._tagSplit(json, options.items); + var carouselOptions = Viewer._extendCarouselOptions(options); + + var childMarkup; + + if( options.items.renderAll ) { + childMarkup = data.map(function(acc, datum){ + return Viewer._renderTag(datum, carouselOptions); + }).join(" "); + } else { + childMarkup = Viewer._renderTag(data[Viewer._firstRenderTag], carouselOptions); + } + + var wrapped = ` +
+ ${childMarkup} +
+ `; + + return unwrapped ? childMarkup : wrapped ; + }; + + /** + * Accessor for child carousel components. + * @method + * @returns { Akamai.Carousel[] } + */ + Viewer.prototype.getCarousels = function(){ + return this._carousels; + }; + + /** + * Set the visible carousel based on the tag. Note the method activity does + * not complete until all the images from the relevant viewer have loaded, use + * the `akamai-viewer-switch-tag` event + * @method + * @fires Akamai.Viewer#akamai-viewer-switch-tag + * @params {String} tag - The tag corresponding to the desired carousel + * @returns {undefined} + */ + Viewer.prototype.switchTag = function(tag){ + var tags = this.getTags(); + + // if the passed tag isn't in the data set return early and log an error in + // the console + if( tags.indexOf(tag) == -1){ + Akamai.Util.log(`tag: ${tag} is not present in the data for this viewer`, 'error'); + return; + } + + var selector = `[${this.constructor._tagAttr}="${tag}"]`; + + // try to find an existing element with the tag + var $taggedViewer = this._$el.find(selector); + + // if there's no element append the newly rendered tag markup + if( !$taggedViewer.length ){ + // make sure the config takes into account the current breakpoint + var options = this._resolveBreakpointOptions(); + var carouselOptions = Viewer._extendCarouselOptions(options); + + // create the markup that will be inserted + var $markup = $(this.constructor._renderTag(this._tagMapping[tag], carouselOptions, true)); + var $imgs = $markup.find("img"); + var imgCount = $imgs.length; + var loaded = 0; + + // hide the new carousel initially + $markup.css("display", "none"); + + $imgs.bind("load", function(){ + if( ++loaded !== imgCount ) { + return; + } + + // TODO namespace? + $imgs.unbind("load"); + + var carousels = Akamai.Carousel.createMany($markup[0], carouselOptions); + + carousels.forEach(function(c){ + // TODO figure out why the carousel constructor doesn't apply the + // active index class we want on instantiation, likely due to the fact + // that the carousel is hidden so the "getIndex" calc is broken + // mark the carousel item as active + c.goto(c.getIndex()); + }); + + // create and store the new carousels (should be one) + this._carousels = this._carousels.concat(carousels); + this._showViewer($markup, carousels); + }.bind(this)); + + // append the new markup to the existing viewer + this._$el.append($markup); + } else { + this._showViewer($taggedViewer); + } + }; + + // TODO should be handled in CSS + Viewer.prototype._showViewer = function($viewer, carousels){ + this._$el + .find(`[${this.constructor._tagAttr}]`) + .css("display", "none") + .removeClass("focused"); + + $viewer + .css("display", "block") + .addClass("focused"); + + if( carousels ){ + carousels.map(function(c) { c.refresh(); }); + } + + // TODO the placement here seems arbitrary, probably belongs in `_showViewer` + // TODO also sucks to be so tightly coupled + this._fullscreen.addButton(); + + this._trigger( "switch-tag" ); + }; + + /** + * Accessor for tags derived from Akamai JSON data + * @method + * @returns { String[] } + */ + Viewer.prototype.getTags = function(){ + if( this._tags ) { + return this._tags; + }; + + var tags = []; + + // map and store all the carousel tags + for(var tag in this._tagMapping){ + if(this._tagMapping.hasOwnProperty(tag) && tag !== Viewer._firstRenderTag ) { + // otherwise grab the list of tags + tags.push(tag); + } + } + + return this._tags = tags; + }; + + /** + * Triggered when the viewer switches tag views. This includes waiting for + * images to load for carousels dedicated to previously unviewed tags. + * {@link Akamai.Viewer#switchTag}. + * + * @event Akamai.Viewer#akamai-viewer-switch-tag + */ + + /** + * Triggered when initialization finishes + * {@link Akamai.Viewer}. + * + * @event Akamai.Viewer#akamai-viewer-init + */ + + exports.Akamai = exports.Akamai || {}; + exports.Akamai.Viewer = Viewer; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(exports, $){ + $.fn.akamaiViewer = function(options){ + this.each(function(i, element){ + new Akamai.Viewer(element, options); + }); + }; +})(typeof exports === 'undefined'? window : exports, this.jQuery); + +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.action = options.action; + this.container = options.container; + this.emitter = options.emitter; + this.target = options.target; + this.text = options.text; + this.trigger = options.trigger; + + this.selectedText = ''; + } + }, { + key: 'initSelection', + value: function initSelection() { + if (this.text) { + this.selectFake(); + } else if (this.target) { + this.selectTarget(); + } + } + }, { + key: 'selectFake', + value: function selectFake() { + var _this = this; + + var isRTL = document.documentElement.getAttribute('dir') == 'rtl'; + + this.removeFake(); + + this.fakeHandlerCallback = function () { + return _this.removeFake(); + }; + this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true; + + this.fakeElem = document.createElement('textarea'); + // Prevent zooming on iOS + this.fakeElem.style.fontSize = '12pt'; + // Reset box model + this.fakeElem.style.border = '0'; + this.fakeElem.style.padding = '0'; + this.fakeElem.style.margin = '0'; + // Move element out of screen horizontally + this.fakeElem.style.position = 'absolute'; + this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; + // Move element to the same position vertically + var yPosition = window.pageYOffset || document.documentElement.scrollTop; + this.fakeElem.style.top = yPosition + 'px'; + + this.fakeElem.setAttribute('readonly', ''); + this.fakeElem.value = this.text; + + this.container.appendChild(this.fakeElem); + + this.selectedText = (0, _select2.default)(this.fakeElem); + this.copyText(); + } + }, { + key: 'removeFake', + value: function removeFake() { + if (this.fakeHandler) { + this.container.removeEventListener('click', this.fakeHandlerCallback); + this.fakeHandler = null; + this.fakeHandlerCallback = null; + } + + if (this.fakeElem) { + this.container.removeChild(this.fakeElem); + this.fakeElem = null; + } + } + }, { + key: 'selectTarget', + value: function selectTarget() { + this.selectedText = (0, _select2.default)(this.target); + this.copyText(); + } + }, { + key: 'copyText', + value: function copyText() { + var succeeded = void 0; + + try { + succeeded = document.execCommand(this.action); + } catch (err) { + succeeded = false; + } + + this.handleResult(succeeded); + } + }, { + key: 'handleResult', + value: function handleResult(succeeded) { + this.emitter.emit(succeeded ? 'success' : 'error', { + action: this.action, + text: this.selectedText, + trigger: this.trigger, + clearSelection: this.clearSelection.bind(this) + }); + } + }, { + key: 'clearSelection', + value: function clearSelection() { + if (this.trigger) { + this.trigger.focus(); + } + + window.getSelection().removeAllRanges(); + } + }, { + key: 'destroy', + value: function destroy() { + this.removeFake(); + } + }, { + key: 'action', + set: function set() { + var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy'; + + this._action = action; + + if (this._action !== 'copy' && this._action !== 'cut') { + throw new Error('Invalid "action" value, use either "copy" or "cut"'); + } + }, + get: function get() { + return this._action; + } + }, { + key: 'target', + set: function set(target) { + if (target !== undefined) { + if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) { + if (this.action === 'copy' && target.hasAttribute('disabled')) { + throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); + } + + if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) { + throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'); + } + + this._target = target; + } else { + throw new Error('Invalid "target" value, use a valid Element'); + } + } + }, + get: function get() { + return this._target; + } + }]); + + return ClipboardAction; + }(); + + module.exports = ClipboardAction; +}); + +},{"select":5}],8:[function(require,module,exports){ +(function (global, factory) { + if (typeof define === "function" && define.amd) { + define(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], factory); + } else if (typeof exports !== "undefined") { + factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener')); + } else { + var mod = { + exports: {} + }; + factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener); + global.clipboard = mod.exports; + } +})(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) { + 'use strict'; + + var _clipboardAction2 = _interopRequireDefault(_clipboardAction); + + var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter); + + var _goodListener2 = _interopRequireDefault(_goodListener); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; + } + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + var _createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + } + + var Clipboard = function (_Emitter) { + _inherits(Clipboard, _Emitter); + + /** + * @param {String|HTMLElement|HTMLCollection|NodeList} trigger + * @param {Object} options + */ + function Clipboard(trigger, options) { + _classCallCheck(this, Clipboard); + + var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this)); + + _this.resolveOptions(options); + _this.listenClick(trigger); + return _this; + } + + /** + * Defines if attributes would be resolved using internal setter functions + * or custom functions that were passed in the constructor. + * @param {Object} options + */ + + + _createClass(Clipboard, [{ + key: 'resolveOptions', + value: function resolveOptions() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.action = typeof options.action === 'function' ? options.action : this.defaultAction; + this.target = typeof options.target === 'function' ? options.target : this.defaultTarget; + this.text = typeof options.text === 'function' ? options.text : this.defaultText; + this.container = _typeof(options.container) === 'object' ? options.container : document.body; + } + }, { + key: 'listenClick', + value: function listenClick(trigger) { + var _this2 = this; + + this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) { + return _this2.onClick(e); + }); + } + }, { + key: 'onClick', + value: function onClick(e) { + var trigger = e.delegateTarget || e.currentTarget; + + if (this.clipboardAction) { + this.clipboardAction = null; + } + + this.clipboardAction = new _clipboardAction2.default({ + action: this.action(trigger), + target: this.target(trigger), + text: this.text(trigger), + container: this.container, + trigger: trigger, + emitter: this + }); + } + }, { + key: 'defaultAction', + value: function defaultAction(trigger) { + return getAttributeValue('action', trigger); + } + }, { + key: 'defaultTarget', + value: function defaultTarget(trigger) { + var selector = getAttributeValue('target', trigger); + + if (selector) { + return document.querySelector(selector); + } + } + }, { + key: 'defaultText', + value: function defaultText(trigger) { + return getAttributeValue('text', trigger); + } + }, { + key: 'destroy', + value: function destroy() { + this.listener.destroy(); + + if (this.clipboardAction) { + this.clipboardAction.destroy(); + this.clipboardAction = null; + } + } + }], [{ + key: 'isSupported', + value: function isSupported() { + var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut']; + + var actions = typeof action === 'string' ? [action] : action; + var support = !!document.queryCommandSupported; + + actions.forEach(function (action) { + support = support && !!document.queryCommandSupported(action); + }); + + return support; + } + }]); + + return Clipboard; + }(_tinyEmitter2.default); + + /** + * Helper function to retrieve attribute value. + * @param {String} suffix + * @param {Element} element + */ + function getAttributeValue(suffix, element) { + var attribute = 'data-clipboard-' + suffix; + + if (!element.hasAttribute(attribute)) { + return; + } + + return element.getAttribute(attribute); + } + + module.exports = Clipboard; +}); + +},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8) +}); +window.jQuery = window.jQuery || window.shoestring; + +(function( $ ) { + var xrayiframeid = 0; + var pluginName = "xrayhtml", + o = { + text: { + open: "View Source", + close: "View Demo", + titlePrefix: "Example", + antipattern: "Do Not Use" + }, + classes: { + button: "btn btn-small btn-xrayhtml-flipsource", + open: "view-source", + sourcepanel: "source-panel", + title: "xraytitle", + antipattern: "antipattern" + }, + initSelector: "[data-" + pluginName + "]", + defaultReveal: "inline" + }, + methods = { + _create: function() { + return $( this ).each(function() { + var init = $( this ).data( "init." + pluginName ); + + if( init ) { + return false; + } + + $( this ) + .data( "init." + pluginName, true ) + [ pluginName ]( "_init" ) + .trigger( "create." + pluginName ); + }); + }, + _init: function() { + var $self = $(this); + + $self.data( "id." + pluginName, xrayiframeid++); + + var method = $( this ).attr( "data-" + pluginName ) || o.defaultReveal; + + if( method === "flip" ) { + $( this )[ pluginName ]( "_createButton" ); + } + + $( this ) + .addClass( pluginName + " " + "method-" + method ) + [ pluginName ]( "_createSource" ); + + // use an iframe to host the source + if( $(this).is("[data-" + pluginName + "-iframe]") ){ + + // grab the snippet html to ship to the iframe + var snippetHTML = $(this).find(".snippet").html(); + + // grab the url of the iframe to load + var url = $(this).attr("data-" + pluginName + "-iframe"); + + // grab the selector for the element in the iframe to put the html in + var selector = $(this).attr("data-" + pluginName + "-iframe-target"); + + // create the iframe element, so we can bind to the load event + var $iframe = $("