6 | */
7 | var easings = module.exports = {
8 | easeOut: function (t) {
9 | return Math.sin(t * Math.PI / 2);
10 | }
11 |
12 | , easeOutStrong: function (t) {
13 | return (t == 1) ? 1 : 1 - Math.pow(2, -10 * t);
14 | }
15 |
16 | , easeIn: function (t) {
17 | return t * t;
18 | }
19 |
20 | , easeInStrong: function (t) {
21 | return (t == 0) ? 0 : Math.pow(2, 10 * (t - 1));
22 | }
23 |
24 | , easeOutBounce: function(pos) {
25 | if ((pos) < (1/2.75)) {
26 | return (7.5625*pos*pos);
27 | } else if (pos < (2/2.75)) {
28 | return (7.5625*(pos-=(1.5/2.75))*pos + .75);
29 | } else if (pos < (2.5/2.75)) {
30 | return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
31 | } else {
32 | return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
33 | }
34 | }
35 |
36 | , easeInBack: function(pos){
37 | var s = 1.70158;
38 | return (pos)*pos*((s+1)*pos - s);
39 | }
40 |
41 | , easeOutBack: function(pos){
42 | var s = 1.70158;
43 | return (pos=pos-1)*pos*((s+1)*pos + s) + 1;
44 | }
45 |
46 | , bounce: function (t) {
47 | if (t < (1 / 2.75)) {
48 | return 7.5625 * t * t;
49 | }
50 | if (t < (2 / 2.75)) {
51 | return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
52 | }
53 | if (t < (2.5 / 2.75)) {
54 | return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
55 | }
56 | return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
57 | }
58 |
59 | , bouncePast: function (pos) {
60 | if (pos < (1 / 2.75)) {
61 | return (7.5625 * pos * pos);
62 | } else if (pos < (2 / 2.75)) {
63 | return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75);
64 | } else if (pos < (2.5 / 2.75)) {
65 | return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375);
66 | } else {
67 | return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375);
68 | }
69 | }
70 |
71 | , swingTo: function(pos) {
72 | var s = 1.70158;
73 | return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
74 | }
75 |
76 | , swingFrom: function (pos) {
77 | var s = 1.70158;
78 | return pos * pos * ((s + 1) * pos - s);
79 | }
80 |
81 | , elastic: function(pos) {
82 | return -1 * Math.pow(4, -8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
83 | }
84 |
85 | , spring: function(pos) {
86 | return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
87 | }
88 |
89 | , blink: function(pos, blinks) {
90 | return Math.round(pos*(blinks||5)) % 2;
91 | }
92 |
93 | , pulse: function(pos, pulses) {
94 | return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
95 | }
96 |
97 | , wobble: function(pos) {
98 | return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
99 | }
100 |
101 | , sinusoidal: function(pos) {
102 | return (-Math.cos(pos*Math.PI)/2) + 0.5;
103 | }
104 |
105 | , flicker: function(pos) {
106 | var pos = pos + (Math.random()-0.5)/5;
107 | return easings.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos);
108 | }
109 |
110 | , mirror: function(pos) {
111 | if (pos < 0.5)
112 | return easings.sinusoidal(pos*2);
113 | else
114 | return easings.sinusoidal(1-(pos-0.5)*2);
115 | }
116 |
117 | };
--------------------------------------------------------------------------------
/src/stylesheets/mobile.styl:
--------------------------------------------------------------------------------
1 | instead-of-viewport-height-margin = 40px
2 |
3 | @keyframes code-repeat
4 | 0%
5 | margin-top 0%
6 | animation-timing-function linear
7 | 50%
8 | margin-top -50%
9 | animation-timing-function linear
10 | 100%
11 | margin-top -100%
12 | animation-timing-function linear
13 |
14 | // Landscape iPad
15 | @media (max-width: 1024px)
16 | #graph-wrapper h4
17 | margin-bottom -80px !important
18 | #intro-statement .half-viewport-height
19 | display none
20 | #foreground header
21 | width calc(40% - 40px)
22 |
23 | // Portrait iPad
24 | @media (max-width: 800px)
25 | #graph-wrapper h4
26 | margin-bottom -120px !important
27 | #foreground-content li h1
28 | br:nth-of-type(2)
29 | display none
30 | .faux-underline
31 | display inline !important
32 | &:before
33 | display none !important
34 | text-decoration underline !important
35 |
36 | // Phone
37 | @media (max-width: 640px)
38 | .phone-foreground-container header h1
39 | font-size 36px
40 | line-height 1em
41 | #intro-statement #intro-statement-inner
42 | position relative
43 | font-size 30px !important
44 | hr
45 | width 100%
46 | left 0
47 | #foreground
48 | display none
49 | .phone-foreground-container
50 | display block
51 | margin-bottom 30px
52 | header
53 | padding-top 30px
54 | .foreground-header-body
55 | margin-top 20px
56 | #intro-statement, #background
57 | padding 20px
58 | #intro-statement .half-viewport-height
59 | display none
60 | #intro-statement
61 | margin-top 40px
62 | margin-bottom 0
63 | #background
64 | ul
65 | width 100%
66 | li
67 | margin-bottom instead-of-viewport-height-margin !important
68 | #background .half-viewport-height, #background .viewport-height:not(#background-code-mask)
69 | height instead-of-viewport-height-margin !important
70 | #background-content li figure .source
71 | position relative
72 | display block
73 | float none
74 | .main + .source
75 | margin-top 1em
76 | #footer
77 | background-image url('images/bottom-image-small.jpg')
78 | #footer-header
79 | margin-top -180px
80 | #footer-links
81 | font-size 24px
82 | line-height 1.8em
83 | bottom 160px
84 | #footer-social-buttons
85 | botom 10px
86 | h4
87 | margin-bottom 0
88 | .social-button-container
89 | width 70px
90 | height 70px
91 | small
92 | width 83%
93 | #footer-social-buttons
94 | margin-bottom 5px
95 | .iScrollIndicator
96 | display none !important
97 | #background-code
98 | height 270px !important
99 | #background-code-mask
100 | height auto !important
101 | pre
102 | animation code-repeat 5s infinite
103 | #graph-container
104 | .viewport-height
105 | display none !important
106 | #graph
107 | position relative !important
108 | height 5% !important
109 | #graph-line
110 | stroke-array none !important
111 | stroke-dashoffset none !important
112 | #footer
113 | margin-top 0 !important
114 | .two-column-images.two-column-right
115 | padding-left 0
116 | .two-column-images.two-column-left
117 | padding-right 0
118 | #footer-header
119 | font-size 27px
120 | padding 0 20px
121 | bottom 340px
122 | line-height 1.1em
123 | figcaption
124 | .main, .source
125 | width 100% !important
126 | float none !important
127 | .source
128 | text-align left !important
129 | #footer-links
130 | bottom 190px !important
131 | line-height 1.5em !important
132 | #graph-wrapper
133 | position relative !important
134 | h4
135 | margin-top 0 !important
136 | line-height 1.4em
137 | #graph
138 | height 530px !important
--------------------------------------------------------------------------------
/src/templates/code.txt:
--------------------------------------------------------------------------------
1 | renderHeaderBackgrounds = function() {
2 | var i;
3 | $('#header-background ul').html(((function() {
4 | var _i, _results;
5 | _results = [];
6 | for (i = _i = 0; 0 <= TOTAL_HEADER_BACKGROUNDS ? _i <= TOTAL_HEADER_BACKGROUNDS : _i >= TOTAL_HEADER_BACKGROUNDS; i = 0 <= TOTAL_HEADER_BACKGROUNDS ? ++_i : --_i) {
7 | _results.push("");
8 | }
9 | return _results;
10 | })()).join(''));
11 | return $('#header-background li').first().show();
12 | };
13 |
14 | renderSocialShares = function() {
15 | var shareUrl;
16 | shareUrl = "http://2013.artsy.net/" || location.href;
17 | $.ajax({
18 | url: "http://api.facebook.com/restserver.php?method=links.getStats&urls[]=" + shareUrl,
19 | success: function(res) {
20 | return $('#social-button-facebook-count').html($(res).find('share_count').text() || 0).show();
21 | }
22 | });
23 | window.twitterCountJSONPCallback = function(res) {
24 | if (res.count == null) {
25 | return;
26 | }
27 | return $('#social-button-twitter-count').html(res.count || 0).show();
28 | };
29 | return $.ajax({
30 | url: "http://urls.api.twitter.com/1/urls/count.json?url=" + shareUrl + "&callback=twitterCountJSONPCallback",
31 | dataType: 'jsonp'
32 | });
33 | };
34 |
35 | setupIScroll = function() {
36 | $wrapper.height(viewportHeight);
37 | myScroll = new IScroll('#wrapper', {
38 | probeType: 3,
39 | mouseWheel: true
40 | });
41 | myScroll.on('scroll', setScrollTop);
42 | myScroll.on('scrollEnd', setScrollTop);
43 | myScroll.on('scroll', onScroll);
44 | myScroll.on('scrollEnd', onScroll);
45 | return document.addEventListener('touchmove', (function(e) {
46 | return e.preventDefault();
47 | }), false);
48 | };
49 |
50 | offset = function($el) {
51 | var top, _ref, _ref1, _ref2;
52 | top = -(((_ref = $scroller.offset()) != null ? _ref.top : void 0) - ((_ref1 = $el.offset()) != null ? _ref1.top : void 0));
53 | return {
54 | top: top,
55 | left: (_ref2 = $el.offset()) != null ? _ref2.left : void 0,
56 | bottom: top + $el.height()
57 | };
58 | };
59 |
60 | onClickHeaderDownArrow = function() {
61 | myScroll.scrollToElement('#content', 1200, null, null, IScroll.utils.ease.quadratic);
62 | return false;
63 | };
64 |
65 | shareOnFacebook = function(e) {
66 | var opts, url;
67 | mixpanel.track("Shared on Facebook");
68 | opts = "status=1,width=750,height=400,top=249.5,left=1462";
69 | url = "https://www.facebook.com/sharer/sharer.php?u=" + location.href;
70 | window.open(url, 'facebook', opts);
71 | return false;
72 | };
73 |
74 | shareOnTwitter = function(e) {
75 | var $curHeader, opts, text, url;
76 | mixpanel.track("Shared on Twitter");
77 | opts = "status=1,width=750,height=400,top=249.5,left=1462";
78 | $curHeader = $("#foreground li[data-slug='" + (location.hash.replace('#', '')) + "'] h1");
79 | text = encodeURIComponent($curHeader.text() + ' | ' + $('title').text());
80 | url = "https://twitter.com/intent/tweet?" + ("original_referer=" + location.href) + ("&text=" + text) + ("&url=" + location.href);
81 | window.open(url, 'twitter', opts);
82 | return false;
83 | };
84 |
85 | onScroll = function() {
86 | popLockForeground();
87 | fadeBetweenForegroundItems();
88 | fadeOutHeaderImage();
89 | fadeInFirstForegroundItem();
90 | return popLockCodeMask();
91 | };
92 |
93 | setScrollTop = function() {
94 | return scrollTop = -(this.y >> 0);
95 | };
96 |
97 | fadeBetweenForegroundItems = function() {
98 | return $backgroundItems.each(function() {
99 | var $curItem, $nextItem, elBottom, elTop, endPoint, firstMidPoint, index, midPoint, nextTop, percentNextItem, percentPrevItem, startPoint, viewportBottom;
100 | index = $(this).index();
101 | $curItem = $foregroundItems.eq(index);
102 | $nextItem = $foregroundItems.eq(index + 1);
103 | viewportBottom = scrollTop + viewportHeight;
104 | elTop = offset($(this)).top;
105 | elBottom = offset($(this)).bottom;
106 | nextTop = offset($(this).next()).top;
107 | startPoint = elBottom + startOffest(viewportHeight);
108 | endPoint = nextTop + endOffest(viewportHeight);
109 | midPoint = (endPoint - startPoint) * MID_FADE_PERCENT + startPoint;
110 | firstMidPoint = midPoint - (viewportHeight * GAP_PERCENT_OF_VIEWPORT) * FADE_GAP_OF_BLACK;
111 | if (scrollTop > elTop && viewportBottom < elBottom) {
112 | $foregroundItems.removeClass('foreground-item-active');
113 | return $curItem.css({
114 | opacity: 1
115 | }).addClass('foreground-item-active');
116 | } else if (viewportBottom > startPoint && viewportBottom < endPoint) {
117 | percentPrevItem = 1 - (viewportBottom - startPoint) / (firstMidPoint - startPoint);
118 | percentNextItem = (viewportBottom - midPoint) / (endPoint - midPoint);
119 | $curItem.css({
120 | opacity: percentPrevItem
121 | });
122 | return $nextItem.css({
123 | opacity: percentNextItem
124 | });
125 | }
126 | });
127 | };
128 |
129 | transitionHeaderBackground = function() {
130 | $headerLogo.addClass('active');
131 | return setTimeout(function() {
132 | return setTimeout(function() {
133 | var $cur, $next, index, nextIndex;
134 | index = $($headerBackgrounds.filter(function() {
135 | return $(this).hasClass('active');
136 | })[0]).index();
137 | nextIndex = index + 1 >= TOTAL_HEADER_BACKGROUNDS ? 0 : index + 1;
138 | $cur = $($headerBackgrounds.eq(index));
139 | $next = $($headerBackgrounds.eq(nextIndex));
140 | $cur.removeClass('active');
141 | $next.addClass('active');
142 | return transitionHeaderBackground();
143 | }, 700);
144 | }, 1000);
145 | };
--------------------------------------------------------------------------------
/src/scripts/vendor/zepto.touch.js:
--------------------------------------------------------------------------------
1 | // Zepto.js
2 | // (c) 2010-2014 Thomas Fuchs
3 | // Zepto.js may be freely distributed under the MIT license.
4 |
5 | ;(function($){
6 | var touch = {},
7 | touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
8 | longTapDelay = 750,
9 | gesture
10 |
11 | function swipeDirection(x1, x2, y1, y2) {
12 | return Math.abs(x1 - x2) >=
13 | Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
14 | }
15 |
16 | function longTap() {
17 | longTapTimeout = null
18 | if (touch.last) {
19 | touch.el.trigger('longTap')
20 | touch = {}
21 | }
22 | }
23 |
24 | function cancelLongTap() {
25 | if (longTapTimeout) clearTimeout(longTapTimeout)
26 | longTapTimeout = null
27 | }
28 |
29 | function cancelAll() {
30 | if (touchTimeout) clearTimeout(touchTimeout)
31 | if (tapTimeout) clearTimeout(tapTimeout)
32 | if (swipeTimeout) clearTimeout(swipeTimeout)
33 | if (longTapTimeout) clearTimeout(longTapTimeout)
34 | touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null
35 | touch = {}
36 | }
37 |
38 | function isPrimaryTouch(event){
39 | return (event.pointerType == 'touch' ||
40 | event.pointerType == event.MSPOINTER_TYPE_TOUCH)
41 | && event.isPrimary
42 | }
43 |
44 | function isPointerEventType(e, type){
45 | return (e.type == 'pointer'+type ||
46 | e.type.toLowerCase() == 'mspointer'+type)
47 | }
48 |
49 | $(document).ready(function(){
50 | var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType
51 |
52 | if ('MSGesture' in window) {
53 | gesture = new MSGesture()
54 | gesture.target = document.body
55 | }
56 |
57 | $(document)
58 | .bind('MSGestureEnd', function(e){
59 | var swipeDirectionFromVelocity =
60 | e.velocityX > 1 ? 'Right' : e.velocityX < -1 ? 'Left' : e.velocityY > 1 ? 'Down' : e.velocityY < -1 ? 'Up' : null;
61 | if (swipeDirectionFromVelocity) {
62 | touch.el.trigger('swipe')
63 | touch.el.trigger('swipe'+ swipeDirectionFromVelocity)
64 | }
65 | })
66 | .on('touchstart MSPointerDown pointerdown', function(e){
67 | if((_isPointerType = isPointerEventType(e, 'down')) &&
68 | !isPrimaryTouch(e)) return
69 | firstTouch = _isPointerType ? e : e.touches[0]
70 | if (e.touches && e.touches.length === 1 && touch.x2) {
71 | // Clear out touch movement data if we have it sticking around
72 | // This can occur if touchcancel doesn't fire due to preventDefault, etc.
73 | touch.x2 = undefined
74 | touch.y2 = undefined
75 | }
76 | now = Date.now()
77 | delta = now - (touch.last || now)
78 | touch.el = $('tagName' in firstTouch.target ?
79 | firstTouch.target : firstTouch.target.parentNode)
80 | touchTimeout && clearTimeout(touchTimeout)
81 | touch.x1 = firstTouch.pageX
82 | touch.y1 = firstTouch.pageY
83 | if (delta > 0 && delta <= 250) touch.isDoubleTap = true
84 | touch.last = now
85 | longTapTimeout = setTimeout(longTap, longTapDelay)
86 | // adds the current touch contact for IE gesture recognition
87 | if (gesture && _isPointerType) gesture.addPointer(e.pointerId);
88 | })
89 | .on('touchmove MSPointerMove pointermove', function(e){
90 | if((_isPointerType = isPointerEventType(e, 'move')) &&
91 | !isPrimaryTouch(e)) return
92 | firstTouch = _isPointerType ? e : e.touches[0]
93 | cancelLongTap()
94 | touch.x2 = firstTouch.pageX
95 | touch.y2 = firstTouch.pageY
96 |
97 | deltaX += Math.abs(touch.x1 - touch.x2)
98 | deltaY += Math.abs(touch.y1 - touch.y2)
99 | })
100 | .on('touchend MSPointerUp pointerup', function(e){
101 | if((_isPointerType = isPointerEventType(e, 'up')) &&
102 | !isPrimaryTouch(e)) return
103 | cancelLongTap()
104 |
105 | // swipe
106 | if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
107 | (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
108 |
109 | swipeTimeout = setTimeout(function() {
110 | touch.el.trigger('swipe')
111 | touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
112 | touch = {}
113 | }, 0)
114 |
115 | // normal tap
116 | else if ('last' in touch)
117 | // don't fire tap when delta position changed by more than 30 pixels,
118 | // for instance when moving to a point and back to origin
119 | if (deltaX < 30 && deltaY < 30) {
120 | // delay by one tick so we can cancel the 'tap' event if 'scroll' fires
121 | // ('tap' fires before 'scroll')
122 | tapTimeout = setTimeout(function() {
123 |
124 | // trigger universal 'tap' with the option to cancelTouch()
125 | // (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
126 | var event = $.Event('tap')
127 | event.cancelTouch = cancelAll
128 | touch.el.trigger(event)
129 |
130 | // trigger double tap immediately
131 | if (touch.isDoubleTap) {
132 | if (touch.el) touch.el.trigger('doubleTap')
133 | touch = {}
134 | }
135 |
136 | // trigger single tap after 250ms of inactivity
137 | else {
138 | touchTimeout = setTimeout(function(){
139 | touchTimeout = null
140 | if (touch.el) touch.el.trigger('singleTap')
141 | touch = {}
142 | }, 250)
143 | }
144 | }, 0)
145 | } else {
146 | touch = {}
147 | }
148 | deltaX = deltaY = 0
149 |
150 | })
151 | // when the browser window loses focus,
152 | // for example when a modal dialog is shown,
153 | // cancel all ongoing events
154 | .on('touchcancel MSPointerCancel pointercancel', cancelAll)
155 |
156 | // scrolling the window indicates intention of the user
157 | // to scroll, not tap or swipe, so cancel all ongoing events
158 | $(window).on('scroll', cancelAll)
159 | })
160 |
161 | ;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown',
162 | 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(eventName){
163 | $.fn[eventName] = function(callback){ return this.on(eventName, callback) }
164 | })
165 | })(Zepto);
--------------------------------------------------------------------------------
/src/templates/graph-svg.html:
--------------------------------------------------------------------------------
1 |
92 |
--------------------------------------------------------------------------------
/src/templates/content.jade:
--------------------------------------------------------------------------------
1 | extends content-layout
2 |
3 | //- Janurary: Sales Distance
4 | append foreground
5 | +fg-item('January', 'A World View')
6 | p In 2013, people from over 180 countries visited Artsy.
7 | p In January, artworks were sold between New York and Oslo, London and Los Angeles, Singapore and Houston through Artsy. This year, the average distance between a buyer and a seller was 2,086 miles.
8 |
9 | append background
10 | +bg-item
11 | img( src='images/content/map-small.jpg' )
12 | .half-viewport-height
13 |
14 | //- Feburary: Eye Contact
15 | append foreground
16 | +fg-item('February', 'Mind’s Eye')
17 | p This year, we added our 1,000th “gene” to The Art Genome Project, the classification system that powers Artsy.
18 | p Over the course of 2013, our Art Genome team continued to map the characteristics that define art, applying over 700,000 of them to over 44,000 artworks.
19 | p In February, we added one of our favorite new genes: “Eye Contact.”
20 | a.faux-underline( href="http://artsy.net/gene/eye-contact", target='_blank' ) Explore Eye Contact
21 | append background
22 | +bg-item
23 | ul.two-column-images.two-column-left
24 | li
25 | img( src='images/content/eye-contact-thomas-small.jpg' )
26 | p Mickalene Thomas
27 | em Qusuquzah, Une Trés Belle Négresse 1,
28 | span 2012
29 | p Lehmann Maupin
30 | p $18,500
31 | li
32 | img( src='images/content/eye-contact-longo-small.jpg' )
33 | p Robert Longo
34 | em Untitled (Leo)
35 | p Christie's Post-War & Contemporary Art
36 | p $250,000 - 350,000
37 | li
38 | img( src='images/content/eye-contact-abramovic-small.jpg' )
39 | p Marina Abramović
40 | em Golden Lips,
41 | span 2009
42 | p Sean Kelly Gallery
43 | ul.two-column-images.two-column-right
44 | li
45 | img( src='images/content/eye-contact-vermeer-small.jpg' )
46 | p Johannes Vermeer
47 | em Girl with the Red Hat,
48 | span ca. 1665/1666
49 | p National Gallery of Art, Washington D.C.
50 | li
51 | img( src='images/content/eye-contact-close-small.jpg' )
52 | p Chuck Close
53 | em Untitled (Kate - 16),
54 | span 2011
55 | p Adamson Gallery
56 | p $38,000
57 | li
58 | img( src='images/content/eye-contact-roversi-small.jpg' )
59 | p Paolo Roversi
60 | em Tanel Bedrossiantz (Jean Paul Gaultier’s “Barbès” women’s ready-to-wear fall-winter collection of 1984–85),
61 | span 1992
62 | p Brooklyn Museum
63 | .half-viewport-height
64 |
65 | //- March: The Armory Show
66 | append foreground
67 | +fg-item('March', 'The Armory Show')
68 | p Over 200,000 visitors from around the world viewed artworks from The Armory Show more than 10 million times on Artsy.
69 | p In 2013, we featured 17 international art fairs, including Art Basel in Basel, Hong Kong, and Miami, Frieze London, ArtRio and Contemporary Istanbul.
70 | a.faux-underline( href="http://artsy.net/thearmoryshow", target='_blank' ) Revisit The Armory Show
71 | append background
72 | +bg-item
73 | figure
74 | img( src='images/content/fairs-armory-small.jpg' )
75 | figcaption
76 | .main The Armory Show at Pier 92 and Pier 94 in New York
77 |
78 | figure
79 | img( src='images/content/fairs-dm-small.jpg' )
80 | figcaption
81 | .main Design Miami — Formlessfinder's “Tent Pile”
82 | .source Photo: James Harris courtesy Design Miami
83 |
84 | figure
85 | img( src='images/content/fairs-frieze-small.jpg' )
86 | figcaption
87 | .main Frieze — Geometric Mirrors VI by Jeppe Hein in the Frieze Sculpture park
88 | .source © Iwan Baan
89 |
90 | //- April: Turrell
91 | append foreground
92 | +fg-item('April', 'James Turrell at
the Guggenheim')
93 | p In April, we brought the highly-anticipated James Turrell show online through our partnership with the Guggenheim Museum.
94 | p Artsy now partners with over 200 institutions, including The British Museum, J. Paul Getty Museum, Calder Foundation, and Fondation Beyeler.
95 | a.faux-underline( href="https://artsy.net/guggenheim", target='_blank' ) The Guggenheim Museum on Artsy
96 | append background
97 | +bg-item
98 | figure
99 | img( src='images/content/turrell-purple-small.jpg' )
100 | figcaption
101 | .source © James Turrell / Rendering: Andreas Tjeldflaat, 2012 Solomon R. Guggenheim Foundation
102 | figure
103 | img( src='images/content/turrell-blue-small.jpg' )
104 |
105 | //- May: Ragnar Relay
106 | append foreground
107 | +fg-item('May', 'Ragnar Relay')
108 | p In May, we completed the Ragnar Relay, an overnight team relay race. 26 hours and 52 minutes of continuous running. 192 miles covered. 12 people still friends after spending 36 hours in a van together. Less impressive, we were beaten by teams “Fanny Pack Gold” and “U.S. Department of Minimal Effort.”
109 | a.faux-underline( href="https://artsy.net/about/jobs", target='_blank' ) Run the Ragnar Relay with us in 2014
110 | append background
111 | +bg-item
112 | ul.may-images.two-column-images.two-column-left
113 | li
114 | img( src='images/content/ragnar-pose-small.jpg' )
115 | li
116 | img( src='images/content/ragnar-camp-small.jpg' )
117 | li
118 | img( src='images/content/ragnar-carter-small.jpg' )
119 | ul.may-images.two-column-images.two-column-right
120 | li
121 | img( src='images/content/ragnar-party-small.jpg' )
122 | li
123 | img( src='images/content/ragnar-woot-small.jpg' )
124 | li
125 | img( src='images/content/ragnar-group-small.jpg' )
126 | img( src='images/content/ragnar-course-small.jpg' )
127 |
128 | //- June: La Biennale di Venezia
129 | append foreground
130 | +fg-item('June', 'La Biennale di Venezia')
131 | p In June, Artsy celebrated the 55th Venice Biennale with an in-depth look at 77 national pavilions and exhibitions, from Angola to Uruguay. We created a place to explore and learn about one of the art world’s most important events.
132 | a.faux-underline( href="http://artsy.net/feature/the-55th-venice-biennale", target='_blank' ) Revisit the 55th Venice Biennale
133 | append background
134 | +bg-item
135 | figure
136 | img( src='images/content/venice-1-small.jpg' )
137 | figcaption
138 | .main Richard Mosse, Irish Pavilion
139 | .source © Courtesy of the artist and Jack Shainman Gallery. Photo © Tom Powel Imaging inc
140 | figure
141 | img( src='images/content/venice-2-small.jpg' )
142 | figcaption
143 | .main Walter De Maria, Apollo’s Ecstasy, 1990
144 | .source © Photograph by Alex John Beck for Artsy
145 | figure
146 | img( src='images/content/venice-3-small.jpg' )
147 | figcaption
148 | .main Phyllida Barlow, Untitled: hanginglumpcoalblack, 2012
149 | .source © Photograph by Alex John Beck for Artsy
150 |
151 | //- July: Open Source
152 | append foreground
153 | +fg-item('July', 'An Open-Source Milestone')
154 | p In July, we released our 30th open-source project. We love open source at Artsy—it lets us build better software and contribute back to the community. We even open sourced
155 | | the code used to build the 2013 annual report that you are currently reading.
156 | a.faux-underline( href="http://artsy.github.io/open-source", target='_blank' ) See all of Artsy’s open-source projects
157 | append background
158 | +bg-item
159 | #background-code
160 | .viewport-height
161 | pre
162 | include code.txt
163 | .viewport-height
164 | img#background-code-mask.viewport-height( src='images/logo-mask.svg')
165 |
166 | //- August: Art & Science
167 | append foreground
168 | +fg-item('August', 'Art Meets Science')
169 | p In August, an
170 | a.faux-underline( href='https://artsy.net/artist/damon-zucconi', target="_blank" ) artist on Artsy
171 | | joined our engineering team—Artsy’s seventh engineer-artist crossover. We are now 66 engineers, art historians, designers, curators, writers, mathematicians … and artists. 40 of us are women, 26 are men, 9 of us are parents, and 8 are trained dancers.
172 | a.faux-underline( href="https://artsy.net/about/jobs", target='_blank' ) Join the Artsy team
173 | append background
174 | +bg-item
175 | figure
176 | img( src='images/content/damon-small.jpg' )
177 | figcaption.artist-info
178 | p Damon Zucconi
179 | p Tetradic Edit, 2013
180 | p JTT Gallery
181 | .half-viewport-height
182 |
183 | //- September: Art in your pocket
184 | append foreground
185 | +fg-item('September', 'Art in Your Pocket')
186 | p In September, we shrunk Artsy. 120,000 people downloaded our iPhone app, named a “Best New App” by Apple. Art lovers have viewed and saved more than 4 million artworks since the release.
187 | a.faux-underline( href="http://iphone.artsy.net/", target='_blank' ) Download the Artsy iPhone app
188 | append background
189 | +bg-item
190 | #iphone-screens
191 | img( src='images/content/iphone-screens-1-small.jpg' )
192 | img( src='images/content/iphone-screens-2-small.jpg' )
193 | img( src='images/content/iphone-screens-3-small.jpg' )
194 |
195 | //- October: Art in Schools
196 | append foreground
197 | +fg-item('October', 'Art in Schools')
198 | p In October, we were honored to become an official part of public education in New York City through
199 | a.faux-underline( href="http://www.nyc.gov/html/digital/html/news/digital-ready.shtml" ) Digital Ready
200 | | —a new NYC Department of Education digital literacy program for high school students.
201 | p More than 25,000 open-access images on Artsy are freely downloadable for educational use.
202 | a.faux-underline( href="http://artsy.net/feature/artsy-education", target='_blank' ) Learn more about Artsy Education
203 | append background
204 | +bg-item
205 | figure
206 | img( src='images/content/doe-kids-small.jpg' )
207 | figcaption
208 | .main Three students at Brooklyn International High School, one of ten schools participating in New York City’s Digital Ready program.
209 | a.source( href='http://www.flickr.com/photos/leprechaunspade/' ) Photo: Terence McCormack
210 | figure
211 | img( src='images/content/doe-meeting-small.jpg' )
212 | figcaption
213 | .main Mayor Bloomberg and Chancellor Walcott announce the expansion of New York City’s Digital Roadmap for New York schools.
214 | .source Photo: nyc.gov
215 |
216 | //- November: Better tools for Benefit Auctions
217 | append foreground
218 | +fg-item('November', 'New Tools for Benefit Auctions')
219 | p In November, we partnered with the nonprofit Independent Curators International (ICI) to support their annual benefit auction. Anyone from around the world could take part from a computer, tablet, or mobile phone. It was exciting to see live bids coming in from places like Oslo and Santa Monica, projected on big screens at the gala event.
220 | p All the lots sold, and we helped raise 50% more than ICI’s target revenue goal to support their educational programming.
221 | a.faux-underline( href="https://artsy.net/about/partnering-with-artsy", target='_blank' ) How we work with our partners
222 | append background
223 | +bg-item
224 | figure
225 | img( src='images/content/auction-phone-small.jpg' )
226 | figcaption
227 | .source Photos: Elliot Black Photography
228 | img( src='images/content/auction-ici-small.jpg' )
229 | img( src='images/content/auction-ici-2-small.jpg' )
230 |
231 | //- December: Gallery meets Collector
232 | append foreground
233 | +fg-item('December', 'Gallery, Meet Collector')
234 | p When a collector is interested in an artwork listed by a gallery on Artsy, we put them in touch. In the first week of December, we made more direct introductions between collectors and galleries than we did in all of 2012.
235 | a.faux-underline( href="http://artsy.net/filter/artworks/sort=-date_added&page=1", target='_blank' ) Find your new favorite work on Artsy
236 | append background
237 | +bg-item
238 | #graph-container
239 | #graph-wrapper
240 | h4 Monthly Collector Introductions to Galleries
241 | include graph-svg.html
242 | .viewport-height
243 | .viewport-height
244 |
--------------------------------------------------------------------------------
/src/scripts/index.coffee:
--------------------------------------------------------------------------------
1 | _ = require 'underscore'
2 | IScroll = require 'iscroll/build/iscroll-probe.js'
3 | require './vendor/zepto.js'
4 | require './vendor/zepto.touch.js'
5 | morpheus = require 'morpheus'
6 | easings = require './vendor/morpheus-easings.js'
7 |
8 | # Constants
9 | # ---------
10 |
11 | MIXPANEL_ID = "297ce2530b6c87b16195b5fb6556b38f"
12 |
13 | # The time it takes to scroll to an element with iscroll
14 | SCROLL_TO_EL_TIME = 700
15 |
16 | # The gap between items is based on the viewport size
17 | GAP_PERCENT_OF_VIEWPORT = 0.6
18 |
19 | # The gap between the content and the header
20 | CONTENT_GAP_PERCENT_OF_VIEWPORT = 0.8
21 |
22 | # The gap between fades of each item. e.g. 0.5 will mean the fade out of the first
23 | # item will end right when the fade in of the next item starts.
24 | FADE_GAP_OFFSET = 0.4
25 |
26 | # Shre on Twitter texts ordered by the content.
27 | TWITTER_TEXTS = [
28 | 'Turns out the average sale on Artsy travels over 2,000 miles. See the rest: 2013.artsy.net'
29 | 'Turns out @Artsy has a gene for Eye Contact, and it makes me uncomfortable: See the rest: 2013.artsy.net'
30 | 'Turns out @Artsy had over 10 million artworks viewed when it launched The Armory Show. See the rest: 2013.artsy.net'
31 | 'Turns out that @Artsy has partnered with over 200 institutions including The Getty and SFMOMA. See the rest: 2013.artsy.net'
32 | 'Turns out the @Artsy team ran 197 miles in 26 hours. But were beaten by team “Fanny Pack Gold.” See the rest: 2013.artsy.net'
33 | 'Turns out that JR (@JRart) made portraits of the entire @Artsy team and turned their office into an artwork. See the rest: 2013.artsy.net'
34 | 'Turns out that @Artsy has released 37 open-source projects: See the rest: 2013.artsy.net'
35 | 'Turns out 7 of @Artsy’s engineers are artists, and 1 is an artist on Artsy... so meta. See the rest: 2013.artsy.net'
36 | 'Turns out over 120,000 people downloaded the @Artsy iPhone app. See the rest: 2013.artsy.net'
37 | 'Turns out @Artsy’s 90,000 artworks are now part of NYC’s Public Schools Digital Literacy curriculum. See the rest: 2013.artsy.net'
38 | "Turns out @Artsy opened ICI's (@CuratorsINTL) benefit auction to the whole world. See the rest: 2013.artsy.net"
39 | 'Turns out @Artsy introed more collectors to galleries in the last week of December, than all of 2012. See the rest: 2013.artsy.net'
40 | ]
41 |
42 | IS_IPHONE = navigator.userAgent.match(/iPhone/i)
43 | IS_IPAD = navigator.userAgent.match(/iPad/i)
44 | IS_IOS6 = (IS_IPAD or IS_IPHONE) and navigator.userAgent.match('Version/6.0')
45 |
46 | # Top-level variables
47 | # -------------------
48 |
49 | # Cached elements
50 | $scroller = null
51 | $backgroundItems = null
52 | $foreground = null
53 | $mainHeader = null
54 | $content = null
55 | $wrapper = null
56 | $mainArrow = null
57 | $foregroundItems = null
58 | $footer = null
59 | $background = null
60 | $fgFacebookLink = null
61 | $fgTwitterLink = null
62 | $headerLogo = null
63 | $headerBackgrounds = null
64 | $firstForegroundItem = null
65 | $viewportHeights = null
66 | $halfViewportHeights = null
67 | $codeMask = null
68 | $code = null
69 | $headerBackground = null
70 | $headerGradient = null
71 | $graphWrapper = null
72 | $graphLine = null
73 | $facebookLinks = null
74 | $twitterLinks = null
75 | $graphContainer = null
76 | $window = null
77 | $body = null
78 | $imgs = null
79 |
80 | # Cached values
81 | totalHeaderBackgrounds = 0 # Used in slideshow
82 | currentItemIndex = 0 # The current index of the item being viewed
83 | graphLineLength = 0 # The total length of the graph line for SVG animation
84 | slideshowTimeout = null # Timeout until next slide is show
85 | stopSlideShow = false # Used to stop the slideshow after scrolling down
86 | myScroll = null # Reference to iScroll instance
87 | contentGap = 0 # The distance from the top of the page to the content
88 |
89 | # Use a custom scrollTop & viewportHeight variable in place of window references for
90 | # iScroll support.
91 | scrollTop = 0
92 | viewportHeight = 0
93 | viewportWidth = null
94 |
95 | # Setup functions
96 | # ---------------
97 |
98 | init = ->
99 | cacheElements()
100 | totalHeaderBackgrounds = $headerBackgrounds.length - 1
101 | setupGraph()
102 | $window.on 'resize', _.throttle onResize, 100
103 | onResize()
104 | adjustForDevices()
105 | setContentGap()
106 | renderSocialShares()
107 | refreshIScrollOnImageLoads()
108 | mixpanel.init MIXPANEL_ID
109 | mixpanel.track "Viewed page"
110 | copyForegroundContentToBackgroundForPhone()
111 | attachClickHandlers()
112 | revealOnFirstBannerLoad()
113 |
114 | adjustForDevices = ->
115 | # Use IScroll to handle scroll events on an IPad, otherwise normal scroll handlers.
116 | # Phone uses a more responsive technique which will just toggle off the `onScroll`
117 | # handler based on screen size.
118 | if IS_IPAD
119 | setupIScroll()
120 | else if not IS_IPHONE
121 | $window.on 'scroll', onScroll
122 | onScroll()
123 | $body.addClass 'ios6' if IS_IOS6
124 |
125 | setupGraph = ->
126 | graphLineLength = $graphLine[0].getTotalLength()
127 | $graphLine.css 'stroke-dasharray': graphLineLength
128 |
129 | revealOnFirstBannerLoad = ->
130 | firstHeader = $headerBackgrounds.first().css('background-image')
131 | firstHeader = firstHeader.replace('url(','').replace(')','')
132 | onLoadImg 'images/logo.png', 500, ->
133 | $('body').removeClass 'logo-loading'
134 | onLoadImg firstHeader, 3000, ->
135 | $('body').removeClass 'body-loading'
136 | setTimeout ->
137 | morpheus.tween 600, ((pos) =>
138 | $mainArrow.css { bottom: -100 + (pos * 100) }
139 | ), (->), easings.swingTo
140 | $mainArrow.css opacity: 1
141 | nextHeaderSlide()
142 | , 1000
143 |
144 | onLoadImg = (src, timeout, callback) ->
145 | callback = _.once callback
146 | image = new Image
147 | image.src = src
148 | image.onload = callback
149 | image.onerror = callback
150 | setTimeout callback, timeout
151 |
152 | renderSocialShares = ->
153 | shareUrl = location.href
154 | $.ajax
155 | url: "http://api.facebook.com/restserver.php?method=links.getStats&urls[]=#{shareUrl}"
156 | success: (res) ->
157 | $('#social-button-facebook-count')
158 | .html($(res).find('share_count').text() or 0).show()
159 | window.twitterCountJSONPCallback = (res) ->
160 | return unless res.count?
161 | $('#social-button-twitter-count').html(res.count or 0).show()
162 | $.ajax
163 | url: "http://urls.api.twitter.com/1/urls/count.json?url=#{shareUrl}&callback=twitterCountJSONPCallback"
164 | dataType: 'jsonp'
165 |
166 | setupIScroll = ->
167 | $body.addClass 'iscroll'
168 | $wrapper.height viewportHeight
169 | window.myScroll = myScroll = new IScroll '#wrapper',
170 | probeType: 3
171 | mouseWheel: true
172 | scrollbars: true
173 | interactiveScrollbars: true
174 | myScroll.on('scroll', onScroll)
175 | document.addEventListener 'touchmove', ((e) -> e.preventDefault()), false
176 |
177 | copyForegroundContentToBackgroundForPhone = ->
178 | $foregroundItems.each (i, el) ->
179 | $container = $backgroundItems.eq(i).find('.phone-foreground-container')
180 | $container.html(
181 | "" +
182 | $(el).html() +
183 | "
"
184 | )
185 |
186 | cacheElements = ->
187 | $scroller = $('#scroller')
188 | $backgroundItems = $('#background-content > li')
189 | $foreground = $('#foreground')
190 | $foregroundItems = $("#foreground li")
191 | $mainHeader = $('#main-header')
192 | $content = $('#content')
193 | $wrapper = $('#wrapper')
194 | $mainArrow = $('#main-header-down-arrow')
195 | $footer = $('#footer')
196 | $background = $('#background')
197 | $facebookLinks = $('.social-button-facebook')
198 | $twitterLinks = $('.social-button-twitter')
199 | $headerBackground = $('#header-background')
200 | $headerBackgrounds = $('#header-background li')
201 | $headerGradient = $('#header-background-gradient')
202 | $firstForegroundItem = $('#foreground li:first-child')
203 | $viewportHeights = $('.viewport-height')
204 | $halfViewportHeights = $('.half-viewport-height')
205 | $codeMask = $('#background-code-mask')
206 | $code = $('#background-code')
207 | $graphLine = $('#graph-line')
208 | $graphWrapper = $('#graph-wrapper')
209 | $graphContainer = $('#graph-container')
210 | $window = $(window)
211 | $body = $('body')
212 | $imgs = $('img')
213 |
214 | refreshIScrollOnImageLoads = ->
215 | $('#background img').on 'load', _.debounce (-> myScroll?.refresh()), 1000
216 |
217 | # Utility functions
218 | # -----------------
219 |
220 | # Used instead of $(el).offset to support IScroll
221 | offset = ($el) ->
222 | top = -($scroller.offset()?.top - $el.offset()?.top)
223 | {
224 | top: top
225 | left: $el.offset()?.left
226 | bottom: top + $el.height()
227 | }
228 |
229 | # Returns how far between scrolling two points you are. e.g. If you're halway between
230 | # the start point and end point this will return 0.5.
231 | percentBetween = (start, end) ->
232 | perc = 1 - (end - scrollTop) / (end - start)
233 | perc = 0 if perc < 0
234 | perc = 1 if perc > 1
235 | perc
236 |
237 | # Get scroll top from iScroll or plain ol' window
238 | getScrollTop = ->
239 | scrollTop = -myScroll?.getComputedPosition().y or $window.scrollTop()
240 |
241 | # Wrapper over IScroll's scrollToElement to use normal window animation.
242 | scrollToElement = (selector) ->
243 | time = 1000
244 | if myScroll
245 | myScroll.scrollToElement selector, time, null, null, IScroll.utils.ease.quadratic
246 | else
247 | elTop = $(selector).offset().top
248 | # Phone has trouble animating
249 | if viewportWidth <= 640
250 | $body[0].scrollTop = elTop
251 | else
252 | morpheus.tween time, ((pos) =>
253 | $body[0].scrollTop = elTop * pos
254 | ), easings.quadratic
255 |
256 |
257 | # Click handlers
258 | # --------------
259 |
260 | attachClickHandlers = ->
261 | $mainArrow.on 'tap click', onClickHeaderDownArrow
262 | $facebookLinks.on 'tap click', shareOnFacebook
263 | $twitterLinks.on 'tap click', shareOnTwitter
264 | $('a').on 'tap', followLinksOnTap
265 |
266 | onClickHeaderDownArrow = ->
267 | scrollToElement '#intro-statement-inner'
268 | false
269 |
270 | shareOnFacebook = (e) ->
271 | opts = "status=1,width=750,height=400,top=249.5,left=1462"
272 | url = "https://www.facebook.com/sharer/sharer.php?u=#{location.href}"
273 | open url, 'facebook', opts
274 | mixpanel.track "Shared on Facebook"
275 | false
276 |
277 | shareOnTwitter = (e) ->
278 | opts = "status=1,width=750,height=400,top=249.5,left=1462"
279 | text = if $(e.target).hasClass('final-twitter-button')
280 | "The Year in Artsy: 2013.artsy.net"
281 | else
282 | TWITTER_TEXTS[currentItemIndex]
283 | url = "https://twitter.com/intent/tweet?" +
284 | "original_referer=#{location.href}" +
285 | "&text=#{text}"
286 | open url, 'twitter', opts
287 | mixpanel.track "Shared on Twitter", { text: text }
288 | false
289 |
290 | followLinksOnTap = (e) ->
291 | e.preventDefault()
292 | _.defer -> open $(e.target).attr('href'), '_blank'
293 | false
294 |
295 | # On scroll functions
296 | # -------------------
297 |
298 | onScroll = ->
299 | return if viewportWidth <= 640 # For phone we ignore scroll transitions
300 | getScrollTop()
301 | toggleSlideShow()
302 | animateGraphLine()
303 | fadeOutHeaderImage()
304 | fadeInFirstForegroundItem()
305 | fadeBetweenBackgroundItems()
306 | popLockForeground()
307 | popLockCodeMask()
308 | popLockGraph()
309 |
310 | fadeBetweenBackgroundItems = ->
311 | for el, index in $backgroundItems
312 | $el = $ el
313 |
314 | # Alias current and next foreground items
315 | $curItem = $foregroundItems.eq(index)
316 | $nextItem = $foregroundItems.eq(index + 1)
317 |
318 | # Alias common positions we'll be calculating
319 | elTop = offset($el).top
320 | elBottom = elTop + $el.height()
321 | nextTop = elBottom + (viewportHeight * GAP_PERCENT_OF_VIEWPORT)
322 | gapSize = nextTop - elBottom
323 |
324 | # Values pertaining to when to start fading and when to fade in the next one
325 | endFadeOutPoint = elBottom - (gapSize * FADE_GAP_OFFSET)
326 | startFadeInPoint = nextTop - (gapSize * FADE_GAP_OFFSET)
327 | endFadeOutPoint -= viewportHeight * FADE_GAP_OFFSET
328 | startFadeInPoint -= viewportHeight * FADE_GAP_OFFSET
329 |
330 | # In between an item so ensure that this item is at opacity 1.
331 | if scrollTop > elTop and (scrollTop + viewportHeight) < elBottom and
332 | currentItemIndex isnt index
333 | $foregroundItems.css opacity: 0
334 | $curItem.css opacity: 1
335 | currentItemIndex = index
336 | break
337 |
338 | # In the gap between items so transition opacities as you scroll
339 | else if (scrollTop + viewportHeight) > elBottom and scrollTop < nextTop
340 | percentCurItem = 1 - percentBetween (elBottom - viewportHeight), endFadeOutPoint
341 | percentNextItem = percentBetween startFadeInPoint, nextTop
342 | # Fade out the entire foreground if it's the last item
343 | if index is $backgroundItems.length - 1
344 | $foreground.css opacity: percentCurItem
345 | $curItem.css 'z-index': Math.round(percentCurItem)
346 | else
347 | $foreground.css opacity: 1
348 | $curItem.css opacity: percentCurItem, 'z-index': Math.ceil(percentCurItem)
349 | $nextItem.css opacity: percentNextItem, 'z-index': Math.ceil(percentNextItem)
350 | break
351 |
352 | fadeOutHeaderImage = ->
353 | return if scrollTop > viewportHeight
354 | $headerBackground.css opacity: 1 - (scrollTop / viewportHeight)
355 | $headerGradient.css opacity: (scrollTop / viewportHeight) * 2
356 |
357 | popLockForeground = ->
358 | top = scrollTop - contentGap
359 | end = (offset($background).bottom - viewportHeight - contentGap)
360 | top = Math.round(Math.max 0, Math.min(top, end))
361 | if myScroll?
362 | $foreground.css(top: top)
363 | # Because Safari can't handle manual fixing without jitters we do this
364 | # hacky use of plain ol' fixed position... ironic iPad's Safari choke on fixed
365 | # and desktop Safari chokes on fixed work-arounds.
366 | else if top > 0 and top < end
367 | $foreground.css(top: 0, position: 'fixed')
368 | else if top <= 0
369 | $foreground.css(top: 0, position: 'absolute')
370 | else if top >= end
371 | $foreground.css(bottom: 0, top: 'auto', position: 'absolute')
372 |
373 |
374 | popLockCodeMask = ->
375 | codeTop = offset($code).top
376 | codeBottom = codeTop + $code.height()
377 | return if scrollTop < codeTop or (scrollTop + viewportHeight) > codeBottom
378 | maskTop = scrollTop - codeTop
379 | $codeMask.css 'margin-top': maskTop
380 |
381 | fadeInFirstForegroundItem = ->
382 | return if $foreground.css('position') is 'fixed' or
383 | $foreground.css('bottom') is '0px' or
384 | parseInt($foreground.css('top')) > 0
385 | if viewportWidth <= 1024 # iPad will see the text above fold
386 | opacity = 1
387 | else
388 | end = offset($firstForegroundItem).top
389 | start = end - (viewportHeight / 2)
390 | opacity = (scrollTop - start) / (end - start)
391 | $firstForegroundItem.css opacity: opacity
392 |
393 | toggleSlideShow = ->
394 | if stopSlideShow and scrollTop <= 10
395 | stopSlideShow = false
396 | nextHeaderSlide()
397 | else if scrollTop > 10
398 | stopSlideShow = true
399 | clearTimeout slideshowTimeout
400 | if scrollTop > viewportHeight
401 | $headerBackgrounds.removeClass('active')
402 | else
403 | $headerBackgrounds.removeClass('active')
404 | $headerBackgrounds.first().addClass('active')
405 |
406 | nextHeaderSlide = ->
407 | return if stopSlideShow
408 | slideshowTimeout = setTimeout ->
409 | slideshowTimeout = setTimeout ->
410 | index = $($headerBackgrounds.filter(-> $(@).hasClass('active'))[0]).index()
411 | nextIndex = if index + 1 > totalHeaderBackgrounds then 0 else index + 1
412 | $cur = $ $headerBackgrounds.eq(index)
413 | $next = $ $headerBackgrounds.eq(nextIndex)
414 | $cur.removeClass 'active'
415 | $next.addClass 'active'
416 | nextHeaderSlide()
417 | , 700
418 | , 1500
419 |
420 | animateGraphLine = ->
421 | start = offset($backgroundItems.last()).top
422 | end = start + (viewportHeight * 0.8)
423 | pos = graphLineLength - (graphLineLength * percentBetween(start, end))
424 | pos = Math.max pos, 0
425 | $graphLine.css 'stroke-dashoffset': pos
426 |
427 | popLockGraph = ->
428 | graphContainerTop = offset($graphContainer).top
429 | graphContainerBottom = graphContainerTop + $graphContainer.height()
430 | return if scrollTop < graphContainerTop or scrollTop + viewportHeight >= graphContainerBottom
431 | $graphWrapper.css 'margin-top': scrollTop - graphContainerTop
432 |
433 | # On resize functions
434 | # -------------------
435 |
436 | onResize = ->
437 | viewportHeight = $window.height()
438 | viewportWidth = $window.width()
439 | setBackgroundItemGap()
440 | setContentGap()
441 | setHeaderSize()
442 | swapForHigherResImages()
443 | setViewportHeights()
444 | _.defer -> myScroll?.refresh()
445 | setTimeout relockItems, 500
446 |
447 | relockItems = ->
448 | getScrollTop()
449 | popLockForeground()
450 |
451 | setViewportHeights = ->
452 | $viewportHeights.height viewportHeight
453 | $halfViewportHeights.height viewportHeight / 2
454 |
455 | setHeaderSize = ->
456 | $('#header-background').height viewportHeight
457 |
458 | setContentGap = ->
459 | contentGap = offset($content).top
460 |
461 | setBackgroundItemGap = ->
462 | $backgroundItems.css('margin-bottom': viewportHeight * GAP_PERCENT_OF_VIEWPORT)
463 | $backgroundItems.last().css('margin-bottom': 0)
464 |
465 | swapForHigherResImages = ->
466 | if viewportWidth >= 640
467 | $imgs.each -> $(@).attr 'src', $(@).attr('src').replace('small', 'large')
468 | else
469 | $imgs.each -> $(@).attr 'src', $(@).attr('src').replace('large', 'small')
470 |
471 | # Start your engines
472 | # ------------------
473 |
474 | $ init
475 |
--------------------------------------------------------------------------------
/src/scripts/vendor/zepto.js:
--------------------------------------------------------------------------------
1 | /* Zepto v1.1.2 - zepto event ajax form ie - zeptojs.com/license */
2 |
3 |
4 | var Zepto = (function() {
5 | var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
6 | document = window.document,
7 | elementDisplay = {}, classCache = {},
8 | cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
9 | fragmentRE = /^\s*<(\w+|!)[^>]*>/,
10 | singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
11 | tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
12 | rootNodeRE = /^(?:body|html)$/i,
13 | capitalRE = /([A-Z])/g,
14 |
15 | // special attributes that should be get/set via method calls
16 | methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
17 |
18 | adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
19 | table = document.createElement('table'),
20 | tableRow = document.createElement('tr'),
21 | containers = {
22 | 'tr': document.createElement('tbody'),
23 | 'tbody': table, 'thead': table, 'tfoot': table,
24 | 'td': tableRow, 'th': tableRow,
25 | '*': document.createElement('div')
26 | },
27 | readyRE = /complete|loaded|interactive/,
28 | classSelectorRE = /^\.([\w-]+)$/,
29 | idSelectorRE = /^#([\w-]*)$/,
30 | simpleSelectorRE = /^[\w-]*$/,
31 | class2type = {},
32 | toString = class2type.toString,
33 | zepto = {},
34 | camelize, uniq,
35 | tempParent = document.createElement('div'),
36 | propMap = {
37 | 'tabindex': 'tabIndex',
38 | 'readonly': 'readOnly',
39 | 'for': 'htmlFor',
40 | 'class': 'className',
41 | 'maxlength': 'maxLength',
42 | 'cellspacing': 'cellSpacing',
43 | 'cellpadding': 'cellPadding',
44 | 'rowspan': 'rowSpan',
45 | 'colspan': 'colSpan',
46 | 'usemap': 'useMap',
47 | 'frameborder': 'frameBorder',
48 | 'contenteditable': 'contentEditable'
49 | }
50 |
51 | zepto.matches = function(element, selector) {
52 | if (!selector || !element || element.nodeType !== 1) return false
53 | var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector ||
54 | element.oMatchesSelector || element.matchesSelector
55 | if (matchesSelector) return matchesSelector.call(element, selector)
56 | // fall back to performing a selector:
57 | var match, parent = element.parentNode, temp = !parent
58 | if (temp) (parent = tempParent).appendChild(element)
59 | match = ~zepto.qsa(parent, selector).indexOf(element)
60 | temp && tempParent.removeChild(element)
61 | return match
62 | }
63 |
64 | function type(obj) {
65 | return obj == null ? String(obj) :
66 | class2type[toString.call(obj)] || "object"
67 | }
68 |
69 | function isFunction(value) { return type(value) == "function" }
70 | function isWindow(obj) { return obj != null && obj == obj.window }
71 | function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
72 | function isObject(obj) { return type(obj) == "object" }
73 | function isPlainObject(obj) {
74 | return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
75 | }
76 | function isArray(value) { return value instanceof Array }
77 | function likeArray(obj) { return typeof obj.length == 'number' }
78 |
79 | function compact(array) { return filter.call(array, function(item){ return item != null }) }
80 | function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array }
81 | camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
82 | function dasherize(str) {
83 | return str.replace(/::/g, '/')
84 | .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
85 | .replace(/([a-z\d])([A-Z])/g, '$1_$2')
86 | .replace(/_/g, '-')
87 | .toLowerCase()
88 | }
89 | uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }
90 |
91 | function classRE(name) {
92 | return name in classCache ?
93 | classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
94 | }
95 |
96 | function maybeAddPx(name, value) {
97 | return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
98 | }
99 |
100 | function defaultDisplay(nodeName) {
101 | var element, display
102 | if (!elementDisplay[nodeName]) {
103 | element = document.createElement(nodeName)
104 | document.body.appendChild(element)
105 | display = getComputedStyle(element, '').getPropertyValue("display")
106 | element.parentNode.removeChild(element)
107 | display == "none" && (display = "block")
108 | elementDisplay[nodeName] = display
109 | }
110 | return elementDisplay[nodeName]
111 | }
112 |
113 | function children(element) {
114 | return 'children' in element ?
115 | slice.call(element.children) :
116 | $.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
117 | }
118 |
119 | // `$.zepto.fragment` takes a html string and an optional tag name
120 | // to generate DOM nodes nodes from the given html string.
121 | // The generated DOM nodes are returned as an array.
122 | // This function can be overriden in plugins for example to make
123 | // it compatible with browsers that don't support the DOM fully.
124 | zepto.fragment = function(html, name, properties) {
125 | var dom, nodes, container
126 |
127 | // A special case optimization for a single tag
128 | if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
129 |
130 | if (!dom) {
131 | if (html.replace) html = html.replace(tagExpanderRE, "<$1>$2>")
132 | if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
133 | if (!(name in containers)) name = '*'
134 |
135 | container = containers[name]
136 | container.innerHTML = '' + html
137 | dom = $.each(slice.call(container.childNodes), function(){
138 | container.removeChild(this)
139 | })
140 | }
141 |
142 | if (isPlainObject(properties)) {
143 | nodes = $(dom)
144 | $.each(properties, function(key, value) {
145 | if (methodAttributes.indexOf(key) > -1) nodes[key](value)
146 | else nodes.attr(key, value)
147 | })
148 | }
149 |
150 | return dom
151 | }
152 |
153 | // `$.zepto.Z` swaps out the prototype of the given `dom` array
154 | // of nodes with `$.fn` and thus supplying all the Zepto functions
155 | // to the array. Note that `__proto__` is not supported on Internet
156 | // Explorer. This method can be overriden in plugins.
157 | zepto.Z = function(dom, selector) {
158 | dom = dom || []
159 | dom.__proto__ = $.fn
160 | dom.selector = selector || ''
161 | return dom
162 | }
163 |
164 | // `$.zepto.isZ` should return `true` if the given object is a Zepto
165 | // collection. This method can be overriden in plugins.
166 | zepto.isZ = function(object) {
167 | return object instanceof zepto.Z
168 | }
169 |
170 | // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
171 | // takes a CSS selector and an optional context (and handles various
172 | // special cases).
173 | // This method can be overriden in plugins.
174 | zepto.init = function(selector, context) {
175 | var dom
176 | // If nothing given, return an empty Zepto collection
177 | if (!selector) return zepto.Z()
178 | // Optimize for string selectors
179 | else if (typeof selector == 'string') {
180 | selector = selector.trim()
181 | // If it's a html fragment, create nodes from it
182 | // Note: In both Chrome 21 and Firefox 15, DOM error 12
183 | // is thrown if the fragment doesn't begin with <
184 | if (selector[0] == '<' && fragmentRE.test(selector))
185 | dom = zepto.fragment(selector, RegExp.$1, context), selector = null
186 | // If there's a context, create a collection on that context first, and select
187 | // nodes from there
188 | else if (context !== undefined) return $(context).find(selector)
189 | // If it's a CSS selector, use it to select nodes.
190 | else dom = zepto.qsa(document, selector)
191 | }
192 | // If a function is given, call it when the DOM is ready
193 | else if (isFunction(selector)) return $(document).ready(selector)
194 | // If a Zepto collection is given, just return it
195 | else if (zepto.isZ(selector)) return selector
196 | else {
197 | // normalize array if an array of nodes is given
198 | if (isArray(selector)) dom = compact(selector)
199 | // Wrap DOM nodes.
200 | else if (isObject(selector))
201 | dom = [selector], selector = null
202 | // If it's a html fragment, create nodes from it
203 | else if (fragmentRE.test(selector))
204 | dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
205 | // If there's a context, create a collection on that context first, and select
206 | // nodes from there
207 | else if (context !== undefined) return $(context).find(selector)
208 | // And last but no least, if it's a CSS selector, use it to select nodes.
209 | else dom = zepto.qsa(document, selector)
210 | }
211 | // create a new Zepto collection from the nodes found
212 | return zepto.Z(dom, selector)
213 | }
214 |
215 | // `$` will be the base `Zepto` object. When calling this
216 | // function just call `$.zepto.init, which makes the implementation
217 | // details of selecting nodes and creating Zepto collections
218 | // patchable in plugins.
219 | $ = function(selector, context){
220 | return zepto.init(selector, context)
221 | }
222 |
223 | function extend(target, source, deep) {
224 | for (key in source)
225 | if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
226 | if (isPlainObject(source[key]) && !isPlainObject(target[key]))
227 | target[key] = {}
228 | if (isArray(source[key]) && !isArray(target[key]))
229 | target[key] = []
230 | extend(target[key], source[key], deep)
231 | }
232 | else if (source[key] !== undefined) target[key] = source[key]
233 | }
234 |
235 | // Copy all but undefined properties from one or more
236 | // objects to the `target` object.
237 | $.extend = function(target){
238 | var deep, args = slice.call(arguments, 1)
239 | if (typeof target == 'boolean') {
240 | deep = target
241 | target = args.shift()
242 | }
243 | args.forEach(function(arg){ extend(target, arg, deep) })
244 | return target
245 | }
246 |
247 | // `$.zepto.qsa` is Zepto's CSS selector implementation which
248 | // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
249 | // This method can be overriden in plugins.
250 | zepto.qsa = function(element, selector){
251 | var found,
252 | maybeID = selector[0] == '#',
253 | maybeClass = !maybeID && selector[0] == '.',
254 | nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
255 | isSimple = simpleSelectorRE.test(nameOnly)
256 | return (isDocument(element) && isSimple && maybeID) ?
257 | ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
258 | (element.nodeType !== 1 && element.nodeType !== 9) ? [] :
259 | slice.call(
260 | isSimple && !maybeID ?
261 | maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
262 | element.getElementsByTagName(selector) : // Or a tag
263 | element.querySelectorAll(selector) // Or it's not simple, and we need to query all
264 | )
265 | }
266 |
267 | function filtered(nodes, selector) {
268 | return selector == null ? $(nodes) : $(nodes).filter(selector)
269 | }
270 |
271 | $.contains = function(parent, node) {
272 | return parent !== node && parent.contains(node)
273 | }
274 |
275 | function funcArg(context, arg, idx, payload) {
276 | return isFunction(arg) ? arg.call(context, idx, payload) : arg
277 | }
278 |
279 | function setAttribute(node, name, value) {
280 | value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
281 | }
282 |
283 | // access className property while respecting SVGAnimatedString
284 | function className(node, value){
285 | var klass = node.className,
286 | svg = klass && klass.baseVal !== undefined
287 |
288 | if (value === undefined) return svg ? klass.baseVal : klass
289 | svg ? (klass.baseVal = value) : (node.className = value)
290 | }
291 |
292 | // "true" => true
293 | // "false" => false
294 | // "null" => null
295 | // "42" => 42
296 | // "42.5" => 42.5
297 | // "08" => "08"
298 | // JSON => parse if valid
299 | // String => self
300 | function deserializeValue(value) {
301 | var num
302 | try {
303 | return value ?
304 | value == "true" ||
305 | ( value == "false" ? false :
306 | value == "null" ? null :
307 | !/^0/.test(value) && !isNaN(num = Number(value)) ? num :
308 | /^[\[\{]/.test(value) ? $.parseJSON(value) :
309 | value )
310 | : value
311 | } catch(e) {
312 | return value
313 | }
314 | }
315 |
316 | $.type = type
317 | $.isFunction = isFunction
318 | $.isWindow = isWindow
319 | $.isArray = isArray
320 | $.isPlainObject = isPlainObject
321 |
322 | $.isEmptyObject = function(obj) {
323 | var name
324 | for (name in obj) return false
325 | return true
326 | }
327 |
328 | $.inArray = function(elem, array, i){
329 | return emptyArray.indexOf.call(array, elem, i)
330 | }
331 |
332 | $.camelCase = camelize
333 | $.trim = function(str) {
334 | return str == null ? "" : String.prototype.trim.call(str)
335 | }
336 |
337 | // plugin compatibility
338 | $.uuid = 0
339 | $.support = { }
340 | $.expr = { }
341 |
342 | $.map = function(elements, callback){
343 | var value, values = [], i, key
344 | if (likeArray(elements))
345 | for (i = 0; i < elements.length; i++) {
346 | value = callback(elements[i], i)
347 | if (value != null) values.push(value)
348 | }
349 | else
350 | for (key in elements) {
351 | value = callback(elements[key], key)
352 | if (value != null) values.push(value)
353 | }
354 | return flatten(values)
355 | }
356 |
357 | $.each = function(elements, callback){
358 | var i, key
359 | if (likeArray(elements)) {
360 | for (i = 0; i < elements.length; i++)
361 | if (callback.call(elements[i], i, elements[i]) === false) return elements
362 | } else {
363 | for (key in elements)
364 | if (callback.call(elements[key], key, elements[key]) === false) return elements
365 | }
366 |
367 | return elements
368 | }
369 |
370 | $.grep = function(elements, callback){
371 | return filter.call(elements, callback)
372 | }
373 |
374 | if (window.JSON) $.parseJSON = JSON.parse
375 |
376 | // Populate the class2type map
377 | $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
378 | class2type[ "[object " + name + "]" ] = name.toLowerCase()
379 | })
380 |
381 | // Define methods that will be available on all
382 | // Zepto collections
383 | $.fn = {
384 | // Because a collection acts like an array
385 | // copy over these useful array functions.
386 | forEach: emptyArray.forEach,
387 | reduce: emptyArray.reduce,
388 | push: emptyArray.push,
389 | sort: emptyArray.sort,
390 | indexOf: emptyArray.indexOf,
391 | concat: emptyArray.concat,
392 |
393 | // `map` and `slice` in the jQuery API work differently
394 | // from their array counterparts
395 | map: function(fn){
396 | return $($.map(this, function(el, i){ return fn.call(el, i, el) }))
397 | },
398 | slice: function(){
399 | return $(slice.apply(this, arguments))
400 | },
401 |
402 | ready: function(callback){
403 | // need to check if document.body exists for IE as that browser reports
404 | // document ready when it hasn't yet created the body element
405 | if (readyRE.test(document.readyState) && document.body) callback($)
406 | else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
407 | return this
408 | },
409 | get: function(idx){
410 | return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
411 | },
412 | toArray: function(){ return this.get() },
413 | size: function(){
414 | return this.length
415 | },
416 | remove: function(){
417 | return this.each(function(){
418 | if (this.parentNode != null)
419 | this.parentNode.removeChild(this)
420 | })
421 | },
422 | each: function(callback){
423 | emptyArray.every.call(this, function(el, idx){
424 | return callback.call(el, idx, el) !== false
425 | })
426 | return this
427 | },
428 | filter: function(selector){
429 | if (isFunction(selector)) return this.not(this.not(selector))
430 | return $(filter.call(this, function(element){
431 | return zepto.matches(element, selector)
432 | }))
433 | },
434 | add: function(selector,context){
435 | return $(uniq(this.concat($(selector,context))))
436 | },
437 | is: function(selector){
438 | return this.length > 0 && zepto.matches(this[0], selector)
439 | },
440 | not: function(selector){
441 | var nodes=[]
442 | if (isFunction(selector) && selector.call !== undefined)
443 | this.each(function(idx){
444 | if (!selector.call(this,idx)) nodes.push(this)
445 | })
446 | else {
447 | var excludes = typeof selector == 'string' ? this.filter(selector) :
448 | (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
449 | this.forEach(function(el){
450 | if (excludes.indexOf(el) < 0) nodes.push(el)
451 | })
452 | }
453 | return $(nodes)
454 | },
455 | has: function(selector){
456 | return this.filter(function(){
457 | return isObject(selector) ?
458 | $.contains(this, selector) :
459 | $(this).find(selector).size()
460 | })
461 | },
462 | eq: function(idx){
463 | return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
464 | },
465 | first: function(){
466 | var el = this[0]
467 | return el && !isObject(el) ? el : $(el)
468 | },
469 | last: function(){
470 | var el = this[this.length - 1]
471 | return el && !isObject(el) ? el : $(el)
472 | },
473 | find: function(selector){
474 | var result, $this = this
475 | if (typeof selector == 'object')
476 | result = $(selector).filter(function(){
477 | var node = this
478 | return emptyArray.some.call($this, function(parent){
479 | return $.contains(parent, node)
480 | })
481 | })
482 | else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
483 | else result = this.map(function(){ return zepto.qsa(this, selector) })
484 | return result
485 | },
486 | closest: function(selector, context){
487 | var node = this[0], collection = false
488 | if (typeof selector == 'object') collection = $(selector)
489 | while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
490 | node = node !== context && !isDocument(node) && node.parentNode
491 | return $(node)
492 | },
493 | parents: function(selector){
494 | var ancestors = [], nodes = this
495 | while (nodes.length > 0)
496 | nodes = $.map(nodes, function(node){
497 | if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
498 | ancestors.push(node)
499 | return node
500 | }
501 | })
502 | return filtered(ancestors, selector)
503 | },
504 | parent: function(selector){
505 | return filtered(uniq(this.pluck('parentNode')), selector)
506 | },
507 | children: function(selector){
508 | return filtered(this.map(function(){ return children(this) }), selector)
509 | },
510 | contents: function() {
511 | return this.map(function() { return slice.call(this.childNodes) })
512 | },
513 | siblings: function(selector){
514 | return filtered(this.map(function(i, el){
515 | return filter.call(children(el.parentNode), function(child){ return child!==el })
516 | }), selector)
517 | },
518 | empty: function(){
519 | return this.each(function(){ this.innerHTML = '' })
520 | },
521 | // `pluck` is borrowed from Prototype.js
522 | pluck: function(property){
523 | return $.map(this, function(el){ return el[property] })
524 | },
525 | show: function(){
526 | return this.each(function(){
527 | this.style.display == "none" && (this.style.display = '')
528 | if (getComputedStyle(this, '').getPropertyValue("display") == "none")
529 | this.style.display = defaultDisplay(this.nodeName)
530 | })
531 | },
532 | replaceWith: function(newContent){
533 | return this.before(newContent).remove()
534 | },
535 | wrap: function(structure){
536 | var func = isFunction(structure)
537 | if (this[0] && !func)
538 | var dom = $(structure).get(0),
539 | clone = dom.parentNode || this.length > 1
540 |
541 | return this.each(function(index){
542 | $(this).wrapAll(
543 | func ? structure.call(this, index) :
544 | clone ? dom.cloneNode(true) : dom
545 | )
546 | })
547 | },
548 | wrapAll: function(structure){
549 | if (this[0]) {
550 | $(this[0]).before(structure = $(structure))
551 | var children
552 | // drill down to the inmost element
553 | while ((children = structure.children()).length) structure = children.first()
554 | $(structure).append(this)
555 | }
556 | return this
557 | },
558 | wrapInner: function(structure){
559 | var func = isFunction(structure)
560 | return this.each(function(index){
561 | var self = $(this), contents = self.contents(),
562 | dom = func ? structure.call(this, index) : structure
563 | contents.length ? contents.wrapAll(dom) : self.append(dom)
564 | })
565 | },
566 | unwrap: function(){
567 | this.parent().each(function(){
568 | $(this).replaceWith($(this).children())
569 | })
570 | return this
571 | },
572 | clone: function(){
573 | return this.map(function(){ return this.cloneNode(true) })
574 | },
575 | hide: function(){
576 | return this.css("display", "none")
577 | },
578 | toggle: function(setting){
579 | return this.each(function(){
580 | var el = $(this)
581 | ;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
582 | })
583 | },
584 | prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') },
585 | next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') },
586 | html: function(html){
587 | return arguments.length === 0 ?
588 | (this.length > 0 ? this[0].innerHTML : null) :
589 | this.each(function(idx){
590 | var originHtml = this.innerHTML
591 | $(this).empty().append( funcArg(this, html, idx, originHtml) )
592 | })
593 | },
594 | text: function(text){
595 | return arguments.length === 0 ?
596 | (this.length > 0 ? this[0].textContent : null) :
597 | this.each(function(){ this.textContent = (text === undefined) ? '' : ''+text })
598 | },
599 | attr: function(name, value){
600 | var result
601 | return (typeof name == 'string' && value === undefined) ?
602 | (this.length == 0 || this[0].nodeType !== 1 ? undefined :
603 | (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
604 | (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
605 | ) :
606 | this.each(function(idx){
607 | if (this.nodeType !== 1) return
608 | if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
609 | else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
610 | })
611 | },
612 | removeAttr: function(name){
613 | return this.each(function(){ this.nodeType === 1 && setAttribute(this, name) })
614 | },
615 | prop: function(name, value){
616 | name = propMap[name] || name
617 | return (value === undefined) ?
618 | (this[0] && this[0][name]) :
619 | this.each(function(idx){
620 | this[name] = funcArg(this, value, idx, this[name])
621 | })
622 | },
623 | data: function(name, value){
624 | var data = this.attr('data-' + name.replace(capitalRE, '-$1').toLowerCase(), value)
625 | return data !== null ? deserializeValue(data) : undefined
626 | },
627 | val: function(value){
628 | return arguments.length === 0 ?
629 | (this[0] && (this[0].multiple ?
630 | $(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') :
631 | this[0].value)
632 | ) :
633 | this.each(function(idx){
634 | this.value = funcArg(this, value, idx, this.value)
635 | })
636 | },
637 | offset: function(coordinates){
638 | if (coordinates) return this.each(function(index){
639 | var $this = $(this),
640 | coords = funcArg(this, coordinates, index, $this.offset()),
641 | parentOffset = $this.offsetParent().offset(),
642 | props = {
643 | top: coords.top - parentOffset.top,
644 | left: coords.left - parentOffset.left
645 | }
646 |
647 | if ($this.css('position') == 'static') props['position'] = 'relative'
648 | $this.css(props)
649 | })
650 | if (this.length==0) return null
651 | var obj = this[0].getBoundingClientRect()
652 | return {
653 | left: obj.left + window.pageXOffset,
654 | top: obj.top + window.pageYOffset,
655 | width: Math.round(obj.width),
656 | height: Math.round(obj.height)
657 | }
658 | },
659 | css: function(property, value){
660 | if (arguments.length < 2) {
661 | var element = this[0], computedStyle = getComputedStyle(element, '')
662 | if(!element) return
663 | if (typeof property == 'string')
664 | return element.style[camelize(property)] || computedStyle.getPropertyValue(property)
665 | else if (isArray(property)) {
666 | var props = {}
667 | $.each(isArray(property) ? property: [property], function(_, prop){
668 | props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
669 | })
670 | return props
671 | }
672 | }
673 |
674 | var css = ''
675 | if (type(property) == 'string') {
676 | if (!value && value !== 0)
677 | this.each(function(){ this.style.removeProperty(dasherize(property)) })
678 | else
679 | css = dasherize(property) + ":" + maybeAddPx(property, value)
680 | } else {
681 | for (key in property)
682 | if (!property[key] && property[key] !== 0)
683 | this.each(function(){ this.style.removeProperty(dasherize(key)) })
684 | else
685 | css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
686 | }
687 |
688 | return this.each(function(){ this.style.cssText += ';' + css })
689 | },
690 | index: function(element){
691 | return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
692 | },
693 | hasClass: function(name){
694 | if (!name) return false
695 | return emptyArray.some.call(this, function(el){
696 | return this.test(className(el))
697 | }, classRE(name))
698 | },
699 | addClass: function(name){
700 | if (!name) return this
701 | return this.each(function(idx){
702 | classList = []
703 | var cls = className(this), newName = funcArg(this, name, idx, cls)
704 | newName.split(/\s+/g).forEach(function(klass){
705 | if (!$(this).hasClass(klass)) classList.push(klass)
706 | }, this)
707 | classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
708 | })
709 | },
710 | removeClass: function(name){
711 | return this.each(function(idx){
712 | if (name === undefined) return className(this, '')
713 | classList = className(this)
714 | funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
715 | classList = classList.replace(classRE(klass), " ")
716 | })
717 | className(this, classList.trim())
718 | })
719 | },
720 | toggleClass: function(name, when){
721 | if (!name) return this
722 | return this.each(function(idx){
723 | var $this = $(this), names = funcArg(this, name, idx, className(this))
724 | names.split(/\s+/g).forEach(function(klass){
725 | (when === undefined ? !$this.hasClass(klass) : when) ?
726 | $this.addClass(klass) : $this.removeClass(klass)
727 | })
728 | })
729 | },
730 | scrollTop: function(value){
731 | if (!this.length) return
732 | var hasScrollTop = 'scrollTop' in this[0]
733 | if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
734 | return this.each(hasScrollTop ?
735 | function(){ this.scrollTop = value } :
736 | function(){ this.scrollTo(this.scrollX, value) })
737 | },
738 | scrollLeft: function(value){
739 | if (!this.length) return
740 | var hasScrollLeft = 'scrollLeft' in this[0]
741 | if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
742 | return this.each(hasScrollLeft ?
743 | function(){ this.scrollLeft = value } :
744 | function(){ this.scrollTo(value, this.scrollY) })
745 | },
746 | position: function() {
747 | if (!this.length) return
748 |
749 | var elem = this[0],
750 | // Get *real* offsetParent
751 | offsetParent = this.offsetParent(),
752 | // Get correct offsets
753 | offset = this.offset(),
754 | parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()
755 |
756 | // Subtract element margins
757 | // note: when an element has margin: auto the offsetLeft and marginLeft
758 | // are the same in Safari causing offset.left to incorrectly be 0
759 | offset.top -= parseFloat( $(elem).css('margin-top') ) || 0
760 | offset.left -= parseFloat( $(elem).css('margin-left') ) || 0
761 |
762 | // Add offsetParent borders
763 | parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0
764 | parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0
765 |
766 | // Subtract the two offsets
767 | return {
768 | top: offset.top - parentOffset.top,
769 | left: offset.left - parentOffset.left
770 | }
771 | },
772 | offsetParent: function() {
773 | return this.map(function(){
774 | var parent = this.offsetParent || document.body
775 | while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
776 | parent = parent.offsetParent
777 | return parent
778 | })
779 | }
780 | }
781 |
782 | // for now
783 | $.fn.detach = $.fn.remove
784 |
785 | // Generate the `width` and `height` functions
786 | ;['width', 'height'].forEach(function(dimension){
787 | var dimensionProperty =
788 | dimension.replace(/./, function(m){ return m[0].toUpperCase() })
789 |
790 | $.fn[dimension] = function(value){
791 | var offset, el = this[0]
792 | if (value === undefined) return isWindow(el) ? el['inner' + dimensionProperty] :
793 | isDocument(el) ? el.documentElement['scroll' + dimensionProperty] :
794 | (offset = this.offset()) && offset[dimension]
795 | else return this.each(function(idx){
796 | el = $(this)
797 | el.css(dimension, funcArg(this, value, idx, el[dimension]()))
798 | })
799 | }
800 | })
801 |
802 | function traverseNode(node, fun) {
803 | fun(node)
804 | for (var key in node.childNodes) traverseNode(node.childNodes[key], fun)
805 | }
806 |
807 | // Generate the `after`, `prepend`, `before`, `append`,
808 | // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
809 | adjacencyOperators.forEach(function(operator, operatorIndex) {
810 | var inside = operatorIndex % 2 //=> prepend, append
811 |
812 | $.fn[operator] = function(){
813 | // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
814 | var argType, nodes = $.map(arguments, function(arg) {
815 | argType = type(arg)
816 | return argType == "object" || argType == "array" || arg == null ?
817 | arg : zepto.fragment(arg)
818 | }),
819 | parent, copyByClone = this.length > 1
820 | if (nodes.length < 1) return this
821 |
822 | return this.each(function(_, target){
823 | parent = inside ? target : target.parentNode
824 |
825 | // convert all methods to a "before" operation
826 | target = operatorIndex == 0 ? target.nextSibling :
827 | operatorIndex == 1 ? target.firstChild :
828 | operatorIndex == 2 ? target :
829 | null
830 |
831 | nodes.forEach(function(node){
832 | if (copyByClone) node = node.cloneNode(true)
833 | else if (!parent) return $(node).remove()
834 |
835 | traverseNode(parent.insertBefore(node, target), function(el){
836 | if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
837 | (!el.type || el.type === 'text/javascript') && !el.src)
838 | window['eval'].call(window, el.innerHTML)
839 | })
840 | })
841 | })
842 | }
843 |
844 | // after => insertAfter
845 | // prepend => prependTo
846 | // before => insertBefore
847 | // append => appendTo
848 | $.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){
849 | $(html)[operator](this)
850 | return this
851 | }
852 | })
853 |
854 | zepto.Z.prototype = $.fn
855 |
856 | // Export internal API functions in the `$.zepto` namespace
857 | zepto.uniq = uniq
858 | zepto.deserializeValue = deserializeValue
859 | $.zepto = zepto
860 |
861 | return $
862 | })()
863 |
864 | window.Zepto = Zepto
865 | window.$ === undefined && (window.$ = Zepto)
866 |
867 | ;(function($){
868 | var $$ = $.zepto.qsa, _zid = 1, undefined,
869 | slice = Array.prototype.slice,
870 | isFunction = $.isFunction,
871 | isString = function(obj){ return typeof obj == 'string' },
872 | handlers = {},
873 | specialEvents={},
874 | focusinSupported = 'onfocusin' in window,
875 | focus = { focus: 'focusin', blur: 'focusout' },
876 | hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }
877 |
878 | specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
879 |
880 | function zid(element) {
881 | return element._zid || (element._zid = _zid++)
882 | }
883 | function findHandlers(element, event, fn, selector) {
884 | event = parse(event)
885 | if (event.ns) var matcher = matcherFor(event.ns)
886 | return (handlers[zid(element)] || []).filter(function(handler) {
887 | return handler
888 | && (!event.e || handler.e == event.e)
889 | && (!event.ns || matcher.test(handler.ns))
890 | && (!fn || zid(handler.fn) === zid(fn))
891 | && (!selector || handler.sel == selector)
892 | })
893 | }
894 | function parse(event) {
895 | var parts = ('' + event).split('.')
896 | return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
897 | }
898 | function matcherFor(ns) {
899 | return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
900 | }
901 |
902 | function eventCapture(handler, captureSetting) {
903 | return handler.del &&
904 | (!focusinSupported && (handler.e in focus)) ||
905 | !!captureSetting
906 | }
907 |
908 | function realEvent(type) {
909 | return hover[type] || (focusinSupported && focus[type]) || type
910 | }
911 |
912 | function add(element, events, fn, data, selector, delegator, capture){
913 | var id = zid(element), set = (handlers[id] || (handlers[id] = []))
914 | events.split(/\s/).forEach(function(event){
915 | if (event == 'ready') return $(document).ready(fn)
916 | var handler = parse(event)
917 | handler.fn = fn
918 | handler.sel = selector
919 | // emulate mouseenter, mouseleave
920 | if (handler.e in hover) fn = function(e){
921 | var related = e.relatedTarget
922 | if (!related || (related !== this && !$.contains(this, related)))
923 | return handler.fn.apply(this, arguments)
924 | }
925 | handler.del = delegator
926 | var callback = delegator || fn
927 | handler.proxy = function(e){
928 | e = compatible(e)
929 | if (e.isImmediatePropagationStopped()) return
930 | e.data = data
931 | var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
932 | if (result === false) e.preventDefault(), e.stopPropagation()
933 | return result
934 | }
935 | handler.i = set.length
936 | set.push(handler)
937 | if ('addEventListener' in element)
938 | element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
939 | })
940 | }
941 | function remove(element, events, fn, selector, capture){
942 | var id = zid(element)
943 | ;(events || '').split(/\s/).forEach(function(event){
944 | findHandlers(element, event, fn, selector).forEach(function(handler){
945 | delete handlers[id][handler.i]
946 | if ('removeEventListener' in element)
947 | element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
948 | })
949 | })
950 | }
951 |
952 | $.event = { add: add, remove: remove }
953 |
954 | $.proxy = function(fn, context) {
955 | if (isFunction(fn)) {
956 | var proxyFn = function(){ return fn.apply(context, arguments) }
957 | proxyFn._zid = zid(fn)
958 | return proxyFn
959 | } else if (isString(context)) {
960 | return $.proxy(fn[context], fn)
961 | } else {
962 | throw new TypeError("expected function")
963 | }
964 | }
965 |
966 | $.fn.bind = function(event, data, callback){
967 | return this.on(event, data, callback)
968 | }
969 | $.fn.unbind = function(event, callback){
970 | return this.off(event, callback)
971 | }
972 | $.fn.one = function(event, selector, data, callback){
973 | return this.on(event, selector, data, callback, 1)
974 | }
975 |
976 | var returnTrue = function(){return true},
977 | returnFalse = function(){return false},
978 | ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$)/,
979 | eventMethods = {
980 | preventDefault: 'isDefaultPrevented',
981 | stopImmediatePropagation: 'isImmediatePropagationStopped',
982 | stopPropagation: 'isPropagationStopped'
983 | }
984 |
985 | function compatible(event, source) {
986 | if (source || !event.isDefaultPrevented) {
987 | source || (source = event)
988 |
989 | $.each(eventMethods, function(name, predicate) {
990 | var sourceMethod = source[name]
991 | event[name] = function(){
992 | this[predicate] = returnTrue
993 | return sourceMethod && sourceMethod.apply(source, arguments)
994 | }
995 | event[predicate] = returnFalse
996 | })
997 |
998 | if (source.defaultPrevented !== undefined ? source.defaultPrevented :
999 | 'returnValue' in source ? source.returnValue === false :
1000 | source.getPreventDefault && source.getPreventDefault())
1001 | event.isDefaultPrevented = returnTrue
1002 | }
1003 | return event
1004 | }
1005 |
1006 | function createProxy(event) {
1007 | var key, proxy = { originalEvent: event }
1008 | for (key in event)
1009 | if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]
1010 |
1011 | return compatible(proxy, event)
1012 | }
1013 |
1014 | $.fn.delegate = function(selector, event, callback){
1015 | return this.on(event, selector, callback)
1016 | }
1017 | $.fn.undelegate = function(selector, event, callback){
1018 | return this.off(event, selector, callback)
1019 | }
1020 |
1021 | $.fn.live = function(event, callback){
1022 | $(document.body).delegate(this.selector, event, callback)
1023 | return this
1024 | }
1025 | $.fn.die = function(event, callback){
1026 | $(document.body).undelegate(this.selector, event, callback)
1027 | return this
1028 | }
1029 |
1030 | $.fn.on = function(event, selector, data, callback, one){
1031 | var autoRemove, delegator, $this = this
1032 | if (event && !isString(event)) {
1033 | $.each(event, function(type, fn){
1034 | $this.on(type, selector, data, fn, one)
1035 | })
1036 | return $this
1037 | }
1038 |
1039 | if (!isString(selector) && !isFunction(callback) && callback !== false)
1040 | callback = data, data = selector, selector = undefined
1041 | if (isFunction(data) || data === false)
1042 | callback = data, data = undefined
1043 |
1044 | if (callback === false) callback = returnFalse
1045 |
1046 | return $this.each(function(_, element){
1047 | if (one) autoRemove = function(e){
1048 | remove(element, e.type, callback)
1049 | return callback.apply(this, arguments)
1050 | }
1051 |
1052 | if (selector) delegator = function(e){
1053 | var evt, match = $(e.target).closest(selector, element).get(0)
1054 | if (match && match !== element) {
1055 | evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
1056 | return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
1057 | }
1058 | }
1059 |
1060 | add(element, event, callback, data, selector, delegator || autoRemove)
1061 | })
1062 | }
1063 | $.fn.off = function(event, selector, callback){
1064 | var $this = this
1065 | if (event && !isString(event)) {
1066 | $.each(event, function(type, fn){
1067 | $this.off(type, selector, fn)
1068 | })
1069 | return $this
1070 | }
1071 |
1072 | if (!isString(selector) && !isFunction(callback) && callback !== false)
1073 | callback = selector, selector = undefined
1074 |
1075 | if (callback === false) callback = returnFalse
1076 |
1077 | return $this.each(function(){
1078 | remove(this, event, callback, selector)
1079 | })
1080 | }
1081 |
1082 | $.fn.trigger = function(event, args){
1083 | event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
1084 | event._args = args
1085 | return this.each(function(){
1086 | // items in the collection might not be DOM elements
1087 | if('dispatchEvent' in this) this.dispatchEvent(event)
1088 | else $(this).triggerHandler(event, args)
1089 | })
1090 | }
1091 |
1092 | // triggers event handlers on current element just as if an event occurred,
1093 | // doesn't trigger an actual event, doesn't bubble
1094 | $.fn.triggerHandler = function(event, args){
1095 | var e, result
1096 | this.each(function(i, element){
1097 | e = createProxy(isString(event) ? $.Event(event) : event)
1098 | e._args = args
1099 | e.target = element
1100 | $.each(findHandlers(element, event.type || event), function(i, handler){
1101 | result = handler.proxy(e)
1102 | if (e.isImmediatePropagationStopped()) return false
1103 | })
1104 | })
1105 | return result
1106 | }
1107 |
1108 | // shortcut methods for `.bind(event, fn)` for each event type
1109 | ;('focusin focusout load resize scroll unload click dblclick '+
1110 | 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+
1111 | 'change select keydown keypress keyup error').split(' ').forEach(function(event) {
1112 | $.fn[event] = function(callback) {
1113 | return callback ?
1114 | this.bind(event, callback) :
1115 | this.trigger(event)
1116 | }
1117 | })
1118 |
1119 | ;['focus', 'blur'].forEach(function(name) {
1120 | $.fn[name] = function(callback) {
1121 | if (callback) this.bind(name, callback)
1122 | else this.each(function(){
1123 | try { this[name]() }
1124 | catch(e) {}
1125 | })
1126 | return this
1127 | }
1128 | })
1129 |
1130 | $.Event = function(type, props) {
1131 | if (!isString(type)) props = type, type = props.type
1132 | var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
1133 | if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
1134 | event.initEvent(type, bubbles, true)
1135 | return compatible(event)
1136 | }
1137 |
1138 | })(Zepto)
1139 |
1140 | ;(function($){
1141 | var jsonpID = 0,
1142 | document = window.document,
1143 | key,
1144 | name,
1145 | rscript = /